import { AsyncStorage, AsyncStringStorage, SyncStorage, SyncStringStorage, } from 'jotai/vanilla/utils/atomWithStorage' import superjson from 'superjson' const isPromiseLike = (x: unknown): x is PromiseLike => typeof (x as any)?.then === 'function' type Unsubscribe = () => void type Subscribe = ( key: string, callback: (value: Value) => void, initialValue: Value, ) => Unsubscribe | undefined type StringSubscribe = ( key: string, callback: (value: string | null) => void, ) => Unsubscribe | undefined export function createSuperjsonStorage(): SyncStorage export function createSuperjsonStorage( getStringStorage: () => AsyncStringStorage | SyncStringStorage | undefined = () => { try { return window.localStorage } catch (_e) { if (import.meta.env?.MODE !== 'production') { if (typeof window !== 'undefined') { } } return undefined } }, ): AsyncStorage | SyncStorage { let lastStr: string | undefined let lastValue: Value const storage: AsyncStorage | SyncStorage = { getItem: (key, initialValue) => { const parse = (str: string | null) => { str = str || '' if (lastStr !== str) { try { lastValue = superjson.parse(str) } 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, superjson.stringify(newValue)), removeItem: key => getStringStorage()?.removeItem(key), } const createHandleSubscribe = (subscriber: StringSubscribe): Subscribe => (key, callback, initialValue) => subscriber(key, v => { let newValue: Value try { newValue = superjson.parse(v || '') } catch { newValue = initialValue } callback(newValue) }) let subscriber: StringSubscribe | undefined try { subscriber = getStringStorage()?.subscribe } catch { // ignore } if ( !subscriber && typeof window !== 'undefined' && typeof window.addEventListener === 'function' && window.Storage ) { subscriber = (key, callback) => { if (!(getStringStorage() instanceof window.Storage)) { return () => {} } const storageEventCallback = (e: StorageEvent) => { if (e.storageArea === getStringStorage() && e.key === key) { callback(e.newValue) } } window.addEventListener('storage', storageEventCallback) return () => { window.removeEventListener('storage', storageEventCallback) } } } if (subscriber) { storage.subscribe = createHandleSubscribe(subscriber) } return storage } export const superJsonStorage = createSuperjsonStorage()