stackchain-dashboard/frontend/private-device-data.js
timmy b4dc785dd8
All checks were successful
CI / lint (pull_request) Successful in 37s
CI / build-frontend (pull_request) Successful in 6s
security: purge data after remote session revocation (#337)
2026-08-08 20:17:13 +00:00

62 lines
2.4 KiB
JavaScript

(function (root, factory) {
if (typeof module !== 'undefined' && module.exports) module.exports = factory;
else root.stackchainPrivateDeviceData = factory({
localStorage: root.localStorage,
sessionStorage: root.sessionStorage,
indexedDB: root.indexedDB,
caches: root.caches,
serviceWorker: root.navigator?.serviceWorker,
MessageChannel: root.MessageChannel,
});
})(typeof window !== 'undefined' ? window : this, function createPrivateDeviceDataPurger({
localStorage, sessionStorage, indexedDB, caches, serviceWorker, MessageChannel,
}) {
function removeOwnedStorage(storage) {
if (!storage) return;
const keys = [];
for (let index = 0; index < storage.length; index += 1) {
const key = storage.key(index);
if (key?.startsWith('stackchain.')) keys.push(key);
}
keys.forEach(key => storage.removeItem(key));
}
async function stopWorkerOutbox() {
const registration = await serviceWorker?.ready;
if (!registration?.active || !MessageChannel) return;
await new Promise((resolve, reject) => {
const channel = new MessageChannel();
const timeout = setTimeout(() => reject(new Error('Background outbox purge timed out.')), 3000);
channel.port1.onmessage = event => {
clearTimeout(timeout);
if (event.data?.ok) resolve();
else reject(new Error(event.data?.error || 'Background outbox purge failed.'));
};
registration.active.postMessage({ type: 'stackchain-purge-outbox' }, [channel.port2]);
});
}
function deletePrivateOutbox() {
if (!indexedDB) return Promise.resolve();
return new Promise((resolve, reject) => {
let request;
try { request = indexedDB.deleteDatabase('stackchain-background-outbox-v1'); }
catch (error) { reject(error); return; }
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error || new Error('IndexedDB deletion failed.'));
request.onblocked = () => reject(new Error('IndexedDB deletion was blocked.'));
});
}
return async function clearPrivateDeviceData() {
removeOwnedStorage(localStorage);
if (sessionStorage !== localStorage) removeOwnedStorage(sessionStorage);
await stopWorkerOutbox();
await deletePrivateOutbox();
const keys = await caches?.keys?.() || [];
await Promise.all(
keys.filter(key => key.startsWith('stackchain-dashboard-')).map(key => caches.delete(key))
);
};
});