drop the agent url reads in link meta and push registration
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import {type AtpAgent} from '@atproto/api'
|
||||
import {type SessionData} from '@atproto/lex-password-session'
|
||||
import {describe, expect, it, jest} from '@jest/globals'
|
||||
|
||||
@@ -18,21 +17,21 @@ jest.mock('../../../ageAssurance/state', () => ({
|
||||
unsafeGetAndComputeAgeAssurance: () => ({state: {}}),
|
||||
}))
|
||||
jest.mock('#/lib/notifications/notifications', () => ({
|
||||
unregisterPushToken(_agents: AtpAgent[]) {
|
||||
unregisterPushToken(_clients: unknown[]) {
|
||||
return Promise.resolve()
|
||||
},
|
||||
}))
|
||||
/*
|
||||
* The logout and account-removal reducer cases fire a push-token side effect
|
||||
* whose first step, `createTemporaryAgentsAndResume`, builds real `AtpAgent`s
|
||||
* and resumes them over the real network. Under jest that request outlives the
|
||||
* whose first step, `createTemporaryClientsAndResume`, resumes real
|
||||
* `PasswordSession`s over the real network. Under jest that request outlives the
|
||||
* suite: it rejects after teardown, and the resulting `logger.error` reaches
|
||||
* for `nanoid` in an environment that no longer has it, failing whichever suite
|
||||
* happens to be running at that moment. Stubbing the module keeps the side
|
||||
* effect synchronous and offline.
|
||||
*/
|
||||
jest.mock('../util', () => ({
|
||||
createTemporaryAgentsAndResume: () => Promise.resolve([]),
|
||||
createTemporaryClientsAndResume: () => Promise.resolve([]),
|
||||
}))
|
||||
|
||||
// Reuse a bundle within each test: session events are scoped by bundle identity.
|
||||
|
||||
@@ -3,7 +3,7 @@ import {logger} from '#/lib/notifications/util'
|
||||
import {wrapSessionReducerForLogging} from './logging'
|
||||
import {createPublicSessionBundle} from './session-core'
|
||||
import {type AtpSessionEvent, type SessionAccount} from './types'
|
||||
import {createTemporaryAgentsAndResume} from './util'
|
||||
import {createTemporaryClientsAndResume} from './util'
|
||||
|
||||
// Keep session internals outside the reducer's static view of a bundle.
|
||||
type OpaqueSessionBundle = {
|
||||
@@ -155,8 +155,8 @@ let reducer = (state: State, action: Action): State => {
|
||||
// side effect
|
||||
const account = state.accounts.find(a => a.did === accountDid)
|
||||
if (account) {
|
||||
createTemporaryAgentsAndResume([account])
|
||||
.then(agents => unregisterPushToken(agents))
|
||||
createTemporaryClientsAndResume([account])
|
||||
.then(clients => unregisterPushToken(clients))
|
||||
.then(() =>
|
||||
logger.debug('Push token unregistered', {did: accountDid}),
|
||||
)
|
||||
@@ -183,8 +183,8 @@ let reducer = (state: State, action: Action): State => {
|
||||
// side effect
|
||||
const account = state.accounts.find(a => a.did === accountDid)
|
||||
if (account && accountDid) {
|
||||
createTemporaryAgentsAndResume([account])
|
||||
.then(agents => unregisterPushToken(agents))
|
||||
createTemporaryClientsAndResume([account])
|
||||
.then(clients => unregisterPushToken(clients))
|
||||
.then(() =>
|
||||
logger.debug('Push token unregistered', {did: accountDid}),
|
||||
)
|
||||
@@ -211,8 +211,8 @@ let reducer = (state: State, action: Action): State => {
|
||||
}
|
||||
}
|
||||
case 'logged-out-every-account': {
|
||||
createTemporaryAgentsAndResume(state.accounts)
|
||||
.then(agents => unregisterPushToken(agents))
|
||||
createTemporaryClientsAndResume(state.accounts)
|
||||
.then(clients => unregisterPushToken(clients))
|
||||
.then(() => logger.debug('Push token unregistered'))
|
||||
.catch(err => {
|
||||
logger.error('Failed to unregister push token', {
|
||||
|
||||
+34
-20
@@ -1,7 +1,10 @@
|
||||
import AtpAgent from '@atproto/api'
|
||||
import {PasswordSession} from '@atproto/lex-password-session'
|
||||
|
||||
import {createLexClient} from '#/lib/lexClient'
|
||||
import {type TemporaryPushClient} from '#/lib/notifications/notifications'
|
||||
import * as persisted from '#/state/persisted'
|
||||
import {sessionAccountToSession} from './session-data'
|
||||
import {networkAwareFetch} from './network'
|
||||
import {sessionAccountToSessionData} from './session-data'
|
||||
import {type SessionAccount} from './types'
|
||||
|
||||
export {isSessionExpired, isSignupQueued} from './session-data'
|
||||
@@ -12,30 +15,41 @@ export function readLastActiveAccount() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and attempted to resumeSession for every stored session.
|
||||
* Intended to be used to send push token revokations just before logout.
|
||||
* Resume a single-use session per stored account, for the push-token revocation
|
||||
* sent just before logout.
|
||||
*
|
||||
* The sessions carry no lifecycle hooks - no `onUpdated`, no `onDeleted` - so a
|
||||
* rotation one of them performs can neither persist over nor race the live
|
||||
* session's tokens. That isolation is load-bearing: each exists only long enough
|
||||
* to authenticate one `unregisterPush` call.
|
||||
*
|
||||
* PDS routing is left to the session rather than pinned from the stored
|
||||
* `pdsUrl`, because `resume` refreshes (and fills in a missing didDoc from
|
||||
* `getSession`) before the client issues anything, so the request already goes
|
||||
* to the didDoc PDS.
|
||||
*
|
||||
* `resume` rejects only when a session is definitively dead; a transient network
|
||||
* failure resolves with the stored tokens, which are the same ones the old agent
|
||||
* path would have sent. Definitively dead sessions drop out of the settled list.
|
||||
*/
|
||||
export async function createTemporaryAgentsAndResume(
|
||||
export async function createTemporaryClientsAndResume(
|
||||
accounts: SessionAccount[],
|
||||
) {
|
||||
const agents = await Promise.allSettled(
|
||||
): Promise<TemporaryPushClient[]> {
|
||||
const settled = await Promise.allSettled(
|
||||
accounts.map(async account => {
|
||||
const agent: AtpAgent = new AtpAgent({service: account.service})
|
||||
if (account.pdsUrl) {
|
||||
agent.sessionManager.pdsUrl = new URL(account.pdsUrl)
|
||||
}
|
||||
|
||||
const session = sessionAccountToSession(account)
|
||||
const res = await agent.resumeSession(session)
|
||||
if (!res.success) throw new Error('Failed to resume session')
|
||||
|
||||
agent.assertAuthenticated() // confirm auth success
|
||||
|
||||
return agent
|
||||
const session = await PasswordSession.resume(
|
||||
sessionAccountToSessionData(account),
|
||||
{fetch: networkAwareFetch},
|
||||
)
|
||||
return {
|
||||
client: createLexClient(session),
|
||||
service: session.session.service,
|
||||
handle: session.session.handle,
|
||||
} satisfies TemporaryPushClient
|
||||
}),
|
||||
)
|
||||
|
||||
return agents
|
||||
return settled
|
||||
.filter(x => x.status === 'fulfilled')
|
||||
.map(promise => promise.value)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user