* Split utils into files * Move reducer to another file * Write types explicitly * Remove unnnecessary check * Move things around a bit * Move more stuff into agent factories * Move more stuff into agent * Fix gates await * Clarify comments * Enforce more via types * Nit * initSession -> resumeSession * Protect against races * Make agent opaque to reducer * Check using plain condition
190 lines
5.3 KiB
TypeScript
190 lines
5.3 KiB
TypeScript
import {BskyAgent} from '@atproto/api'
|
|
import {AtpSessionEvent} from '@atproto-labs/api'
|
|
|
|
import {networkRetry} from '#/lib/async/retry'
|
|
import {PUBLIC_BSKY_SERVICE} from '#/lib/constants'
|
|
import {tryFetchGates} from '#/lib/statsig/statsig'
|
|
import {
|
|
configureModerationForAccount,
|
|
configureModerationForGuest,
|
|
} from './moderation'
|
|
import {SessionAccount} from './types'
|
|
import {isSessionDeactivated, isSessionExpired} from './util'
|
|
import {IS_PROD_SERVICE} from '#/lib/constants'
|
|
import {DEFAULT_PROD_FEEDS} from '../queries/preferences'
|
|
|
|
export function createPublicAgent() {
|
|
configureModerationForGuest() // Side effect but only relevant for tests
|
|
return new BskyAgent({service: PUBLIC_BSKY_SERVICE})
|
|
}
|
|
|
|
export async function createAgentAndResume(
|
|
storedAccount: SessionAccount,
|
|
onSessionChange: (
|
|
agent: BskyAgent,
|
|
did: string,
|
|
event: AtpSessionEvent,
|
|
) => void,
|
|
) {
|
|
const agent = new BskyAgent({service: storedAccount.service})
|
|
if (storedAccount.pdsUrl) {
|
|
agent.pdsUrl = agent.api.xrpc.uri = new URL(storedAccount.pdsUrl)
|
|
}
|
|
const gates = tryFetchGates(storedAccount.did, 'prefer-low-latency')
|
|
const moderation = configureModerationForAccount(agent, storedAccount)
|
|
const prevSession = {
|
|
accessJwt: storedAccount.accessJwt ?? '',
|
|
refreshJwt: storedAccount.refreshJwt ?? '',
|
|
did: storedAccount.did,
|
|
handle: storedAccount.handle,
|
|
}
|
|
if (isSessionExpired(storedAccount)) {
|
|
await networkRetry(1, () => agent.resumeSession(prevSession))
|
|
} else {
|
|
agent.session = prevSession
|
|
if (!storedAccount.deactivated) {
|
|
// Intentionally not awaited to unblock the UI:
|
|
networkRetry(1, () => agent.resumeSession(prevSession))
|
|
}
|
|
}
|
|
|
|
return prepareAgent(agent, gates, moderation, onSessionChange)
|
|
}
|
|
|
|
export async function createAgentAndLogin(
|
|
{
|
|
service,
|
|
identifier,
|
|
password,
|
|
authFactorToken,
|
|
}: {
|
|
service: string
|
|
identifier: string
|
|
password: string
|
|
authFactorToken?: string
|
|
},
|
|
onSessionChange: (
|
|
agent: BskyAgent,
|
|
did: string,
|
|
event: AtpSessionEvent,
|
|
) => void,
|
|
) {
|
|
const agent = new BskyAgent({service})
|
|
await agent.login({identifier, password, authFactorToken})
|
|
|
|
const account = agentToSessionAccountOrThrow(agent)
|
|
const gates = tryFetchGates(account.did, 'prefer-fresh-gates')
|
|
const moderation = configureModerationForAccount(agent, account)
|
|
return prepareAgent(agent, moderation, gates, onSessionChange)
|
|
}
|
|
|
|
export async function createAgentAndCreateAccount(
|
|
{
|
|
service,
|
|
email,
|
|
password,
|
|
handle,
|
|
birthDate,
|
|
inviteCode,
|
|
verificationPhone,
|
|
verificationCode,
|
|
}: {
|
|
service: string
|
|
email: string
|
|
password: string
|
|
handle: string
|
|
birthDate: Date
|
|
inviteCode?: string
|
|
verificationPhone?: string
|
|
verificationCode?: string
|
|
},
|
|
onSessionChange: (
|
|
agent: BskyAgent,
|
|
did: string,
|
|
event: AtpSessionEvent,
|
|
) => void,
|
|
) {
|
|
const agent = new BskyAgent({service})
|
|
await agent.createAccount({
|
|
email,
|
|
password,
|
|
handle,
|
|
inviteCode,
|
|
verificationPhone,
|
|
verificationCode,
|
|
})
|
|
const account = agentToSessionAccountOrThrow(agent)
|
|
const gates = tryFetchGates(account.did, 'prefer-fresh-gates')
|
|
const moderation = configureModerationForAccount(agent, account)
|
|
if (!account.deactivated) {
|
|
/*dont await*/ agent.upsertProfile(_existing => {
|
|
return {
|
|
displayName: '',
|
|
// HACKFIX
|
|
// creating a bunch of identical profile objects is breaking the relay
|
|
// tossing this unspecced field onto it to reduce the size of the problem
|
|
// -prf
|
|
createdAt: new Date().toISOString(),
|
|
}
|
|
})
|
|
}
|
|
|
|
// Not awaited so that we can still get into onboarding.
|
|
// This is OK because we won't let you toggle adult stuff until you set the date.
|
|
agent.setPersonalDetails({birthDate: birthDate.toISOString()})
|
|
if (IS_PROD_SERVICE(service)) {
|
|
agent.setSavedFeeds(DEFAULT_PROD_FEEDS.saved, DEFAULT_PROD_FEEDS.pinned)
|
|
}
|
|
|
|
return prepareAgent(agent, gates, moderation, onSessionChange)
|
|
}
|
|
|
|
async function prepareAgent(
|
|
agent: BskyAgent,
|
|
// Not awaited in the calling code so we can delay blocking on them.
|
|
gates: Promise<void>,
|
|
moderation: Promise<void>,
|
|
onSessionChange: (
|
|
agent: BskyAgent,
|
|
did: string,
|
|
event: AtpSessionEvent,
|
|
) => void,
|
|
) {
|
|
// There's nothing else left to do, so block on them here.
|
|
await Promise.all([gates, moderation])
|
|
|
|
// Now the agent is ready.
|
|
const account = agentToSessionAccountOrThrow(agent)
|
|
agent.setPersistSessionHandler(event => {
|
|
onSessionChange(agent, account.did, event)
|
|
})
|
|
return {agent, account}
|
|
}
|
|
|
|
export function agentToSessionAccountOrThrow(agent: BskyAgent): SessionAccount {
|
|
const account = agentToSessionAccount(agent)
|
|
if (!account) {
|
|
throw Error('Expected an active session')
|
|
}
|
|
return account
|
|
}
|
|
|
|
export function agentToSessionAccount(
|
|
agent: BskyAgent,
|
|
): SessionAccount | undefined {
|
|
if (!agent.session) {
|
|
return undefined
|
|
}
|
|
return {
|
|
service: agent.service.toString(),
|
|
did: agent.session.did,
|
|
handle: agent.session.handle,
|
|
email: agent.session.email,
|
|
emailConfirmed: agent.session.emailConfirmed || false,
|
|
emailAuthFactor: agent.session.emailAuthFactor || false,
|
|
refreshJwt: agent.session.refreshJwt,
|
|
accessJwt: agent.session.accessJwt,
|
|
deactivated: isSessionDeactivated(agent.session.accessJwt),
|
|
pdsUrl: agent.pdsUrl?.toString(),
|
|
}
|
|
}
|