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