about summary refs log tree commit diff
path: root/src/state/session
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/session')
-rw-r--r--src/state/session/index.tsx22
-rw-r--r--src/state/session/reducer.ts42
-rw-r--r--src/state/session/types.ts8
3 files changed, 69 insertions, 3 deletions
diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx
index 45384c4f5..8223a7b3a 100644
--- a/src/state/session/index.tsx
+++ b/src/state/session/index.tsx
@@ -40,6 +40,7 @@ const ApiContext = React.createContext<SessionApiContext>({
   logoutEveryAccount: async () => {},
   resumeSession: async () => {},
   removeAccount: () => {},
+  partialRefreshSession: async () => {},
 })
 
 export function Provider({children}: React.PropsWithChildren<{}>) {
@@ -119,7 +120,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
   )
 
   const logoutCurrentAccount = React.useCallback<
-    SessionApiContext['logoutEveryAccount']
+    SessionApiContext['logoutCurrentAccount']
   >(
     logContext => {
       addSessionDebugLog({type: 'method:start', method: 'logout'})
@@ -182,6 +183,23 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
     [onAgentSessionChange, cancelPendingTask],
   )
 
+  const partialRefreshSession = React.useCallback<
+    SessionApiContext['partialRefreshSession']
+  >(async () => {
+    const agent = state.currentAgentState.agent as BskyAppAgent
+    const signal = cancelPendingTask()
+    const {data} = await agent.com.atproto.server.getSession()
+    if (signal.aborted) return
+    dispatch({
+      type: 'partial-refresh-session',
+      accountDid: agent.session!.did,
+      patch: {
+        emailConfirmed: data.emailConfirmed,
+        emailAuthFactor: data.emailAuthFactor,
+      },
+    })
+  }, [state, cancelPendingTask])
+
   const removeAccount = React.useCallback<SessionApiContext['removeAccount']>(
     account => {
       addSessionDebugLog({
@@ -262,6 +280,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
       logoutEveryAccount,
       resumeSession,
       removeAccount,
+      partialRefreshSession,
     }),
     [
       createAccount,
@@ -270,6 +289,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
       logoutEveryAccount,
       resumeSession,
       removeAccount,
+      partialRefreshSession,
     ],
   )
 
diff --git a/src/state/session/reducer.ts b/src/state/session/reducer.ts
index 22ba47162..f6452a391 100644
--- a/src/state/session/reducer.ts
+++ b/src/state/session/reducer.ts
@@ -1,8 +1,8 @@
-import {AtpSessionEvent} from '@atproto/api'
+import {type AtpSessionEvent, type BskyAgent} from '@atproto/api'
 
 import {createPublicAgent} from './agent'
 import {wrapSessionReducerForLogging} from './logging'
-import {SessionAccount} from './types'
+import {type SessionAccount} from './types'
 
 // A hack so that the reducer can't read anything from the agent.
 // From the reducer's point of view, it should be a completely opaque object.
@@ -52,6 +52,11 @@ export type Action =
       syncedAccounts: SessionAccount[]
       syncedCurrentDid: string | undefined
     }
+  | {
+      type: 'partial-refresh-session'
+      accountDid: string
+      patch: Pick<SessionAccount, 'emailConfirmed' | 'emailAuthFactor'>
+    }
 
 function createPublicAgentState(): AgentState {
   return {
@@ -180,6 +185,39 @@ let reducer = (state: State, action: Action): State => {
         needsPersist: false, // Synced from another tab. Don't persist to avoid cycles.
       }
     }
+    case 'partial-refresh-session': {
+      const {accountDid, patch} = action
+      const agent = state.currentAgentState.agent as BskyAgent
+
+      /*
+       * Only mutating values that are safe. Be very careful with this.
+       */
+      if (agent.session) {
+        agent.session.emailConfirmed =
+          patch.emailConfirmed ?? agent.session.emailConfirmed
+        agent.session.emailAuthFactor =
+          patch.emailAuthFactor ?? agent.session.emailAuthFactor
+      }
+
+      return {
+        ...state,
+        currentAgentState: {
+          ...state.currentAgentState,
+          agent,
+        },
+        accounts: state.accounts.map(a => {
+          if (a.did === accountDid) {
+            return {
+              ...a,
+              emailConfirmed: patch.emailConfirmed ?? a.emailConfirmed,
+              emailAuthFactor: patch.emailAuthFactor ?? a.emailAuthFactor,
+            }
+          }
+          return a
+        }),
+        needsPersist: true,
+      }
+    }
   }
 }
 reducer = wrapSessionReducerForLogging(reducer)
diff --git a/src/state/session/types.ts b/src/state/session/types.ts
index aa8b9a99e..4621b4f04 100644
--- a/src/state/session/types.ts
+++ b/src/state/session/types.ts
@@ -40,4 +40,12 @@ export type SessionApiContext = {
   ) => void
   resumeSession: (account: SessionAccount) => Promise<void>
   removeAccount: (account: SessionAccount) => void
+  /**
+   * Calls `getSession` and updates select fields on the current account and
+   * `BskyAgent`. This is an alternative to `resumeSession`, which updates
+   * current account/agent using the `persistSessionHandler`, but is more load
+   * bearing. This patches in updates without causing any side effects via
+   * `persistSessionHandler`.
+   */
+  partialRefreshSession: () => Promise<void>
 }