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