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