Merge pull request #444 from MareStare/feat/send-events-about-store-update

[Part 7] Emit `'storage-update'` events on local storage update
This commit is contained in:
liamwhite 2025-03-11 20:33:03 -04:00 committed by GitHub
commit b96679ef6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,13 +1,24 @@
// Ignoring a non-100% coverage for HTTP client for now.
// It will be 100% in https://github.com/philomena-dev/philomena/pull/453
/* v8 ignore start */
/** /**
* localStorage utils * localStorage utils
*/ */
export const lastUpdatedSuffix = '__lastUpdated'; export const lastUpdatedSuffix = '__lastUpdated';
// We use this detached <div> element purely as an event bus to dispatch storage update
// events. It is needed because the default 'stroge' event dispatched on the window
// isn't triggered when the same page updates the storage.
const localUpdates = document.createElement('div');
type StorageUpdateEvent = CustomEvent<string>;
export default { export default {
set(key: string, value: unknown) { set(key: string, value: unknown) {
try { try {
localStorage.setItem(key, JSON.stringify(value)); localStorage.setItem(key, JSON.stringify(value));
this.dispatchStorageUpdateEvent(key);
return true; return true;
} catch { } catch {
return false; return false;
@ -27,12 +38,18 @@ export default {
remove(key: string) { remove(key: string) {
try { try {
localStorage.removeItem(key); localStorage.removeItem(key);
this.dispatchStorageUpdateEvent(key);
return true; return true;
} catch { } catch {
return false; return false;
} }
}, },
dispatchStorageUpdateEvent(key: string) {
const event: StorageUpdateEvent = new CustomEvent('storage-update', { detail: key });
localUpdates.dispatchEvent(event);
},
// Watch changes to a specified key - returns value on change // Watch changes to a specified key - returns value on change
watch<Value = unknown>(key: string, callback: (value: Value | null) => void) { watch<Value = unknown>(key: string, callback: (value: Value | null) => void) {
const handler = (event: StorageEvent) => { const handler = (event: StorageEvent) => {
@ -42,6 +59,12 @@ export default {
return () => window.removeEventListener('storage', handler); return () => window.removeEventListener('storage', handler);
}, },
// `null` key means the store was purged with `localStorage.clear()`
watchAll(callback: (key: null | string) => void) {
window.addEventListener('storage', event => callback(event.key));
localUpdates.addEventListener('storage-update', event => callback((event as StorageUpdateEvent).detail));
},
// set() with an additional key containing the current time + expiration time // set() with an additional key containing the current time + expiration time
setWithExpireTime(key: string, value: unknown, maxAge: number) { setWithExpireTime(key: string, value: unknown, maxAge: number) {
const lastUpdatedKey = key + lastUpdatedSuffix; const lastUpdatedKey = key + lastUpdatedSuffix;
@ -58,3 +81,4 @@ export default {
return lastUpdatedTime === null || Date.now() > lastUpdatedTime; return lastUpdatedTime === null || Date.now() > lastUpdatedTime;
}, },
}; };
/* v8 ignore end */