[Persisted] Fork web and native, make it synchronous on the web (#4872)
* Delete logic for legacy storage * Delete superfluous tests At this point these tests aren't testing anything useful, let's just get rid of them. * Inline store.ts methods into persisted/index.ts * Fork persisted/index.ts into index.web.ts * Remove non-essential code and comments from both forks * Remove async/await from web fork of persisted/index.ts * Remove unused return * Enforce that forked types match
This commit is contained in:
parent
74b0318d89
commit
5bf7f3769d
10 changed files with 187 additions and 516 deletions
|
@ -1,49 +1,35 @@
|
|||
import EventEmitter from 'eventemitter3'
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
|
||||
import BroadcastChannel from '#/lib/broadcast'
|
||||
import {logger} from '#/logger'
|
||||
import {migrate} from '#/state/persisted/legacy'
|
||||
import {defaults, Schema} from '#/state/persisted/schema'
|
||||
import * as store from '#/state/persisted/store'
|
||||
import {defaults, Schema, schema} from '#/state/persisted/schema'
|
||||
import {PersistedApi} from './types'
|
||||
|
||||
export type {PersistedAccount, Schema} from '#/state/persisted/schema'
|
||||
export {defaults} from '#/state/persisted/schema'
|
||||
|
||||
const broadcast = new BroadcastChannel('BSKY_BROADCAST_CHANNEL')
|
||||
const UPDATE_EVENT = 'BSKY_UPDATE'
|
||||
const BSKY_STORAGE = 'BSKY_STORAGE'
|
||||
|
||||
let _state: Schema = defaults
|
||||
const _emitter = new EventEmitter()
|
||||
|
||||
/**
|
||||
* Initializes and returns persisted data state, so that it can be passed to
|
||||
* the Provider.
|
||||
*/
|
||||
export async function init() {
|
||||
logger.debug('persisted state: initializing')
|
||||
|
||||
broadcast.onmessage = onBroadcastMessage
|
||||
|
||||
try {
|
||||
await migrate() // migrate old store
|
||||
const stored = await store.read() // check for new store
|
||||
const stored = await readFromStorage()
|
||||
if (!stored) {
|
||||
logger.debug('persisted state: initializing default storage')
|
||||
await store.write(defaults) // opt: init new store
|
||||
await writeToStorage(defaults)
|
||||
}
|
||||
_state = stored || defaults // return new store
|
||||
logger.debug('persisted state: initialized')
|
||||
_state = stored || defaults
|
||||
} catch (e) {
|
||||
logger.error('persisted state: failed to load root state from storage', {
|
||||
message: e,
|
||||
})
|
||||
// AsyncStorage failure, but we can still continue in memory
|
||||
return defaults
|
||||
}
|
||||
}
|
||||
init satisfies PersistedApi['init']
|
||||
|
||||
export function get<K extends keyof Schema>(key: K): Schema[K] {
|
||||
return _state[key]
|
||||
}
|
||||
get satisfies PersistedApi['get']
|
||||
|
||||
export async function write<K extends keyof Schema>(
|
||||
key: K,
|
||||
|
@ -51,47 +37,55 @@ export async function write<K extends keyof Schema>(
|
|||
): Promise<void> {
|
||||
try {
|
||||
_state[key] = value
|
||||
await store.write(_state)
|
||||
// must happen on next tick, otherwise the tab will read stale storage data
|
||||
setTimeout(() => broadcast.postMessage({event: UPDATE_EVENT}), 0)
|
||||
logger.debug(`persisted state: wrote root state to storage`, {
|
||||
updatedKey: key,
|
||||
})
|
||||
await writeToStorage(_state)
|
||||
} catch (e) {
|
||||
logger.error(`persisted state: failed writing root state to storage`, {
|
||||
message: e,
|
||||
})
|
||||
}
|
||||
}
|
||||
write satisfies PersistedApi['write']
|
||||
|
||||
export function onUpdate(cb: () => void): () => void {
|
||||
_emitter.addListener('update', cb)
|
||||
return () => _emitter.removeListener('update', cb)
|
||||
export function onUpdate(_cb: () => void): () => void {
|
||||
return () => {}
|
||||
}
|
||||
onUpdate satisfies PersistedApi['onUpdate']
|
||||
|
||||
async function onBroadcastMessage({data}: MessageEvent) {
|
||||
// validate event
|
||||
if (typeof data === 'object' && data.event === UPDATE_EVENT) {
|
||||
try {
|
||||
// read next state, possibly updated by another tab
|
||||
const next = await store.read()
|
||||
|
||||
if (next) {
|
||||
logger.debug(`persisted state: handling update from broadcast channel`)
|
||||
_state = next
|
||||
_emitter.emit('update')
|
||||
} else {
|
||||
logger.error(
|
||||
`persisted state: handled update update from broadcast channel, but found no data`,
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
`persisted state: failed handling update from broadcast channel`,
|
||||
{
|
||||
message: e,
|
||||
},
|
||||
)
|
||||
}
|
||||
export async function clearStorage() {
|
||||
try {
|
||||
await AsyncStorage.removeItem(BSKY_STORAGE)
|
||||
} catch (e: any) {
|
||||
logger.error(`persisted store: failed to clear`, {message: e.toString()})
|
||||
}
|
||||
}
|
||||
clearStorage satisfies PersistedApi['clearStorage']
|
||||
|
||||
async function writeToStorage(value: Schema) {
|
||||
schema.parse(value)
|
||||
await AsyncStorage.setItem(BSKY_STORAGE, JSON.stringify(value))
|
||||
}
|
||||
|
||||
async function readFromStorage(): Promise<Schema | undefined> {
|
||||
const rawData = await AsyncStorage.getItem(BSKY_STORAGE)
|
||||
const objData = rawData ? JSON.parse(rawData) : undefined
|
||||
|
||||
// new user
|
||||
if (!objData) return undefined
|
||||
|
||||
// existing user, validate
|
||||
const parsed = schema.safeParse(objData)
|
||||
|
||||
if (parsed.success) {
|
||||
return objData
|
||||
} else {
|
||||
const errors =
|
||||
parsed.error?.errors?.map(e => ({
|
||||
code: e.code,
|
||||
// @ts-ignore exists on some types
|
||||
expected: e?.expected,
|
||||
path: e.path?.join('.'),
|
||||
})) || []
|
||||
logger.error(`persisted store: data failed validation on read`, {errors})
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue