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