an app to share curated trails
sidetrail.app
atproto
nextjs
react
rsc
1import "server-only";
2import { Client } from "@atproto/lex";
3import { IdResolver } from "@atproto/identity";
4import { TokenRefreshError } from "@atproto/oauth-client";
5import { refresh } from "next/cache";
6import { getSession } from "@/auth/session";
7import { getOAuthClient } from "@/auth/client";
8
9const idResolver = new IdResolver();
10
11export class AuthRequiredError extends Error {
12 constructor(message = "Authentication required") {
13 super(message);
14 this.name = "AuthRequiredError";
15 }
16}
17
18/**
19 * Get an unauthenticated lex Client for reading from a user's PDS.
20 * Resolves the DID to find the PDS endpoint, then creates a client that hits it directly.
21 * Use this for read-only operations to avoid OAuth client's CachedGetter issues with "use cache".
22 */
23export async function getPublicLexClient(did: string): Promise<Client> {
24 const atprotoData = await idResolver.did.resolveAtprotoData(did);
25 const pdsUrl = atprotoData.pds;
26 return new Client(pdsUrl);
27}
28
29/**
30 * Get an authenticated lex Client for the current user.
31 * Throws AuthRequiredError if not authenticated.
32 * If the OAuth session is invalid/deleted, clears the cookie and throws AuthRequiredError.
33 */
34export async function getLexClient(): Promise<Client> {
35 const session = await getSession();
36 if (!session.did) {
37 throw new AuthRequiredError();
38 }
39
40 const oauthClient = await getOAuthClient();
41
42 let oauthSession;
43 try {
44 oauthSession = await oauthClient.restore(session.did);
45 } catch (err) {
46 if (err instanceof TokenRefreshError) {
47 const cause = err.cause instanceof Error ? err.cause.message : err.cause;
48 console.log(`[auth] TokenRefreshError ${session.did}: ${err.message} (cause: ${cause})`);
49 session.destroy();
50 refresh();
51 } else {
52 const msg = err instanceof Error ? err.message : err;
53 console.log(`[auth] restore failed ${session.did}: ${msg}`);
54 }
55 throw err;
56 }
57
58 if (!oauthSession) {
59 throw new AuthRequiredError();
60 }
61
62 // Create lex Client directly from OAuth session (idiomatic usage)
63 return new Client(oauthSession);
64}