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