Skip to content

Commit

Permalink
[Session] Track agent in state (#3833)
Browse files Browse the repository at this point in the history
* Expand currentAccountDid into currentAgentState

* Inline all callsites of upsertAccount

* Inline all internal callsites of clearCurrentAccount

* Add agent to currentAgentState

* Collapse extra setStates

* Add TODOs
  • Loading branch information
gaearon authored May 3, 2024
1 parent a9af836 commit 0f827c3
Showing 1 changed file with 139 additions and 47 deletions.
186 changes: 139 additions & 47 deletions src/state/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,39 +56,37 @@ function __getAgent() {
return __globalAgent
}

type AgentState = {
readonly agent: BskyAgent
readonly did: string | undefined
}

type State = {
accounts: SessionStateContext['accounts']
currentAccountDid: string | undefined
currentAgentState: AgentState
needsPersist: boolean
}

export function Provider({children}: React.PropsWithChildren<{}>) {
const [state, setState] = React.useState<State>({
const [state, setState] = React.useState<State>(() => ({
accounts: persisted.get('session').accounts,
currentAccountDid: undefined, // assume logged out to start
needsPersist: false,
})

const upsertAccount = React.useCallback(
(account: SessionAccount, expired = false) => {
setState(s => {
return {
accounts: [account, ...s.accounts.filter(a => a.did !== account.did)],
currentAccountDid: expired ? undefined : account.did,
needsPersist: true,
}
})
currentAgentState: {
agent: PUBLIC_BSKY_AGENT,
did: undefined, // assume logged out to start
},
[setState],
)
needsPersist: false,
}))

const clearCurrentAccount = React.useCallback(() => {
logger.warn(`session: clear current account`)
__globalAgent = PUBLIC_BSKY_AGENT
configureModerationForGuest()
setState(s => ({
accounts: s.accounts,
currentAccountDid: undefined,
currentAgentState: {
agent: PUBLIC_BSKY_AGENT,
did: undefined,
},
needsPersist: true,
}))
}, [setState])
Expand All @@ -106,7 +104,17 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
logger.warn(
`session: persistSessionHandler received network-error event`,
)
clearCurrentAccount()
logger.warn(`session: clear current account`)
__globalAgent = PUBLIC_BSKY_AGENT
configureModerationForGuest()
setState(s => ({
accounts: s.accounts,
currentAgentState: {
agent: PUBLIC_BSKY_AGENT,
did: undefined,
},
needsPersist: true,
}))
return
}

Expand Down Expand Up @@ -149,9 +157,23 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
* to persist this data and wipe their tokens, effectively logging them
* out.
*/
upsertAccount(refreshedAccount, expired)
setState(s => {
return {
accounts: [
refreshedAccount,
...s.accounts.filter(a => a.did !== refreshedAccount.did),
],
currentAgentState: expired
? {
agent: PUBLIC_BSKY_AGENT,
did: undefined,
}
: s.currentAgentState,
needsPersist: true,
}
})
},
[clearCurrentAccount, upsertAccount],
[],
)

const createAccount = React.useCallback<SessionApiContext['createAccount']>(
Expand Down Expand Up @@ -185,13 +207,22 @@ export function Provider({children}: React.PropsWithChildren<{}>) {

__globalAgent = agent
await fetchingGates
upsertAccount(account)
setState(s => {
return {
accounts: [account, ...s.accounts.filter(a => a.did !== account.did)],
currentAgentState: {
did: account.did,
agent: agent,
},
needsPersist: true,
}
})

logger.debug(`session: created account`, {}, logger.DebugContext.session)
track('Create Account')
logEvent('account:create:success', {})
},
[upsertAccount, onAgentSessionChange],
[onAgentSessionChange],
)

const login = React.useCallback<SessionApiContext['login']>(
Expand All @@ -212,34 +243,48 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
// @ts-ignore
if (IS_DEV && isWeb) window.agent = agent
await fetchingGates
upsertAccount(account)
setState(s => {
return {
accounts: [account, ...s.accounts.filter(a => a.did !== account.did)],
currentAgentState: {
did: account.did,
agent: agent,
},
needsPersist: true,
}
})

logger.debug(`session: logged in`, {}, logger.DebugContext.session)

track('Sign In', {resumedSession: false})
logEvent('account:loggedIn', {logContext, withPassword: true})
},
[upsertAccount, onAgentSessionChange],
[onAgentSessionChange],
)

const logout = React.useCallback<SessionApiContext['logout']>(
async logContext => {
logger.debug(`session: logout`)
clearCurrentAccount()
logger.warn(`session: clear current account`)
__globalAgent = PUBLIC_BSKY_AGENT
configureModerationForGuest()
setState(s => {
return {
accounts: s.accounts.map(a => ({
...a,
refreshJwt: undefined,
accessJwt: undefined,
})),
currentAccountDid: s.currentAccountDid,
currentAgentState: {
did: undefined,
agent: PUBLIC_BSKY_AGENT,
},
needsPersist: true,
}
})
logEvent('account:loggedOut', {logContext})
},
[clearCurrentAccount, setState],
[setState],
)

