Our Personal Data Server from scratch!
at main 2099 lines 54 kB view raw
1import { err, ok, type Result } from "./types/result.ts"; 2import type { 3 AccessToken, 4 Did, 5 EmailAddress, 6 Handle, 7 Nsid, 8 RefreshToken, 9 Rkey, 10 ScopeSet, 11} from "./types/branded.ts"; 12import { 13 unsafeAsAccessToken, 14 unsafeAsDid, 15 unsafeAsEmail, 16 unsafeAsHandle, 17 unsafeAsISODate, 18 unsafeAsRefreshToken, 19 unsafeAsScopeSet, 20} from "./types/branded.ts"; 21import { 22 createDPoPProofForRequest, 23 getDPoPNonce, 24 setDPoPNonce, 25} from "./oauth.ts"; 26import type { 27 AccountInfo, 28 AccountState, 29 ApiErrorCode, 30 AppPassword, 31 CompletePasskeySetupResponse, 32 ConfirmSignupResult, 33 ContactState, 34 CreateAccountParams, 35 CreateAccountResult, 36 CreateBackupResponse, 37 CreatedAppPassword, 38 CreateRecordResponse, 39 DelegationAuditEntry, 40 DelegationControlledAccount, 41 DelegationController, 42 DelegationScopePreset, 43 DidDocument, 44 DidType, 45 EmailUpdateResponse, 46 EnableTotpResponse, 47 FinishPasskeyRegistrationResponse, 48 GetInviteCodesResponse, 49 InviteCodeInfo, 50 LegacyLoginPreference, 51 ListBackupsResponse, 52 ListPasskeysResponse, 53 ListRecordsResponse, 54 ListReposResponse, 55 ListSessionsResponse, 56 ListTrustedDevicesResponse, 57 NotificationHistoryResponse, 58 NotificationPrefs, 59 PasskeyAccountCreateResponse, 60 PasswordStatus, 61 ReauthPasskeyStartResponse, 62 ReauthResponse, 63 ReauthStatus, 64 RecommendedDidCredentials, 65 RecordResponse, 66 RegenerateBackupCodesResponse, 67 RepoDescription, 68 ResendMigrationVerificationResponse, 69 ReserveSigningKeyResponse, 70 SearchAccountsResponse, 71 ServerConfig, 72 ServerDescription, 73 ServerStats, 74 Session, 75 SetBackupEnabledResponse, 76 SsoLinkedAccount, 77 StartPasskeyRegistrationResponse, 78 SuccessResponse, 79 TotpSecret, 80 TotpStatus, 81 UpdateLegacyLoginResponse, 82 UpdateLocaleResponse, 83 UploadBlobResponse, 84 VerificationChannel, 85 VerifyMigrationEmailResponse, 86 VerifyTokenResponse, 87} from "./types/api.ts"; 88 89const API_BASE = "/xrpc"; 90 91export class ApiError extends Error { 92 public did?: Did; 93 public reauthMethods?: string[]; 94 constructor( 95 public status: number, 96 public error: ApiErrorCode, 97 message: string, 98 did?: string, 99 reauthMethods?: string[], 100 ) { 101 super(message); 102 this.name = "ApiError"; 103 this.did = did ? unsafeAsDid(did) : undefined; 104 this.reauthMethods = reauthMethods; 105 } 106} 107 108let tokenRefreshCallback: (() => Promise<AccessToken | null>) | null = null; 109 110export function setTokenRefreshCallback( 111 callback: () => Promise<AccessToken | null>, 112) { 113 tokenRefreshCallback = callback; 114} 115 116interface AuthenticatedFetchOptions { 117 method?: "GET" | "POST"; 118 token: AccessToken | RefreshToken; 119 headers?: Record<string, string>; 120 body?: BodyInit; 121} 122 123async function authenticatedFetch( 124 url: string, 125 options: AuthenticatedFetchOptions, 126): Promise<Response> { 127 const { method = "GET", token, headers = {}, body } = options; 128 const fullUrl = url.startsWith("http") 129 ? url 130 : `${globalThis.location.origin}${url}`; 131 const dpopProof = await createDPoPProofForRequest(method, fullUrl, token); 132 const res = await fetch(url, { 133 method, 134 headers: { 135 ...headers, 136 Authorization: `DPoP ${token}`, 137 DPoP: dpopProof, 138 }, 139 body, 140 }); 141 const dpopNonce = res.headers.get("DPoP-Nonce"); 142 if (dpopNonce) { 143 setDPoPNonce(dpopNonce); 144 } 145 return res; 146} 147 148interface XrpcOptions { 149 method?: "GET" | "POST"; 150 params?: Record<string, string>; 151 body?: unknown; 152 token?: AccessToken | RefreshToken; 153 skipRetry?: boolean; 154 skipDpopRetry?: boolean; 155} 156 157async function xrpc<T>(method: string, options?: XrpcOptions): Promise<T> { 158 const { 159 method: httpMethod = "GET", 160 params, 161 body, 162 token, 163 skipRetry, 164 skipDpopRetry, 165 } = options ?? {}; 166 let url = `${API_BASE}/${method}`; 167 if (params) { 168 const searchParams = new URLSearchParams(params); 169 url += `?${searchParams}`; 170 } 171 const headers: Record<string, string> = {}; 172 if (body) { 173 headers["Content-Type"] = "application/json"; 174 } 175 const res = token 176 ? await authenticatedFetch(url, { 177 method: httpMethod, 178 token, 179 headers, 180 body: body ? JSON.stringify(body) : undefined, 181 }) 182 : await fetch(url, { 183 method: httpMethod, 184 headers, 185 body: body ? JSON.stringify(body) : undefined, 186 }); 187 if (!res.ok) { 188 const errData = await res.json().catch(() => ({ 189 error: "Unknown", 190 message: res.statusText, 191 })); 192 if ( 193 res.status === 401 && 194 errData.error === "use_dpop_nonce" && 195 token && 196 !skipDpopRetry && 197 getDPoPNonce() 198 ) { 199 return xrpc(method, { ...options, skipDpopRetry: true }); 200 } 201 if ( 202 res.status === 401 && 203 (errData.error === "AuthenticationFailed" || 204 errData.error === "ExpiredToken" || 205 errData.error === "OAuthExpiredToken") && 206 token && 207 tokenRefreshCallback && 208 !skipRetry 209 ) { 210 const newToken = await tokenRefreshCallback(); 211 if (newToken && newToken !== token) { 212 return xrpc(method, { ...options, token: newToken, skipRetry: true }); 213 } 214 } 215 const message = res.status === 429 216 ? (errData.message || "Too many requests. Please try again later.") 217 : errData.message; 218 throw new ApiError( 219 res.status, 220 errData.error as ApiErrorCode, 221 message, 222 errData.did, 223 errData.reauthMethods, 224 ); 225 } 226 return res.json(); 227} 228 229async function xrpcResult<T>( 230 method: string, 231 options?: XrpcOptions, 232): Promise<Result<T, ApiError>> { 233 try { 234 const value = await xrpc<T>(method, options); 235 return ok(value); 236 } catch (e) { 237 if (e instanceof ApiError) { 238 return err(e); 239 } 240 return err( 241 new ApiError(0, "Unknown", e instanceof Error ? e.message : String(e)), 242 ); 243 } 244} 245 246export interface VerificationMethod { 247 id: string; 248 type: string; 249 publicKeyMultibase: string; 250} 251 252export type { AppPassword, DidDocument, InviteCodeInfo as InviteCode, Session }; 253export type { DidType, VerificationChannel }; 254 255function buildContactState(s: Record<string, unknown>): ContactState { 256 const preferredChannel = s.preferredChannel as 257 | VerificationChannel 258 | undefined; 259 const email = s.email ? unsafeAsEmail(s.email as string) : undefined; 260 261 if (preferredChannel) { 262 return { 263 contactKind: "channel", 264 preferredChannel, 265 preferredChannelVerified: Boolean(s.preferredChannelVerified), 266 email, 267 }; 268 } 269 270 if (email) { 271 return { 272 contactKind: "email", 273 email, 274 emailConfirmed: Boolean(s.emailConfirmed), 275 }; 276 } 277 278 return { contactKind: "none" }; 279} 280 281function buildAccountState(s: Record<string, unknown>): AccountState { 282 const status = s.status as string | undefined; 283 const isAdmin = Boolean(s.isAdmin); 284 const active = s.active as boolean | undefined; 285 286 if (status === "migrated") { 287 return { 288 accountKind: "migrated", 289 migratedToPds: (s.migratedToPds as string) || "", 290 migratedAt: s.migratedAt 291 ? unsafeAsISODate(s.migratedAt as string) 292 : unsafeAsISODate(new Date().toISOString()), 293 isAdmin, 294 }; 295 } 296 297 if (status === "deactivated" || active === false) { 298 return { accountKind: "deactivated", isAdmin }; 299 } 300 301 if (status === "suspended") { 302 return { accountKind: "suspended", isAdmin }; 303 } 304 305 return { accountKind: "active", isAdmin }; 306} 307 308export function castSession(raw: unknown): Session { 309 const s = raw as Record<string, unknown>; 310 const contact = buildContactState(s); 311 const account = buildAccountState(s); 312 313 return { 314 did: unsafeAsDid(s.did as string), 315 handle: unsafeAsHandle(s.handle as string), 316 accessJwt: unsafeAsAccessToken(s.accessJwt as string), 317 refreshJwt: unsafeAsRefreshToken(s.refreshJwt as string), 318 preferredLocale: s.preferredLocale as string | null | undefined, 319 ...contact, 320 ...account, 321 }; 322} 323 324function _castDelegationController(raw: unknown): DelegationController { 325 const c = raw as Record<string, unknown>; 326 return { 327 did: unsafeAsDid(c.did as string), 328 granted_scopes: unsafeAsScopeSet(c.granted_scopes as string), 329 added_at: unsafeAsISODate(c.added_at as string), 330 }; 331} 332 333function _castDelegationControlledAccount( 334 raw: unknown, 335): DelegationControlledAccount { 336 const a = raw as Record<string, unknown>; 337 return { 338 did: unsafeAsDid(a.did as string), 339 handle: unsafeAsHandle(a.handle as string), 340 granted_scopes: unsafeAsScopeSet(a.granted_scopes as string), 341 }; 342} 343 344function _castDelegationAuditEntry(raw: unknown): DelegationAuditEntry { 345 const e = raw as Record<string, unknown>; 346 return { 347 id: e.id as string, 348 action: e.action as string, 349 actor_did: unsafeAsDid(e.actor_did as string), 350 target_did: e.target_did ? unsafeAsDid(e.target_did as string) : undefined, 351 details: e.details as string | undefined, 352 created_at: unsafeAsISODate(e.created_at as string), 353 }; 354} 355 356function _castSsoLinkedAccount(raw: unknown): SsoLinkedAccount { 357 const a = raw as Record<string, unknown>; 358 return { 359 id: a.id as string, 360 provider: a.provider as string, 361 provider_name: a.provider_name as string, 362 provider_username: a.provider_username as string, 363 provider_email: a.provider_email as string | undefined, 364 created_at: unsafeAsISODate(a.created_at as string), 365 last_login_at: a.last_login_at 366 ? unsafeAsISODate(a.last_login_at as string) 367 : undefined, 368 }; 369} 370 371export const api = { 372 async createAccount( 373 params: CreateAccountParams, 374 byodToken?: string, 375 ): Promise<CreateAccountResult> { 376 const url = `${API_BASE}/com.atproto.server.createAccount`; 377 const headers: Record<string, string> = { 378 "Content-Type": "application/json", 379 }; 380 if (byodToken) { 381 headers["Authorization"] = `Bearer ${byodToken}`; 382 } 383 const response = await fetch(url, { 384 method: "POST", 385 headers, 386 body: JSON.stringify({ 387 handle: params.handle, 388 email: params.email, 389 password: params.password, 390 inviteCode: params.inviteCode, 391 didType: params.didType, 392 did: params.did, 393 signingKey: params.signingKey, 394 verificationChannel: params.verificationChannel, 395 discordId: params.discordId, 396 telegramUsername: params.telegramUsername, 397 signalNumber: params.signalNumber, 398 }), 399 }); 400 const data = await response.json(); 401 if (!response.ok) { 402 throw new ApiError(response.status, data.error, data.message); 403 } 404 return data; 405 }, 406 407 async createAccountWithServiceAuth( 408 serviceAuthToken: string, 409 params: { 410 did: Did; 411 handle: Handle; 412 email: EmailAddress; 413 password: string; 414 inviteCode?: string; 415 }, 416 ): Promise<Session> { 417 const url = `${API_BASE}/com.atproto.server.createAccount`; 418 const response = await fetch(url, { 419 method: "POST", 420 headers: { 421 "Content-Type": "application/json", 422 "Authorization": `Bearer ${serviceAuthToken}`, 423 }, 424 body: JSON.stringify({ 425 did: params.did, 426 handle: params.handle, 427 email: params.email, 428 password: params.password, 429 inviteCode: params.inviteCode, 430 }), 431 }); 432 const data = await response.json(); 433 if (!response.ok) { 434 throw new ApiError(response.status, data.error, data.message); 435 } 436 return castSession(data); 437 }, 438 439 confirmSignup( 440 did: Did, 441 verificationCode: string, 442 ): Promise<ConfirmSignupResult> { 443 return xrpc("com.atproto.server.confirmSignup", { 444 method: "POST", 445 body: { did, verificationCode }, 446 }); 447 }, 448 449 resendVerification(did: Did): Promise<{ success: boolean }> { 450 return xrpc("com.atproto.server.resendVerification", { 451 method: "POST", 452 body: { did }, 453 }); 454 }, 455 456 async createSession(identifier: string, password: string): Promise<Session> { 457 const raw = await xrpc<unknown>("com.atproto.server.createSession", { 458 method: "POST", 459 body: { identifier, password }, 460 }); 461 return castSession(raw); 462 }, 463 464 checkEmailVerified(identifier: string): Promise<{ verified: boolean }> { 465 return xrpc("_checkEmailVerified", { 466 method: "POST", 467 body: { identifier }, 468 }); 469 }, 470 471 checkEmailInUse(email: string): Promise<{ inUse: boolean }> { 472 return xrpc("_account.checkEmailInUse", { 473 method: "POST", 474 body: { email }, 475 }); 476 }, 477 478 checkCommsChannelInUse( 479 channel: "email" | "discord" | "telegram" | "signal", 480 identifier: string, 481 ): Promise<{ inUse: boolean }> { 482 return xrpc("_account.checkCommsChannelInUse", { 483 method: "POST", 484 body: { channel, identifier }, 485 }); 486 }, 487 488 async getSession(token: AccessToken): Promise<Session> { 489 const raw = await xrpc<unknown>("com.atproto.server.getSession", { token }); 490 return castSession(raw); 491 }, 492 493 async refreshSession(refreshJwt: RefreshToken): Promise<Session> { 494 const raw = await xrpc<unknown>("com.atproto.server.refreshSession", { 495 method: "POST", 496 token: refreshJwt, 497 }); 498 return castSession(raw); 499 }, 500 501 async deleteSession(token: AccessToken): Promise<void> { 502 await xrpc("com.atproto.server.deleteSession", { 503 method: "POST", 504 token, 505 }); 506 }, 507 508 listAppPasswords(token: AccessToken): Promise<{ passwords: AppPassword[] }> { 509 return xrpc("com.atproto.server.listAppPasswords", { token }); 510 }, 511 512 createAppPassword( 513 token: AccessToken, 514 name: string, 515 scopes?: string, 516 ): Promise<CreatedAppPassword> { 517 return xrpc("com.atproto.server.createAppPassword", { 518 method: "POST", 519 token, 520 body: { name, scopes }, 521 }); 522 }, 523 524 async revokeAppPassword(token: AccessToken, name: string): Promise<void> { 525 await xrpc("com.atproto.server.revokeAppPassword", { 526 method: "POST", 527 token, 528 body: { name }, 529 }); 530 }, 531 532 getAccountInviteCodes( 533 token: AccessToken, 534 ): Promise<{ codes: InviteCodeInfo[] }> { 535 return xrpc("com.atproto.server.getAccountInviteCodes", { token }); 536 }, 537 538 createInviteCode( 539 token: AccessToken, 540 useCount: number = 1, 541 ): Promise<{ code: string }> { 542 return xrpc("com.atproto.server.createInviteCode", { 543 method: "POST", 544 token, 545 body: { useCount }, 546 }); 547 }, 548 549 async requestPasswordReset(email: EmailAddress): Promise<void> { 550 await xrpc("com.atproto.server.requestPasswordReset", { 551 method: "POST", 552 body: { email }, 553 }); 554 }, 555 556 async resetPassword(token: string, password: string): Promise<void> { 557 await xrpc("com.atproto.server.resetPassword", { 558 method: "POST", 559 body: { token, password }, 560 }); 561 }, 562 563 requestEmailUpdate( 564 token: AccessToken, 565 newEmail?: string, 566 ): Promise<EmailUpdateResponse> { 567 return xrpc("com.atproto.server.requestEmailUpdate", { 568 method: "POST", 569 token, 570 body: newEmail ? { newEmail } : undefined, 571 }); 572 }, 573 574 async updateEmail( 575 token: AccessToken, 576 email: string, 577 emailToken?: string, 578 ): Promise<void> { 579 await xrpc("com.atproto.server.updateEmail", { 580 method: "POST", 581 token, 582 body: { email, token: emailToken }, 583 }); 584 }, 585 586 checkEmailUpdateStatus( 587 token: AccessToken, 588 ): Promise<{ pending: boolean; authorized: boolean; newEmail?: string }> { 589 return xrpc("_account.checkEmailUpdateStatus", { 590 method: "GET", 591 token, 592 }); 593 }, 594 595 async updateHandle(token: AccessToken, handle: Handle): Promise<void> { 596 await xrpc("com.atproto.identity.updateHandle", { 597 method: "POST", 598 token, 599 body: { handle }, 600 }); 601 }, 602 603 async requestAccountDelete(token: AccessToken): Promise<void> { 604 await xrpc("com.atproto.server.requestAccountDelete", { 605 method: "POST", 606 token, 607 }); 608 }, 609 610 async deleteAccount( 611 did: Did, 612 password: string, 613 deleteToken: string, 614 ): Promise<void> { 615 await xrpc("com.atproto.server.deleteAccount", { 616 method: "POST", 617 body: { did, password, token: deleteToken }, 618 }); 619 }, 620 621 describeServer(): Promise<ServerDescription> { 622 return xrpc("com.atproto.server.describeServer"); 623 }, 624 625 listRepos(limit?: number): Promise<ListReposResponse> { 626 const params: Record<string, string> = {}; 627 if (limit) params.limit = String(limit); 628 return xrpc("com.atproto.sync.listRepos", { params }); 629 }, 630 631 getNotificationPrefs(token: AccessToken): Promise<NotificationPrefs> { 632 return xrpc("_account.getNotificationPrefs", { token }); 633 }, 634 635 updateNotificationPrefs(token: AccessToken, prefs: { 636 preferredChannel?: string; 637 discordId?: string; 638 telegramUsername?: string; 639 signalNumber?: string; 640 }): Promise<SuccessResponse> { 641 return xrpc("_account.updateNotificationPrefs", { 642 method: "POST", 643 token, 644 body: prefs, 645 }); 646 }, 647 648 confirmChannelVerification( 649 token: AccessToken, 650 channel: string, 651 identifier: string, 652 code: string, 653 ): Promise<SuccessResponse> { 654 return xrpc("_account.confirmChannelVerification", { 655 method: "POST", 656 token, 657 body: { channel, identifier, code }, 658 }); 659 }, 660 661 getNotificationHistory( 662 token: AccessToken, 663 ): Promise<NotificationHistoryResponse> { 664 return xrpc("_account.getNotificationHistory", { token }); 665 }, 666 667 getServerStats(token: AccessToken): Promise<ServerStats> { 668 return xrpc("_admin.getServerStats", { token }); 669 }, 670 671 getServerConfig(): Promise<ServerConfig> { 672 return xrpc("_server.getConfig"); 673 }, 674 675 updateServerConfig( 676 token: AccessToken, 677 config: { 678 serverName?: string; 679 primaryColor?: string; 680 primaryColorDark?: string; 681 secondaryColor?: string; 682 secondaryColorDark?: string; 683 logoCid?: string; 684 }, 685 ): Promise<SuccessResponse> { 686 return xrpc("_admin.updateServerConfig", { 687 method: "POST", 688 token, 689 body: config, 690 }); 691 }, 692 693 async uploadBlob( 694 token: AccessToken, 695 file: File, 696 ): Promise<UploadBlobResponse> { 697 const res = await authenticatedFetch("/xrpc/com.atproto.repo.uploadBlob", { 698 method: "POST", 699 token, 700 headers: { "Content-Type": file.type }, 701 body: file, 702 }); 703 if (!res.ok) { 704 const errData = await res.json().catch(() => ({ 705 error: "Unknown", 706 message: res.statusText, 707 })); 708 throw new ApiError(res.status, errData.error, errData.message); 709 } 710 return res.json(); 711 }, 712 713 async changePassword( 714 token: AccessToken, 715 currentPassword: string, 716 newPassword: string, 717 ): Promise<void> { 718 await xrpc("_account.changePassword", { 719 method: "POST", 720 token, 721 body: { currentPassword, newPassword }, 722 }); 723 }, 724 725 removePassword(token: AccessToken): Promise<SuccessResponse> { 726 return xrpc("_account.removePassword", { 727 method: "POST", 728 token, 729 }); 730 }, 731 732 setPassword( 733 token: AccessToken, 734 newPassword: string, 735 ): Promise<SuccessResponse> { 736 return xrpc("_account.setPassword", { 737 method: "POST", 738 token, 739 body: { newPassword }, 740 }); 741 }, 742 743 getPasswordStatus(token: AccessToken): Promise<PasswordStatus> { 744 return xrpc("_account.getPasswordStatus", { token }); 745 }, 746 747 getLegacyLoginPreference(token: AccessToken): Promise<LegacyLoginPreference> { 748 return xrpc("_account.getLegacyLoginPreference", { token }); 749 }, 750 751 updateLegacyLoginPreference( 752 token: AccessToken, 753 allowLegacyLogin: boolean, 754 ): Promise<UpdateLegacyLoginResponse> { 755 return xrpc("_account.updateLegacyLoginPreference", { 756 method: "POST", 757 token, 758 body: { allowLegacyLogin }, 759 }); 760 }, 761 762 updateLocale( 763 token: AccessToken, 764 preferredLocale: string, 765 ): Promise<UpdateLocaleResponse> { 766 return xrpc("_account.updateLocale", { 767 method: "POST", 768 token, 769 body: { preferredLocale }, 770 }); 771 }, 772 773 listSessions(token: AccessToken): Promise<ListSessionsResponse> { 774 return xrpc("_account.listSessions", { token }); 775 }, 776 777 async revokeSession(token: AccessToken, sessionId: string): Promise<void> { 778 await xrpc("_account.revokeSession", { 779 method: "POST", 780 token, 781 body: { sessionId }, 782 }); 783 }, 784 785 revokeAllSessions(token: AccessToken): Promise<{ revokedCount: number }> { 786 return xrpc("_account.revokeAllSessions", { 787 method: "POST", 788 token, 789 }); 790 }, 791 792 searchAccounts(token: AccessToken, options?: { 793 handle?: string; 794 cursor?: string; 795 limit?: number; 796 }): Promise<SearchAccountsResponse> { 797 const params: Record<string, string> = {}; 798 if (options?.handle) params.handle = options.handle; 799 if (options?.cursor) params.cursor = options.cursor; 800 if (options?.limit) params.limit = String(options.limit); 801 return xrpc("com.atproto.admin.searchAccounts", { token, params }); 802 }, 803 804 getInviteCodes(token: AccessToken, options?: { 805 sort?: "recent" | "usage"; 806 cursor?: string; 807 limit?: number; 808 }): Promise<GetInviteCodesResponse> { 809 const params: Record<string, string> = {}; 810 if (options?.sort) params.sort = options.sort; 811 if (options?.cursor) params.cursor = options.cursor; 812 if (options?.limit) params.limit = String(options.limit); 813 return xrpc("com.atproto.admin.getInviteCodes", { token, params }); 814 }, 815 816 async disableInviteCodes( 817 token: AccessToken, 818 codes?: string[], 819 accounts?: string[], 820 ): Promise<void> { 821 await xrpc("com.atproto.admin.disableInviteCodes", { 822 method: "POST", 823 token, 824 body: { codes, accounts }, 825 }); 826 }, 827 828 getAccountInfo(token: AccessToken, did: Did): Promise<AccountInfo> { 829 return xrpc("com.atproto.admin.getAccountInfo", { token, params: { did } }); 830 }, 831 832 async disableAccountInvites(token: AccessToken, account: Did): Promise<void> { 833 await xrpc("com.atproto.admin.disableAccountInvites", { 834 method: "POST", 835 token, 836 body: { account }, 837 }); 838 }, 839 840 async enableAccountInvites(token: AccessToken, account: Did): Promise<void> { 841 await xrpc("com.atproto.admin.enableAccountInvites", { 842 method: "POST", 843 token, 844 body: { account }, 845 }); 846 }, 847 848 async adminDeleteAccount(token: AccessToken, did: Did): Promise<void> { 849 await xrpc("com.atproto.admin.deleteAccount", { 850 method: "POST", 851 token, 852 body: { did }, 853 }); 854 }, 855 856 describeRepo(token: AccessToken, repo: Did): Promise<RepoDescription> { 857 return xrpc("com.atproto.repo.describeRepo", { 858 token, 859 params: { repo }, 860 }); 861 }, 862 863 listRecords(token: AccessToken, repo: Did, collection: Nsid, options?: { 864 limit?: number; 865 cursor?: string; 866 reverse?: boolean; 867 }): Promise<ListRecordsResponse> { 868 const params: Record<string, string> = { repo, collection }; 869 if (options?.limit) params.limit = String(options.limit); 870 if (options?.cursor) params.cursor = options.cursor; 871 if (options?.reverse) params.reverse = "true"; 872 return xrpc("com.atproto.repo.listRecords", { token, params }); 873 }, 874 875 getRecord( 876 token: AccessToken, 877 repo: Did, 878 collection: Nsid, 879 rkey: Rkey, 880 ): Promise<RecordResponse> { 881 return xrpc("com.atproto.repo.getRecord", { 882 token, 883 params: { repo, collection, rkey }, 884 }); 885 }, 886 887 createRecord( 888 token: AccessToken, 889 repo: Did, 890 collection: Nsid, 891 record: unknown, 892 rkey?: Rkey, 893 ): Promise<CreateRecordResponse> { 894 return xrpc("com.atproto.repo.createRecord", { 895 method: "POST", 896 token, 897 body: { repo, collection, record, rkey }, 898 }); 899 }, 900 901 putRecord( 902 token: AccessToken, 903 repo: Did, 904 collection: Nsid, 905 rkey: Rkey, 906 record: unknown, 907 ): Promise<CreateRecordResponse> { 908 return xrpc("com.atproto.repo.putRecord", { 909 method: "POST", 910 token, 911 body: { repo, collection, rkey, record }, 912 }); 913 }, 914 915 async deleteRecord( 916 token: AccessToken, 917 repo: Did, 918 collection: Nsid, 919 rkey: Rkey, 920 ): Promise<void> { 921 await xrpc("com.atproto.repo.deleteRecord", { 922 method: "POST", 923 token, 924 body: { repo, collection, rkey }, 925 }); 926 }, 927 928 getTotpStatus(token: AccessToken): Promise<TotpStatus> { 929 return xrpc("com.atproto.server.getTotpStatus", { token }); 930 }, 931 932 createTotpSecret(token: AccessToken): Promise<TotpSecret> { 933 return xrpc("com.atproto.server.createTotpSecret", { 934 method: "POST", 935 token, 936 }); 937 }, 938 939 enableTotp(token: AccessToken, code: string): Promise<EnableTotpResponse> { 940 return xrpc("com.atproto.server.enableTotp", { 941 method: "POST", 942 token, 943 body: { code }, 944 }); 945 }, 946 947 disableTotp( 948 token: AccessToken, 949 password: string, 950 code: string, 951 ): Promise<SuccessResponse> { 952 return xrpc("com.atproto.server.disableTotp", { 953 method: "POST", 954 token, 955 body: { password, code }, 956 }); 957 }, 958 959 regenerateBackupCodes( 960 token: AccessToken, 961 password: string, 962 code: string, 963 ): Promise<RegenerateBackupCodesResponse> { 964 return xrpc("com.atproto.server.regenerateBackupCodes", { 965 method: "POST", 966 token, 967 body: { password, code }, 968 }); 969 }, 970 971 startPasskeyRegistration( 972 token: AccessToken, 973 friendlyName?: string, 974 ): Promise<StartPasskeyRegistrationResponse> { 975 return xrpc("com.atproto.server.startPasskeyRegistration", { 976 method: "POST", 977 token, 978 body: { friendlyName }, 979 }); 980 }, 981 982 finishPasskeyRegistration( 983 token: AccessToken, 984 credential: unknown, 985 friendlyName?: string, 986 ): Promise<FinishPasskeyRegistrationResponse> { 987 return xrpc("com.atproto.server.finishPasskeyRegistration", { 988 method: "POST", 989 token, 990 body: { credential, friendlyName }, 991 }); 992 }, 993 994 listPasskeys(token: AccessToken): Promise<ListPasskeysResponse> { 995 return xrpc("com.atproto.server.listPasskeys", { token }); 996 }, 997 998 async deletePasskey(token: AccessToken, id: string): Promise<void> { 999 await xrpc("com.atproto.server.deletePasskey", { 1000 method: "POST", 1001 token, 1002 body: { id }, 1003 }); 1004 }, 1005 1006 async updatePasskey( 1007 token: AccessToken, 1008 id: string, 1009 friendlyName: string, 1010 ): Promise<void> { 1011 await xrpc("com.atproto.server.updatePasskey", { 1012 method: "POST", 1013 token, 1014 body: { id, friendlyName }, 1015 }); 1016 }, 1017 1018 listTrustedDevices(token: AccessToken): Promise<ListTrustedDevicesResponse> { 1019 return xrpc("_account.listTrustedDevices", { token }); 1020 }, 1021 1022 revokeTrustedDevice( 1023 token: AccessToken, 1024 deviceId: string, 1025 ): Promise<SuccessResponse> { 1026 return xrpc("_account.revokeTrustedDevice", { 1027 method: "POST", 1028 token, 1029 body: { deviceId }, 1030 }); 1031 }, 1032 1033 updateTrustedDevice( 1034 token: AccessToken, 1035 deviceId: string, 1036 friendlyName: string, 1037 ): Promise<SuccessResponse> { 1038 return xrpc("_account.updateTrustedDevice", { 1039 method: "POST", 1040 token, 1041 body: { deviceId, friendlyName }, 1042 }); 1043 }, 1044 1045 getReauthStatus(token: AccessToken): Promise<ReauthStatus> { 1046 return xrpc("_account.getReauthStatus", { token }); 1047 }, 1048 1049 reauthPassword( 1050 token: AccessToken, 1051 password: string, 1052 ): Promise<ReauthResponse> { 1053 return xrpc("_account.reauthPassword", { 1054 method: "POST", 1055 token, 1056 body: { password }, 1057 }); 1058 }, 1059 1060 reauthTotp(token: AccessToken, code: string): Promise<ReauthResponse> { 1061 return xrpc("_account.reauthTotp", { 1062 method: "POST", 1063 token, 1064 body: { code }, 1065 }); 1066 }, 1067 1068 reauthPasskeyStart(token: AccessToken): Promise<ReauthPasskeyStartResponse> { 1069 return xrpc("_account.reauthPasskeyStart", { 1070 method: "POST", 1071 token, 1072 }); 1073 }, 1074 1075 reauthPasskeyFinish( 1076 token: AccessToken, 1077 credential: unknown, 1078 ): Promise<ReauthResponse> { 1079 return xrpc("_account.reauthPasskeyFinish", { 1080 method: "POST", 1081 token, 1082 body: { credential }, 1083 }); 1084 }, 1085 1086 reserveSigningKey(did?: Did): Promise<ReserveSigningKeyResponse> { 1087 return xrpc("com.atproto.server.reserveSigningKey", { 1088 method: "POST", 1089 body: { did }, 1090 }); 1091 }, 1092 1093 getRecommendedDidCredentials( 1094 token: AccessToken, 1095 ): Promise<RecommendedDidCredentials> { 1096 return xrpc("com.atproto.identity.getRecommendedDidCredentials", { token }); 1097 }, 1098 1099 async activateAccount(token: AccessToken): Promise<void> { 1100 await xrpc("com.atproto.server.activateAccount", { 1101 method: "POST", 1102 token, 1103 }); 1104 }, 1105 1106 async createPasskeyAccount(params: { 1107 handle: Handle; 1108 email?: EmailAddress; 1109 inviteCode?: string; 1110 didType?: DidType; 1111 did?: Did; 1112 signingKey?: string; 1113 verificationChannel?: VerificationChannel; 1114 discordId?: string; 1115 telegramUsername?: string; 1116 signalNumber?: string; 1117 }, byodToken?: string): Promise<PasskeyAccountCreateResponse> { 1118 const url = `${API_BASE}/_account.createPasskeyAccount`; 1119 const headers: Record<string, string> = { 1120 "Content-Type": "application/json", 1121 }; 1122 if (byodToken) { 1123 headers["Authorization"] = `Bearer ${byodToken}`; 1124 } 1125 const res = await fetch(url, { 1126 method: "POST", 1127 headers, 1128 body: JSON.stringify(params), 1129 }); 1130 if (!res.ok) { 1131 const errData = await res.json().catch(() => ({ 1132 error: "Unknown", 1133 message: res.statusText, 1134 })); 1135 throw new ApiError(res.status, errData.error, errData.message); 1136 } 1137 return res.json(); 1138 }, 1139 1140 startPasskeyRegistrationForSetup( 1141 did: Did, 1142 setupToken: string, 1143 friendlyName?: string, 1144 ): Promise<StartPasskeyRegistrationResponse> { 1145 return xrpc("_account.startPasskeyRegistrationForSetup", { 1146 method: "POST", 1147 body: { did, setupToken, friendlyName }, 1148 }); 1149 }, 1150 1151 completePasskeySetup( 1152 did: Did, 1153 setupToken: string, 1154 passkeyCredential: unknown, 1155 passkeyFriendlyName?: string, 1156 ): Promise<CompletePasskeySetupResponse> { 1157 return xrpc("_account.completePasskeySetup", { 1158 method: "POST", 1159 body: { did, setupToken, passkeyCredential, passkeyFriendlyName }, 1160 }); 1161 }, 1162 1163 requestPasskeyRecovery(email: EmailAddress): Promise<SuccessResponse> { 1164 return xrpc("_account.requestPasskeyRecovery", { 1165 method: "POST", 1166 body: { email }, 1167 }); 1168 }, 1169 1170 recoverPasskeyAccount( 1171 did: Did, 1172 recoveryToken: string, 1173 newPassword: string, 1174 ): Promise<SuccessResponse> { 1175 return xrpc("_account.recoverPasskeyAccount", { 1176 method: "POST", 1177 body: { did, recoveryToken, newPassword }, 1178 }); 1179 }, 1180 1181 verifyMigrationEmail( 1182 token: string, 1183 email: EmailAddress, 1184 ): Promise<VerifyMigrationEmailResponse> { 1185 return xrpc("com.atproto.server.verifyMigrationEmail", { 1186 method: "POST", 1187 body: { token, email }, 1188 }); 1189 }, 1190 1191 resendMigrationVerification( 1192 email: EmailAddress, 1193 ): Promise<ResendMigrationVerificationResponse> { 1194 return xrpc("com.atproto.server.resendMigrationVerification", { 1195 method: "POST", 1196 body: { email }, 1197 }); 1198 }, 1199 1200 verifyToken( 1201 token: string, 1202 identifier: string, 1203 accessToken?: AccessToken, 1204 ): Promise<VerifyTokenResponse> { 1205 return xrpc("_account.verifyToken", { 1206 method: "POST", 1207 body: { token, identifier }, 1208 token: accessToken, 1209 }); 1210 }, 1211 1212 getDidDocument(token: AccessToken): Promise<DidDocument> { 1213 return xrpc("_account.getDidDocument", { token }); 1214 }, 1215 1216 updateDidDocument( 1217 token: AccessToken, 1218 params: { 1219 verificationMethods?: VerificationMethod[]; 1220 alsoKnownAs?: string[]; 1221 serviceEndpoint?: string; 1222 }, 1223 ): Promise<SuccessResponse> { 1224 return xrpc("_account.updateDidDocument", { 1225 method: "POST", 1226 token, 1227 body: params, 1228 }); 1229 }, 1230 1231 async deactivateAccount( 1232 token: AccessToken, 1233 deleteAfter?: string, 1234 ): Promise<void> { 1235 await xrpc("com.atproto.server.deactivateAccount", { 1236 method: "POST", 1237 token, 1238 body: { deleteAfter }, 1239 }); 1240 }, 1241 1242 async getRepo(token: AccessToken, did: Did): Promise<ArrayBuffer> { 1243 const url = `${API_BASE}/com.atproto.sync.getRepo?did=${ 1244 encodeURIComponent(did) 1245 }`; 1246 const res = await authenticatedFetch(url, { token }); 1247 if (!res.ok) { 1248 const errData = await res.json().catch(() => ({ 1249 error: "Unknown", 1250 message: res.statusText, 1251 })); 1252 throw new ApiError(res.status, errData.error, errData.message); 1253 } 1254 return res.arrayBuffer(); 1255 }, 1256 1257 listBackups(token: AccessToken): Promise<ListBackupsResponse> { 1258 return xrpc("_backup.listBackups", { token }); 1259 }, 1260 1261 async getBackup(token: AccessToken, id: string): Promise<Blob> { 1262 const url = `${API_BASE}/_backup.getBackup?id=${encodeURIComponent(id)}`; 1263 const res = await authenticatedFetch(url, { token }); 1264 if (!res.ok) { 1265 const errData = await res.json().catch(() => ({ 1266 error: "Unknown", 1267 message: res.statusText, 1268 })); 1269 throw new ApiError(res.status, errData.error, errData.message); 1270 } 1271 return res.blob(); 1272 }, 1273 1274 createBackup(token: AccessToken): Promise<CreateBackupResponse> { 1275 return xrpc("_backup.createBackup", { 1276 method: "POST", 1277 token, 1278 }); 1279 }, 1280 1281 async deleteBackup(token: AccessToken, id: string): Promise<void> { 1282 await xrpc("_backup.deleteBackup", { 1283 method: "POST", 1284 token, 1285 params: { id }, 1286 }); 1287 }, 1288 1289 setBackupEnabled( 1290 token: AccessToken, 1291 enabled: boolean, 1292 ): Promise<SetBackupEnabledResponse> { 1293 return xrpc("_backup.setEnabled", { 1294 method: "POST", 1295 token, 1296 body: { enabled }, 1297 }); 1298 }, 1299 1300 async importRepo(token: AccessToken, car: Uint8Array): Promise<void> { 1301 const res = await authenticatedFetch( 1302 `${API_BASE}/com.atproto.repo.importRepo`, 1303 { 1304 method: "POST", 1305 token, 1306 headers: { "Content-Type": "application/vnd.ipld.car" }, 1307 body: car as unknown as BodyInit, 1308 }, 1309 ); 1310 if (!res.ok) { 1311 const errData = await res.json().catch(() => ({ 1312 error: "Unknown", 1313 message: res.statusText, 1314 })); 1315 throw new ApiError(res.status, errData.error, errData.message); 1316 } 1317 }, 1318 1319 async establishOAuthSession( 1320 token: AccessToken, 1321 ): Promise<{ success: boolean; device_id: string }> { 1322 const res = await authenticatedFetch("/oauth/establish-session", { 1323 method: "POST", 1324 token, 1325 headers: { "Content-Type": "application/json" }, 1326 }); 1327 if (!res.ok) { 1328 const errData = await res.json().catch(() => ({ 1329 error: "Unknown", 1330 message: res.statusText, 1331 })); 1332 throw new ApiError(res.status, errData.error, errData.message); 1333 } 1334 return res.json(); 1335 }, 1336 1337 async getSsoLinkedAccounts( 1338 token: AccessToken, 1339 ): Promise<{ accounts: SsoLinkedAccount[] }> { 1340 const res = await authenticatedFetch("/oauth/sso/linked", { token }); 1341 if (!res.ok) { 1342 const errData = await res.json().catch(() => ({ 1343 error: "Unknown", 1344 message: res.statusText, 1345 })); 1346 throw new ApiError(res.status, errData.error, errData.message); 1347 } 1348 return res.json(); 1349 }, 1350 1351 listDelegationControllers( 1352 token: AccessToken, 1353 ): Promise<Result<{ controllers: DelegationController[] }, ApiError>> { 1354 return xrpcResult("_delegation.listControllers", { token }); 1355 }, 1356 1357 listDelegationControlledAccounts( 1358 token: AccessToken, 1359 ): Promise<Result<{ accounts: DelegationControlledAccount[] }, ApiError>> { 1360 return xrpcResult("_delegation.listControlledAccounts", { token }); 1361 }, 1362 1363 getDelegationScopePresets(): Promise< 1364 Result<{ presets: DelegationScopePreset[] }, ApiError> 1365 > { 1366 return xrpcResult("_delegation.getScopePresets"); 1367 }, 1368 1369 addDelegationController( 1370 token: AccessToken, 1371 controllerDid: Did, 1372 grantedScopes: ScopeSet, 1373 ): Promise<Result<{ success: boolean }, ApiError>> { 1374 return xrpcResult("_delegation.addController", { 1375 method: "POST", 1376 token, 1377 body: { controller_did: controllerDid, granted_scopes: grantedScopes }, 1378 }); 1379 }, 1380 1381 removeDelegationController( 1382 token: AccessToken, 1383 controllerDid: Did, 1384 ): Promise<Result<{ success: boolean }, ApiError>> { 1385 return xrpcResult("_delegation.removeController", { 1386 method: "POST", 1387 token, 1388 body: { controller_did: controllerDid }, 1389 }); 1390 }, 1391 1392 createDelegatedAccount( 1393 token: AccessToken, 1394 handle: Handle, 1395 email?: EmailAddress, 1396 controllerScopes?: ScopeSet, 1397 ): Promise<Result<{ did: Did; handle: Handle }, ApiError>> { 1398 return xrpcResult("_delegation.createDelegatedAccount", { 1399 method: "POST", 1400 token, 1401 body: { handle, email, controllerScopes }, 1402 }); 1403 }, 1404 1405 getDelegationAuditLog( 1406 token: AccessToken, 1407 limit: number, 1408 offset: number, 1409 ): Promise< 1410 Result<{ entries: DelegationAuditEntry[]; total: number }, ApiError> 1411 > { 1412 return xrpcResult("_delegation.getAuditLog", { 1413 token, 1414 params: { limit: String(limit), offset: String(offset) }, 1415 }); 1416 }, 1417 1418 async exportBlobs(token: AccessToken): Promise<Blob> { 1419 const res = await authenticatedFetch(`${API_BASE}/_backup.exportBlobs`, { 1420 token, 1421 }); 1422 if (!res.ok) { 1423 const errData = await res.json().catch(() => ({ 1424 error: "Unknown", 1425 message: res.statusText, 1426 })); 1427 throw new ApiError(res.status, errData.error, errData.message); 1428 } 1429 return res.blob(); 1430 }, 1431}; 1432 1433export const typedApi = { 1434 createSession( 1435 identifier: string, 1436 password: string, 1437 ): Promise<Result<Session, ApiError>> { 1438 return xrpcResult<Session>("com.atproto.server.createSession", { 1439 method: "POST", 1440 body: { identifier, password }, 1441 }).then((r) => r.ok ? ok(castSession(r.value)) : r); 1442 }, 1443 1444 getSession(token: AccessToken): Promise<Result<Session, ApiError>> { 1445 return xrpcResult<Session>("com.atproto.server.getSession", { token }) 1446 .then((r) => r.ok ? ok(castSession(r.value)) : r); 1447 }, 1448 1449 refreshSession(refreshJwt: RefreshToken): Promise<Result<Session, ApiError>> { 1450 return xrpcResult<Session>("com.atproto.server.refreshSession", { 1451 method: "POST", 1452 token: refreshJwt, 1453 }).then((r) => r.ok ? ok(castSession(r.value)) : r); 1454 }, 1455 1456 describeServer(): Promise<Result<ServerDescription, ApiError>> { 1457 return xrpcResult("com.atproto.server.describeServer"); 1458 }, 1459 1460 listAppPasswords( 1461 token: AccessToken, 1462 ): Promise<Result<{ passwords: AppPassword[] }, ApiError>> { 1463 return xrpcResult("com.atproto.server.listAppPasswords", { token }); 1464 }, 1465 1466 createAppPassword( 1467 token: AccessToken, 1468 name: string, 1469 scopes?: string, 1470 ): Promise<Result<CreatedAppPassword, ApiError>> { 1471 return xrpcResult("com.atproto.server.createAppPassword", { 1472 method: "POST", 1473 token, 1474 body: { name, scopes }, 1475 }); 1476 }, 1477 1478 revokeAppPassword( 1479 token: AccessToken, 1480 name: string, 1481 ): Promise<Result<void, ApiError>> { 1482 return xrpcResult<void>("com.atproto.server.revokeAppPassword", { 1483 method: "POST", 1484 token, 1485 body: { name }, 1486 }); 1487 }, 1488 1489 listSessions( 1490 token: AccessToken, 1491 ): Promise<Result<ListSessionsResponse, ApiError>> { 1492 return xrpcResult("_account.listSessions", { token }); 1493 }, 1494 1495 revokeSession( 1496 token: AccessToken, 1497 sessionId: string, 1498 ): Promise<Result<void, ApiError>> { 1499 return xrpcResult<void>("_account.revokeSession", { 1500 method: "POST", 1501 token, 1502 body: { sessionId }, 1503 }); 1504 }, 1505 1506 getTotpStatus(token: AccessToken): Promise<Result<TotpStatus, ApiError>> { 1507 return xrpcResult("com.atproto.server.getTotpStatus", { token }); 1508 }, 1509 1510 createTotpSecret(token: AccessToken): Promise<Result<TotpSecret, ApiError>> { 1511 return xrpcResult("com.atproto.server.createTotpSecret", { 1512 method: "POST", 1513 token, 1514 }); 1515 }, 1516 1517 enableTotp( 1518 token: AccessToken, 1519 code: string, 1520 ): Promise<Result<EnableTotpResponse, ApiError>> { 1521 return xrpcResult("com.atproto.server.enableTotp", { 1522 method: "POST", 1523 token, 1524 body: { code }, 1525 }); 1526 }, 1527 1528 disableTotp( 1529 token: AccessToken, 1530 password: string, 1531 code: string, 1532 ): Promise<Result<SuccessResponse, ApiError>> { 1533 return xrpcResult("com.atproto.server.disableTotp", { 1534 method: "POST", 1535 token, 1536 body: { password, code }, 1537 }); 1538 }, 1539 1540 listPasskeys( 1541 token: AccessToken, 1542 ): Promise<Result<ListPasskeysResponse, ApiError>> { 1543 return xrpcResult("com.atproto.server.listPasskeys", { token }); 1544 }, 1545 1546 deletePasskey( 1547 token: AccessToken, 1548 id: string, 1549 ): Promise<Result<void, ApiError>> { 1550 return xrpcResult<void>("com.atproto.server.deletePasskey", { 1551 method: "POST", 1552 token, 1553 body: { id }, 1554 }); 1555 }, 1556 1557 listTrustedDevices( 1558 token: AccessToken, 1559 ): Promise<Result<ListTrustedDevicesResponse, ApiError>> { 1560 return xrpcResult("_account.listTrustedDevices", { token }); 1561 }, 1562 1563 getReauthStatus(token: AccessToken): Promise<Result<ReauthStatus, ApiError>> { 1564 return xrpcResult("_account.getReauthStatus", { token }); 1565 }, 1566 1567 getNotificationPrefs( 1568 token: AccessToken, 1569 ): Promise<Result<NotificationPrefs, ApiError>> { 1570 return xrpcResult("_account.getNotificationPrefs", { token }); 1571 }, 1572 1573 updateHandle( 1574 token: AccessToken, 1575 handle: Handle, 1576 ): Promise<Result<void, ApiError>> { 1577 return xrpcResult<void>("com.atproto.identity.updateHandle", { 1578 method: "POST", 1579 token, 1580 body: { handle }, 1581 }); 1582 }, 1583 1584 describeRepo( 1585 token: AccessToken, 1586 repo: Did, 1587 ): Promise<Result<RepoDescription, ApiError>> { 1588 return xrpcResult("com.atproto.repo.describeRepo", { 1589 token, 1590 params: { repo }, 1591 }); 1592 }, 1593 1594 listRecords( 1595 token: AccessToken, 1596 repo: Did, 1597 collection: Nsid, 1598 options?: { limit?: number; cursor?: string; reverse?: boolean }, 1599 ): Promise<Result<ListRecordsResponse, ApiError>> { 1600 const params: Record<string, string> = { repo, collection }; 1601 if (options?.limit) params.limit = String(options.limit); 1602 if (options?.cursor) params.cursor = options.cursor; 1603 if (options?.reverse) params.reverse = "true"; 1604 return xrpcResult("com.atproto.repo.listRecords", { token, params }); 1605 }, 1606 1607 getRecord( 1608 token: AccessToken, 1609 repo: Did, 1610 collection: Nsid, 1611 rkey: Rkey, 1612 ): Promise<Result<RecordResponse, ApiError>> { 1613 return xrpcResult("com.atproto.repo.getRecord", { 1614 token, 1615 params: { repo, collection, rkey }, 1616 }); 1617 }, 1618 1619 deleteRecord( 1620 token: AccessToken, 1621 repo: Did, 1622 collection: Nsid, 1623 rkey: Rkey, 1624 ): Promise<Result<void, ApiError>> { 1625 return xrpcResult<void>("com.atproto.repo.deleteRecord", { 1626 method: "POST", 1627 token, 1628 body: { repo, collection, rkey }, 1629 }); 1630 }, 1631 1632 searchAccounts( 1633 token: AccessToken, 1634 options?: { handle?: string; cursor?: string; limit?: number }, 1635 ): Promise<Result<SearchAccountsResponse, ApiError>> { 1636 const params: Record<string, string> = {}; 1637 if (options?.handle) params.handle = options.handle; 1638 if (options?.cursor) params.cursor = options.cursor; 1639 if (options?.limit) params.limit = String(options.limit); 1640 return xrpcResult("com.atproto.admin.searchAccounts", { token, params }); 1641 }, 1642 1643 getAccountInfo( 1644 token: AccessToken, 1645 did: Did, 1646 ): Promise<Result<AccountInfo, ApiError>> { 1647 return xrpcResult("com.atproto.admin.getAccountInfo", { 1648 token, 1649 params: { did }, 1650 }); 1651 }, 1652 1653 getServerStats(token: AccessToken): Promise<Result<ServerStats, ApiError>> { 1654 return xrpcResult("_admin.getServerStats", { token }); 1655 }, 1656 1657 listBackups( 1658 token: AccessToken, 1659 ): Promise<Result<ListBackupsResponse, ApiError>> { 1660 return xrpcResult("_backup.listBackups", { token }); 1661 }, 1662 1663 createBackup( 1664 token: AccessToken, 1665 ): Promise<Result<CreateBackupResponse, ApiError>> { 1666 return xrpcResult("_backup.createBackup", { 1667 method: "POST", 1668 token, 1669 }); 1670 }, 1671 1672 getDidDocument(token: AccessToken): Promise<Result<DidDocument, ApiError>> { 1673 return xrpcResult("_account.getDidDocument", { token }); 1674 }, 1675 1676 deleteSession(token: AccessToken): Promise<Result<void, ApiError>> { 1677 return xrpcResult<void>("com.atproto.server.deleteSession", { 1678 method: "POST", 1679 token, 1680 }); 1681 }, 1682 1683 revokeAllSessions( 1684 token: AccessToken, 1685 ): Promise<Result<{ revokedCount: number }, ApiError>> { 1686 return xrpcResult("_account.revokeAllSessions", { 1687 method: "POST", 1688 token, 1689 }); 1690 }, 1691 1692 getAccountInviteCodes( 1693 token: AccessToken, 1694 ): Promise<Result<{ codes: InviteCodeInfo[] }, ApiError>> { 1695 return xrpcResult("com.atproto.server.getAccountInviteCodes", { token }); 1696 }, 1697 1698 createInviteCode( 1699 token: AccessToken, 1700 useCount: number = 1, 1701 ): Promise<Result<{ code: string }, ApiError>> { 1702 return xrpcResult("com.atproto.server.createInviteCode", { 1703 method: "POST", 1704 token, 1705 body: { useCount }, 1706 }); 1707 }, 1708 1709 changePassword( 1710 token: AccessToken, 1711 currentPassword: string, 1712 newPassword: string, 1713 ): Promise<Result<void, ApiError>> { 1714 return xrpcResult<void>("_account.changePassword", { 1715 method: "POST", 1716 token, 1717 body: { currentPassword, newPassword }, 1718 }); 1719 }, 1720 1721 getPasswordStatus( 1722 token: AccessToken, 1723 ): Promise<Result<PasswordStatus, ApiError>> { 1724 return xrpcResult("_account.getPasswordStatus", { token }); 1725 }, 1726 1727 getServerConfig(): Promise<Result<ServerConfig, ApiError>> { 1728 return xrpcResult("_server.getConfig"); 1729 }, 1730 1731 getLegacyLoginPreference( 1732 token: AccessToken, 1733 ): Promise<Result<LegacyLoginPreference, ApiError>> { 1734 return xrpcResult("_account.getLegacyLoginPreference", { token }); 1735 }, 1736 1737 updateLegacyLoginPreference( 1738 token: AccessToken, 1739 allowLegacyLogin: boolean, 1740 ): Promise<Result<UpdateLegacyLoginResponse, ApiError>> { 1741 return xrpcResult("_account.updateLegacyLoginPreference", { 1742 method: "POST", 1743 token, 1744 body: { allowLegacyLogin }, 1745 }); 1746 }, 1747 1748 getNotificationHistory( 1749 token: AccessToken, 1750 ): Promise<Result<NotificationHistoryResponse, ApiError>> { 1751 return xrpcResult("_account.getNotificationHistory", { token }); 1752 }, 1753 1754 updateNotificationPrefs( 1755 token: AccessToken, 1756 prefs: { 1757 preferredChannel?: string; 1758 discordId?: string; 1759 telegramUsername?: string; 1760 signalNumber?: string; 1761 }, 1762 ): Promise<Result<SuccessResponse, ApiError>> { 1763 return xrpcResult("_account.updateNotificationPrefs", { 1764 method: "POST", 1765 token, 1766 body: prefs, 1767 }); 1768 }, 1769 1770 revokeTrustedDevice( 1771 token: AccessToken, 1772 deviceId: string, 1773 ): Promise<Result<SuccessResponse, ApiError>> { 1774 return xrpcResult("_account.revokeTrustedDevice", { 1775 method: "POST", 1776 token, 1777 body: { deviceId }, 1778 }); 1779 }, 1780 1781 updateTrustedDevice( 1782 token: AccessToken, 1783 deviceId: string, 1784 friendlyName: string, 1785 ): Promise<Result<SuccessResponse, ApiError>> { 1786 return xrpcResult("_account.updateTrustedDevice", { 1787 method: "POST", 1788 token, 1789 body: { deviceId, friendlyName }, 1790 }); 1791 }, 1792 1793 reauthPassword( 1794 token: AccessToken, 1795 password: string, 1796 ): Promise<Result<ReauthResponse, ApiError>> { 1797 return xrpcResult("_account.reauthPassword", { 1798 method: "POST", 1799 token, 1800 body: { password }, 1801 }); 1802 }, 1803 1804 reauthTotp( 1805 token: AccessToken, 1806 code: string, 1807 ): Promise<Result<ReauthResponse, ApiError>> { 1808 return xrpcResult("_account.reauthTotp", { 1809 method: "POST", 1810 token, 1811 body: { code }, 1812 }); 1813 }, 1814 1815 reauthPasskeyStart( 1816 token: AccessToken, 1817 ): Promise<Result<ReauthPasskeyStartResponse, ApiError>> { 1818 return xrpcResult("_account.reauthPasskeyStart", { 1819 method: "POST", 1820 token, 1821 }); 1822 }, 1823 1824 reauthPasskeyFinish( 1825 token: AccessToken, 1826 credential: unknown, 1827 ): Promise<Result<ReauthResponse, ApiError>> { 1828 return xrpcResult("_account.reauthPasskeyFinish", { 1829 method: "POST", 1830 token, 1831 body: { credential }, 1832 }); 1833 }, 1834 1835 confirmSignup( 1836 did: Did, 1837 verificationCode: string, 1838 ): Promise<Result<ConfirmSignupResult, ApiError>> { 1839 return xrpcResult("com.atproto.server.confirmSignup", { 1840 method: "POST", 1841 body: { did, verificationCode }, 1842 }); 1843 }, 1844 1845 resendVerification( 1846 did: Did, 1847 ): Promise<Result<{ success: boolean }, ApiError>> { 1848 return xrpcResult("com.atproto.server.resendVerification", { 1849 method: "POST", 1850 body: { did }, 1851 }); 1852 }, 1853 1854 requestEmailUpdate( 1855 token: AccessToken, 1856 ): Promise<Result<EmailUpdateResponse, ApiError>> { 1857 return xrpcResult("com.atproto.server.requestEmailUpdate", { 1858 method: "POST", 1859 token, 1860 }); 1861 }, 1862 1863 updateEmail( 1864 token: AccessToken, 1865 email: string, 1866 emailToken?: string, 1867 ): Promise<Result<void, ApiError>> { 1868 return xrpcResult<void>("com.atproto.server.updateEmail", { 1869 method: "POST", 1870 token, 1871 body: { email, token: emailToken }, 1872 }); 1873 }, 1874 1875 requestAccountDelete(token: AccessToken): Promise<Result<void, ApiError>> { 1876 return xrpcResult<void>("com.atproto.server.requestAccountDelete", { 1877 method: "POST", 1878 token, 1879 }); 1880 }, 1881 1882 deleteAccount( 1883 did: Did, 1884 password: string, 1885 deleteToken: string, 1886 ): Promise<Result<void, ApiError>> { 1887 return xrpcResult<void>("com.atproto.server.deleteAccount", { 1888 method: "POST", 1889 body: { did, password, token: deleteToken }, 1890 }); 1891 }, 1892 1893 updateDidDocument( 1894 token: AccessToken, 1895 params: { 1896 verificationMethods?: VerificationMethod[]; 1897 alsoKnownAs?: string[]; 1898 serviceEndpoint?: string; 1899 }, 1900 ): Promise<Result<SuccessResponse, ApiError>> { 1901 return xrpcResult("_account.updateDidDocument", { 1902 method: "POST", 1903 token, 1904 body: params, 1905 }); 1906 }, 1907 1908 deactivateAccount( 1909 token: AccessToken, 1910 deleteAfter?: string, 1911 ): Promise<Result<void, ApiError>> { 1912 return xrpcResult<void>("com.atproto.server.deactivateAccount", { 1913 method: "POST", 1914 token, 1915 body: { deleteAfter }, 1916 }); 1917 }, 1918 1919 activateAccount(token: AccessToken): Promise<Result<void, ApiError>> { 1920 return xrpcResult<void>("com.atproto.server.activateAccount", { 1921 method: "POST", 1922 token, 1923 }); 1924 }, 1925 1926 setBackupEnabled( 1927 token: AccessToken, 1928 enabled: boolean, 1929 ): Promise<Result<SetBackupEnabledResponse, ApiError>> { 1930 return xrpcResult("_backup.setEnabled", { 1931 method: "POST", 1932 token, 1933 body: { enabled }, 1934 }); 1935 }, 1936 1937 deleteBackup( 1938 token: AccessToken, 1939 id: string, 1940 ): Promise<Result<void, ApiError>> { 1941 return xrpcResult<void>("_backup.deleteBackup", { 1942 method: "POST", 1943 token, 1944 params: { id }, 1945 }); 1946 }, 1947 1948 createRecord( 1949 token: AccessToken, 1950 repo: Did, 1951 collection: Nsid, 1952 record: unknown, 1953 rkey?: Rkey, 1954 ): Promise<Result<CreateRecordResponse, ApiError>> { 1955 return xrpcResult("com.atproto.repo.createRecord", { 1956 method: "POST", 1957 token, 1958 body: { repo, collection, record, rkey }, 1959 }); 1960 }, 1961 1962 putRecord( 1963 token: AccessToken, 1964 repo: Did, 1965 collection: Nsid, 1966 rkey: Rkey, 1967 record: unknown, 1968 ): Promise<Result<CreateRecordResponse, ApiError>> { 1969 return xrpcResult("com.atproto.repo.putRecord", { 1970 method: "POST", 1971 token, 1972 body: { repo, collection, rkey, record }, 1973 }); 1974 }, 1975 1976 getInviteCodes( 1977 token: AccessToken, 1978 options?: { sort?: "recent" | "usage"; cursor?: string; limit?: number }, 1979 ): Promise<Result<GetInviteCodesResponse, ApiError>> { 1980 const params: Record<string, string> = {}; 1981 if (options?.sort) params.sort = options.sort; 1982 if (options?.cursor) params.cursor = options.cursor; 1983 if (options?.limit) params.limit = String(options.limit); 1984 return xrpcResult("com.atproto.admin.getInviteCodes", { token, params }); 1985 }, 1986 1987 disableAccountInvites( 1988 token: AccessToken, 1989 account: Did, 1990 ): Promise<Result<void, ApiError>> { 1991 return xrpcResult<void>("com.atproto.admin.disableAccountInvites", { 1992 method: "POST", 1993 token, 1994 body: { account }, 1995 }); 1996 }, 1997 1998 enableAccountInvites( 1999 token: AccessToken, 2000 account: Did, 2001 ): Promise<Result<void, ApiError>> { 2002 return xrpcResult<void>("com.atproto.admin.enableAccountInvites", { 2003 method: "POST", 2004 token, 2005 body: { account }, 2006 }); 2007 }, 2008 2009 adminDeleteAccount( 2010 token: AccessToken, 2011 did: Did, 2012 ): Promise<Result<void, ApiError>> { 2013 return xrpcResult<void>("com.atproto.admin.deleteAccount", { 2014 method: "POST", 2015 token, 2016 body: { did }, 2017 }); 2018 }, 2019 2020 startPasskeyRegistration( 2021 token: AccessToken, 2022 friendlyName?: string, 2023 ): Promise<Result<StartPasskeyRegistrationResponse, ApiError>> { 2024 return xrpcResult("com.atproto.server.startPasskeyRegistration", { 2025 method: "POST", 2026 token, 2027 body: { friendlyName }, 2028 }); 2029 }, 2030 2031 finishPasskeyRegistration( 2032 token: AccessToken, 2033 credential: unknown, 2034 friendlyName?: string, 2035 ): Promise<Result<FinishPasskeyRegistrationResponse, ApiError>> { 2036 return xrpcResult("com.atproto.server.finishPasskeyRegistration", { 2037 method: "POST", 2038 token, 2039 body: { credential, friendlyName }, 2040 }); 2041 }, 2042 2043 updatePasskey( 2044 token: AccessToken, 2045 id: string, 2046 friendlyName: string, 2047 ): Promise<Result<void, ApiError>> { 2048 return xrpcResult<void>("com.atproto.server.updatePasskey", { 2049 method: "POST", 2050 token, 2051 body: { id, friendlyName }, 2052 }); 2053 }, 2054 2055 regenerateBackupCodes( 2056 token: AccessToken, 2057 password: string, 2058 code: string, 2059 ): Promise<Result<RegenerateBackupCodesResponse, ApiError>> { 2060 return xrpcResult("com.atproto.server.regenerateBackupCodes", { 2061 method: "POST", 2062 token, 2063 body: { password, code }, 2064 }); 2065 }, 2066 2067 updateLocale( 2068 token: AccessToken, 2069 preferredLocale: string, 2070 ): Promise<Result<UpdateLocaleResponse, ApiError>> { 2071 return xrpcResult("_account.updateLocale", { 2072 method: "POST", 2073 token, 2074 body: { preferredLocale }, 2075 }); 2076 }, 2077 2078 confirmChannelVerification( 2079 token: AccessToken, 2080 channel: string, 2081 identifier: string, 2082 code: string, 2083 ): Promise<Result<SuccessResponse, ApiError>> { 2084 return xrpcResult("_account.confirmChannelVerification", { 2085 method: "POST", 2086 token, 2087 body: { channel, identifier, code }, 2088 }); 2089 }, 2090 2091 removePassword( 2092 token: AccessToken, 2093 ): Promise<Result<SuccessResponse, ApiError>> { 2094 return xrpcResult("_account.removePassword", { 2095 method: "POST", 2096 token, 2097 }); 2098 }, 2099};