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};