[Session] Use flag on state for persistence (#3793)
* Move isInitialLoad and isSwitchingAccounts out of main state * Remove spreads, order object keys * Track need to persist on state object * Reoder state variableszio/stable
parent
a6061489ff
commit
df9af92eb2
|
@ -26,7 +26,6 @@ export type {SessionAccount} from '#/state/session/types'
|
||||||
import {
|
import {
|
||||||
SessionAccount,
|
SessionAccount,
|
||||||
SessionApiContext,
|
SessionApiContext,
|
||||||
SessionState,
|
|
||||||
SessionStateContext,
|
SessionStateContext,
|
||||||
} from '#/state/session/types'
|
} from '#/state/session/types'
|
||||||
|
|
||||||
|
@ -61,45 +60,44 @@ function __getAgent() {
|
||||||
return __globalAgent
|
return __globalAgent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
accounts: SessionStateContext['accounts']
|
||||||
|
currentAccount: SessionStateContext['currentAccount']
|
||||||
|
needsPersist: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
const isDirty = React.useRef(false)
|
const [isInitialLoad, setIsInitialLoad] = React.useState(true)
|
||||||
const [state, setState] = React.useState<SessionState>({
|
const [isSwitchingAccounts, setIsSwitchingAccounts] = React.useState(false)
|
||||||
isInitialLoad: true,
|
const [state, setState] = React.useState<State>({
|
||||||
isSwitchingAccounts: false,
|
|
||||||
accounts: persisted.get('session').accounts,
|
accounts: persisted.get('session').accounts,
|
||||||
currentAccount: undefined, // assume logged out to start
|
currentAccount: undefined, // assume logged out to start
|
||||||
|
needsPersist: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const setStateAndPersist = React.useCallback(
|
|
||||||
(fn: (prev: SessionState) => SessionState) => {
|
|
||||||
isDirty.current = true
|
|
||||||
setState(fn)
|
|
||||||
},
|
|
||||||
[setState],
|
|
||||||
)
|
|
||||||
|
|
||||||
const upsertAccount = React.useCallback(
|
const upsertAccount = React.useCallback(
|
||||||
(account: SessionAccount, expired = false) => {
|
(account: SessionAccount, expired = false) => {
|
||||||
setStateAndPersist(s => {
|
setState(s => {
|
||||||
return {
|
return {
|
||||||
...s,
|
|
||||||
currentAccount: expired ? undefined : account,
|
|
||||||
accounts: [account, ...s.accounts.filter(a => a.did !== account.did)],
|
accounts: [account, ...s.accounts.filter(a => a.did !== account.did)],
|
||||||
|
currentAccount: expired ? undefined : account,
|
||||||
|
needsPersist: true,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[setStateAndPersist],
|
[setState],
|
||||||
)
|
)
|
||||||
|
|
||||||
const clearCurrentAccount = React.useCallback(() => {
|
const clearCurrentAccount = React.useCallback(() => {
|
||||||
logger.warn(`session: clear current account`)
|
logger.warn(`session: clear current account`)
|
||||||
__globalAgent = PUBLIC_BSKY_AGENT
|
__globalAgent = PUBLIC_BSKY_AGENT
|
||||||
configureModerationForGuest()
|
configureModerationForGuest()
|
||||||
setStateAndPersist(s => ({
|
setState(s => ({
|
||||||
...s,
|
accounts: s.accounts,
|
||||||
currentAccount: undefined,
|
currentAccount: undefined,
|
||||||
|
needsPersist: true,
|
||||||
}))
|
}))
|
||||||
}, [setStateAndPersist])
|
}, [setState])
|
||||||
|
|
||||||
const createPersistSessionHandler = React.useCallback(
|
const createPersistSessionHandler = React.useCallback(
|
||||||
(
|
(
|
||||||
|
@ -260,19 +258,20 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
async logContext => {
|
async logContext => {
|
||||||
logger.debug(`session: logout`)
|
logger.debug(`session: logout`)
|
||||||
clearCurrentAccount()
|
clearCurrentAccount()
|
||||||
setStateAndPersist(s => {
|
setState(s => {
|
||||||
return {
|
return {
|
||||||
...s,
|
|
||||||
accounts: s.accounts.map(a => ({
|
accounts: s.accounts.map(a => ({
|
||||||
...a,
|
...a,
|
||||||
refreshJwt: undefined,
|
refreshJwt: undefined,
|
||||||
accessJwt: undefined,
|
accessJwt: undefined,
|
||||||
})),
|
})),
|
||||||
|
currentAccount: s.currentAccount,
|
||||||
|
needsPersist: true,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
logEvent('account:loggedOut', {logContext})
|
logEvent('account:loggedOut', {logContext})
|
||||||
},
|
},
|
||||||
[clearCurrentAccount, setStateAndPersist],
|
[clearCurrentAccount, setState],
|
||||||
)
|
)
|
||||||
|
|
||||||
const initSession = React.useCallback<SessionApiContext['initSession']>(
|
const initSession = React.useCallback<SessionApiContext['initSession']>(
|
||||||
|
@ -397,10 +396,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`session: resumeSession failed`, {message: e})
|
logger.error(`session: resumeSession failed`, {message: e})
|
||||||
} finally {
|
} finally {
|
||||||
setState(s => ({
|
setIsInitialLoad(false)
|
||||||
...s,
|
|
||||||
isInitialLoad: false,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[initSession],
|
[initSession],
|
||||||
|
@ -408,21 +404,22 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
|
|
||||||
const removeAccount = React.useCallback<SessionApiContext['removeAccount']>(
|
const removeAccount = React.useCallback<SessionApiContext['removeAccount']>(
|
||||||
account => {
|
account => {
|
||||||
setStateAndPersist(s => {
|
setState(s => {
|
||||||
return {
|
return {
|
||||||
...s,
|
|
||||||
accounts: s.accounts.filter(a => a.did !== account.did),
|
accounts: s.accounts.filter(a => a.did !== account.did),
|
||||||
|
currentAccount: s.currentAccount,
|
||||||
|
needsPersist: true,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[setStateAndPersist],
|
[setState],
|
||||||
)
|
)
|
||||||
|
|
||||||
const updateCurrentAccount = React.useCallback<
|
const updateCurrentAccount = React.useCallback<
|
||||||
SessionApiContext['updateCurrentAccount']
|
SessionApiContext['updateCurrentAccount']
|
||||||
>(
|
>(
|
||||||
account => {
|
account => {
|
||||||
setStateAndPersist(s => {
|
setState(s => {
|
||||||
const currentAccount = s.currentAccount
|
const currentAccount = s.currentAccount
|
||||||
|
|
||||||
// ignore, should never happen
|
// ignore, should never happen
|
||||||
|
@ -443,38 +440,38 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...s,
|
|
||||||
currentAccount: updatedAccount,
|
|
||||||
accounts: [
|
accounts: [
|
||||||
updatedAccount,
|
updatedAccount,
|
||||||
...s.accounts.filter(a => a.did !== currentAccount.did),
|
...s.accounts.filter(a => a.did !== currentAccount.did),
|
||||||
],
|
],
|
||||||
|
currentAccount: updatedAccount,
|
||||||
|
needsPersist: true,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[setStateAndPersist],
|
[setState],
|
||||||
)
|
)
|
||||||
|
|
||||||
const selectAccount = React.useCallback<SessionApiContext['selectAccount']>(
|
const selectAccount = React.useCallback<SessionApiContext['selectAccount']>(
|
||||||
async (account, logContext) => {
|
async (account, logContext) => {
|
||||||
setState(s => ({...s, isSwitchingAccounts: true}))
|
setIsSwitchingAccounts(true)
|
||||||
try {
|
try {
|
||||||
await initSession(account)
|
await initSession(account)
|
||||||
setState(s => ({...s, isSwitchingAccounts: false}))
|
setIsSwitchingAccounts(false)
|
||||||
logEvent('account:loggedIn', {logContext, withPassword: false})
|
logEvent('account:loggedIn', {logContext, withPassword: false})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// reset this in case of error
|
// reset this in case of error
|
||||||
setState(s => ({...s, isSwitchingAccounts: false}))
|
setIsSwitchingAccounts(false)
|
||||||
// but other listeners need a throw
|
// but other listeners need a throw
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[setState, initSession],
|
[initSession],
|
||||||
)
|
)
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isDirty.current) {
|
if (state.needsPersist) {
|
||||||
isDirty.current = false
|
state.needsPersist = false
|
||||||
persisted.write('session', {
|
persisted.write('session', {
|
||||||
accounts: state.accounts,
|
accounts: state.accounts,
|
||||||
currentAccount: state.currentAccount,
|
currentAccount: state.currentAccount,
|
||||||
|
@ -533,10 +530,10 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
clearCurrentAccount()
|
clearCurrentAccount()
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(s => ({
|
setState(() => ({
|
||||||
...s,
|
|
||||||
accounts: session.accounts,
|
accounts: session.accounts,
|
||||||
currentAccount: selectedAccount,
|
currentAccount: selectedAccount,
|
||||||
|
needsPersist: false, // Synced from another tab. Don't persist to avoid cycles.
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
}, [state, setState, clearCurrentAccount, initSession])
|
}, [state, setState, clearCurrentAccount, initSession])
|
||||||
|
@ -544,9 +541,11 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
const stateContext = React.useMemo(
|
const stateContext = React.useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
...state,
|
...state,
|
||||||
|
isInitialLoad,
|
||||||
|
isSwitchingAccounts,
|
||||||
hasSession: !!state.currentAccount,
|
hasSession: !!state.currentAccount,
|
||||||
}),
|
}),
|
||||||
[state],
|
[state, isInitialLoad, isSwitchingAccounts],
|
||||||
)
|
)
|
||||||
|
|
||||||
const api = React.useMemo(
|
const api = React.useMemo(
|
||||||
|
|
|
@ -3,13 +3,11 @@ import {PersistedAccount} from '#/state/persisted'
|
||||||
|
|
||||||
export type SessionAccount = PersistedAccount
|
export type SessionAccount = PersistedAccount
|
||||||
|
|
||||||
export type SessionState = {
|
export type SessionStateContext = {
|
||||||
isInitialLoad: boolean
|
|
||||||
isSwitchingAccounts: boolean
|
|
||||||
accounts: SessionAccount[]
|
accounts: SessionAccount[]
|
||||||
currentAccount: SessionAccount | undefined
|
currentAccount: SessionAccount | undefined
|
||||||
}
|
isInitialLoad: boolean
|
||||||
export type SessionStateContext = SessionState & {
|
isSwitchingAccounts: boolean
|
||||||
hasSession: boolean
|
hasSession: boolean
|
||||||
}
|
}
|
||||||
export type SessionApiContext = {
|
export type SessionApiContext = {
|
||||||
|
|
Loading…
Reference in New Issue