add aden's requested changes

This commit is contained in:
2026-06-10 11:00:09 +09:30
parent ec94376e1f
commit 57a1965a6d
13 changed files with 403 additions and 44 deletions
+71 -13
View File
@@ -1,4 +1,5 @@
import browser from "webextension-polyfill";
import isEqual from "lodash/isEqual";
/** Matches the contract in docs/CLOUD_SETTINGS_SYNC_SERVER.md */
export const CLOUD_SETTINGS_SYNC_SCHEMA_VERSION = 1;
@@ -17,6 +18,13 @@ export const BSPLUS_CLOUD_KNOWN_REMOTE_UPDATED_AT_KEY =
export const BSPLUS_PENDING_THEME_ENSURE_AFTER_CLOUD_KEY =
"bsplus_pending_theme_ensure_after_cloud";
/**
* Client-only: normalized syncable storage last acked by a successful PUT.
* Never uploaded; used to compute sparse upload patches.
*/
export const BSPLUS_CLOUD_LAST_UPLOADED_SNAPSHOT_KEY =
"bsplus_cloud_settings_last_uploaded_snapshot";
/**
* Never uploaded to the cloud backup (OAuth and legacy keys).
* IndexedDB (e.g. Global Searchs `betterseqta-index` database) is not part of
@@ -48,6 +56,7 @@ export const SENSITIVE_DEVICE_STORAGE_KEY_PREFIXES = [
const CLIENT_ONLY_CLOUD_KEYS_EXACT = [
BSPLUS_CLOUD_KNOWN_REMOTE_UPDATED_AT_KEY,
BSPLUS_CLOUD_LAST_UPLOADED_SNAPSHOT_KEY,
"bsplus_lastCloudPoll",
BSPLUS_PENDING_THEME_ENSURE_AFTER_CLOUD_KEY,
] as const;
@@ -118,30 +127,79 @@ export function normalizeThemeIdForSync(raw: unknown): string {
return raw.trim();
}
export function buildUploadPayload(all: Record<string, unknown>): {
schemaVersion: number;
themeId: string;
data: Record<string, unknown>;
} {
/** Filter omit lists and migrate legacy keys → full syncable map for diff/export. */
export function normalizeStorageForSync(all: Record<string, unknown>): Record<string, unknown> {
const filtered: Record<string, unknown> = {};
for (const [k, v] of Object.entries(all)) {
if (shouldOmitKeyFromCloudPayload(k)) continue;
filtered[k] = v;
}
const data = migrateLegacyToPluginSettings(filtered);
const themeId = normalizeThemeIdForSync(all.selectedTheme);
return migrateLegacyToPluginSettings(filtered);
}
/** Keys in `current` whose values differ from `baseline` (sparse PUT body). */
export function diffSyncableStorage(
current: Record<string, unknown>,
baseline: Record<string, unknown>,
): Record<string, unknown> {
const patch: Record<string, unknown> = {};
for (const [key, value] of Object.entries(current)) {
if (!isEqual(value, baseline[key])) {
patch[key] = value;
}
}
return patch;
}
export type CloudSettingsUploadEnvelope = {
schemaVersion: number;
themeId: string;
data: Record<string, unknown>;
};
/** Sparse upload envelope, or null when nothing changed vs baseline. */
export function buildUploadPatch(
all: Record<string, unknown>,
baseline: Record<string, unknown>,
): CloudSettingsUploadEnvelope | null {
const normalized = normalizeStorageForSync(all);
const data = diffSyncableStorage(normalized, baseline);
if (Object.keys(data).length === 0) return null;
return {
schemaVersion: CLOUD_SETTINGS_SYNC_SCHEMA_VERSION,
themeId,
themeId: normalizeThemeIdForSync(all.selectedTheme),
data,
};
}
export async function getSnapshotForUpload(): Promise<{
schemaVersion: number;
themeId: string;
data: Record<string, unknown>;
}> {
/** Full normalized snapshot (dev export / debugging). */
export function buildUploadPayload(all: Record<string, unknown>): CloudSettingsUploadEnvelope {
const data = normalizeStorageForSync(all);
return {
schemaVersion: CLOUD_SETTINGS_SYNC_SCHEMA_VERSION,
themeId: normalizeThemeIdForSync(all.selectedTheme),
data,
};
}
export async function getLastUploadedSnapshot(): Promise<Record<string, unknown> | null> {
const stored = await browser.storage.local.get(BSPLUS_CLOUD_LAST_UPLOADED_SNAPSHOT_KEY);
const snapshot = stored[BSPLUS_CLOUD_LAST_UPLOADED_SNAPSHOT_KEY];
if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot)) return null;
return snapshot as Record<string, unknown>;
}
export async function saveLastUploadedSnapshot(
snapshot: Record<string, unknown>,
): Promise<void> {
await browser.storage.local.set({ [BSPLUS_CLOUD_LAST_UPLOADED_SNAPSHOT_KEY]: snapshot });
}
export async function clearLastUploadedSnapshot(): Promise<void> {
await browser.storage.local.remove(BSPLUS_CLOUD_LAST_UPLOADED_SNAPSHOT_KEY);
}
export async function getSnapshotForUpload(): Promise<CloudSettingsUploadEnvelope> {
const all = await browser.storage.local.get();
return buildUploadPayload(all as Record<string, unknown>);
}