diff options
Diffstat (limited to 'src/state/persisted/index.ts')
-rw-r--r-- | src/state/persisted/index.ts | 106 |
1 files changed, 50 insertions, 56 deletions
diff --git a/src/state/persisted/index.ts b/src/state/persisted/index.ts index 5fe0f9bd0..639e4e47f 100644 --- a/src/state/persisted/index.ts +++ b/src/state/persisted/index.ts @@ -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() +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'] - 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, - }, - ) - } +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 } } |