extract session-data, network, and expiry-rescue modules
Session-account conversion, the network-aware fetch wrapper, and the expiry-rescue candidate picker were tangled into agent.ts and util.ts. Pulling them into agent-agnostic modules lets the upcoming session layer share them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import {type AtpSessionData} from '@atproto/api'
|
||||
import {getPdsEndpoint, isValidDidDoc} from '@atproto/common-web'
|
||||
import {type SessionData} from '@atproto/lex-password-session'
|
||||
import {jwtDecode} from 'jwt-decode'
|
||||
|
||||
import {BSKY_SERVICE} from '#/lib/constants'
|
||||
import {isJwtExpired} from '#/lib/jwt'
|
||||
import {hasProp} from '#/lib/type-guards'
|
||||
import {type SessionAccount} from './types'
|
||||
|
||||
/** Whether an access token was issued for a queued (waitlisted) signup. */
|
||||
export function isSignupQueued(accessJwt: string | undefined) {
|
||||
if (accessJwt) {
|
||||
const sessData = jwtDecode(accessJwt)
|
||||
return (
|
||||
hasProp(sessData, 'scope') &&
|
||||
sessData.scope === 'com.atproto.signupQueued'
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert live `PasswordSession` session data into the persisted
|
||||
* `SessionAccount` snapshot.
|
||||
*
|
||||
* The object literal's field order is load-bearing: the reducer's
|
||||
* `JSON.stringify` fast path and the session test snapshots depend on
|
||||
* byte-stable serialization. `service` and `pdsUrl` are normalized through
|
||||
* `new URL().toString()` for a stable trailing slash.
|
||||
*
|
||||
* `pdsUrl` comes from the DID document or a pre-refresh stored value. It does
|
||||
* not fall back to the login service.
|
||||
*/
|
||||
export function sessionDataToSessionAccount(
|
||||
session: SessionData | null | undefined,
|
||||
service: string,
|
||||
storedPdsUrl?: string,
|
||||
): SessionAccount | undefined {
|
||||
if (!session) {
|
||||
return undefined
|
||||
}
|
||||
const normalizedService = new URL(service).toString()
|
||||
const didDocPdsUrl =
|
||||
session.didDoc && isValidDidDoc(session.didDoc)
|
||||
? getPdsEndpoint(session.didDoc)
|
||||
: undefined
|
||||
const pdsUrl = didDocPdsUrl ?? storedPdsUrl
|
||||
return {
|
||||
service: normalizedService,
|
||||
did: session.did,
|
||||
handle: session.handle,
|
||||
email: session.email,
|
||||
emailConfirmed: session.emailConfirmed || false,
|
||||
emailAuthFactor: session.emailAuthFactor || false,
|
||||
refreshJwt: session.refreshJwt,
|
||||
accessJwt: session.accessJwt,
|
||||
signupQueued: isSignupQueued(session.accessJwt),
|
||||
active: session.active,
|
||||
status: session.status,
|
||||
pdsUrl: pdsUrl ? new URL(pdsUrl).toString() : undefined,
|
||||
isSelfHosted: !normalizedService.startsWith(BSKY_SERVICE),
|
||||
}
|
||||
}
|
||||
|
||||
/** Convert a persisted account into data suitable for `PasswordSession`. */
|
||||
export function sessionAccountToSessionData(
|
||||
account: SessionAccount,
|
||||
): SessionData {
|
||||
return {
|
||||
accessJwt: account.accessJwt ?? '',
|
||||
active: account.active ?? true,
|
||||
did: account.did as SessionData['did'],
|
||||
email: account.email,
|
||||
emailAuthFactor: account.emailAuthFactor,
|
||||
emailConfirmed: account.emailConfirmed,
|
||||
handle: account.handle as SessionData['handle'],
|
||||
refreshJwt: account.refreshJwt ?? '',
|
||||
status: account.status,
|
||||
service: account.service,
|
||||
}
|
||||
}
|
||||
|
||||
/** Convert a persisted account into data suitable for `AtpAgent`. */
|
||||
export function sessionAccountToSession(
|
||||
account: SessionAccount,
|
||||
): AtpSessionData {
|
||||
return {
|
||||
// Sorted in the same property order as when returned by BskyAgent (alphabetical).
|
||||
accessJwt: account.accessJwt ?? '',
|
||||
did: account.did,
|
||||
email: account.email,
|
||||
emailAuthFactor: account.emailAuthFactor,
|
||||
emailConfirmed: account.emailConfirmed,
|
||||
handle: account.handle,
|
||||
refreshJwt: account.refreshJwt ?? '',
|
||||
/**
|
||||
* @see https://github.com/bluesky-social/atproto/blob/c5d36d5ba2a2c2a5c4f366a5621c06a5608e361e/packages/api/src/agent.ts#L188
|
||||
*/
|
||||
active: account.active ?? true,
|
||||
status: account.status,
|
||||
}
|
||||
}
|
||||
|
||||
export function isSessionExpired(account: SessionAccount) {
|
||||
return account.accessJwt ? isJwtExpired(account.accessJwt) : true
|
||||
}
|
||||
Reference in New Issue
Block a user