You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
53 lines
1.6 KiB
53 lines
1.6 KiB
import type { SyncStorageAdapter } from '../types';
|
|
import { BROWSER_EVENT_STORAGE, STORAGE_ADAPTER_LOCAL } from '../consts';
|
|
|
|
function getLocalStorage(): Storage | null {
|
|
if (typeof window === 'undefined') return null;
|
|
return window.localStorage;
|
|
}
|
|
|
|
/**
|
|
* `localStorage` adapter. Singleton — the underlying store is process-wide,
|
|
* so multiple `EngineStorage` instances over `localAdapter` write to the
|
|
* same backend.
|
|
*
|
|
* `onChange` subscribes to the `storage` event for cross-tab sync. The event
|
|
* does **not** fire in the originating tab; intra-tab sync is handled by the
|
|
* EntryBus inside `EngineStorage`.
|
|
*
|
|
* SSR-safe: every method is a no-op outside the browser. Reads return `null`
|
|
* so consumers fall back to defaults.
|
|
*/
|
|
export const localAdapter: SyncStorageAdapter = {
|
|
name: STORAGE_ADAPTER_LOCAL,
|
|
getItem(key) {
|
|
return getLocalStorage()?.getItem(key) ?? null;
|
|
},
|
|
setItem(key, value) {
|
|
getLocalStorage()?.setItem(key, value);
|
|
},
|
|
removeItem(key) {
|
|
getLocalStorage()?.removeItem(key);
|
|
},
|
|
onChange: (fn) => {
|
|
if (typeof window === 'undefined') return () => {};
|
|
let storage: Storage;
|
|
try {
|
|
const resolved = getLocalStorage();
|
|
if (resolved === null) return () => {};
|
|
storage = resolved;
|
|
} catch {
|
|
return () => {};
|
|
}
|
|
|
|
const listener = (event: StorageEvent): void => {
|
|
// Filter out events from sessionStorage / clearAll (key === null).
|
|
if (event.storageArea !== storage) return;
|
|
if (event.key === null) return;
|
|
fn(event.key, event.newValue);
|
|
};
|
|
window.addEventListener(BROWSER_EVENT_STORAGE, listener);
|
|
return () => window.removeEventListener(BROWSER_EVENT_STORAGE, listener);
|
|
}
|
|
};
|