From c12c39bd4ab70cb68320cb70a07f414f6e235b2b Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 27 Feb 2026 14:12:19 +0200 Subject: [PATCH] abstract out sessionstore --- src/state/session/index.tsx | 53 ++-------------------------------- src/state/session/store.ts | 57 +++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 51 deletions(-) create mode 100644 src/state/session/store.ts diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx index e63e180a4e..af799c2641 100644 --- a/src/state/session/index.tsx +++ b/src/state/session/index.tsx @@ -10,7 +10,6 @@ import { } from 'react' import {type AtpSessionEvent, type BskyAgent} from '@atproto/api' -import * as persisted from '#/state/persisted' import {useCloseAllActiveElements} from '#/state/util' import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' import {AnalyticsContext, useAnalyticsBase, utils} from '#/analytics' @@ -24,7 +23,7 @@ import { createAgentAndResume, sessionAccountToSession, } from './agent' -import {type Action, getInitialState, reducer, type State} from './reducer' +import {SessionStore} from './store' export {isSignupQueued} from './util' import {addSessionDebugLog} from './logging' export type {SessionAccount} from '#/state/session/types' @@ -61,47 +60,6 @@ const ApiContext = createContext({ }) ApiContext.displayName = 'SessionApiContext' -class SessionStore { - private state: State - private listeners = new Set<() => void>() - - constructor() { - // Careful: By the time this runs, `persisted` needs to already be filled. - const initialState = getInitialState(persisted.get('session').accounts) - addSessionDebugLog({type: 'reducer:init', state: initialState}) - this.state = initialState - } - - getState = (): State => { - return this.state - } - - subscribe = (listener: () => void) => { - this.listeners.add(listener) - return () => { - this.listeners.delete(listener) - } - } - - dispatch = (action: Action) => { - const nextState = reducer(this.state, action) - this.state = nextState - // Persist synchronously without waiting for the React render cycle. - if (nextState.needsPersist) { - nextState.needsPersist = false - const persistedData = { - accounts: nextState.accounts, - currentAccount: nextState.accounts.find( - a => a.did === nextState.currentAgentState.did, - ), - } - addSessionDebugLog({type: 'persisted:broadcast', data: persistedData}) - persisted.write('session', persistedData) - } - this.listeners.forEach(listener => listener()) - } -} - export function Provider({children}: React.PropsWithChildren<{}>) { const ax = useAnalyticsBase() const cancelPendingTask = useOneTaskAtATime() @@ -308,14 +266,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { [store, cancelPendingTask], ) useEffect(() => { - return persisted.onUpdate('session', nextSession => { - const synced = nextSession - addSessionDebugLog({type: 'persisted:receive', data: synced}) - store.dispatch({ - type: 'synced-accounts', - syncedAccounts: synced.accounts, - syncedCurrentDid: synced.currentAccount?.did, - }) + return store.onUpdate(synced => { const syncedAccount = synced.accounts.find( a => a.did === synced.currentAccount?.did, ) diff --git a/src/state/session/store.ts b/src/state/session/store.ts new file mode 100644 index 0000000000..b527be9209 --- /dev/null +++ b/src/state/session/store.ts @@ -0,0 +1,57 @@ +import * as persisted from '#/state/persisted' +import {type Schema} from '#/state/persisted/schema' +import {addSessionDebugLog} from './logging' +import {type Action, getInitialState, reducer, type State} from './reducer' + +export class SessionStore { + private state: State + private listeners = new Set<() => void>() + + constructor() { + // Careful: By the time this runs, `persisted` needs to already be filled. + const initialState = getInitialState(persisted.get('session').accounts) + addSessionDebugLog({type: 'reducer:init', state: initialState}) + this.state = initialState + } + + getState = (): State => { + return this.state + } + + subscribe = (listener: () => void) => { + this.listeners.add(listener) + return () => { + this.listeners.delete(listener) + } + } + + dispatch = (action: Action) => { + const nextState = reducer(this.state, action) + this.state = nextState + // Persist synchronously without waiting for the React render cycle. + if (nextState.needsPersist) { + nextState.needsPersist = false + const persistedData = { + accounts: nextState.accounts, + currentAccount: nextState.accounts.find( + a => a.did === nextState.currentAgentState.did, + ), + } + addSessionDebugLog({type: 'persisted:broadcast', data: persistedData}) + persisted.write('session', persistedData) + } + this.listeners.forEach(listener => listener()) + } + + onUpdate = (callback: (synced: Schema['session']) => void): (() => void) => { + return persisted.onUpdate('session', nextSession => { + addSessionDebugLog({type: 'persisted:receive', data: nextSession}) + this.dispatch({ + type: 'synced-accounts', + syncedAccounts: nextSession.accounts, + syncedCurrentDid: nextSession.currentAccount?.did, + }) + callback(nextSession) + }) + } +}