Bluesky app fork with some witchin' additions 💫

subclass agent to add setPersistSessionHandler (#4928)

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>

authored by hailey.at

Dan Abramov and committed by
GitHub
3c04d9bd 99d1a881

+60 -82
+56 -62
src/state/session/agent.ts
··· 1 - import { 2 - AtpPersistSessionHandler, 3 - AtpSessionData, 4 - AtpSessionEvent, 5 - BskyAgent, 6 - } from '@atproto/api' 1 + import {AtpSessionData, AtpSessionEvent, BskyAgent} from '@atproto/api' 7 2 import {TID} from '@atproto/common-web' 8 3 9 4 import {networkRetry} from '#/lib/async/retry' ··· 25 20 import {SessionAccount} from './types' 26 21 import {isSessionExpired, isSignupQueued} from './util' 27 22 28 - type SetPersistSessionHandler = (cb: AtpPersistSessionHandler) => void 29 - 30 23 export function createPublicAgent() { 31 24 configureModerationForGuest() // Side effect but only relevant for tests 32 - return new BskyAgent({service: PUBLIC_BSKY_SERVICE}) 25 + return new BskyAppAgent({service: PUBLIC_BSKY_SERVICE}) 33 26 } 34 27 35 28 export async function createAgentAndResume( ··· 39 32 did: string, 40 33 event: AtpSessionEvent, 41 34 ) => void, 42 - setPersistSessionHandler: SetPersistSessionHandler, 43 35 ) { 44 - const agent = new BskyAgent({service: storedAccount.service}) 36 + const agent = new BskyAppAgent({service: storedAccount.service}) 45 37 if (storedAccount.pdsUrl) { 46 38 agent.sessionManager.pdsUrl = new URL(storedAccount.pdsUrl) 47 39 } ··· 67 59 } 68 60 } 69 61 70 - return prepareAgent( 71 - agent, 72 - gates, 73 - moderation, 74 - onSessionChange, 75 - setPersistSessionHandler, 76 - ) 62 + return agent.prepare(gates, moderation, onSessionChange) 77 63 } 78 64 79 65 export async function createAgentAndLogin( ··· 93 79 did: string, 94 80 event: AtpSessionEvent, 95 81 ) => void, 96 - setPersistSessionHandler: SetPersistSessionHandler, 97 82 ) { 98 - const agent = new BskyAgent({service}) 83 + const agent = new BskyAppAgent({service}) 99 84 await agent.login({identifier, password, authFactorToken}) 100 85 101 86 const account = agentToSessionAccountOrThrow(agent) 102 87 const gates = tryFetchGates(account.did, 'prefer-fresh-gates') 103 88 const moderation = configureModerationForAccount(agent, account) 104 - return prepareAgent( 105 - agent, 106 - moderation, 107 - gates, 108 - onSessionChange, 109 - setPersistSessionHandler, 110 - ) 89 + return agent.prepare(gates, moderation, onSessionChange) 111 90 } 112 91 113 92 export async function createAgentAndCreateAccount( ··· 135 114 did: string, 136 115 event: AtpSessionEvent, 137 116 ) => void, 138 - setPersistSessionHandler: SetPersistSessionHandler, 139 117 ) { 140 - const agent = new BskyAgent({service}) 118 + const agent = new BskyAppAgent({service}) 141 119 await agent.createAccount({ 142 120 email, 143 121 password, ··· 195 173 logger.error(e, {context: `session: failed snoozeEmailConfirmationPrompt`}) 196 174 } 197 175 198 - return prepareAgent( 199 - agent, 200 - gates, 201 - moderation, 202 - onSessionChange, 203 - setPersistSessionHandler, 204 - ) 205 - } 206 - 207 - async function prepareAgent( 208 - agent: BskyAgent, 209 - // Not awaited in the calling code so we can delay blocking on them. 210 - gates: Promise<void>, 211 - moderation: Promise<void>, 212 - onSessionChange: ( 213 - agent: BskyAgent, 214 - did: string, 215 - event: AtpSessionEvent, 216 - ) => void, 217 - setPersistSessionHandler: (cb: AtpPersistSessionHandler) => void, 218 - ) { 219 - // There's nothing else left to do, so block on them here. 220 - await Promise.all([gates, moderation]) 221 - 222 - // Now the agent is ready. 223 - const account = agentToSessionAccountOrThrow(agent) 224 - setPersistSessionHandler(event => { 225 - onSessionChange(agent, account.did, event) 226 - if (event !== 'create' && event !== 'update') { 227 - addSessionErrorLog(account.did, event) 228 - } 229 - }) 230 - return {agent, account} 176 + return agent.prepare(gates, moderation, onSessionChange) 231 177 } 232 178 233 179 export function agentToSessionAccountOrThrow(agent: BskyAgent): SessionAccount { ··· 279 225 status: account.status, 280 226 } 281 227 } 228 + 229 + // Not exported. Use factories above to create it. 230 + class BskyAppAgent extends BskyAgent { 231 + persistSessionHandler: ((event: AtpSessionEvent) => void) | undefined = 232 + undefined 233 + 234 + constructor({service}: {service: string}) { 235 + super({ 236 + service, 237 + persistSession: (event: AtpSessionEvent) => { 238 + if (this.persistSessionHandler) { 239 + this.persistSessionHandler(event) 240 + } 241 + }, 242 + }) 243 + } 244 + 245 + async prepare( 246 + // Not awaited in the calling code so we can delay blocking on them. 247 + gates: Promise<void>, 248 + moderation: Promise<void>, 249 + onSessionChange: ( 250 + agent: BskyAgent, 251 + did: string, 252 + event: AtpSessionEvent, 253 + ) => void, 254 + ) { 255 + // There's nothing else left to do, so block on them here. 256 + await Promise.all([gates, moderation]) 257 + 258 + // Now the agent is ready. 259 + const account = agentToSessionAccountOrThrow(this) 260 + this.persistSessionHandler = event => { 261 + onSessionChange(this, account.did, event) 262 + if (event !== 'create' && event !== 'update') { 263 + addSessionErrorLog(account.did, event) 264 + } 265 + } 266 + return {account, agent: this} 267 + } 268 + 269 + dispose() { 270 + this.sessionManager.session = undefined 271 + this.persistSessionHandler = undefined 272 + } 273 + } 274 + 275 + export type {BskyAppAgent}
+4 -20
src/state/session/index.tsx
··· 1 1 import React from 'react' 2 - import { 3 - AtpPersistSessionHandler, 4 - AtpSessionEvent, 5 - BskyAgent, 6 - } from '@atproto/api' 2 + import {AtpSessionEvent, BskyAgent} from '@atproto/api' 7 3 8 4 import {track} from '#/lib/analytics/analytics' 9 5 import {logEvent} from '#/lib/statsig/statsig' ··· 15 11 import {emitSessionDropped} from '../events' 16 12 import { 17 13 agentToSessionAccount, 14 + BskyAppAgent, 18 15 createAgentAndCreateAccount, 19 16 createAgentAndLogin, 20 17 createAgentAndResume, ··· 51 48 return initialState 52 49 }) 53 50 54 - const persistSessionHandler = React.useRef< 55 - AtpPersistSessionHandler | undefined 56 - >(undefined) 57 - const setPersistSessionHandler = ( 58 - newHandler: AtpPersistSessionHandler | undefined, 59 - ) => { 60 - persistSessionHandler.current = newHandler 61 - } 62 - 63 51 const onAgentSessionChange = React.useCallback( 64 52 (agent: BskyAgent, accountDid: string, sessionEvent: AtpSessionEvent) => { 65 53 const refreshedAccount = agentToSessionAccount(agent) // Mutable, so snapshot it right away. ··· 86 74 const {agent, account} = await createAgentAndCreateAccount( 87 75 params, 88 76 onAgentSessionChange, 89 - setPersistSessionHandler, 90 77 ) 91 78 92 79 if (signal.aborted) { ··· 111 98 const {agent, account} = await createAgentAndLogin( 112 99 params, 113 100 onAgentSessionChange, 114 - setPersistSessionHandler, 115 101 ) 116 102 117 103 if (signal.aborted) { ··· 153 139 const {agent, account} = await createAgentAndResume( 154 140 storedAccount, 155 141 onAgentSessionChange, 156 - setPersistSessionHandler, 157 142 ) 158 143 159 144 if (signal.aborted) { ··· 255 240 // @ts-ignore 256 241 if (IS_DEV && isWeb) window.agent = state.currentAgentState.agent 257 242 258 - const agent = state.currentAgentState.agent as BskyAgent 243 + const agent = state.currentAgentState.agent as BskyAppAgent 259 244 const currentAgentRef = React.useRef(agent) 260 245 React.useEffect(() => { 261 246 if (currentAgentRef.current !== agent) { ··· 265 250 addSessionDebugLog({type: 'agent:switch', prevAgent, nextAgent: agent}) 266 251 // We never reuse agents so let's fully neutralize the previous one. 267 252 // This ensures it won't try to consume any refresh tokens. 268 - prevAgent.sessionManager.session = undefined 269 - setPersistSessionHandler(undefined) 253 + prevAgent.dispose() 270 254 } 271 255 }, [agent]) 272 256