about summary refs log tree commit diff
path: root/src/state
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2025-07-23 19:52:38 +0300
committerGitHub <noreply@github.com>2025-07-23 11:52:38 -0500
commit8fdcc3ee31aefae91ce5552c3aa74bfb867893ac (patch)
treed9cdf9186300f7e25c04cb9161be25744fb16850 /src/state
parentb4938bc9df3cc9bd2588ab0e34fd8cfda095c797 (diff)
downloadvoidsky-8fdcc3ee31aefae91ce5552c3aa74bfb867893ac.tar.zst
Replace `resumeSession` with `getSession` in the email check (#8670)
* replace resumeSession with getSession

* copy lil type tweak from the other PR

* Add partialRefreshSession to session API context, use session state to infer state further down tree

* Review

---------

Co-authored-by: Eric Bailey <git@esb.lol>
Diffstat (limited to 'src/state')
-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>
 }