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