rework session layer on PasswordSession + three-client pattern

- PasswordSession (@atproto/lex-password-session) is now the single auth
  core; SessionAgent bridges it to the base Agent from @atproto/api so
  the ~148 useAgent() consumer files keep working unchanged
- SessionBundle carries {session, agent, accountClient, appviewClient};
  useLexClient() now returns the authed appview client, new
  useAppviewClient()/usePdsClient() hooks
- reducer: OpaqueSessionBundle, new replaced-current-bundle action for
  network-free cross-tab same-did sync (PasswordSession is immutable, so
  the bundle is rebuilt instead of mutated in place)
- moderation: AtpAgent.configure -> Client.configure + Agent.configure,
  per-account labelers on both the bridge and the appview client
- push-token unregistration uses temporary PasswordSessions
- hard-tail migrations: SignupQueued refresh via resumeSession shim,
  useBeginAgeAssurance scoped-token raw Client, useAccountEmailState
  reads currentAccount
- PDS routing preserved on the no-network resume fast path by
  synthesizing a minimal didDoc from the persisted pdsUrl

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-07-16 14:40:01 +03:00
parent 105e68c12c
commit bf8b6bc0c7
58 changed files with 2826 additions and 996 deletions
+3 -3
View File
@@ -2,7 +2,6 @@ import {
type $Typed,
type AppBskyGraphFollow,
type AppBskyGraphGetFollows,
type AtpAgent,
type ComAtprotoRepoApplyWrites,
type ComAtprotoRepoStrongRef,
} from '@atproto/api'
@@ -10,9 +9,10 @@ import {TID} from '@atproto/common-web'
import chunk from 'lodash.chunk'
import {until} from '#/lib/async/until'
import {type SessionAgent} from '#/state/session'
export async function bulkWriteFollows(
agent: AtpAgent,
agent: SessionAgent,
dids: string[],
via?: ComAtprotoRepoStrongRef.Main,
) {
@@ -59,7 +59,7 @@ export async function bulkWriteFollows(
}
async function whenFollowsIndexed(
agent: AtpAgent,
agent: SessionAgent,
actor: string,
fn: (res: AppBskyGraphGetFollows.Response) => boolean,
) {
@@ -1,10 +1,6 @@
import {Platform, View} from 'react-native'
import {Image} from 'expo-image'
import {
type AppBskyActorDefs,
type AppBskyActorGetProfile,
type AtpAgent,
} from '@atproto/api'
import {type AppBskyActorDefs, type AppBskyActorGetProfile} from '@atproto/api'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
@@ -13,7 +9,7 @@ import {useMutation, useQueryClient} from '@tanstack/react-query'
import {until} from '#/lib/async/until'
import {isNetworkError} from '#/lib/strings/errors'
import {RQKEY} from '#/state/queries/profile'
import {useAgent, useSession} from '#/state/session'
import {type SessionAgent, useAgent, useSession} from '#/state/session'
import {atoms as a, useTheme, web} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
@@ -323,7 +319,7 @@ function platform() {
}
async function whenAppViewReady(
agent: AtpAgent,
agent: SessionAgent,
actor: string,
fn: (res: AppBskyActorGetProfile.Response) => boolean,
) {
+1 -1
View File
@@ -41,7 +41,7 @@ export function SignupQueued() {
const res = await agent.com.atproto.temp.checkSignupQueue()
if (res.data.activated) {
// ready to go, exchange the access token for a usable one and kick off onboarding
await agent.sessionManager.refreshSession()
await agent.resumeSession()
if (!isSignupQueued(agent.session?.accessJwt)) {
onboardingDispatch({type: 'start'})
}