an atproto based link aggregator
1/**
2 * Lex client utilities for ATProto record operations
3 */
4
5import { Client } from '@atproto/lex';
6import { IdResolver } from '@atproto/identity';
7import { TokenRefreshError } from '@atproto/oauth-client-node';
8import { createOAuthClient, getSession } from './auth';
9import { localDb } from './db';
10
11const idResolver = new IdResolver();
12
13export class AuthRequiredError extends Error {
14 constructor(message = 'Authentication required') {
15 super(message);
16 this.name = 'AuthRequiredError';
17 }
18}
19
20/**
21 * Get an unauthenticated lex Client for reading from a user's PDS.
22 * Resolves the DID to find the PDS endpoint.
23 */
24export async function getPublicLexClient(did: string): Promise<Client> {
25 const atprotoData = await idResolver.did.resolveAtprotoData(did);
26 const pdsUrl = atprotoData.pds;
27 return new Client(pdsUrl);
28}
29
30/**
31 * Get an authenticated lex Client for the current user.
32 * Throws AuthRequiredError if not authenticated.
33 */
34export async function getLexClient(cookies: Parameters<typeof getSession>[0]): Promise<Client> {
35 const session = await getSession(cookies);
36 if (!session.did) {
37 throw new AuthRequiredError();
38 }
39
40 const oauthClient = await createOAuthClient(localDb);
41
42 let oauthSession;
43 try {
44 oauthSession = await oauthClient.restore(session.did);
45 } catch (err) {
46 if (err instanceof TokenRefreshError) {
47 // Session was deleted or token refresh failed - clear stale cookie
48 console.log(`[auth] Clearing stale session for ${session.did}: ${(err as Error).message}`);
49 session.did = undefined;
50 await session.save();
51 }
52 throw err;
53 }
54
55 if (!oauthSession) {
56 throw new AuthRequiredError();
57 }
58
59 // Create lex Client directly from OAuth session
60 return new Client(oauthSession);
61}