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