[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:
dan
2024-08-06 00:30:58 +01:00
committed by GitHub
parent 74b0318d89
commit 5bf7f3769d
10 changed files with 187 additions and 516 deletions
+126
View File
@@ -0,0 +1,126 @@
import EventEmitter from 'eventemitter3'
import BroadcastChannel from '#/lib/broadcast'
import {logger} from '#/logger'
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 BSKY_STORAGE = 'BSKY_STORAGE'
const broadcast = new BroadcastChannel('BSKY_BROADCAST_CHANNEL')
const UPDATE_EVENT = 'BSKY_UPDATE'
let _state: Schema = defaults
const _emitter = new EventEmitter()
export async function init() {
broadcast.onmessage = onBroadcastMessage
try {
const stored = readFromStorage()
if (!stored) {
writeToStorage(defaults)
}
_state = stored || defaults
} catch (e) {
logger.error('persisted state: failed to load root state from storage', {
message: e,
})
}
}
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,
value: Schema[K],
): Promise<void> {
try {
_state[key] = value
writeToStorage(_state)
// must happen on next tick, otherwise the tab will read stale storage data
setTimeout(() => broadcast.postMessage({event: UPDATE_EVENT}), 0)
} 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)
}
onUpdate satisfies PersistedApi['onUpdate']
export async function clearStorage() {
try {
localStorage.removeItem(BSKY_STORAGE)
} catch (e: any) {
logger.error(`persisted store: failed to clear`, {message: e.toString()})
}
}
clearStorage satisfies PersistedApi['clearStorage']
async function onBroadcastMessage({data}: MessageEvent) {
if (typeof data === 'object' && data.event === UPDATE_EVENT) {
try {
// read next state, possibly updated by another tab
const next = readFromStorage()
if (next) {
_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,
},
)
}
}
}
function writeToStorage(value: Schema) {
schema.parse(value)
localStorage.setItem(BSKY_STORAGE, JSON.stringify(value))
}
function readFromStorage(): Schema | undefined {
const rawData = localStorage.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
}
}