Barazo AppView backend barazo.forum
at main 161 lines 4.8 kB view raw
1import { Agent } from '@atproto/api' 2import type { NodeOAuthClient } from '@atproto/oauth-client-node' 3import type { Logger } from './logger.js' 4 5// --------------------------------------------------------------------------- 6// Types 7// --------------------------------------------------------------------------- 8 9/** Result of a successful record creation or update on the user's PDS. */ 10export interface PdsWriteResult { 11 uri: string 12 cid: string 13} 14 15/** PDS client interface for creating, updating, and deleting AT Protocol records. */ 16export interface PdsClient { 17 createRecord( 18 did: string, 19 collection: string, 20 record: Record<string, unknown> 21 ): Promise<PdsWriteResult> 22 23 updateRecord( 24 did: string, 25 collection: string, 26 rkey: string, 27 record: Record<string, unknown> 28 ): Promise<PdsWriteResult> 29 30 deleteRecord(did: string, collection: string, rkey: string): Promise<void> 31 32 /** 33 * Upload a binary blob (e.g. an image) to the user's PDS. 34 * Returns the blob reference object suitable for embedding in records. 35 */ 36 uploadBlob(did: string, data: Uint8Array, mimeType: string): Promise<unknown> 37} 38 39// --------------------------------------------------------------------------- 40// Error handling 41// --------------------------------------------------------------------------- 42 43/** 44 * Extract a meaningful message from an unknown PDS error. 45 */ 46function pdsErrorMessage(err: unknown): string { 47 if (err instanceof Error) { 48 return err.message 49 } 50 return String(err) 51} 52 53// --------------------------------------------------------------------------- 54// Factory 55// --------------------------------------------------------------------------- 56 57/** 58 * Create a PDS client that uses the OAuth client to restore user sessions 59 * and perform XRPC operations on the user's PDS. 60 * 61 * @param oauthClient - The AT Protocol OAuth client (provides session restore) 62 * @param logger - Pino logger for structured logging 63 */ 64export function createPdsClient(oauthClient: NodeOAuthClient, logger: Logger): PdsClient { 65 /** 66 * Restore an authenticated Agent for the given DID. 67 * The OAuth client manages token refresh transparently. 68 */ 69 async function getAgent(did: string): Promise<Agent> { 70 const session = await oauthClient.restore(did) 71 return new Agent(session) 72 } 73 74 return { 75 async createRecord( 76 did: string, 77 collection: string, 78 record: Record<string, unknown> 79 ): Promise<PdsWriteResult> { 80 logger.debug({ did, collection }, 'PDS createRecord') 81 82 try { 83 const agent = await getAgent(did) 84 const response = await agent.com.atproto.repo.createRecord({ 85 repo: did, 86 collection, 87 record: { $type: collection, ...record }, 88 }) 89 90 return { uri: response.data.uri, cid: response.data.cid } 91 } catch (err: unknown) { 92 logger.error({ err, did, collection }, 'PDS createRecord failed: %s', pdsErrorMessage(err)) 93 throw err 94 } 95 }, 96 97 async updateRecord( 98 did: string, 99 collection: string, 100 rkey: string, 101 record: Record<string, unknown> 102 ): Promise<PdsWriteResult> { 103 logger.debug({ did, collection, rkey }, 'PDS updateRecord') 104 105 try { 106 const agent = await getAgent(did) 107 const response = await agent.com.atproto.repo.putRecord({ 108 repo: did, 109 collection, 110 rkey, 111 record: { $type: collection, ...record }, 112 }) 113 114 return { uri: response.data.uri, cid: response.data.cid } 115 } catch (err: unknown) { 116 logger.error( 117 { err, did, collection, rkey }, 118 'PDS updateRecord failed: %s', 119 pdsErrorMessage(err) 120 ) 121 throw err 122 } 123 }, 124 125 async deleteRecord(did: string, collection: string, rkey: string): Promise<void> { 126 logger.debug({ did, collection, rkey }, 'PDS deleteRecord') 127 128 try { 129 const agent = await getAgent(did) 130 await agent.com.atproto.repo.deleteRecord({ 131 repo: did, 132 collection, 133 rkey, 134 }) 135 } catch (err: unknown) { 136 logger.error( 137 { err, did, collection, rkey }, 138 'PDS deleteRecord failed: %s', 139 pdsErrorMessage(err) 140 ) 141 throw err 142 } 143 }, 144 145 async uploadBlob(did: string, data: Uint8Array, mimeType: string): Promise<unknown> { 146 logger.debug({ did, mimeType, size: data.length }, 'PDS uploadBlob') 147 148 try { 149 const agent = await getAgent(did) 150 const response = await agent.uploadBlob(data, { 151 encoding: mimeType, 152 }) 153 154 return response.data.blob 155 } catch (err: unknown) { 156 logger.error({ err, did, mimeType }, 'PDS uploadBlob failed: %s', pdsErrorMessage(err)) 157 throw err 158 } 159 }, 160 } 161}