mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Use BskyAgent as the source of truth

+163 -179
+163 -179
src/state/session/index.tsx
··· 19 19 import {emitSessionDropped} from '../events' 20 20 import {readLabelers} from './agent-config' 21 21 22 + /** 23 + * @deprecated use `agent` from `useSession` instead 24 + */ 22 25 let __globalAgent: BskyAgent = PUBLIC_BSKY_AGENT 23 26 24 27 /** ··· 26 29 * Never hold on to the object returned by this function. 27 30 * Call `getAgent()` at the time of invocation to ensure 28 31 * that you never have a stale agent. 32 + * 33 + * @deprecated use `agent` from `useSession` instead 29 34 */ 30 35 export function getAgent() { 31 36 return __globalAgent ··· 33 38 34 39 export type SessionAccount = persisted.PersistedAccount 35 40 36 - export type SessionState = { 41 + export type StateContext = { 42 + agent: BskyAgent 37 43 isInitialLoad: boolean 38 44 isSwitchingAccounts: boolean 45 + hasSession: boolean 39 46 accounts: SessionAccount[] 40 47 currentAccount: SessionAccount | undefined 41 - } 42 - export type StateContext = SessionState & { 43 - hasSession: boolean 44 48 } 45 49 export type ApiContext = { 46 50 createAccount: (props: { ··· 83 87 account: SessionAccount, 84 88 logContext: LogEvents['account:loggedIn']['logContext'], 85 89 ) => Promise<void> 86 - updateCurrentAccount: ( 87 - account: Partial< 88 - Pick<SessionAccount, 'handle' | 'email' | 'emailConfirmed'> 89 - >, 90 - ) => void 90 + /** 91 + * Refreshes the BskyAgent's session and derive a fresh `currentAccount` 92 + */ 93 + refreshSession: () => void 91 94 } 92 95 93 96 const StateContext = React.createContext<StateContext>({ 97 + agent: PUBLIC_BSKY_AGENT, 94 98 isInitialLoad: true, 95 99 isSwitchingAccounts: false, 96 100 accounts: [], ··· 106 110 resumeSession: async () => {}, 107 111 removeAccount: () => {}, 108 112 selectAccount: async () => {}, 109 - updateCurrentAccount: () => {}, 113 + refreshSession: () => {}, 110 114 clearCurrentAccount: () => {}, 111 115 }) 112 116 117 + function agentToSessionAccount(agent: BskyAgent): SessionAccount | undefined { 118 + if (!agent.session) return undefined 119 + 120 + return { 121 + service: agent.service.toString(), 122 + did: agent.session.did, 123 + handle: agent.session.handle, 124 + email: agent.session.email, 125 + emailConfirmed: agent.session.emailConfirmed, 126 + deactivated: isSessionDeactivated(agent.session.accessJwt), 127 + 128 + /* 129 + * Tokens are undefined if the session expires, or if creation fails for 130 + * any reason e.g. tokens are invalid, network error, etc. 131 + */ 132 + refreshJwt: agent.session.refreshJwt, 133 + accessJwt: agent.session.accessJwt, 134 + } 135 + } 136 + 137 + function sessionAccountToAgentSession( 138 + account: SessionAccount, 139 + ): BskyAgent['session'] { 140 + return { 141 + did: account.did, 142 + handle: account.handle, 143 + email: account.email, 144 + emailConfirmed: account.emailConfirmed, 145 + accessJwt: account.accessJwt || '', 146 + refreshJwt: account.refreshJwt || '', 147 + } 148 + } 149 + 113 150 function createPersistSessionHandler( 114 151 account: SessionAccount, 115 152 persistSessionCallback: (props: { ··· 176 213 177 214 export function Provider({children}: React.PropsWithChildren<{}>) { 178 215 const isDirty = React.useRef(false) 179 - const [state, setState] = React.useState<SessionState>({ 180 - isInitialLoad: true, 181 - isSwitchingAccounts: false, 182 - accounts: persisted.get('session').accounts, 183 - currentAccount: undefined, // assume logged out to start 184 - }) 216 + const [agent, setAgent] = React.useState<BskyAgent>(PUBLIC_BSKY_AGENT) 217 + const [accounts, setAccounts] = React.useState<SessionAccount[]>( 218 + persisted.get('session').accounts, 219 + ) 220 + const [isInitialLoad, setIsInitialLoad] = React.useState(true) 221 + const [isSwitchingAccounts, setIsSwitchingAccounts] = React.useState(false) 222 + const currentAccount = React.useMemo( 223 + () => agentToSessionAccount(agent), 224 + [agent], 225 + ) 185 226 186 - const setStateAndPersist = React.useCallback( 187 - (fn: (prev: SessionState) => SessionState) => { 188 - isDirty.current = true 189 - setState(fn) 190 - }, 191 - [setState], 227 + const persistNextUpdate = React.useCallback( 228 + () => (isDirty.current = true), 229 + [], 192 230 ) 193 231 194 - const setCurrentAccount = React.useCallback( 232 + const upsertAccount = React.useCallback( 195 233 (account: SessionAccount) => { 196 - setStateAndPersist(s => { 197 - return { 198 - ...s, 199 - currentAccount: account, 200 - accounts: [account, ...s.accounts.filter(a => a.did !== account.did)], 201 - } 202 - }) 234 + persistNextUpdate() 235 + setAccounts(accounts => [ 236 + account, 237 + ...accounts.filter(a => a.did !== account.did), 238 + ]) 203 239 }, 204 - [setStateAndPersist], 240 + [setAccounts, persistNextUpdate], 205 241 ) 206 242 207 243 const clearCurrentAccount = React.useCallback(() => { 208 244 logger.warn(`session: clear current account`) 209 - __globalAgent = PUBLIC_BSKY_AGENT 245 + 246 + persistNextUpdate() 247 + setAgent(PUBLIC_BSKY_AGENT) 210 248 BskyAgent.configure({appLabelers: [BSKY_LABELER_DID]}) 211 - setStateAndPersist(s => ({ 212 - ...s, 213 - currentAccount: undefined, 214 - })) 215 - }, [setStateAndPersist]) 249 + }, [persistNextUpdate, setAgent]) 216 250 217 251 const createAccount = React.useCallback<ApiContext['createAccount']>( 218 252 async ({ ··· 258 292 }) 259 293 } 260 294 261 - const account: SessionAccount = { 262 - service: agent.service.toString(), 263 - did: agent.session.did, 264 - handle: agent.session.handle, 265 - email: agent.session.email!, // TODO this is always defined? 266 - emailConfirmed: false, 267 - refreshJwt: agent.session.refreshJwt, 268 - accessJwt: agent.session.accessJwt, 269 - deactivated, 270 - } 295 + const account = agentToSessionAccount(agent)! 271 296 272 297 await configureModeration(agent, account) 273 298 ··· 275 300 createPersistSessionHandler( 276 301 account, 277 302 ({expired, refreshedAccount}) => { 278 - if (expired) { 279 - clearCurrentAccount() 280 - } else { 281 - setCurrentAccount(refreshedAccount) 282 - } 303 + upsertAccount(refreshedAccount) 304 + if (expired) clearCurrentAccount() 283 305 }, 284 306 {networkErrorCallback: clearCurrentAccount}, 285 307 ), 286 308 ) 287 309 288 - __globalAgent = agent 289 - setCurrentAccount(account) 310 + setAgent(agent) 311 + upsertAccount(account) 290 312 291 313 logger.debug(`session: created account`, {}, logger.DebugContext.session) 292 314 track('Create Account') 293 315 logEvent('account:create:success', {}) 294 316 }, 295 - [setCurrentAccount, clearCurrentAccount], 317 + [upsertAccount, clearCurrentAccount], 296 318 ) 297 319 298 320 const login = React.useCallback<ApiContext['login']>( ··· 300 322 logger.debug(`session: login`, {}, logger.DebugContext.session) 301 323 302 324 const agent = new BskyAgent({service}) 303 - 304 325 await agent.login({identifier, password}) 305 326 306 327 if (!agent.session) { 307 328 throw new Error(`session: login failed to establish a session`) 308 329 } 309 330 310 - const account: SessionAccount = { 311 - service: agent.service.toString(), 312 - did: agent.session.did, 313 - handle: agent.session.handle, 314 - email: agent.session.email, 315 - emailConfirmed: agent.session.emailConfirmed || false, 316 - refreshJwt: agent.session.refreshJwt, 317 - accessJwt: agent.session.accessJwt, 318 - deactivated: isSessionDeactivated(agent.session.accessJwt), 319 - } 320 - 331 + const account = agentToSessionAccount(agent)! 321 332 await configureModeration(agent, account) 322 333 323 334 agent.setPersistSessionHandler( 324 335 createPersistSessionHandler( 325 336 account, 326 337 ({expired, refreshedAccount}) => { 327 - if (expired) { 328 - clearCurrentAccount() 329 - } else { 330 - setCurrentAccount(refreshedAccount) 331 - } 338 + upsertAccount(refreshedAccount) 339 + if (expired) clearCurrentAccount() 332 340 }, 333 341 {networkErrorCallback: clearCurrentAccount}, 334 342 ), 335 343 ) 336 344 337 - __globalAgent = agent 338 - setCurrentAccount(account) 345 + setAgent(agent) 346 + upsertAccount(account) 339 347 340 348 logger.debug(`session: logged in`, {}, logger.DebugContext.session) 341 349 342 350 track('Sign In', {resumedSession: false}) 343 351 logEvent('account:loggedIn', {logContext, withPassword: true}) 344 352 }, 345 - [setCurrentAccount, clearCurrentAccount], 353 + [upsertAccount, clearCurrentAccount], 346 354 ) 347 355 348 356 const logout = React.useCallback<ApiContext['logout']>( 349 357 async logContext => { 350 358 logger.debug(`session: logout`) 359 + 351 360 clearCurrentAccount() 352 - setStateAndPersist(s => { 353 - return { 354 - ...s, 355 - accounts: s.accounts.map(a => ({ 356 - ...a, 357 - refreshJwt: undefined, 358 - accessJwt: undefined, 359 - })), 360 - } 361 - }) 361 + persistNextUpdate() 362 + setAccounts(accounts => 363 + accounts.map(a => ({ 364 + ...a, 365 + accessJwt: undefined, 366 + refreshJwt: undefined, 367 + })), 368 + ) 369 + 362 370 logEvent('account:loggedOut', {logContext}) 363 371 }, 364 - [clearCurrentAccount, setStateAndPersist], 372 + [clearCurrentAccount, persistNextUpdate, setAccounts], 365 373 ) 366 374 367 375 const initSession = React.useCallback<ApiContext['initSession']>( ··· 373 381 persistSession: createPersistSessionHandler( 374 382 account, 375 383 ({expired, refreshedAccount}) => { 376 - if (expired) { 377 - clearCurrentAccount() 378 - } else { 379 - __globalAgent = agent 380 - setCurrentAccount(refreshedAccount) 381 - } 384 + upsertAccount(refreshedAccount) 385 + if (expired) clearCurrentAccount() 382 386 }, 383 387 {networkErrorCallback: clearCurrentAccount}, 384 388 ), 385 389 }) 386 390 387 391 const prevSession = { 392 + ...account, 388 393 accessJwt: account.accessJwt || '', 389 394 refreshJwt: account.refreshJwt || '', 390 - did: account.did, 391 - handle: account.handle, 392 - deactivated: 393 - isSessionDeactivated(account.accessJwt) || account.deactivated, 394 395 } 395 396 396 397 let canReusePrevSession = false ··· 418 419 logger.DebugContext.session, 419 420 ) 420 421 agent.session = prevSession 421 - __globalAgent = agent 422 - setCurrentAccount(account) 422 + setAgent(agent) 423 + upsertAccount(account) 423 424 } else { 424 425 logger.debug( 425 426 `session: attempting to resumeSession using previous session`, ··· 429 430 try { 430 431 // will call `persistSession` on `BskyAgent` instance above if success 431 432 await networkRetry(1, () => agent.resumeSession(prevSession)) 433 + setAgent(agent) 432 434 } catch (e) { 433 435 logger.error(`session: resumeSession failed`, {message: e}) 434 436 clearCurrentAccount() 435 437 } 436 438 } 437 439 }, 438 - [setCurrentAccount, clearCurrentAccount], 440 + [upsertAccount, clearCurrentAccount], 439 441 ) 440 442 441 443 const resumeSession = React.useCallback<ApiContext['resumeSession']>( ··· 447 449 } catch (e) { 448 450 logger.error(`session: resumeSession failed`, {message: e}) 449 451 } finally { 450 - setState(s => ({ 451 - ...s, 452 - isInitialLoad: false, 453 - })) 452 + setIsInitialLoad(false) 454 453 } 455 454 }, 456 - [initSession], 455 + [initSession, setIsInitialLoad], 457 456 ) 458 457 459 458 const removeAccount = React.useCallback<ApiContext['removeAccount']>( 460 459 account => { 461 - setStateAndPersist(s => { 462 - return { 463 - ...s, 464 - accounts: s.accounts.filter(a => a.did !== account.did), 465 - } 466 - }) 460 + persistNextUpdate() 461 + setAccounts(accounts => accounts.filter(a => a.did !== account.did)) 467 462 }, 468 - [setStateAndPersist], 463 + [setAccounts, persistNextUpdate], 469 464 ) 470 465 471 - const updateCurrentAccount = React.useCallback< 472 - ApiContext['updateCurrentAccount'] 473 - >( 474 - account => { 475 - setStateAndPersist(s => { 476 - const currentAccount = s.currentAccount 477 - 478 - // ignore, should never happen 479 - if (!currentAccount) return s 480 - 481 - const updatedAccount = { 482 - ...currentAccount, 483 - handle: account.handle || currentAccount.handle, 484 - email: account.email || currentAccount.email, 485 - emailConfirmed: 486 - account.emailConfirmed !== undefined 487 - ? account.emailConfirmed 488 - : currentAccount.emailConfirmed, 489 - } 490 - 491 - return { 492 - ...s, 493 - currentAccount: updatedAccount, 494 - accounts: [ 495 - updatedAccount, 496 - ...s.accounts.filter(a => a.did !== currentAccount.did), 497 - ], 498 - } 499 - }) 500 - }, 501 - [setStateAndPersist], 502 - ) 466 + const refreshSession = React.useCallback< 467 + ApiContext['refreshSession'] 468 + >(async () => { 469 + await agent.refreshSession() 470 + persistNextUpdate() 471 + upsertAccount(agentToSessionAccount(agent)!) 472 + setAgent(agent.clone()) 473 + }, [agent, setAgent, persistNextUpdate, upsertAccount]) 503 474 504 475 const selectAccount = React.useCallback<ApiContext['selectAccount']>( 505 476 async (account, logContext) => { 506 - setState(s => ({...s, isSwitchingAccounts: true})) 477 + setIsSwitchingAccounts(true) 507 478 try { 508 479 await initSession(account) 509 - setState(s => ({...s, isSwitchingAccounts: false})) 480 + setIsSwitchingAccounts(false) 510 481 logEvent('account:loggedIn', {logContext, withPassword: false}) 511 482 } catch (e) { 512 483 // reset this in case of error 513 - setState(s => ({...s, isSwitchingAccounts: false})) 484 + setIsSwitchingAccounts(false) 514 485 // but other listeners need a throw 515 486 throw e 516 487 } 517 488 }, 518 - [setState, initSession], 489 + [setIsSwitchingAccounts, initSession], 519 490 ) 520 491 521 492 React.useEffect(() => { 522 493 if (isDirty.current) { 523 494 isDirty.current = false 524 495 persisted.write('session', { 525 - accounts: state.accounts, 526 - currentAccount: state.currentAccount, 496 + accounts, 497 + currentAccount, 527 498 }) 528 499 } 529 - }, [state]) 500 + }, [accounts, currentAccount]) 530 501 531 502 React.useEffect(() => { 532 503 return persisted.onUpdate(async () => { 533 - const session = persisted.get('session') 504 + const persistedSession = persisted.get('session') 505 + 506 + logger.debug(`session: persisted onUpdate`, { 507 + persistedCurrentAccount: persistedSession.currentAccount, 508 + currentAccount, 509 + }) 534 510 535 - logger.debug(`session: persisted onUpdate`, {}) 511 + setAccounts(persistedSession.accounts) 536 512 537 - if (session.currentAccount && session.currentAccount.refreshJwt) { 538 - if (session.currentAccount?.did !== state.currentAccount?.did) { 513 + if ( 514 + persistedSession.currentAccount && 515 + persistedSession.currentAccount.refreshJwt 516 + ) { 517 + if (persistedSession.currentAccount?.did !== currentAccount?.did) { 539 518 logger.debug(`session: persisted onUpdate, switching accounts`, { 540 519 from: { 541 - did: state.currentAccount?.did, 542 - handle: state.currentAccount?.handle, 520 + did: currentAccount?.did, 521 + handle: currentAccount?.handle, 543 522 }, 544 523 to: { 545 - did: session.currentAccount.did, 546 - handle: session.currentAccount.handle, 524 + did: persistedSession.currentAccount.did, 525 + handle: persistedSession.currentAccount.handle, 547 526 }, 548 527 }) 549 528 550 - await initSession(session.currentAccount) 529 + await initSession(persistedSession.currentAccount) 551 530 } else { 552 531 logger.debug(`session: persisted onUpdate, updating session`, {}) 553 - 554 - /* 555 - * Use updated session in this tab's agent. Do not call 556 - * setCurrentAccount, since that will only persist the session that's 557 - * already persisted, and we'll get a loop between tabs. 558 - */ 559 - // @ts-ignore we checked for `refreshJwt` above 560 - __globalAgent.session = session.currentAccount 532 + agent.session = sessionAccountToAgentSession( 533 + persistedSession.currentAccount, 534 + ) 535 + setAgent(agent.clone()) 561 536 } 562 - } else if (!session.currentAccount && state.currentAccount) { 537 + } else if (!persistedSession.currentAccount && currentAccount) { 563 538 logger.debug( 564 539 `session: persisted onUpdate, logging out`, 565 540 {}, ··· 574 549 */ 575 550 clearCurrentAccount() 576 551 } 577 - 578 - setState(s => ({ 579 - ...s, 580 - accounts: session.accounts, 581 - })) 582 552 }) 583 - }, [state, setState, clearCurrentAccount, initSession]) 553 + }, [ 554 + currentAccount, 555 + setAccounts, 556 + clearCurrentAccount, 557 + initSession, 558 + agent, 559 + setAgent, 560 + ]) 584 561 585 562 const stateContext = React.useMemo( 586 563 () => ({ 587 - ...state, 588 - hasSession: !!state.currentAccount, 564 + agent, 565 + isInitialLoad, 566 + isSwitchingAccounts, 567 + currentAccount, 568 + accounts, 569 + hasSession: Boolean(currentAccount), 589 570 }), 590 - [state], 571 + [agent, isInitialLoad, isSwitchingAccounts, accounts, currentAccount], 591 572 ) 592 573 593 574 const api = React.useMemo( ··· 599 580 resumeSession, 600 581 removeAccount, 601 582 selectAccount, 602 - updateCurrentAccount, 583 + refreshSession, 603 584 clearCurrentAccount, 604 585 }), 605 586 [ ··· 610 591 resumeSession, 611 592 removeAccount, 612 593 selectAccount, 613 - updateCurrentAccount, 594 + refreshSession, 614 595 clearCurrentAccount, 615 596 ], 616 597 ) 598 + 599 + // as we migrate, continue to keep this updated 600 + __globalAgent = agent 617 601 618 602 return ( 619 603 <StateContext.Provider value={stateContext}>