UNPKG

wxt-zustand

Version:

High-performance Zustand state management for WXT web extensions with seamless cross-tab synchronization and sub-10ms React re-renders

107 lines 4.23 kB
import { createStorageKey, createStorageWatcher, createVersionedItemWatcher, defineVersionedStoreItem, } from '../storage'; import { shallowDiff } from '../utils'; import { mergeStatePreservingFunctions, stripFunctionProps, } from '../utils/stateTransforms'; import { getBackendService } from './connect'; import { isExtensionContextInvalidated, reloadPageOnInvalidation, } from './errors'; /** * Set up bidirectional sync between a local Zustand store and the background. * - Local → Background: subscribe to local changes and dispatch full state. * - Background → Local: watch WXT storage and apply remote state. * - Loop prevention: temporarily unsubscribe local listener when applying remote state. * * Uses WXT-native storage.watch for background→local updates * instead of proxy service callbacks for optimal performance. */ export function setupBidirectionalSync(storeName, store, config = {}) { // Resolve service without probing; caller should ensure readiness via initial sync. const service = getBackendService(storeName); // Prepare storage key and (de)serializers. const area = config.storageStrategy || 'local'; const usingVersioned = typeof config.storageVersion === 'number'; const storageKey = usingVersioned ? undefined : createStorageKey(storeName, area); const deserializer = (config.deserializer || JSON.parse); // Local → Background const localCallback = (state /*, prev: S */) => { // Dispatch full state. Background handles broadcast via storage. // We intentionally don't await to keep UI responsive. const serializable = stripFunctionProps(state); service .dispatch({ type: '__WXT_ZUSTAND_SYNC__', state: serializable }) .catch((err) => { if (isExtensionContextInvalidated(err)) { reloadPageOnInvalidation(); return; } console.error('WXT Zustand: dispatch error', err); }); }; let unsubscribeLocal = store.subscribe(localCallback); // Background → Local via storage watcher or versioned item watcher const unwatchRemote = (() => { const onRemote = (newValue) => { if (newValue === undefined) return; const curr = store.getState(); try { const diff = shallowDiff(curr, newValue); if (!diff || diff.length === 0) return; } catch { } unsubscribeLocal(); try { const merged = mergeStatePreservingFunctions(curr, newValue); store.setState(merged, true); } finally { unsubscribeLocal = store.subscribe(localCallback); } }; if (usingVersioned) { const version = config.storageVersion; const item = defineVersionedStoreItem(storeName, { area, version, ...(config.storageMigrations && { migrations: config.storageMigrations, }), ...(config.storageFallback !== undefined && { fallback: config.storageFallback, }), }); return createVersionedItemWatcher(item, (n) => onRemote(n)).unwatch; } // String-key watcher fallback (non-versioned) const watcher = createStorageWatcher(storageKey, (n) => onRemote(n), deserializer); return watcher.unwatch; })(); // Unified cleanup (idempotent) for component/page unmount. let cleaned = false; const cleanup = () => { if (cleaned) return; cleaned = true; try { unsubscribeLocal?.(); } catch (err) { console.warn('WXT Zustand: error during local unsubscribe', err); } try { unwatchRemote?.(); } catch (err) { console.warn('WXT Zustand: error during remote unwatch', err); } }; return { service, unsubscribeLocal, unwatchRemote, cleanup, }; } //# sourceMappingURL=sync.js.map