fix: date false-positive, partial-word highlights, cross-browser sync
Bug fixes: - Date (possible DOB) pattern now requires context words (born, birthday, dob, etc.) before firing — prevents spurious warnings on page-load API calls that happen to contain ISO dates in conversation history. - Highlight regex now uses word boundaries (\b) so short substitute values (e.g. "aud") no longer match inside unrelated words like "Claude". - Both TreeWalkers in content.js now skip the extension's own UI elements (.ss-autodetect-warning, .ss-presend-warning, .ss-reveal-badge) to prevent the highlight API from marking text in the extension's banners. Settings sync: - New src/lib/sync.js: exportSyncCode / importSyncCode (base64 JSON) for manual copy-paste across any browser combination. Newest lastModified timestamp wins; force flag available to override. - browser.storage.sync support: when "Browser account sync" is enabled the extension automatically pushes/pulls via Firefox Sync or Chrome account, chunked to stay within per-item quota limits. - storage.js now writes ss_lastModified on every save so conflict resolution has an accurate timestamp. - service-worker.js listens for both local and sync storage changes to keep all copies in sync. - New "Sync Between Browsers" section in options.html with Generate/Copy/ Import Sync Code UI and the browser sync toggle. https://claude.ai/code/session_01TKpSR9M8JgHLXCp5CeDsQP
This commit is contained in:
@@ -96,13 +96,59 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<h2>Sync Between Browsers</h2>
|
||||
<p class="section-desc">Keep your identities, mappings, and settings in sync across browsers.</p>
|
||||
|
||||
<div class="setting-row">
|
||||
<div>
|
||||
<label>Browser account sync</label>
|
||||
<p class="setting-desc">Automatically sync via your browser account (Firefox Sync / Chrome account). Works across devices using the same browser.</p>
|
||||
</div>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="browserSync">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:16px">
|
||||
<h3 style="font-size:13px;font-weight:600;margin:0 0 4px">Sync Code — cross-browser copy-paste</h3>
|
||||
<p class="section-desc" style="margin-bottom:8px">Generate a code in one browser, paste it in another. Newest data wins automatically.</p>
|
||||
<div class="bulk-actions">
|
||||
<button class="btn btn-primary" id="btnGenerateSyncCode">Generate Sync Code</button>
|
||||
<button class="btn" id="btnImportSyncCode">Import from Code</button>
|
||||
</div>
|
||||
|
||||
<div id="syncCodeDisplay" style="display:none;margin-top:10px">
|
||||
<textarea id="syncCodeText" rows="3" style="width:100%;box-sizing:border-box;font-family:monospace;font-size:11px;resize:vertical;padding:6px;border:1px solid #d1d5db;border-radius:6px" readonly></textarea>
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-top:6px">
|
||||
<button class="btn btn-primary" id="btnCopySyncCode">Copy to Clipboard</button>
|
||||
<span id="syncCodeTime" style="font-size:11px;color:#6b7280"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="syncImportSection" style="display:none;margin-top:10px">
|
||||
<textarea id="syncImportText" rows="3" placeholder="Paste sync code here…" style="width:100%;box-sizing:border-box;font-family:monospace;font-size:11px;resize:vertical;padding:6px;border:1px solid #d1d5db;border-radius:6px"></textarea>
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-top:6px;flex-wrap:wrap">
|
||||
<button class="btn btn-primary" id="btnApplySyncCode">Apply</button>
|
||||
<button class="btn" id="btnCancelSyncImport">Cancel</button>
|
||||
<label style="font-size:12px;display:flex;align-items:center;gap:4px;cursor:pointer">
|
||||
<input type="checkbox" id="syncForce"> Force (override if local is newer)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="syncStatus" style="margin-top:8px;font-size:12px;min-height:16px"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<h2>Transfer Data</h2>
|
||||
<p class="section-desc">Export all your identities, mappings, and settings to move between browsers. Encrypted exports require a password to decrypt.</p>
|
||||
<p class="section-desc">Export all your identities, mappings, and settings to a file. Encrypted exports require a password to decrypt.</p>
|
||||
<div class="bulk-actions">
|
||||
<button class="btn" id="btnExportAll">Export All (plain)</button>
|
||||
<button class="btn" id="btnExportEncrypted">Export Encrypted</button>
|
||||
<button class="btn" id="btnImportAll">Import</button>
|
||||
<button class="btn" id="btnImportAll">Import from File</button>
|
||||
<input type="file" id="fileImportAll" accept=".json,.ssbackup" hidden>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Storage from '../lib/storage.js';
|
||||
import SilentSendCrypto from '../lib/crypto.js';
|
||||
import SilentSendSync from '../lib/sync.js';
|
||||
import api from '../lib/browser-polyfill.js';
|
||||
|
||||
let mappings = [];
|
||||
@@ -18,11 +19,82 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
$('#autoRedactDetected').checked = settings.autoRedactDetected !== false;
|
||||
$('#autoAddDetected').checked = settings.autoAddDetected !== false;
|
||||
$('#maxLogEntries').value = settings.maxLogEntries || 200;
|
||||
$('#browserSync').checked = settings.browserSync === true;
|
||||
|
||||
renderMappings();
|
||||
renderDomains();
|
||||
renderLog();
|
||||
|
||||
// --- Sync section ---
|
||||
$('#browserSync').addEventListener('change', async (e) => {
|
||||
await Storage.saveSettings({ browserSync: e.target.checked });
|
||||
if (e.target.checked) {
|
||||
await SilentSendSync.pushToSyncStorage();
|
||||
setSyncStatus('Browser account sync enabled. Your settings will sync automatically.', 'ok');
|
||||
} else {
|
||||
setSyncStatus('Browser account sync disabled.', 'neutral');
|
||||
}
|
||||
});
|
||||
|
||||
$('#btnGenerateSyncCode').addEventListener('click', async () => {
|
||||
const code = await SilentSendSync.exportSyncCode();
|
||||
const data = await SilentSendSync._getAllData();
|
||||
$('#syncCodeText').value = code;
|
||||
$('#syncCodeDisplay').style.display = 'block';
|
||||
$('#syncImportSection').style.display = 'none';
|
||||
$('#syncCodeTime').textContent = 'Generated: ' + new Date(data.lastModified).toLocaleString();
|
||||
setSyncStatus('', 'neutral');
|
||||
});
|
||||
|
||||
$('#btnCopySyncCode').addEventListener('click', async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText($('#syncCodeText').value);
|
||||
$('#btnCopySyncCode').textContent = 'Copied!';
|
||||
setTimeout(() => { $('#btnCopySyncCode').textContent = 'Copy to Clipboard'; }, 2000);
|
||||
} catch {
|
||||
$('#syncCodeText').select();
|
||||
document.execCommand('copy');
|
||||
}
|
||||
});
|
||||
|
||||
$('#btnImportSyncCode').addEventListener('click', () => {
|
||||
$('#syncImportSection').style.display = 'block';
|
||||
$('#syncCodeDisplay').style.display = 'none';
|
||||
$('#syncImportText').focus();
|
||||
setSyncStatus('', 'neutral');
|
||||
});
|
||||
|
||||
$('#btnApplySyncCode').addEventListener('click', async () => {
|
||||
const code = $('#syncImportText').value.trim();
|
||||
if (!code) return;
|
||||
const force = $('#syncForce').checked;
|
||||
const result = await SilentSendSync.importSyncCode(code, { force });
|
||||
if (result.success) {
|
||||
setSyncStatus(`Imported successfully (data from ${result.importTime}).`, 'ok');
|
||||
$('#syncImportSection').style.display = 'none';
|
||||
$('#syncImportText').value = '';
|
||||
mappings = await Storage.getMappings();
|
||||
settings = await Storage.getSettings();
|
||||
$('#browserSync').checked = settings.browserSync === true;
|
||||
renderMappings();
|
||||
renderDomains();
|
||||
renderLog();
|
||||
} else if (result.skipped) {
|
||||
setSyncStatus(
|
||||
`Skipped: local data is newer (local: ${result.localTime} vs import: ${result.importTime}). Check "Force" to override.`,
|
||||
'warn'
|
||||
);
|
||||
} else {
|
||||
setSyncStatus(`Failed: ${result.reason}`, 'error');
|
||||
}
|
||||
});
|
||||
|
||||
$('#btnCancelSyncImport').addEventListener('click', () => {
|
||||
$('#syncImportSection').style.display = 'none';
|
||||
$('#syncImportText').value = '';
|
||||
setSyncStatus('', 'neutral');
|
||||
});
|
||||
|
||||
// Transfer data
|
||||
$('#btnExportAll').addEventListener('click', exportAllPlain);
|
||||
$('#btnExportEncrypted').addEventListener('click', exportAllEncrypted);
|
||||
@@ -393,6 +465,13 @@ async function importAll(e) {
|
||||
e.target.value = '';
|
||||
}
|
||||
|
||||
function setSyncStatus(msg, type) {
|
||||
const el = $('#syncStatus');
|
||||
if (!el) return;
|
||||
el.textContent = msg;
|
||||
el.style.color = type === 'ok' ? '#10b981' : type === 'warn' ? '#f59e0b' : type === 'error' ? '#dc2626' : '#6b7280';
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
|
||||
Reference in New Issue
Block a user