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