assets/js/services/offline-session-service.js

import vault, { purgeLegacyOfflineStorage } from './offline-vault-service.js';
import { updateTrustedDevice } from './offline-runtime-service.js';

/** Trust survives navigation and session timeout; explicit exit and actor changes remove it. */
export function startOfflineSessionObserver() {
    const shell = !!document.querySelector('meta[name="kmp-offline-shell"]');
    const inspect = async () => {
        await purgeLegacyOfflineStorage();
        const meta = document.querySelector('meta[name="kmp-offline-session"]');
        let context = null;
        try { context = meta ? JSON.parse(meta.content) : null; } catch { await vault.clear(); return; }
        if (!shell && context && !context.impersonating) sessionStorage.removeItem('kmp.offline.resume');
        const record = await vault.metadata();
        if (!record) return;
        if (context && (context.impersonating || context.owner !== record.owner || context.epoch !== record.epoch)) {
            await vault.clear(); return;
        }
        if (!context && record.wrapper.method !== 'trusted' && !shell) { await vault.clear(); return; }
        // Deliberate browser trust permits local opening even when a nominal connection is unusable.
        // The coordinator verifies the live account before refreshing or sending any requests.
        await updateTrustedDevice();
    };
    const retry = () => inspect().catch(() => vault.lock(false));
    retry();
    navigator.serviceWorker?.getRegistration?.('/').then(registration => registration?.update()).catch(() => {});
    window.addEventListener('pagehide', () => { if (!vault.trusted) vault.lock(); });
    window.addEventListener('pageshow', event => {
        if (!event.persisted) return;
        vault.lock(false);
        if (!shell) location.replace(navigator.onLine ? location.href : '/offline');
        else retry();
    });
    window.addEventListener('online', retry);
    document.addEventListener('visibilitychange', () => { if (!document.hidden) retry(); });
    setInterval(() => { if (!document.hidden && navigator.onLine) retry(); }, 60000);
    window.addEventListener('rsvp-cache:rsvp-queued', () => updateTrustedDevice(true).catch(() => {}));
    const exitPath = path => /\/members\/(?:logout|impersonate|stop-impersonating)(?:\/|$)/i.test(path);
    document.addEventListener('submit', event => {
        const path = new URL(event.target.action || location.href, location.origin).pathname;
        if (/\/members\/logout\/?$/i.test(path)) vault.signOut();
        else if (exitPath(path)) vault.clear().catch(() => vault.lock());
    }, true);
    document.addEventListener('click', event => {
        const link = event.target.closest?.('a[href]');
        if (!link) return;
        const path = new URL(link.href, location.origin).pathname;
        if (/\/members\/logout\/?$/i.test(path)) vault.signOut();
        else if (exitPath(path)) vault.clear().catch(() => vault.lock());
    }, true);
    navigator.serviceWorker?.addEventListener('message', event => {
        if (event.data?.type === 'OFFLINE_SECURITY_UPDATE') retry();
    });
    window.addEventListener('storage', event => {
        if (event.key === 'kmp.offline.lock' || event.key === 'kmp.offline.revoked') vault.lock(false);
    });
}