Merge pull request #66 from outis1one/claude/read-repo-yLNcT
Claude/read repo y l nc t
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Silent Send",
|
"name": "Silent Send",
|
||||||
"version": "0.9.33",
|
"version": "0.9.34",
|
||||||
"description": "Intercepts personal info and substitutes it with user-defined replacements before sending to AI services.",
|
"description": "Intercepts personal info and substitutes it with user-defined replacements before sending to AI services.",
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Silent Send",
|
"name": "Silent Send",
|
||||||
"version": "0.9.33",
|
"version": "0.9.34",
|
||||||
"description": "Intercepts personal info and substitutes it with user-defined replacements before sending to AI services.",
|
"description": "Intercepts personal info and substitutes it with user-defined replacements before sending to AI services.",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"storage",
|
"storage",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "silent-send",
|
"name": "silent-send",
|
||||||
"version": "0.9.33",
|
"version": "0.9.34",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "Browser extension that substitutes personal data before sending to AI services",
|
"description": "Browser extension that substitutes personal data before sending to AI services",
|
||||||
|
|||||||
@@ -472,6 +472,20 @@ api.alarms.onAlarm.addListener(async (alarm) => {
|
|||||||
const result = await SilentSendSync.performAutoSync();
|
const result = await SilentSendSync.performAutoSync();
|
||||||
if (result.pulled) {
|
if (result.pulled) {
|
||||||
console.log('[Silent Send] Auto-sync pulled new data');
|
console.log('[Silent Send] Auto-sync pulled new data');
|
||||||
|
// Broadcast decrypted data to all open content scripts so
|
||||||
|
// substitution works immediately without a page reload.
|
||||||
|
const mappings = await Storage.getMappings();
|
||||||
|
const identity = await Storage.getIdentity();
|
||||||
|
const settings = await Storage.getSettings();
|
||||||
|
const allPatterns = [...BUILTIN_URL_PATTERNS];
|
||||||
|
const customDomains = settings.customDomains || [];
|
||||||
|
for (const domain of customDomains) allPatterns.push(domain + '/*');
|
||||||
|
for (const urlPattern of allPatterns) {
|
||||||
|
const tabs = await api.tabs.query({ url: urlPattern }).catch(() => []);
|
||||||
|
for (const tab of tabs) {
|
||||||
|
api.tabs.sendMessage(tab.id, { type: 'vault:unlocked', mappings, identity, settings }).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
console.warn('[Silent Send] Auto-sync error:', result.error);
|
console.warn('[Silent Send] Auto-sync error:', result.error);
|
||||||
|
|||||||
+13
-1
@@ -226,7 +226,19 @@ const Storage = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async saveProfiles(profiles) {
|
async saveProfiles(profiles) {
|
||||||
await this._writeSecure(KEYS.IDENTITY, { profiles }, { ss_lastModified: Date.now() });
|
// Only advance ss_lastModified if at least one profile contains real PII.
|
||||||
|
// Creating the default empty profile on a fresh install should not count
|
||||||
|
// as "this browser has data" — that would make the pull timestamp check
|
||||||
|
// think remote Gist data is stale and skip the import.
|
||||||
|
const hasRealData = (profiles || []).some(p =>
|
||||||
|
(p.names || []).some(n => n.real?.trim()) ||
|
||||||
|
(p.emails || []).some(e => e.real?.trim()) ||
|
||||||
|
(p.usernames || []).some(u => u.real?.trim()) ||
|
||||||
|
(p.phones || []).some(ph => ph.real?.trim()) ||
|
||||||
|
p.catchAllEmail?.trim()
|
||||||
|
);
|
||||||
|
const extras = hasRealData ? { ss_lastModified: Date.now() } : {};
|
||||||
|
await this._writeSecure(KEYS.IDENTITY, { profiles }, extras);
|
||||||
},
|
},
|
||||||
|
|
||||||
async addProfile(name) {
|
async addProfile(name) {
|
||||||
|
|||||||
+23
-7
@@ -709,6 +709,12 @@ const SilentSendSync = {
|
|||||||
try {
|
try {
|
||||||
const data = await this._getAllData();
|
const data = await this._getAllData();
|
||||||
|
|
||||||
|
// Don't push if this browser has no saved data — it would overwrite
|
||||||
|
// real data on the Gist with an empty payload.
|
||||||
|
if (data.lastModified === 0) {
|
||||||
|
return { success: false, reason: 'Nothing to push — no data has been saved on this browser yet. Pull first.' };
|
||||||
|
}
|
||||||
|
|
||||||
// Encrypt if enabled
|
// Encrypt if enabled
|
||||||
const encResult = await this._encryptForSync(data);
|
const encResult = await this._encryptForSync(data);
|
||||||
if (encResult.needsAuth) {
|
if (encResult.needsAuth) {
|
||||||
@@ -745,7 +751,8 @@ const SilentSendSync = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const json = await resp.json();
|
const json = await resp.json();
|
||||||
await api.storage.local.set({ ss_gist_id: json.id });
|
const now = Date.now();
|
||||||
|
await api.storage.local.set({ ss_gist_id: json.id, ss_last_push_time: now, ss_last_push_source: 'gist' });
|
||||||
return { success: true, gistId: json.id };
|
return { success: true, gistId: json.id };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { success: false, reason: e.message };
|
return { success: false, reason: e.message };
|
||||||
@@ -804,9 +811,14 @@ const SilentSendSync = {
|
|||||||
const rawResp = await fetch(file.raw_url);
|
const rawResp = await fetch(file.raw_url);
|
||||||
let data = JSON.parse(await rawResp.text());
|
let data = JSON.parse(await rawResp.text());
|
||||||
|
|
||||||
// Check if new data exists before requiring auth
|
// Check if new data exists before requiring auth.
|
||||||
|
// remoteMod === 0 means the Gist was clobbered by an empty push — treat
|
||||||
|
// as stale rather than skipping so the user sees a useful error.
|
||||||
const local = await this._getAllData();
|
const local = await this._getAllData();
|
||||||
const remoteMod = data._ssEncrypted ? data.lastModified : data.lastModified;
|
const remoteMod = data.lastModified || 0;
|
||||||
|
if (remoteMod === 0) {
|
||||||
|
return { success: false, reason: 'Gist contains no data (timestamp is 0). Push from the source browser first.' };
|
||||||
|
}
|
||||||
if (remoteMod <= (local.lastModified || 0)) {
|
if (remoteMod <= (local.lastModified || 0)) {
|
||||||
return { success: true, imported: false };
|
return { success: true, imported: false };
|
||||||
}
|
}
|
||||||
@@ -839,6 +851,10 @@ const SilentSendSync = {
|
|||||||
try {
|
try {
|
||||||
const data = await this._getAllData();
|
const data = await this._getAllData();
|
||||||
|
|
||||||
|
if (data.lastModified === 0) {
|
||||||
|
return { success: false, reason: 'Nothing to push — no data has been saved on this browser yet. Pull first.' };
|
||||||
|
}
|
||||||
|
|
||||||
const encResult = await this._encryptForSync(data);
|
const encResult = await this._encryptForSync(data);
|
||||||
if (encResult.needsAuth) {
|
if (encResult.needsAuth) {
|
||||||
return { success: false, needsAuth: true, reason: 'Authentication required.' };
|
return { success: false, needsAuth: true, reason: 'Authentication required.' };
|
||||||
@@ -1032,9 +1048,9 @@ const SilentSendSync = {
|
|||||||
const pullResult = await this.pullFromGist(config.gistToken);
|
const pullResult = await this.pullFromGist(config.gistToken);
|
||||||
if (pullResult.success && pullResult.imported) pulled = true;
|
if (pullResult.success && pullResult.imported) pulled = true;
|
||||||
|
|
||||||
// Push if local data changed since last push
|
// Push if local data changed since last push (skip if no real data yet)
|
||||||
const local = await this._getAllData();
|
const local = await this._getAllData();
|
||||||
if (!config.lastPush || local.lastModified > config.lastPush) {
|
if (local.lastModified > 0 && (!config.lastPush || local.lastModified > config.lastPush)) {
|
||||||
const pushResult = await this.pushToGist(config.gistToken);
|
const pushResult = await this.pushToGist(config.gistToken);
|
||||||
if (pushResult.success) {
|
if (pushResult.success) {
|
||||||
pushed = true;
|
pushed = true;
|
||||||
@@ -1047,9 +1063,9 @@ const SilentSendSync = {
|
|||||||
const pullResult = await this.pullFromUrl({ url: config.url, headers });
|
const pullResult = await this.pullFromUrl({ url: config.url, headers });
|
||||||
if (pullResult.success && pullResult.imported) pulled = true;
|
if (pullResult.success && pullResult.imported) pulled = true;
|
||||||
|
|
||||||
// Push if local data changed since last push
|
// Push if local data changed since last push (skip if no real data yet)
|
||||||
const local = await this._getAllData();
|
const local = await this._getAllData();
|
||||||
if (!config.lastPush || local.lastModified > config.lastPush) {
|
if (local.lastModified > 0 && (!config.lastPush || local.lastModified > config.lastPush)) {
|
||||||
const pushResult = await this.pushToUrl({
|
const pushResult = await this.pushToUrl({
|
||||||
url: config.url,
|
url: config.url,
|
||||||
method: config.httpMethod || 'PUT',
|
method: config.httpMethod || 'PUT',
|
||||||
|
|||||||
@@ -161,11 +161,17 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
// --- GitHub Gist sync ---
|
// --- GitHub Gist sync ---
|
||||||
// Restore saved token (session only — never persisted to storage)
|
// Restore saved token (session only — never persisted to storage)
|
||||||
{
|
{
|
||||||
const stored = await api.storage.local.get(['ss_gist_id', 'ss_last_pull_time', 'ss_last_pull_source']);
|
const stored = await api.storage.local.get([
|
||||||
|
'ss_gist_id', 'ss_last_pull_time', 'ss_last_pull_source',
|
||||||
|
'ss_last_push_time', 'ss_last_push_source',
|
||||||
|
]);
|
||||||
if (stored.ss_gist_id) {
|
if (stored.ss_gist_id) {
|
||||||
let status = `Gist ID: ${stored.ss_gist_id.slice(0, 12)}…`;
|
let status = `Gist ID: ${stored.ss_gist_id.slice(0, 12)}…`;
|
||||||
|
if (stored.ss_last_push_time && stored.ss_last_push_source === 'gist') {
|
||||||
|
status += ` · Pushed: ${new Date(stored.ss_last_push_time).toLocaleString()}`;
|
||||||
|
}
|
||||||
if (stored.ss_last_pull_time && stored.ss_last_pull_source === 'gist') {
|
if (stored.ss_last_pull_time && stored.ss_last_pull_source === 'gist') {
|
||||||
status += ` · Last pulled: ${new Date(stored.ss_last_pull_time).toLocaleString()}`;
|
status += ` · Pulled: ${new Date(stored.ss_last_pull_time).toLocaleString()}`;
|
||||||
}
|
}
|
||||||
setGistSyncStatus(status, 'ok');
|
setGistSyncStatus(status, 'ok');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user