163 lines
4.8 KiB
JavaScript
163 lines
4.8 KiB
JavaScript
const KIOSK_QUERY_VALUE = '1';
|
|
const REVISION_KEY = 'analyzer_kiosk_backup_revision_v1';
|
|
const SYNC_INTERVAL_MS = 2000;
|
|
|
|
function isKioskBrowser() {
|
|
try {
|
|
return new URLSearchParams(globalThis.location?.search || '').get('kiosk') === KIOSK_QUERY_VALUE;
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function isPhoenixStorageKey(key) {
|
|
return key !== REVISION_KEY && (
|
|
key.startsWith('analyzer_') ||
|
|
key.startsWith('MIGRATE_') ||
|
|
[
|
|
'ppm_din_mode',
|
|
'calibration_notice_seen_v1',
|
|
'recorder_warning_ack_v1',
|
|
'ppm_din_loudness_warn_ack_v1',
|
|
'migrate_rta_peak_fall_default_v1',
|
|
'migrate_vu_offset_default_zero_v2',
|
|
'migrate_lr_delay_zero_to_0_05_v1',
|
|
].includes(key)
|
|
);
|
|
}
|
|
|
|
function collectStorage() {
|
|
const storage = {};
|
|
try {
|
|
for (let index = 0; index < localStorage.length; index += 1) {
|
|
const key = localStorage.key(index);
|
|
if (!key || !isPhoenixStorageKey(key)) continue;
|
|
const value = localStorage.getItem(key);
|
|
if (value !== null) storage[key] = value;
|
|
}
|
|
} catch (_) {}
|
|
return storage;
|
|
}
|
|
|
|
function storageFingerprint(storage) {
|
|
return JSON.stringify(Object.entries(storage).sort(([left], [right]) => left.localeCompare(right)));
|
|
}
|
|
|
|
function localRevision() {
|
|
try {
|
|
const value = Number(localStorage.getItem(REVISION_KEY));
|
|
return Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
} catch (_) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
function setLocalRevision(revision) {
|
|
try { localStorage.setItem(REVISION_KEY, String(revision)); } catch (_) {}
|
|
}
|
|
|
|
function applyStorage(storage, revision) {
|
|
try {
|
|
const remove = [];
|
|
for (let index = 0; index < localStorage.length; index += 1) {
|
|
const key = localStorage.key(index);
|
|
if (key && isPhoenixStorageKey(key)) remove.push(key);
|
|
}
|
|
remove.forEach((key) => localStorage.removeItem(key));
|
|
Object.entries(storage || {}).forEach(([key, value]) => {
|
|
if (isPhoenixStorageKey(key) && typeof value === 'string') localStorage.setItem(key, value);
|
|
});
|
|
setLocalRevision(revision);
|
|
return true;
|
|
} catch (error) {
|
|
console.warn('Kiosk browser restore failed:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function apiUrl() {
|
|
const hostname = String(globalThis.location?.hostname || '127.0.0.1');
|
|
return `http://${hostname}:8789/api/v1/kiosk-browser-config`;
|
|
}
|
|
|
|
async function readRemote() {
|
|
const response = await fetch(`${apiUrl()}?_=${Date.now()}`, { cache: 'no-store' });
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
return response.json();
|
|
}
|
|
|
|
async function writeRemote(baseRevision, storage) {
|
|
const response = await fetch(apiUrl(), {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json' },
|
|
cache: 'no-store',
|
|
body: JSON.stringify({ baseRevision, storage }),
|
|
});
|
|
const payload = await response.json().catch(() => null);
|
|
if (!response.ok) {
|
|
const error = new Error(payload?.error || `HTTP ${response.status}`);
|
|
error.status = response.status;
|
|
throw error;
|
|
}
|
|
return payload;
|
|
}
|
|
|
|
export async function setupKioskBrowserBackup() {
|
|
if (!isKioskBrowser()) return true;
|
|
|
|
let remote;
|
|
try {
|
|
remote = await readRemote();
|
|
} catch (error) {
|
|
console.warn('Kiosk browser backup unavailable:', error);
|
|
return true;
|
|
}
|
|
|
|
let revision = Number(remote?.revision) || 0;
|
|
const storedRevision = localRevision();
|
|
if (revision > storedRevision) {
|
|
if (applyStorage(remote?.storage || {}, revision)) {
|
|
globalThis.location?.reload?.();
|
|
return false;
|
|
}
|
|
} else if (revision === 0 && Object.keys(remote?.storage || {}).length === 0) {
|
|
try {
|
|
const result = await writeRemote(0, collectStorage());
|
|
revision = Number(result?.revision) || 1;
|
|
setLocalRevision(revision);
|
|
} catch (error) {
|
|
console.warn('Initial kiosk browser backup failed:', error);
|
|
}
|
|
} else {
|
|
setLocalRevision(revision);
|
|
}
|
|
|
|
let fingerprint = storageFingerprint(collectStorage());
|
|
let running = false;
|
|
globalThis.setInterval(async () => {
|
|
if (running) return;
|
|
running = true;
|
|
try {
|
|
const latest = await readRemote();
|
|
const latestRevision = Number(latest?.revision) || 0;
|
|
if (latestRevision > revision) {
|
|
if (applyStorage(latest?.storage || {}, latestRevision)) globalThis.location?.reload?.();
|
|
return;
|
|
}
|
|
const storage = collectStorage();
|
|
const nextFingerprint = storageFingerprint(storage);
|
|
if (nextFingerprint !== fingerprint) {
|
|
const result = await writeRemote(revision, storage);
|
|
revision = Number(result?.revision) || (revision + 1);
|
|
setLocalRevision(revision);
|
|
fingerprint = nextFingerprint;
|
|
}
|
|
} catch (error) {
|
|
if (error?.status !== 409) console.warn('Kiosk browser backup sync failed:', error);
|
|
} finally {
|
|
running = false;
|
|
}
|
|
}, SYNC_INTERVAL_MS);
|
|
return true;
|
|
}
|