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 }> { 270 return xrpc('com.atproto.server.describeServer') 271 }, 272 273 async listRepos(limit?: number): Promise<{ 274 repos: Array<{ did: string; head: string; rev: string }> 275 cursor?: string 276 }> { 277 const params: Record<string, string> = {} 278 if (limit) params.limit = String(limit) 279 return xrpc('com.atproto.sync.listRepos', { params }) 280 }, 281 282 async getNotificationPrefs(token: string): Promise<{ 283 preferredChannel: string 284 email: string 285 discordId: string | null 286 discordVerified: boolean 287 telegramUsername: string | null 288 telegramVerified: boolean 289 signalNumber: string | null 290 signalVerified: boolean 291 }> { 292 return xrpc('com.tranquil.account.getNotificationPrefs', { token }) 293 }, 294 295 async updateNotificationPrefs(token: string, prefs: { 296 preferredChannel?: string 297 discordId?: string 298 telegramUsername?: string 299 signalNumber?: string 300 }): Promise<{ success: boolean }> { 301 return xrpc('com.tranquil.account.updateNotificationPrefs', { 302 method: 'POST', 303 token, 304 body: prefs, 305 }) 306 }, 307 308 async confirmChannelVerification(token: string, channel: string, code: string): Promise<{ success: boolean }> { 309 return xrpc('com.tranquil.account.confirmChannelVerification', { 310 method: 'POST', 311 token, 312 body: { channel, code }, 313 }) 314 }, 315 316 async getNotificationHistory(token: string): Promise<{ 317 notifications: Array<{ 318 createdAt: string 319 channel: string 320 notificationType: string 321 status: string 322 subject: string | null 323 body: string 324 }> 325 }> { 326 return xrpc('com.tranquil.account.getNotificationHistory', { token }) 327 }, 328 329 async getServerStats(token: string): Promise<{ 330 userCount: number 331 repoCount: number 332 recordCount: number 333 blobStorageBytes: number 334 }> { 335 return xrpc('com.tranquil.admin.getServerStats', { token }) 336 }, 337 338 async getServerConfig(): Promise<{ 339 serverName: string 340 primaryColor: string | null 341 primaryColorDark: string | null 342 secondaryColor: string | null 343 secondaryColorDark: string | null 344 logoCid: string | null 345 }> { 346 return xrpc('com.tranquil.server.getConfig') 347 }, 348 349 async updateServerConfig( 350 token: string, 351 config: { 352 serverName?: string 353 primaryColor?: string 354 primaryColorDark?: string 355 secondaryColor?: string 356 secondaryColorDark?: string 357 logoCid?: string 358 } 359 ): Promise<{ success: boolean }> { 360 return xrpc('com.tranquil.admin.updateServerConfig', { 361 method: 'POST', 362 token, 363 body: config, 364 }) 365 }, 366 367 async uploadBlob(token: string, file: File): Promise<{ blob: { $type: string; ref: { $link: string }; mimeType: string; size: number } }> { 368 const res = await fetch('/xrpc/com.atproto.repo.uploadBlob', { 369 method: 'POST', 370 headers: { 371 'Authorization': `Bearer ${token}`, 372 'Content-Type': file.type, 373 }, 374 body: file, 375 }) 376 if (!res.ok) { 377 const err = await res.json().catch(() => ({ error: 'Unknown', message: res.statusText })) 378 throw new ApiError(res.status, err.error, err.message) 379 } 380 return res.json() 381 }, 382 383 async changePassword(token: string, currentPassword: string, newPassword: string): Promise<void> { 384 await xrpc('com.tranquil.account.changePassword', { 385 method: 'POST', 386 token, 387 body: { currentPassword, newPassword }, 388 }) 389 }, 390 391 async removePassword(token: string): Promise<{ success: boolean }> { 392 return xrpc('com.tranquil.account.removePassword', { 393 method: 'POST', 394 token, 395 }) 396 }, 397 398 async getPasswordStatus(token: string): Promise<{ hasPassword: boolean }> { 399 return xrpc('com.tranquil.account.getPasswordStatus', { token }) 400 }, 401 402 async getLegacyLoginPreference(token: string): Promise<{ allowLegacyLogin: boolean; hasMfa: boolean }> { 403 return xrpc('com.tranquil.account.getLegacyLoginPreference', { token }) 404 }, 405 406 async updateLegacyLoginPreference(token: string, allowLegacyLogin: boolean): Promise<{ allowLegacyLogin: boolean }> { 407 return xrpc('com.tranquil.account.updateLegacyLoginPreference', { 408 method: 'POST', 409 token, 410 body: { allowLegacyLogin }, 411 }) 412 }, 413 414 async updateLocale(token: string, preferredLocale: string): Promise<{ preferredLocale: string }> { 415 return xrpc('com.tranquil.account.updateLocale', { 416 method: 'POST', 417 token, 418 body: { preferredLocale }, 419 }) 420 }, 421 422 async listSessions(token: string): Promise<{ 423 sessions: Array<{ 424 id: string 425 sessionType: string 426 clientName: string | null 427 createdAt: string 428 expiresAt: string 429 isCurrent: boolean 430 }> 431 }> { 432 return xrpc('com.tranquil.account.listSessions', { token }) 433 }, 434 435 async revokeSession(token: string, sessionId: string): Promise<void> { 436 await xrpc('com.tranquil.account.revokeSession', { 437 method: 'POST', 438 token, 439 body: { sessionId }, 440 }) 441 }, 442 443 async revokeAllSessions(token: string): Promise<{ revokedCount: number }> { 444 return xrpc('com.tranquil.account.revokeAllSessions', { 445 method: 'POST', 446 token, 447 }) 448 }, 449 450 async searchAccounts(token: string, options?: { 451 handle?: string 452 cursor?: string 453 limit?: number 454 }): Promise<{ 455 cursor?: string 456 accounts: Array<{ 457 did: string 458 handle: string 459 email?: string 460 indexedAt: string 461 emailConfirmedAt?: string 462 deactivatedAt?: string 463 }> 464 }> { 465 const params: Record<string, string> = {} 466 if (options?.handle) params.handle = options.handle 467 if (options?.cursor) params.cursor = options.cursor 468 if (options?.limit) params.limit = String(options.limit) 469 return xrpc('com.atproto.admin.searchAccounts', { token, params }) 470 }, 471 472 async getInviteCodes(token: string, options?: { 473 sort?: 'recent' | 'usage' 474 cursor?: string 475 limit?: number 476 }): Promise<{ 477 cursor?: string 478 codes: Array<{ 479 code: string 480 available: number 481 disabled: boolean 482 forAccount: string 483 createdBy: string 484 createdAt: string 485 uses: Array<{ usedBy: string; usedAt: string }> 486 }> 487 }> { 488 const params: Record<string, string> = {} 489 if (options?.sort) params.sort = options.sort 490 if (options?.cursor) params.cursor = options.cursor 491 if (options?.limit) params.limit = String(options.limit) 492 return xrpc('com.atproto.admin.getInviteCodes', { token, params }) 493 }, 494 495 async disableInviteCodes(token: string, codes?: string[], accounts?: string[]): Promise<void> { 496 await xrpc('com.atproto.admin.disableInviteCodes', { 497 method: 'POST', 498 token, 499 body: { codes, accounts }, 500 }) 501 }, 502 503 async getAccountInfo(token: string, did: string): Promise<{ 504 did: string 505 handle: string 506 email?: string 507 indexedAt: string 508 emailConfirmedAt?: string 509 invitesDisabled?: boolean 510 deactivatedAt?: string 511 }> { 512 return xrpc('com.atproto.admin.getAccountInfo', { token, params: { did } }) 513 }, 514 515 async disableAccountInvites(token: string, account: string): Promise<void> { 516 await xrpc('com.atproto.admin.disableAccountInvites', { 517 method: 'POST', 518 token, 519 body: { account }, 520 }) 521 }, 522 523 async enableAccountInvites(token: string, account: string): Promise<void> { 524 await xrpc('com.atproto.admin.enableAccountInvites', { 525 method: 'POST', 526 token, 527 body: { account }, 528 }) 529 }, 530 531 async adminDeleteAccount(token: string, did: string): Promise<void> { 532 await xrpc('com.atproto.admin.deleteAccount', { 533 method: 'POST', 534 token, 535 body: { did }, 536 }) 537 }, 538 539 async describeRepo(token: string, repo: string): Promise<{ 540 handle: string 541 did: string 542 didDoc: unknown 543 collections: string[] 544 handleIsCorrect: boolean 545 }> { 546 return xrpc('com.atproto.repo.describeRepo', { 547 token, 548 params: { repo }, 549 }) 550 }, 551 552 async listRecords(token: string, repo: string, collection: string, options?: { 553 limit?: number 554 cursor?: string 555 reverse?: boolean 556 }): Promise<{ 557 records: Array<{ uri: string; cid: string; value: unknown }> 558 cursor?: string 559 }> { 560 const params: Record<string, string> = { repo, collection } 561 if (options?.limit) params.limit = String(options.limit) 562 if (options?.cursor) params.cursor = options.cursor 563 if (options?.reverse) params.reverse = 'true' 564 return xrpc('com.atproto.repo.listRecords', { token, params }) 565 }, 566 567 async getRecord(token: string, repo: string, collection: string, rkey: string): Promise<{ 568 uri: string 569 cid: string 570 value: unknown 571 }> { 572 return xrpc('com.atproto.repo.getRecord', { 573 token, 574 params: { repo, collection, rkey }, 575 }) 576 }, 577 578 async createRecord(token: string, repo: string, collection: string, record: unknown, rkey?: string): Promise<{ 579 uri: string 580 cid: string 581 }> { 582 return xrpc('com.atproto.repo.createRecord', { 583 method: 'POST', 584 token, 585 body: { repo, collection, record, rkey }, 586 }) 587 }, 588 589 async putRecord(token: string, repo: string, collection: string, rkey: string, record: unknown): Promise<{ 590 uri: string 591 cid: string 592 }> { 593 return xrpc('com.atproto.repo.putRecord', { 594 method: 'POST', 595 token, 596 body: { repo, collection, rkey, record }, 597 }) 598 }, 599 600 async deleteRecord(token: string, repo: string, collection: string, rkey: string): Promise<void> { 601 await xrpc('com.atproto.repo.deleteRecord', { 602 method: 'POST', 603 token, 604 body: { repo, collection, rkey }, 605 }) 606 }, 607 608 async getTotpStatus(token: string): Promise<{ enabled: boolean; hasBackupCodes: boolean }> { 609 return xrpc('com.atproto.server.getTotpStatus', { token }) 610 }, 611 612 async createTotpSecret(token: string): Promise<{ uri: string; qrBase64: string }> { 613 return xrpc('com.atproto.server.createTotpSecret', { method: 'POST', token }) 614 }, 615 616 async enableTotp(token: string, code: string): Promise<{ success: boolean; backupCodes: string[] }> { 617 return xrpc('com.atproto.server.enableTotp', { 618 method: 'POST', 619 token, 620 body: { code }, 621 }) 622 }, 623 624 async disableTotp(token: string, password: string, code: string): Promise<{ success: boolean }> { 625 return xrpc('com.atproto.server.disableTotp', { 626 method: 'POST', 627 token, 628 body: { password, code }, 629 }) 630 }, 631 632 async regenerateBackupCodes(token: string, password: string, code: string): Promise<{ backupCodes: string[] }> { 633 return xrpc('com.atproto.server.regenerateBackupCodes', { 634 method: 'POST', 635 token, 636 body: { password, code }, 637 }) 638 }, 639 640 async startPasskeyRegistration(token: string, friendlyName?: string): Promise<{ options: unknown }> { 641 return xrpc('com.atproto.server.startPasskeyRegistration', { 642 method: 'POST', 643 token, 644 body: { friendlyName }, 645 }) 646 }, 647 648 async finishPasskeyRegistration(token: string, credential: unknown, friendlyName?: string): Promise<{ id: string; credentialId: string }> { 649 return xrpc('com.atproto.server.finishPasskeyRegistration', { 650 method: 'POST', 651 token, 652 body: { credential, friendlyName }, 653 }) 654 }, 655 656 async listPasskeys(token: string): Promise<{ 657 passkeys: Array<{ 658 id: string 659 credentialId: string 660 friendlyName: string | null 661 createdAt: string 662 lastUsed: string | null 663 }> 664 }> { 665 return xrpc('com.atproto.server.listPasskeys', { token }) 666 }, 667 668 async deletePasskey(token: string, id: string): Promise<void> { 669 await xrpc('com.atproto.server.deletePasskey', { 670 method: 'POST', 671 token, 672 body: { id }, 673 }) 674 }, 675 676 async updatePasskey(token: string, id: string, friendlyName: string): Promise<void> { 677 await xrpc('com.atproto.server.updatePasskey', { 678 method: 'POST', 679 token, 680 body: { id, friendlyName }, 681 }) 682 }, 683 684 async listTrustedDevices(token: string): Promise<{ 685 devices: Array<{ 686 id: string 687 userAgent: string | null 688 friendlyName: string | null 689 trustedAt: string | null 690 trustedUntil: string | null 691 lastSeenAt: string 692 }> 693 }> { 694 return xrpc('com.tranquil.account.listTrustedDevices', { token }) 695 }, 696 697 async revokeTrustedDevice(token: string, deviceId: string): Promise<{ success: boolean }> { 698 return xrpc('com.tranquil.account.revokeTrustedDevice', { 699 method: 'POST', 700 token, 701 body: { deviceId }, 702 }) 703 }, 704 705 async updateTrustedDevice(token: string, deviceId: string, friendlyName: string): Promise<{ success: boolean }> { 706 return xrpc('com.tranquil.account.updateTrustedDevice', { 707 method: 'POST', 708 token, 709 body: { deviceId, friendlyName }, 710 }) 711 }, 712 713 async getReauthStatus(token: string): Promise<{ 714 requiresReauth: boolean 715 lastReauthAt: string | null 716 availableMethods: string[] 717 }> { 718 return xrpc('com.tranquil.account.getReauthStatus', { token }) 719 }, 720 721 async reauthPassword(token: string, password: string): Promise<{ success: boolean; reauthAt: string }> { 722 return xrpc('com.tranquil.account.reauthPassword', { 723 method: 'POST', 724 token, 725 body: { password }, 726 }) 727 }, 728 729 async reauthTotp(token: string, code: string): Promise<{ success: boolean; reauthAt: string }> { 730 return xrpc('com.tranquil.account.reauthTotp', { 731 method: 'POST', 732 token, 733 body: { code }, 734 }) 735 }, 736 737 async reauthPasskeyStart(token: string): Promise<{ options: unknown }> { 738 return xrpc('com.tranquil.account.reauthPasskeyStart', { 739 method: 'POST', 740 token, 741 }) 742 }, 743 744 async reauthPasskeyFinish(token: string, credential: unknown): Promise<{ success: boolean; reauthAt: string }> { 745 return xrpc('com.tranquil.account.reauthPasskeyFinish', { 746 method: 'POST', 747 token, 748 body: { credential }, 749 }) 750 }, 751 752 async createPasskeyAccount(params: { 753 handle: string 754 email?: string 755 inviteCode?: string 756 didType?: DidType 757 did?: string 758 signingKey?: string 759 verificationChannel?: VerificationChannel 760 discordId?: string 761 telegramUsername?: string 762 signalNumber?: string 763 }): Promise<{ 764 did: string 765 handle: string 766 setupToken: string 767 setupExpiresAt: string 768 }> { 769 return xrpc('com.tranquil.account.createPasskeyAccount', { 770 method: 'POST', 771 body: params, 772 }) 773 }, 774 775 async startPasskeyRegistrationForSetup(did: string, setupToken: string, friendlyName?: string): Promise<{ options: unknown }> { 776 return xrpc('com.tranquil.account.startPasskeyRegistrationForSetup', { 777 method: 'POST', 778 body: { did, setupToken, friendlyName }, 779 }) 780 }, 781 782 async completePasskeySetup(did: string, setupToken: string, passkeyCredential: unknown, passkeyFriendlyName?: string): Promise<{ 783 did: string 784 handle: string 785 appPassword: string 786 appPasswordName: string 787 }> { 788 return xrpc('com.tranquil.account.completePasskeySetup', { 789 method: 'POST', 790 body: { did, setupToken, passkeyCredential, passkeyFriendlyName }, 791 }) 792 }, 793 794 async requestPasskeyRecovery(email: string): Promise<{ success: boolean }> { 795 return xrpc('com.tranquil.account.requestPasskeyRecovery', { 796 method: 'POST', 797 body: { email }, 798 }) 799 }, 800 801 async recoverPasskeyAccount(did: string, recoveryToken: string, newPassword: string): Promise<{ success: boolean }> { 802 return xrpc('com.tranquil.account.recoverPasskeyAccount', { 803 method: 'POST', 804 body: { did, recoveryToken, newPassword }, 805 }) 806 }, 807}