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