import { AsyncStorage, SyncStorage } from 'jotai/vanilla/utils/atomWithStorage'; type Unsubscribe = () => void; type Subscribe = ( key: string, callback: (value: Value) => void, initialValue: Value ) => Unsubscribe; type SubscribeHandler = ( subscribe: Subscribe, key: string, callback: (value: Value) => void, initialValue: Value ) => Unsubscribe; export interface AsyncStringStorage { getItem: (key: string) => PromiseLike; setItem: (key: string, newValue: string) => PromiseLike; removeItem: (key: string) => PromiseLike; subscribe?: Subscribe; } export interface SyncStringStorage { getItem: (key: string) => string | null; setItem: (key: string, newValue: string) => void; removeItem: (key: string) => void; subscribe?: Subscribe; } type JsonStorageOptions = { reviver?: (key: string, value: unknown) => unknown; replacer?: (key: string, value: unknown) => unknown; subscribe?: Subscribe; }; const isPromiseLike = (x: unknown): x is PromiseLike => typeof (x as any)?.then === 'function'; export function createJSONStorage__custom( getStringStorage: () => AsyncStringStorage, options?: JsonStorageOptions ): AsyncStorage; export function createJSONStorage__custom( getStringStorage: () => SyncStringStorage, options?: JsonStorageOptions ): SyncStorage; export function createJSONStorage__custom( getStringStorage: () => | AsyncStringStorage | SyncStringStorage | undefined = () => { try { return window.localStorage; } catch (e) { if (process.env.NODE_ENV !== 'production') { if (typeof window !== 'undefined') { console.warn(e); } } return undefined; } }, options?: JsonStorageOptions ): AsyncStorage | SyncStorage { let lastStr: string | undefined; let lastValue: Value; const webStorageSubscribe = ((key, callback) => { const storageEventCallback = (e: StorageEvent) => { if (e.storageArea === getStringStorage() && e.key === key) { callback((e.newValue || '') as Value); } }; window.addEventListener('storage', storageEventCallback); return () => { window.removeEventListener('storage', storageEventCallback); }; }) satisfies Subscribe; const handleSubscribe: SubscribeHandler = ( subscriber, key, callback, initialValue ) => { function cb(v: Value) { let newValue: Value; try { newValue = JSON.parse((v as string) || ''); } catch { newValue = initialValue; } callback(newValue); } return subscriber(key, cb, initialValue); }; const storage: AsyncStorage | SyncStorage = { getItem: (key, initialValue) => { const parse = (str: string | null) => { str = str || ''; if (lastStr !== str) { try { lastValue = JSON.parse(str, options?.reviver); } catch { return initialValue; } lastStr = str; } return lastValue; }; const str = getStringStorage()?.getItem(key) ?? null; if (isPromiseLike(str)) { return str.then(parse) as never; } return parse(str) as never; }, setItem: (key, newValue) => getStringStorage()?.setItem( key, JSON.stringify(newValue, options?.replacer) ), removeItem: (key) => getStringStorage()?.removeItem(key), }; if ( typeof window !== 'undefined' && typeof window.addEventListener === 'function' ) { storage.subscribe = handleSubscribe.bind( null, getStringStorage()?.subscribe ?? webStorageSubscribe ); } return storage; }