forked from a/lifeto-shop
117 lines
3.0 KiB
TypeScript
117 lines
3.0 KiB
TypeScript
|
|
import { AsyncStorage, AsyncStringStorage, SyncStorage, SyncStringStorage } from "jotai/vanilla/utils/atomWithStorage"
|
||
|
|
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>(
|
||
|
|
getStringStorage: () =>
|
||
|
|
| AsyncStringStorage
|
||
|
|
| SyncStringStorage
|
||
|
|
| undefined = () => {
|
||
|
|
try {
|
||
|
|
return window.localStorage
|
||
|
|
} catch (e) {
|
||
|
|
if (import.meta.env?.MODE !== 'production') {
|
||
|
|
if (typeof window !== 'undefined') {
|
||
|
|
console.warn(e)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
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
|
||
|
|
},
|
||
|
|
setItem: (key, newValue) =>
|
||
|
|
getStringStorage()?.setItem(
|
||
|
|
key,
|
||
|
|
superjson.stringify(newValue),
|
||
|
|
),
|
||
|
|
removeItem: (key) => getStringStorage()?.removeItem(key),
|
||
|
|
}
|
||
|
|
|
||
|
|
const createHandleSubscribe =
|
||
|
|
(subscriber: StringSubscribe): Subscribe<Value> =>
|
||
|
|
(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()
|