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(public status: number, public error: string, message: string, did?: string, reauthMethods?: string[]) { 7 super(message) 8 this.name = 'ApiError' 9 this.did = did 10 this.reauthMethods = reauthMethods 11 } 12} 13 14let tokenRefreshCallback: (() => Promise<string | null>) | null = null 15 16export function setTokenRefreshCallback(callback: () => Promise<string | null>) { 17 tokenRefreshCallback = callback 18} 19 20async function xrpc<T>(method: string, options?: { 21 method?: 'GET' | 'POST' 22 params?: Record<string, string> 23 body?: unknown 24 token?: string 25 skipRetry?: boolean 26}): Promise<T> { 27 const { method: httpMethod = 'GET', params, body, token, skipRetry } = options ?? {} 28 let url = `${API_BASE}/${method}` 29 if (params) { 30 const searchParams = new URLSearchParams(params) 31 url += `?${searchParams}` 32 } 33 const headers: Record<string, string> = {} 34 if (token) { 35 headers['Authorization'] = `Bearer ${token}` 36 } 37 if (body) { 38 headers['Content-Type'] = 'application/json' 39 } 40 const res = await fetch(url, { 41 method: httpMethod, 42 headers, 43 body: body ? JSON.stringify(body) : undefined, 44 }) 45 if (!res.ok) { 46 const err = await res.json().catch(() => ({ error: 'Unknown', message: res.statusText })) 47 if (res.status === 401 && err.error === 'AuthenticationFailed' && token && tokenRefreshCallback && !skipRetry) { 48 const newToken = await tokenRefreshCallback() 49 if (newToken && newToken !== token) { 50 return xrpc(method, { ...options, token: newToken, skipRetry: true }) 51 } 52 } 53 throw new ApiError(res.status, err.error, err.message, err.did, err.reauthMethods) 54 } 55 return res.json() 56} 57 58export interface Session { 59 did: string 60 handle: string 61 email?: string 62 emailConfirmed?: boolean 63 preferredChannel?: string 64 preferredChannelVerified?: boolean 65 isAdmin?: boolean 66 active?: boolean 67 status?: 'active' | 'deactivated' 68 accessJwt: string 69 refreshJwt: string 70} 71 72export interface AppPassword { 73 name: string 74 createdAt: string 75} 76 77export interface InviteCode { 78 code: string 79 available: number 80 disabled: boolean 81 forAccount: string 82 createdBy: string 83 createdAt: string 84 uses: { usedBy: string; usedAt: string }[] 85} 86 87export type VerificationChannel = 'email' | 'discord' | 'telegram' | 'signal' 88 89export type DidType = 'plc' | 'web' | 'web-external' 90 91export interface CreateAccountParams { 92 handle: string 93 email: string 94 password: string 95 inviteCode?: string 96 didType?: DidType 97 did?: string 98 verificationChannel?: VerificationChannel 99 discordId?: string 100 telegramUsername?: string 101 signalNumber?: string 102} 103 104export interface CreateAccountResult { 105 handle: string 106 did: string 107 verificationRequired: boolean 108 verificationChannel: string 109} 110 111export interface ConfirmSignupResult { 112 accessJwt: string 113 refreshJwt: string 114 handle: string 115 did: string 116 email?: string 117 emailConfirmed?: boolean 118 preferredChannel?: string 119 preferredChannelVerified?: boolean 120} 121 122export const api = { 123 async createAccount(params: CreateAccountParams): Promise<CreateAccountResult> { 124 return xrpc('com.atproto.server.createAccount', { 125 method: 'POST', 126 body: { 127 handle: params.handle, 128 email: params.email, 129 password: params.password, 130 inviteCode: params.inviteCode, 131 didType: params.didType, 132 did: params.did, 133 verificationChannel: params.verificationChannel, 134 discordId: params.discordId, 135 telegramUsername: params.telegramUsername, 136 signalNumber: params.signalNumber, 137 }, 138 }) 139 }, 140 141 async confirmSignup(did: string, verificationCode: string): Promise<ConfirmSignupResult> { 142 return xrpc('com.atproto.server.confirmSignup', { 143 method: 'POST', 144 body: { did, verificationCode }, 145 }) 146 }, 147 148 async resendVerification(did: string): Promise<{ success: boolean }> { 149 return xrpc('com.atproto.server.resendVerification', { 150 method: 'POST', 151 body: { did }, 152 }) 153 }, 154 155 async createSession(identifier: string, password: string): Promise<Session> { 156 return xrpc('com.atproto.server.createSession', { 157 method: 'POST', 158 body: { identifier, password }, 159 }) 160 }, 161 162 async getSession(token: string): Promise<Session> { 163 return xrpc('com.atproto.server.getSession', { token }) 164 }, 165 166 async refreshSession(refreshJwt: string): Promise<Session> { 167 return xrpc('com.atproto.server.refreshSession', { 168 method: 'POST', 169 token: refreshJwt, 170 }) 171 }, 172 173 async deleteSession(token: string): Promise<void> { 174 await xrpc('com.atproto.server.deleteSession', { 175 method: 'POST', 176 token, 177 }) 178 }, 179 180 async listAppPasswords(token: string): Promise<{ passwords: AppPassword[] }> { 181 return xrpc('com.atproto.server.listAppPasswords', { token }) 182 }, 183 184 async createAppPassword(token: string, name: string): Promise<{ name: string; password: string; createdAt: string }> { 185 return xrpc('com.atproto.server.createAppPassword', { 186 method: 'POST', 187 token, 188 body: { name }, 189 }) 190 }, 191 192 async revokeAppPassword(token: string, name: string): Promise<void> { 193 await xrpc('com.atproto.server.revokeAppPassword', { 194 method: 'POST', 195 token, 196 body: { name }, 197 }) 198 }, 199 200 async getAccountInviteCodes(token: string): Promise<{ codes: InviteCode[] }> { 201 return xrpc('com.atproto.server.getAccountInviteCodes', { token }) 202 }, 203 204 async createInviteCode(token: string, useCount: number = 1): Promise<{ code: string }> { 205 return xrpc('com.atproto.server.createInviteCode', { 206 method: 'POST', 207 token, 208 body: { useCount }, 209 }) 210 }, 211 212 async requestPasswordReset(email: string): Promise<void> { 213 await xrpc('com.atproto.server.requestPasswordReset', { 214 method: 'POST', 215 body: { email }, 216 }) 217 }, 218 219 async resetPassword(token: string, password: string): Promise<void> { 220 await xrpc('com.atproto.server.resetPassword', { 221 method: 'POST', 222 body: { token, password }, 223 }) 224 }, 225 226 async requestEmailUpdate(token: string, email: string): Promise<{ tokenRequired: boolean }> { 227 return xrpc('com.atproto.server.requestEmailUpdate', { 228 method: 'POST', 229 token, 230 body: { email }, 231 }) 232 }, 233 234 async updateEmail(token: string, email: string, emailToken?: string): Promise<void> { 235 await xrpc('com.atproto.server.updateEmail', { 236 method: 'POST', 237 token, 238 body: { email, token: emailToken }, 239 }) 240 }, 241 242 async updateHandle(token: string, handle: string): Promise<void> { 243 await xrpc('com.atproto.identity.updateHandle', { 244 method: 'POST', 245 token, 246 body: { handle }, 247 }) 248 }, 249 250 async requestAccountDelete(token: string): Promise<void> { 251 await xrpc('com.atproto.server.requestAccountDelete', { 252 method: 'POST', 253 token, 254 }) 255 }, 256 257 async deleteAccount(did: string, password: string, deleteToken: string): Promise<void> { 258 await xrpc('com.atproto.server.deleteAccount', { 259 method: 'POST', 260 body: { did, password, token: deleteToken }, 261 }) 262 }, 263 264 async describeServer(): Promise<{ 265 availableUserDomains: string[] 266 inviteCodeRequired: boolean 267 links?: { privacyPolicy?: string; termsOfService?: string } 268 version?: string 269 availableCommsChannels?: string[] 270 }> { 271 return xrpc('com.atproto.server.describeServer') 272 }, 273 274 async listRepos(limit?: number): Promise<{ 275 repos: Array<{ did: string; head: string; rev: string }> 276 cursor?: string 277 }> { 278 const params: Record<string, string> = {} 279 if (limit) params.limit = String(limit) 280 return xrpc('com.atproto.sync.listRepos', { params }) 281 }, 282 283 async getNotificationPrefs(token: string): Promise<{ 284 preferredChannel: string 285 email: string 286 discordId: string | null 287 discordVerified: boolean 288 telegramUsername: string | null 289 telegramVerified: boolean 290 signalNumber: string | null 291 signalVerified: boolean 292 }> { 293 return xrpc('com.tranquil.account.getNotificationPrefs', { token }) 294 }, 295 296 async updateNotificationPrefs(token: string, prefs: { 297 preferredChannel?: string 298 discordId?: string 299 telegramUsername?: string 300 signalNumber?: string 301 }): Promise<{ success: boolean }> { 302 return xrpc('com.tranquil.account.updateNotificationPrefs', { 303 method: 'POST', 304 token, 305 body: prefs, 306 }) 307 }, 308 309 async confirmChannelVerification(token: string, channel: string, code: string): Promise<{ success: boolean }> { 310 return xrpc('com.tranquil.account.confirmChannelVerification', { 311 method: 'POST', 312 token, 313 body: { channel, code }, 314 }) 315 }, 316 317 async getNotificationHistory(token: string): Promise<{ 318 notifications: Array<{ 319 createdAt: string 320 channel: string 321 notificationType: string 322 status: string 323 subject: string | null 324 body: string 325 }> 326 }> { 327 return xrpc('com.tranquil.account.getNotificationHistory', { token }) 328 }, 329 330 async getServerStats(token: string): Promise<{ 331 userCount: number 332 repoCount: number 333 recordCount: number 334 blobStorageBytes: number 335 }> { 336 return xrpc('com.tranquil.admin.getServerStats', { token }) 337 }, 338 339 async getServerConfig(): Promise<{ 340 serverName: string 341 primaryColor: string | null 342 primaryColorDark: string | null 343 secondaryColor: string | null 344 secondaryColorDark: string | null 345 logoCid: string | null 346 }> { 347 return xrpc('com.tranquil.server.getConfig') 348 }, 349 350 async updateServerConfig( 351 token: string, 352 config: { 353 serverName?: string 354 primaryColor?: string 355 primaryColorDark?: string 356 secondaryColor?: string 357 secondaryColorDark?: string 358 logoCid?: string 359 } 360 ): Promise<{ success: boolean }> { 361 return xrpc('com.tranquil.admin.updateServerConfig', { 362 method: 'POST', 363 token, 364 body: config, 365 }) 366 }, 367 368 async uploadBlob(token: string, file: File): Promise<{ blob: { $type: string; ref: { $link: string }; mimeType: string; size: number } }> { 369 const res = await fetch('/xrpc/com.atproto.repo.uploadBlob', { 370 method: 'POST', 371 headers: { 372 'Authorization': `Bearer ${token}`, 373 'Content-Type': file.type, 374 }, 375 body: file, 376 }) 377 if (!res.ok) { 378 const err = await res.json().catch(() => ({ error: 'Unknown', message: res.statusText })) 379 throw new ApiError(res.status, err.error, err.message) 380 } 381 return res.json() 382 }, 383 384 async changePassword(token: string, currentPassword: string, newPassword: string): Promise<void> { 385 await xrpc('com.tranquil.account.changePassword', { 386 method: 'POST', 387 token, 388 body: { currentPassword, newPassword }, 389 }) 390 }, 391 392 async removePassword(token: string): Promise<{ success: boolean }> { 393 return xrpc('com.tranquil.account.removePassword', { 394 method: 'POST', 395 token, 396 }) 397 }, 398 399 async getPasswordStatus(token: string): Promise<{ hasPassword: boolean }> { 400 return xrpc('com.tranquil.account.getPasswordStatus', { token }) 401 }, 402 403 async getLegacyLoginPreference(token: string): Promise<{ allowLegacyLogin: boolean; hasMfa: boolean }> { 404 return xrpc('com.tranquil.account.getLegacyLoginPreference', { token }) 405 }, 406 407 async updateLegacyLoginPreference(token: string, allowLegacyLogin: boolean): Promise<{ allowLegacyLogin: boolean }> { 408 return xrpc('com.tranquil.account.updateLegacyLoginPreference', { 409 method: 'POST', 410 token, 411 body: { allowLegacyLogin }, 412 }) 413 }, 414 415 async updateLocale(token: string, preferredLocale: string): Promise<{ preferredLocale: string }> { 416 return xrpc('com.tranquil.account.updateLocale', { 417 method: 'POST', 418 token, 419 body: { preferredLocale }, 420 }) 421 }, 422 423 async listSessions(token: string): Promise<{ 424 sessions: Array<{ 425 id: string 426 sessionType: string 427 clientName: string | null 428 createdAt: string 429 expiresAt: string 430 isCurrent: boolean 431 }> 432 }> { 433 return xrpc('com.tranquil.account.listSessions', { token }) 434 }, 435 436 async revokeSession(token: string, sessionId: string): Promise<void> { 437 await xrpc('com.tranquil.account.revokeSession', { 438 method: 'POST', 439 token, 440 body: { sessionId }, 441 }) 442 }, 443 444 async revokeAllSessions(token: string): Promise<{ revokedCount: number }> { 445 return xrpc('com.tranquil.account.revokeAllSessions', { 446 method: 'POST', 447 token, 448 }) 449 }, 450 451 async searchAccounts(token: string, options?: { 452 handle?: string 453 cursor?: string 454 limit?: number 455 }): Promise<{ 456 cursor?: string 457 accounts: Array<{ 458 did: string 459 handle: string 460 email?: string 461 indexedAt: string 462 emailConfirmedAt?: string 463 deactivatedAt?: string 464 }> 465 }> { 466 const params: Record<string, string> = {} 467 if (options?.handle) params.handle = options.handle 468 if (options?.cursor) params.cursor = options.cursor 469 if (options?.limit) params.limit = String(options.limit) 470 return xrpc('com.atproto.admin.searchAccounts', { token, params }) 471 }, 472 473 async getInviteCodes(token: string, options?: { 474 sort?: 'recent' | 'usage' 475 cursor?: string 476 limit?: number 477 }): Promise<{ 478 cursor?: string 479 codes: Array<{ 480 code: string 481 available: number 482 disabled: boolean 483 forAccount: string 484 createdBy: string 485 createdAt: string 486 uses: Array<{ usedBy: string; usedAt: string }> 487 }> 488 }> { 489 const params: Record<string, string> = {} 490 if (options?.sort) params.sort = options.sort 491 if (options?.cursor) params.cursor = options.cursor 492 if (options?.limit) params.limit = String(options.limit) 493 return xrpc('com.atproto.admin.getInviteCodes', { token, params }) 494 }, 495 496 async disableInviteCodes(token: string, codes?: string[], accounts?: string[]): Promise<void> { 497 await xrpc('com.atproto.admin.disableInviteCodes', { 498 method: 'POST', 499 token, 500 body: { codes, accounts }, 501 }) 502 }, 503 504 async getAccountInfo(token: string, did: string): Promise<{ 505 did: string 506 handle: string 507 email?: string 508 indexedAt: string 509 emailConfirmedAt?: string 510 invitesDisabled?: boolean 511 deactivatedAt?: string 512 }> { 513 return xrpc('com.atproto.admin.getAccountInfo', { token, params: { did } }) 514 }, 515 516 async disableAccountInvites(token: string, account: string): Promise<void> { 517 await xrpc('com.atproto.admin.disableAccountInvites', { 518 method: 'POST', 519 token, 520 body: { account }, 521 }) 522 }, 523 524 async enableAccountInvites(token: string, account: string): Promise<void> { 525 await xrpc('com.atproto.admin.enableAccountInvites', { 526 method: 'POST', 527 token, 528 body: { account }, 529 }) 530 }, 531 532 async adminDeleteAccount(token: string, did: string): Promise<void> { 533 await xrpc('com.atproto.admin.deleteAccount', { 534 method: 'POST', 535 token, 536 body: { did }, 537 }) 538 }, 539 540 async describeRepo(token: string, repo: string): Promise<{ 541 handle: string 542 did: string 543 didDoc: unknown 544 collections: string[] 545 handleIsCorrect: boolean 546 }> { 547 return xrpc('com.atproto.repo.describeRepo', { 548 token, 549 params: { repo }, 550 }) 551 }, 552 553 async listRecords(token: string, repo: string, collection: string, options?: { 554 limit?: number 555 cursor?: string 556 reverse?: boolean 557 }): Promise<{ 558 records: Array<{ uri: string; cid: string; value: unknown }> 559 cursor?: string 560 }> { 561 const params: Record<string, string> = { repo, collection } 562 if (options?.limit) params.limit = String(options.limit) 563 if (options?.cursor) params.cursor = options.cursor 564 if (options?.reverse) params.reverse = 'true' 565 return xrpc('com.atproto.repo.listRecords', { token, params }) 566 }, 567 568 async getRecord(token: string, repo: string, collection: string, rkey: string): Promise<{ 569 uri: string 570 cid: string 571 value: unknown 572 }> { 573 return xrpc('com.atproto.repo.getRecord', { 574 token, 575 params: { repo, collection, rkey }, 576 }) 577 }, 578 579 async createRecord(token: string, repo: string, collection: string, record: unknown, rkey?: string): Promise<{ 580 uri: string 581 cid: string 582 }> { 583 return xrpc('com.atproto.repo.createRecord', { 584 method: 'POST', 585 token, 586 body: { repo, collection, record, rkey }, 587 }) 588 }, 589 590 async putRecord(token: string, repo: string, collection: string, rkey: string, record: unknown): Promise<{ 591 uri: string 592 cid: string 593 }> { 594 return xrpc('com.atproto.repo.putRecord', { 595 method: 'POST', 596 token, 597 body: { repo, collection, rkey, record }, 598 }) 599 }, 600 601 async deleteRecord(token: string, repo: string, collection: string, rkey: string): Promise<void> { 602 await xrpc('com.atproto.repo.deleteRecord', { 603 method: 'POST', 604 token, 605 body: { repo, collection, rkey }, 606 }) 607 }, 608 609 async getTotpStatus(token: string): Promise<{ enabled: boolean; hasBackupCodes: boolean }> { 610 return xrpc('com.atproto.server.getTotpStatus', { token }) 611 }, 612 613 async createTotpSecret(token: string): Promise<{ uri: string; qrBase64: string }> { 614 return xrpc('com.atproto.server.createTotpSecret', { method: 'POST', token }) 615 }, 616 617 async enableTotp(token: string, code: string): Promise<{ success: boolean; backupCodes: string[] }> { 618 return xrpc('com.atproto.server.enableTotp', { 619 method: 'POST', 620 token, 621 body: { code }, 622 }) 623 }, 624 625 async disableTotp(token: string, password: string, code: string): Promise<{ success: boolean }> { 626 return xrpc('com.atproto.server.disableTotp', { 627 method: 'POST', 628 token, 629 body: { password, code }, 630 }) 631 }, 632 633 async regenerateBackupCodes(token: string, password: string, code: string): Promise<{ backupCodes: string[] }> { 634 return xrpc('com.atproto.server.regenerateBackupCodes', { 635 method: 'POST', 636 token, 637 body: { password, code }, 638 }) 639 }, 640 641 async startPasskeyRegistration(token: string, friendlyName?: string): Promise<{ options: unknown }> { 642 return xrpc('com.atproto.server.startPasskeyRegistration', { 643 method: 'POST', 644 token, 645 body: { friendlyName }, 646 }) 647 }, 648 649 async finishPasskeyRegistration(token: string, credential: unknown, friendlyName?: string): Promise<{ id: string; credentialId: string }> { 650 return xrpc('com.atproto.server.finishPasskeyRegistration', { 651 method: 'POST', 652 token, 653 body: { credential, friendlyName }, 654 }) 655 }, 656 657 async listPasskeys(token: string): Promise<{ 658 passkeys: Array<{ 659 id: string 660 credentialId: string 661 friendlyName: string | null 662 createdAt: string 663 lastUsed: string | null 664 }> 665 }> { 666 return xrpc('com.atproto.server.listPasskeys', { token }) 667 }, 668 669 async deletePasskey(token: string, id: string): Promise<void> { 670 await xrpc('com.atproto.server.deletePasskey', { 671 method: 'POST', 672 token, 673 body: { id }, 674 }) 675 }, 676 677 async updatePasskey(token: string, id: string, friendlyName: string): Promise<void> { 678 await xrpc('com.atproto.server.updatePasskey', { 679 method: 'POST', 680 token, 681 body: { id, friendlyName }, 682 }) 683 }, 684 685 async listTrustedDevices(token: string): Promise<{ 686 devices: Array<{ 687 id: string 688 userAgent: string | null 689 friendlyName: string | null 690 trustedAt: string | null 691 trustedUntil: string | null 692 lastSeenAt: string 693 }> 694 }> { 695 return xrpc('com.tranquil.account.listTrustedDevices', { token }) 696 }, 697 698 async revokeTrustedDevice(token: string, deviceId: string): Promise<{ success: boolean }> { 699 return xrpc('com.tranquil.account.revokeTrustedDevice', { 700 method: 'POST', 701 token, 702 body: { deviceId }, 703 }) 704 }, 705 706 async updateTrustedDevice(token: string, deviceId: string, friendlyName: string): Promise<{ success: boolean }> { 707 return xrpc('com.tranquil.account.updateTrustedDevice', { 708 method: 'POST', 709 token, 710 body: { deviceId, friendlyName }, 711 }) 712 }, 713 714 async getReauthStatus(token: string): Promise<{ 715 requiresReauth: boolean 716 lastReauthAt: string | null 717 availableMethods: string[] 718 }> { 719 return xrpc('com.tranquil.account.getReauthStatus', { token }) 720 }, 721 722 async reauthPassword(token: string, password: string): Promise<{ success: boolean; reauthAt: string }> { 723 return xrpc('com.tranquil.account.reauthPassword', { 724 method: 'POST', 725 token, 726 body: { password }, 727 }) 728 }, 729 730 async reauthTotp(token: string, code: string): Promise<{ success: boolean; reauthAt: string }> { 731 return xrpc('com.tranquil.account.reauthTotp', { 732 method: 'POST', 733 token, 734 body: { code }, 735 }) 736 }, 737 738 async reauthPasskeyStart(token: string): Promise<{ options: unknown }> { 739 return xrpc('com.tranquil.account.reauthPasskeyStart', { 740 method: 'POST', 741 token, 742 }) 743 }, 744 745 async reauthPasskeyFinish(token: string, credential: unknown): Promise<{ success: boolean; reauthAt: string }> { 746 return xrpc('com.tranquil.account.reauthPasskeyFinish', { 747 method: 'POST', 748 token, 749 body: { credential }, 750 }) 751 }, 752 753 async createPasskeyAccount(params: { 754 handle: string 755 email?: string 756 inviteCode?: string 757 didType?: DidType 758 did?: string 759 signingKey?: string 760 verificationChannel?: VerificationChannel 761 discordId?: string 762 telegramUsername?: string 763 signalNumber?: string 764 }): Promise<{ 765 did: string 766 handle: string 767 setupToken: string 768 setupExpiresAt: string 769 }> { 770 return xrpc('com.tranquil.account.createPasskeyAccount', { 771 method: 'POST', 772 body: params, 773 }) 774 }, 775 776 async startPasskeyRegistrationForSetup(did: string, setupToken: string, friendlyName?: string): Promise<{ options: unknown }> { 777 return xrpc('com.tranquil.account.startPasskeyRegistrationForSetup', { 778 method: 'POST', 779 body: { did, setupToken, friendlyName }, 780 }) 781 }, 782 783 async completePasskeySetup(did: string, setupToken: string, passkeyCredential: unknown, passkeyFriendlyName?: string): Promise<{ 784 did: string 785 handle: string 786 appPassword: string 787 appPasswordName: string 788 }> { 789 return xrpc('com.tranquil.account.completePasskeySetup', { 790 method: 'POST', 791 body: { did, setupToken, passkeyCredential, passkeyFriendlyName }, 792 }) 793 }, 794 795 async requestPasskeyRecovery(email: string): Promise<{ success: boolean }> { 796 return xrpc('com.tranquil.account.requestPasskeyRecovery', { 797 method: 'POST', 798 body: { email }, 799 }) 800 }, 801 802 async recoverPasskeyAccount(did: string, recoveryToken: string, newPassword: string): Promise<{ success: boolean }> { 803 return xrpc('com.tranquil.account.recoverPasskeyAccount', { 804 method: 'POST', 805 body: { did, recoveryToken, newPassword }, 806 }) 807 }, 808}