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