const initSession = React.useCallback<SessionApiContext['initSession']>(
Expand Down Expand Up @@ -279,7 +324,19 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
const freshAccount = await resumeSessionWithFreshAccount()
__globalAgent = agent
await fetchingGates
upsertAccount(freshAccount)
setState(s => {
return {
accounts: [
freshAccount,
...s.accounts.filter(a => a.did !== freshAccount.did),
],
currentAgentState: {
did: freshAccount.did,
agent: agent,
},
needsPersist: true,
}
})
} catch (e) {
/*
* Note: `agent.persistSession` is also called when this fails, and
Expand All @@ -290,7 +347,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
})

__globalAgent = PUBLIC_BSKY_AGENT
// TODO: Should this update currentAccountDid?
// TODO: This needs a setState.
}
} else {
logger.debug(`session: attempting to reuse previous session`)
Expand All @@ -299,7 +356,19 @@ export function Provider({children}: React.PropsWithChildren<{}>) {

__globalAgent = agent
await fetchingGates
upsertAccount(account)
setState(s => {
return {
accounts: [
account,
...s.accounts.filter(a => a.did !== account.did),
],
currentAgentState: {
did: account.did,
agent: agent,
},
needsPersist: true,
}
})

if (accountOrSessionDeactivated) {
// don't attempt to resume
Expand All @@ -315,7 +384,19 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
logger.info(
`session: reuse of previous session returned a fresh account, upserting`,
)
upsertAccount(freshAccount)
setState(s => {
return {
accounts: [
freshAccount,
...s.accounts.filter(a => a.did !== freshAccount.did),
],
currentAgentState: {
did: freshAccount.did,
agent: agent,
},
needsPersist: true,
}
})
}
})
.catch(e => {
Expand All @@ -328,7 +409,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
})

__globalAgent = PUBLIC_BSKY_AGENT
// TODO: Should this update currentAccountDid?
// TODO: This needs a setState.
})
}

Expand All @@ -347,15 +428,15 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
return sessionAccount
}
},
[upsertAccount, onAgentSessionChange],
[onAgentSessionChange],
)

const removeAccount = React.useCallback<SessionApiContext['removeAccount']>(
account => {
setState(s => {
return {
accounts: s.accounts.filter(a => a.did !== account.did),
currentAccountDid: s.currentAccountDid,
currentAgentState: s.currentAgentState,
needsPersist: true,
}
})
Expand All @@ -369,7 +450,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
account => {
setState(s => {
const currentAccount = s.accounts.find(
a => a.did === s.currentAccountDid,
a => a.did === s.currentAgentState.did,
)
// ignore, should never happen
if (!currentAccount) return s
Expand All @@ -393,7 +474,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
updatedAccount,
...s.accounts.filter(a => a.did !== currentAccount.did),
],
currentAccountDid: s.currentAccountDid,
currentAgentState: s.currentAgentState,
needsPersist: true,
}
})
Expand All @@ -407,7 +488,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
persisted.write('session', {
accounts: state.accounts,
currentAccount: state.accounts.find(
a => a.did === state.currentAccountDid,
a => a.did === state.currentAgentState.did,
),
})
}
Expand All @@ -424,10 +505,10 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
)

if (selectedAccount && selectedAccount.refreshJwt) {
if (selectedAccount.did !== state.currentAccountDid) {
if (selectedAccount.did !== state.currentAgentState.did) {
logger.debug(`session: persisted onUpdate, switching accounts`, {
from: {
did: state.currentAccountDid,
did: state.currentAgentState.did,
},
to: {
did: selectedAccount.did,
Expand All @@ -445,8 +526,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
*/
// @ts-ignore we checked for `refreshJwt` above
__globalAgent.session = selectedAccount
// TODO: This needs a setState.
}
} else if (!selectedAccount && state.currentAccountDid) {
} else if (!selectedAccount && state.currentAgentState.did) {
logger.debug(
`session: persisted onUpdate, logging out`,
{},
Expand All @@ -459,24 +541,34 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
* handled by `persistSession` (which nukes this accounts tokens only),
* or by a `logout` call which nukes all accounts tokens)
*/
clearCurrentAccount()
logger.warn(`session: clear current account`)
__globalAgent = PUBLIC_BSKY_AGENT
configureModerationForGuest()
setState(s => ({
accounts: s.accounts,
currentAgentState: {
did: undefined,
agent: PUBLIC_BSKY_AGENT,
},
needsPersist: true, // TODO: This seems bad in this codepath. Existing behavior.
}))
}

setState(() => ({
setState(s => ({
accounts: persistedSession.accounts,
currentAccountDid: selectedAccount?.did,
currentAgentState: s.currentAgentState,
needsPersist: false, // Synced from another tab. Don't persist to avoid cycles.
}))
})
}, [state, setState, clearCurrentAccount, initSession])
}, [state, setState, initSession])

const stateContext = React.useMemo(
() => ({
accounts: state.accounts,
currentAccount: state.accounts.find(
a => a.did === state.currentAccountDid,
a => a.did === state.currentAgentState.did,
),
hasSession: !!state.currentAccountDid,
hasSession: !!state.currentAgentState.did,
}),
[state],
)
Expand Down

0 comments on commit 0f827c3

Please sign in to comment.