2025-06-20 05:41:10 +00:00
|
|
|
import {
|
|
|
|
|
AsyncStorage,
|
|
|
|
|
AsyncStringStorage,
|
|
|
|
|
SyncStorage,
|
|
|
|
|
SyncStringStorage,
|
|
|
|
|
} from 'jotai/vanilla/utils/atomWithStorage'
|
2025-05-25 05:17:41 +00:00
|
|
|
import superjson from 'superjson'
|
|
|
|
|
|
|
|
|
|
const isPromiseLike = (x: unknown): x is PromiseLike<unknown> =>
|
|
|
|
|
typeof (x as any)?.then === 'function'
|
|
|
|
|
|
|
|
|
|
type Unsubscribe = () => void
|
|
|
|
|
|
|
|
|
|
type Subscribe<Value> = (
|
|
|
|
|
key: string,
|
|
|
|
|
callback: (value: Value) => void,
|
|
|
|
|
initialValue: Value,
|
|
|
|
|
) => Unsubscribe | undefined
|
|
|
|
|
|
|
|
|
|
type StringSubscribe = (
|
|
|
|
|
key: string,
|
|
|
|
|
callback: (value: string | null) => void,
|
|
|
|
|
) => Unsubscribe | undefined
|
|
|
|
|
|
|
|
|
|
export function createSuperjsonStorage<Value>(): SyncStorage<Value>
|
|
|
|
|
export function createSuperjsonStorage<Value>(
|
2025-06-20 05:41:10 +00:00
|
|
|
getStringStorage: () => AsyncStringStorage | SyncStringStorage | undefined = () => {
|
2025-05-25 05:17:41 +00:00
|
|
|
try {
|
|
|
|
|
return window.localStorage
|
2025-06-20 05:41:10 +00:00
|
|
|
} catch (_e) {
|
2025-05-25 05:17:41 +00:00
|
|
|
if (import.meta.env?.MODE !== 'production') {
|
|
|
|
|
if (typeof window !== 'undefined') {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return undefined
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
): AsyncStorage<Value> | SyncStorage<Value> {
|
|
|
|
|
let lastStr: string | undefined
|
|
|
|
|
let lastValue: Value
|
|
|
|
|
|
|
|
|
|
const storage: AsyncStorage<Value> | SyncStorage<Value> = {
|
|
|
|
|
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
|
|
|
|
|
},
|
2025-06-20 05:41:10 +00:00
|
|
|
setItem: (key, newValue) => getStringStorage()?.setItem(key, superjson.stringify(newValue)),
|
|
|
|
|
removeItem: key => getStringStorage()?.removeItem(key),
|
2025-05-25 05:17:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const createHandleSubscribe =
|
|
|
|
|
(subscriber: StringSubscribe): Subscribe<Value> =>
|
|
|
|
|
(key, callback, initialValue) =>
|
2025-06-20 05:41:10 +00:00
|
|
|
subscriber(key, v => {
|
2025-05-25 05:17:41 +00:00
|
|
|
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()
|