Fix fresh-install pull blocked by saveSettings timestamp

After a browser restart with no extension data, enabling encryption calls
saveSettings() which writes ss_lastModified = Date.now(). All pull paths
then compare that fresh timestamp against the remote's older timestamp,
see local >= remote, and skip the pull with "already on latest version" —
even though local is completely empty.

Fix: add _hasRealLocalData() which returns true only if local storage
contains actual identity profiles or mappings with real values. The
timestamp skip condition now requires BOTH a newer local timestamp AND
real local data. An empty/fresh install always falls through to pull.

Affects pullFromGist, pullFromUrl, pullFromSyncStorage, and importSyncCode.

https://claude.ai/code/session_01CwcZK8nqL8pyBH9AxDs9qo
This commit is contained in:
Claude
2026-04-01 23:45:10 +00:00
parent dbf178937f
commit e93e27012e
+23 -4
View File
@@ -613,7 +613,7 @@ const SilentSendSync = {
if (!force) {
const local = await this._getAllData();
if (local.lastModified && local.lastModified >= data.lastModified) {
if (local.lastModified && local.lastModified >= data.lastModified && this._hasRealLocalData(local)) {
return {
success: false,
skipped: true,
@@ -676,7 +676,7 @@ const SilentSendSync = {
// Check if there's new data before requiring auth
const local = await this._getAllData();
if (local.lastModified && local.lastModified >= syncMeta.lastModified) return null;
if (local.lastModified && local.lastModified >= syncMeta.lastModified && this._hasRealLocalData(local)) return null;
// New data exists — reassemble
const chunkKeys = Array.from({ length: syncMeta.chunks }, (_, i) => SYNC_KEY_PREFIX + i);
@@ -819,7 +819,7 @@ const SilentSendSync = {
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) && this._hasRealLocalData(local)) {
return { success: true, imported: false };
}
@@ -882,7 +882,7 @@ const SilentSendSync = {
const local = await this._getAllData();
const remoteMod = data._ssEncrypted ? data.lastModified : data.lastModified;
if (remoteMod <= (local.lastModified || 0)) {
if (remoteMod <= (local.lastModified || 0) && this._hasRealLocalData(local)) {
return { success: true, imported: false };
}
@@ -908,6 +908,25 @@ const SilentSendSync = {
// Internal helpers
// ----------------------------------------------------------------
/**
* Returns true if local storage contains real user data (identity or mappings).
* Used to bypass the lastModified timestamp check on a fresh install where
* saveSettings() has written a recent timestamp even though there is no PII
* configured yet. Without this, a fresh browser after restart would always
* skip pulls because its saveSettings timestamp > the remote's older timestamp.
*/
_hasRealLocalData(local) {
if ((local.mappings || []).length > 0) return true;
const profiles = local.identity?.profiles || [];
return 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()
);
},
async _getAllData() {
// Use dynamic import to avoid circular dependency
const StorageModule = (await import('./storage.js')).default;