diff --git a/manifest.firefox.json b/manifest.firefox.json
index 31e9417..b20038a 100644
--- a/manifest.firefox.json
+++ b/manifest.firefox.json
@@ -12,7 +12,8 @@
"permissions": [
"storage",
"activeTab",
- "scripting"
+ "scripting",
+ "notifications"
],
"host_permissions": [
"https://claude.ai/*",
diff --git a/manifest.json b/manifest.json
index f16e63c..d73335f 100644
--- a/manifest.json
+++ b/manifest.json
@@ -6,7 +6,8 @@
"permissions": [
"storage",
"activeTab",
- "scripting"
+ "scripting",
+ "notifications"
],
"host_permissions": [
"https://claude.ai/*",
diff --git a/src/background/service-worker.js b/src/background/service-worker.js
index 7f04898..a93cb58 100644
--- a/src/background/service-worker.js
+++ b/src/background/service-worker.js
@@ -146,6 +146,13 @@ const messageHandlers = {
sendResponse({ count: tabCounts.get(tabId) || 0 });
},
+ async 'sync:notification-seen'() {
+ // Options page opened — clear the sync badge and pending notification flag
+ await api.storage.local.remove('ss_sync_notification');
+ api.action.setBadgeText({ text: '' });
+ api.action.setBadgeBackgroundColor({ color: '#6b7280' });
+ },
+
async 'update:settings'(message) {
await Storage.saveSettings(message.settings);
@@ -309,6 +316,33 @@ api.storage.onChanged.addListener(async (changes, areaName) => {
}
}
+ // When a sync operation applied new data, show badge + notification
+ if (areaName === 'local' && changes.ss_sync_notification?.newValue) {
+ const notif = changes.ss_sync_notification.newValue;
+ const sourceLabel = {
+ 'file': 'sync folder',
+ 'browser-sync': 'browser account sync',
+ 'code': 'sync code import',
+ }[notif.source] || 'sync';
+
+ // Purple badge — persists until Options is opened
+ api.action.setBadgeText({ text: 'SYN' });
+ api.action.setBadgeBackgroundColor({ color: '#7c3aed' });
+
+ // Desktop notification
+ try {
+ api.notifications.create('ss-sync-applied', {
+ type: 'basic',
+ iconUrl: 'icons/icon48.svg',
+ title: 'Silent Send — Settings Synced',
+ message: `Settings updated via ${sourceLabel}. Open Options to review.`,
+ priority: 1,
+ });
+ } catch (e) {
+ // Notifications permission not granted — badge is still visible
+ }
+ }
+
// When sync storage changes (another device pushed new data), pull it into local
if (areaName === 'sync' && (changes.ss_sync_meta || Object.keys(changes).some(k => k.startsWith('ss_sync_chunk_')))) {
const settings = await Storage.getSettings();
@@ -318,6 +352,14 @@ api.storage.onChanged.addListener(async (changes, areaName) => {
}
});
+// Clicking a sync notification opens the Options page
+api.notifications.onClicked.addListener((notificationId) => {
+ if (notificationId === 'ss-sync-applied') {
+ api.runtime.openOptionsPage();
+ api.notifications.clear(notificationId);
+ }
+});
+
// --- Set initial state ---
api.runtime.onInstalled.addListener(async () => {
api.action.setBadgeBackgroundColor({ color: '#6b7280' });
@@ -329,6 +371,14 @@ api.runtime.onInstalled.addListener(async () => {
(async () => {
const settings = await Storage.getSettings();
await updateIcon(settings);
+
+ // Restore the SYN badge if the user hasn't opened Options since the last sync
+ const stored = await api.storage.local.get('ss_sync_notification');
+ if (stored.ss_sync_notification) {
+ api.action.setBadgeText({ text: 'SYN' });
+ api.action.setBadgeBackgroundColor({ color: '#7c3aed' });
+ }
+
if (settings.browserSync) {
await SilentSendSync.pullFromSyncStorage();
}
diff --git a/src/lib/sync.js b/src/lib/sync.js
index e2a1540..6a6cfd6 100644
--- a/src/lib/sync.js
+++ b/src/lib/sync.js
@@ -65,7 +65,7 @@ const SilentSendSync = {
}
}
- await this._applyData(data);
+ await this._applyData(data, 'code');
return { success: true, importTime: new Date(data.lastModified).toLocaleString() };
} catch (e) {
return { success: false, reason: e.message };
@@ -131,7 +131,7 @@ const SilentSendSync = {
const json = chunkKeys.map(k => chunkResult[k] || '').join('');
const data = JSON.parse(json);
- await this._applyData(data);
+ await this._applyData(data, 'browser-sync');
return { imported: true, time: new Date(data.lastModified).toLocaleString() };
} catch (e) {
console.warn('[Silent Send] pullFromSyncStorage failed:', e);
@@ -154,8 +154,12 @@ const SilentSendSync = {
};
},
- async _applyData(data) {
- const toSet = { ss_lastModified: data.lastModified };
+ async _applyData(data, source = 'unknown') {
+ const toSet = {
+ ss_lastModified: data.lastModified,
+ // Signal the service worker to show a badge/notification
+ ss_sync_notification: { source, time: Date.now() },
+ };
if (data.identity !== undefined) toSet.ss_identity = data.identity;
if (data.mappings !== undefined) toSet.ss_mappings = data.mappings;
if (data.settings !== undefined) toSet.ss_settings = data.settings;
diff --git a/src/options/options.html b/src/options/options.html
index e283434..0634b24 100644
--- a/src/options/options.html
+++ b/src/options/options.html
@@ -147,8 +147,9 @@
Pick the same folder in each browser once. Changes are written to
silent-send-sync.json automatically whenever settings change,
and read back whenever this page is opened or regains focus.
- Use a shared folder (Dropbox, OneDrive, iCloud Drive, or any network share)
- for cross-computer sync too.
+ Works with any cloud storage that has a desktop sync client —
+ Dropbox, OneDrive, Google Drive, iCloud Drive, Box, pCloud, Nextcloud,
+ Synology Drive, or any network share. Just pick the cloud-synced folder.