this repo has no description
1const API_BASE = '/xrpc'
2
3export class ApiError extends Error {
4 public did?: string
5 constructor(public status: number, public error: string, message: string, did?: string) {
6 super(message)
7 this.name = 'ApiError'
8 this.did = did
9 }
10}
11
12async function xrpc<T>(method: string, options?: {
13 method?: 'GET' | 'POST'
14 params?: Record<string, string>
15 body?: unknown
16 token?: string
17}): Promise<T> {
18 const { method: httpMethod = 'GET', params, body, token } = options ?? {}
19 let url = `${API_BASE}/${method}`
20 if (params) {
21 const searchParams = new URLSearchParams(params)
22 url += `?${searchParams}`
23 }
24 const headers: Record<string, string> = {}
25 if (token) {
26 headers['Authorization'] = `Bearer ${token}`
27 }
28 if (body) {
29 headers['Content-Type'] = 'application/json'
30 }
31 const res = await fetch(url, {
32 method: httpMethod,
33 headers,
34 body: body ? JSON.stringify(body) : undefined,
35 })
36 if (!res.ok) {
37 const err = await res.json().catch(() => ({ error: 'Unknown', message: res.statusText }))
38 throw new ApiError(res.status, err.error, err.message, err.did)
39 }
40 return res.json()
41}
42
43export interface Session {
44 did: string
45 handle: string
46 email?: string
47 emailConfirmed?: boolean
48 preferredChannel?: string
49 preferredChannelVerified?: boolean
50 isAdmin?: boolean
51 active?: boolean
52 status?: 'active' | 'deactivated'
53 accessJwt: string
54 refreshJwt: string
55}
56
57export interface AppPassword {
58 name: string
59 createdAt: string
60}
61
62export interface InviteCode {
63 code: string
64 available: number
65 disabled: boolean
66 forAccount: string
67 createdBy: string
68 createdAt: string
69 uses: { usedBy: string; usedAt: string }[]
70}
71
72export type VerificationChannel = 'email' | 'discord' | 'telegram' | 'signal'
73
74export type DidType = 'plc' | 'web' | 'web-external'
75
76export interface CreateAccountParams {
77 handle: string
78 email: string
79 password: string
80 inviteCode?: string
81 didType?: DidType
82 did?: string
83 verificationChannel?: VerificationChannel
84 discordId?: string
85 telegramUsername?: string
86 signalNumber?: string
87}
88
89export interface CreateAccountResult {
90 handle: string
91 did: string
92 verificationRequired: boolean
93 verificationChannel: string
94}
95
96export interface ConfirmSignupResult {
97 accessJwt: string
98 refreshJwt: string
99 handle: string
100 did: string
101 email?: string
102 emailConfirmed?: boolean
103 preferredChannel?: string
104 preferredChannelVerified?: boolean
105}
106
107export const api = {
108 async createAccount(params: CreateAccountParams): Promise<CreateAccountResult> {
109 return xrpc('com.atproto.server.createAccount', {
110 method: 'POST',
111 body: {
112 handle: params.handle,
113 email: params.email,
114 password: params.password,
115 inviteCode: params.inviteCode,
116 didType: params.didType,
117 did: params.did,
118 verificationChannel: params.verificationChannel,
119 discordId: params.discordId,
120 telegramUsername: params.telegramUsername,
121 signalNumber: params.signalNumber,
122 },
123 })
124 },
125
126 async confirmSignup(did: string, verificationCode: string): Promise<ConfirmSignupResult> {
127 return xrpc('com.atproto.server.confirmSignup', {
128 method: 'POST',
129 body: { did, verificationCode },
130 })
131 },
132
133 async resendVerification(did: string): Promise<{ success: boolean }> {
134 return xrpc('com.atproto.server.resendVerification', {
135 method: 'POST',
136 body: { did },
137 })
138 },
139
140 async createSession(identifier: string, password: string): Promise<Session> {
141 return xrpc('com.atproto.server.createSession', {
142 method: 'POST',
143 body: { identifier, password },
144 })
145 },
146
147 async getSession(token: string): Promise<Session> {
148 return xrpc('com.atproto.server.getSession', { token })
149 },
150
151 async refreshSession(refreshJwt: string): Promise<Session> {
152 return xrpc('com.atproto.server.refreshSession', {
153 method: 'POST',
154 token: refreshJwt,
155 })
156 },
157
158 async deleteSession(token: string): Promise<void> {
159 await xrpc('com.atproto.server.deleteSession', {
160 method: 'POST',
161 token,
162 })
163 },
164
165 async listAppPasswords(token: string): Promise<{ passwords: AppPassword[] }> {
166 return xrpc('com.atproto.server.listAppPasswords', { token })
167 },
168
169 async createAppPassword(token: string, name: string): Promise<{ name: string; password: string; createdAt: string }> {
170 return xrpc('com.atproto.server.createAppPassword', {
171 method: 'POST',
172 token,
173 body: { name },
174 })
175 },
176
177 async revokeAppPassword(token: string, name: string): Promise<void> {
178 await xrpc('com.atproto.server.revokeAppPassword', {
179 method: 'POST',
180 token,
181 body: { name },
182 })
183 },
184
185 async getAccountInviteCodes(token: string): Promise<{ codes: InviteCode[] }> {
186 return xrpc('com.atproto.server.getAccountInviteCodes', { token })
187 },
188
189 async createInviteCode(token: string, useCount: number = 1): Promise<{ code: string }> {
190 return xrpc('com.atproto.server.createInviteCode', {
191 method: 'POST',
192 token,
193 body: { useCount },
194 })
195 },
196
197 async requestPasswordReset(email: string): Promise<void> {
198 await xrpc('com.atproto.server.requestPasswordReset', {
199 method: 'POST',
200 body: { email },
201 })
202 },
203
204 async resetPassword(token: string, password: string): Promise<void> {
205 await xrpc('com.atproto.server.resetPassword', {
206 method: 'POST',
207 body: { token, password },
208 })
209 },
210
211 async requestEmailUpdate(token: string): Promise<{ tokenRequired: boolean }> {
212 return xrpc('com.atproto.server.requestEmailUpdate', {
213 method: 'POST',
214 token,
215 })
216 },
217
218 async updateEmail(token: string, email: string, emailToken?: string): Promise<void> {
219 await xrpc('com.atproto.server.updateEmail', {
220 method: 'POST',
221 token,
222 body: { email, token: emailToken },
223 })
224 },
225
226 async updateHandle(token: string, handle: string): Promise<void> {
227 await xrpc('com.atproto.identity.updateHandle', {
228 method: 'POST',
229 token,
230 body: { handle },
231 })
232 },
233
234 async requestAccountDelete(token: string): Promise<void> {
235 await xrpc('com.atproto.server.requestAccountDelete', {
236 method: 'POST',
237 token,
238 })
239 },
240
241 async deleteAccount(did: string, password: string, deleteToken: string): Promise<void> {
242 await xrpc('com.atproto.server.deleteAccount', {
243 method: 'POST',
244 body: { did, password, token: deleteToken },
245 })
246 },
247
248 async describeServer(): Promise<{
249 availableUserDomains: string[]
250 inviteCodeRequired: boolean
251 links?: { privacyPolicy?: string; termsOfService?: string }
252 }> {
253 return xrpc('com.atproto.server.describeServer')
254 },
255
256 async getNotificationPrefs(token: string): Promise<{
257 preferredChannel: string
258 email: string
259 discordId: string | null
260 discordVerified: boolean
261 telegramUsername: string | null
262 telegramVerified: boolean
263 signalNumber: string | null
264 signalVerified: boolean
265 }> {
266 return xrpc('com.tranquil.account.getNotificationPrefs', { token })
267 },
268
269 async updateNotificationPrefs(token: string, prefs: {
270 preferredChannel?: string
271 discordId?: string
272 telegramUsername?: string
273 signalNumber?: string
274 }): Promise<{ success: boolean }> {
275 return xrpc('com.tranquil.account.updateNotificationPrefs', {
276 method: 'POST',
277 token,
278 body: prefs,
279 })
280 },
281
282 async confirmChannelVerification(token: string, channel: string, code: string): Promise<{ success: boolean }> {
283 return xrpc('com.tranquil.account.confirmChannelVerification', {
284 method: 'POST',
285 token,
286 body: { channel, code },
287 })
288 },
289
290 async getNotificationHistory(token: string): Promise<{
291 notifications: Array<{
292 createdAt: string
293 channel: string
294 notificationType: string
295 status: string
296 subject: string | null
297 body: string
298 }>
299 }> {
300 return xrpc('com.tranquil.account.getNotificationHistory', { token })
301 },
302
303 async getServerStats(token: string): Promise<{
304 userCount: number
305 repoCount: number
306 recordCount: number
307 blobStorageBytes: number
308 }> {
309 return xrpc('com.tranquil.admin.getServerStats', { token })
310 },
311
312 async changePassword(token: string, currentPassword: string, newPassword: string): Promise<void> {
313 await xrpc('com.tranquil.account.changePassword', {
314 method: 'POST',
315 token,
316 body: { currentPassword, newPassword },
317 })
318 },
319
320 async listSessions(token: string): Promise<{
321 sessions: Array<{
322 id: string
323 createdAt: string
324 expiresAt: string
325 isCurrent: boolean
326 }>
327 }> {
328 return xrpc('com.tranquil.account.listSessions', { token })
329 },
330
331 async revokeSession(token: string, sessionId: string): Promise<void> {
332 await xrpc('com.tranquil.account.revokeSession', {
333 method: 'POST',
334 token,
335 body: { sessionId },
336 })
337 },
338
339 async searchAccounts(token: string, options?: {
340 handle?: string
341 cursor?: string
342 limit?: number
343 }): Promise<{
344 cursor?: string
345 accounts: Array<{
346 did: string
347 handle: string
348 email?: string
349 indexedAt: string
350 emailConfirmedAt?: string
351 deactivatedAt?: string
352 }>
353 }> {
354 const params: Record<string, string> = {}
355 if (options?.handle) params.handle = options.handle
356 if (options?.cursor) params.cursor = options.cursor
357 if (options?.limit) params.limit = String(options.limit)
358 return xrpc('com.atproto.admin.searchAccounts', { token, params })
359 },
360
361 async getInviteCodes(token: string, options?: {
362 sort?: 'recent' | 'usage'
363 cursor?: string
364 limit?: number
365 }): Promise<{
366 cursor?: string
367 codes: Array<{
368 code: string
369 available: number
370 disabled: boolean
371 forAccount: string
372 createdBy: string
373 createdAt: string
374 uses: Array<{ usedBy: string; usedAt: string }>
375 }>
376 }> {
377 const params: Record<string, string> = {}
378 if (options?.sort) params.sort = options.sort
379 if (options?.cursor) params.cursor = options.cursor
380 if (options?.limit) params.limit = String(options.limit)
381 return xrpc('com.atproto.admin.getInviteCodes', { token, params })
382 },
383
384 async disableInviteCodes(token: string, codes?: string[], accounts?: string[]): Promise<void> {
385 await xrpc('com.atproto.admin.disableInviteCodes', {
386 method: 'POST',
387 token,
388 body: { codes, accounts },
389 })
390 },
391
392 async getAccountInfo(token: string, did: string): Promise<{
393 did: string
394 handle: string
395 email?: string
396 indexedAt: string
397 emailConfirmedAt?: string
398 invitesDisabled?: boolean
399 deactivatedAt?: string
400 }> {
401 return xrpc('com.atproto.admin.getAccountInfo', { token, params: { did } })
402 },
403
404 async disableAccountInvites(token: string, account: string): Promise<void> {
405 await xrpc('com.atproto.admin.disableAccountInvites', {
406 method: 'POST',
407 token,
408 body: { account },
409 })
410 },
411
412 async enableAccountInvites(token: string, account: string): Promise<void> {
413 await xrpc('com.atproto.admin.enableAccountInvites', {
414 method: 'POST',
415 token,
416 body: { account },
417 })
418 },
419
420 async adminDeleteAccount(token: string, did: string): Promise<void> {
421 await xrpc('com.atproto.admin.deleteAccount', {
422 method: 'POST',
423 token,
424 body: { did },
425 })
426 },
427
428 async describeRepo(token: string, repo: string): Promise<{
429 handle: string
430 did: string
431 didDoc: unknown
432 collections: string[]
433 handleIsCorrect: boolean
434 }> {
435 return xrpc('com.atproto.repo.describeRepo', {
436 token,
437 params: { repo },
438 })
439 },
440
441 async listRecords(token: string, repo: string, collection: string, options?: {
442 limit?: number
443 cursor?: string
444 reverse?: boolean
445 }): Promise<{
446 records: Array<{ uri: string; cid: string; value: unknown }>
447 cursor?: string
448 }> {
449 const params: Record<string, string> = { repo, collection }
450 if (options?.limit) params.limit = String(options.limit)
451 if (options?.cursor) params.cursor = options.cursor
452 if (options?.reverse) params.reverse = 'true'
453 return xrpc('com.atproto.repo.listRecords', { token, params })
454 },
455
456 async getRecord(token: string, repo: string, collection: string, rkey: string): Promise<{
457 uri: string
458 cid: string
459 value: unknown
460 }> {
461 return xrpc('com.atproto.repo.getRecord', {
462 token,
463 params: { repo, collection, rkey },
464 })
465 },
466
467 async createRecord(token: string, repo: string, collection: string, record: unknown, rkey?: string): Promise<{
468 uri: string
469 cid: string
470 }> {
471 return xrpc('com.atproto.repo.createRecord', {
472 method: 'POST',
473 token,
474 body: { repo, collection, record, rkey },
475 })
476 },
477
478 async putRecord(token: string, repo: string, collection: string, rkey: string, record: unknown): Promise<{
479 uri: string
480 cid: string
481 }> {
482 return xrpc('com.atproto.repo.putRecord', {
483 method: 'POST',
484 token,
485 body: { repo, collection, rkey, record },
486 })
487 },
488
489 async deleteRecord(token: string, repo: string, collection: string, rkey: string): Promise<void> {
490 await xrpc('com.atproto.repo.deleteRecord', {
491 method: 'POST',
492 token,
493 body: { repo, collection, rkey },
494 })
495 },
496
497 async getTotpStatus(token: string): Promise<{ enabled: boolean; hasBackupCodes: boolean }> {
498 return xrpc('com.atproto.server.getTotpStatus', { token })
499 },
500
501 async createTotpSecret(token: string): Promise<{ uri: string; qrBase64: string }> {
502 return xrpc('com.atproto.server.createTotpSecret', { method: 'POST', token })
503 },
504
505 async enableTotp(token: string, code: string): Promise<{ success: boolean; backupCodes: string[] }> {
506 return xrpc('com.atproto.server.enableTotp', {
507 method: 'POST',
508 token,
509 body: { code },
510 })
511 },
512
513 async disableTotp(token: string, password: string, code: string): Promise<{ success: boolean }> {
514 return xrpc('com.atproto.server.disableTotp', {
515 method: 'POST',
516 token,
517 body: { password, code },
518 })
519 },
520
521 async regenerateBackupCodes(token: string, password: string, code: string): Promise<{ backupCodes: string[] }> {
522 return xrpc('com.atproto.server.regenerateBackupCodes', {
523 method: 'POST',
524 token,
525 body: { password, code },
526 })
527 },
528
529 async startPasskeyRegistration(token: string, friendlyName?: string): Promise<{ options: unknown }> {
530 return xrpc('com.atproto.server.startPasskeyRegistration', {
531 method: 'POST',
532 token,
533 body: { friendlyName },
534 })
535 },
536
537 async finishPasskeyRegistration(token: string, credential: unknown, friendlyName?: string): Promise<{ id: string; credentialId: string }> {
538 return xrpc('com.atproto.server.finishPasskeyRegistration', {
539 method: 'POST',
540 token,
541 body: { credential, friendlyName },
542 })
543 },
544
545 async listPasskeys(token: string): Promise<{
546 passkeys: Array<{
547 id: string
548 credentialId: string
549 friendlyName: string | null
550 createdAt: string
551 lastUsed: string | null
552 }>
553 }> {
554 return xrpc('com.atproto.server.listPasskeys', { token })
555 },
556
557 async deletePasskey(token: string, id: string): Promise<void> {
558 await xrpc('com.atproto.server.deletePasskey', {
559 method: 'POST',
560 token,
561 body: { id },
562 })
563 },
564
565 async updatePasskey(token: string, id: string, friendlyName: string): Promise<void> {
566 await xrpc('com.atproto.server.updatePasskey', {
567 method: 'POST',
568 token,
569 body: { id, friendlyName },
570 })
571 },
572}