diff --git a/.changeset/giant-starfishes-fry.md b/.changeset/giant-starfishes-fry.md new file mode 100644 index 00000000000..8f20fdb00cc --- /dev/null +++ b/.changeset/giant-starfishes-fry.md @@ -0,0 +1,5 @@ +--- +"@atproto/xrpc-server": patch +--- + +Expose the request context for AuthVerifier and StreamAuthVerifier as distinct types diff --git a/.changeset/happy-eggs-swim.md b/.changeset/happy-eggs-swim.md new file mode 100644 index 00000000000..e7da6125c16 --- /dev/null +++ b/.changeset/happy-eggs-swim.md @@ -0,0 +1,5 @@ +--- +"@atproto/pds": patch +--- + +Properly authenticate OAuth requests in catch all handler. diff --git a/.changeset/hot-cows-scream.md b/.changeset/hot-cows-scream.md new file mode 100644 index 00000000000..3460ae59b59 --- /dev/null +++ b/.changeset/hot-cows-scream.md @@ -0,0 +1,5 @@ +--- +"@atproto/oauth-client": patch +--- + +Add CustomEvent ponyfill for enviroments that don't provide it diff --git a/.changeset/smart-ways-trade.md b/.changeset/smart-ways-trade.md deleted file mode 100644 index 086f0293451..00000000000 --- a/.changeset/smart-ways-trade.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@atproto/ozone": patch -"@atproto/bsky": patch -"@atproto/api": patch -"@atproto/pds": patch ---- - -Remove `app.bsky.feed.detach` record, to be replaced by `app.bsky.feed.postgate` record in a future release. diff --git a/.github/workflows/repo.yaml b/.github/workflows/repo.yaml index 39c8fa818a2..0f5859551a7 100644 --- a/.github/workflows/repo.yaml +++ b/.github/workflows/repo.yaml @@ -16,6 +16,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 + name: Install pnpm with: version: 8 run_install: false @@ -42,6 +43,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 + name: Install pnpm with: version: 8 run_install: false @@ -62,6 +64,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 + name: Install pnpm with: version: 8 run_install: false diff --git a/.prettierignore b/.prettierignore index 7bb137f13ea..16e700de3db 100644 --- a/.prettierignore +++ b/.prettierignore @@ -8,4 +8,5 @@ pnpm-lock.yaml .pnpm* .changeset *.d.ts -packages/bsky/src/data-plane/gen \ No newline at end of file +packages/bsky/src/data-plane/gen +CHANGELOG.md diff --git a/Makefile b/Makefile index c2779fb19c2..ee6338e8cf2 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ run-dev-env: ## Run a "development environment" shell .PHONY: run-dev-env-logged run-dev-env-logged: ## Run a "development environment" shell (with logging) - LOG_ENABLED=true cd packages/dev-env; NODE_ENV=development pnpm run start | pnpm exec pino-pretty + cd packages/dev-env; LOG_ENABLED=true NODE_ENV=development pnpm run start | pnpm exec pino-pretty .PHONY: codegen codegen: ## Re-generate packages from lexicon/ files diff --git a/lexicons/app/bsky/feed/defs.json b/lexicons/app/bsky/feed/defs.json index 2189be98de7..3a9cd77ceb3 100644 --- a/lexicons/app/bsky/feed/defs.json +++ b/lexicons/app/bsky/feed/defs.json @@ -219,7 +219,7 @@ }, "feedContext": { "type": "string", - "description": "Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton.", + "description": "Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton.", "maxLength": 2000 } } diff --git a/lexicons/com/atproto/label/defs.json b/lexicons/com/atproto/label/defs.json index 9b1a1196e01..6f4c1ab9dd0 100644 --- a/lexicons/com/atproto/label/defs.json +++ b/lexicons/com/atproto/label/defs.json @@ -77,7 +77,7 @@ }, "labelValueDefinition": { "type": "object", - "description": "Declares a label value and its expected interpertations and behaviors.", + "description": "Declares a label value and its expected interpretations and behaviors.", "required": ["identifier", "severity", "blurs", "locales"], "properties": { "identifier": { diff --git a/lexicons/com/atproto/server/getServiceAuth.json b/lexicons/com/atproto/server/getServiceAuth.json index 95984c186ad..37c150d79e3 100644 --- a/lexicons/com/atproto/server/getServiceAuth.json +++ b/lexicons/com/atproto/server/getServiceAuth.json @@ -13,6 +13,15 @@ "type": "string", "format": "did", "description": "The DID of the service that the token will be used to authenticate with" + }, + "exp": { + "type": "integer", + "description": "The time in Unix Epoch seconds that the JWT expires. Defaults to 60 seconds in the future. The service may enforce certain time bounds on tokens depending on the requested scope." + }, + "lxm": { + "type": "string", + "format": "nsid", + "description": "Lexicon (XRPC) method to bind the requested token to" } } }, @@ -27,7 +36,13 @@ } } } - } + }, + "errors": [ + { + "name": "BadExpiration", + "description": "Indicates that the requested expiration date is not a valid. May be in the past or may be reliant on the requested scopes." + } + ] } } } diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index 5224c45d4e4..aaa2e2aa1a0 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/CHANGELOG.md @@ -1,5 +1,595 @@ # @atproto/api +## 0.13.1 + +### Patch Changes + +- [#2708](https://github.com/bluesky-social/atproto/pull/2708) [`22af354a5`](https://github.com/bluesky-social/atproto/commit/22af354a5db595d7cbc0e65f02601de3565337e1) Thanks [@devinivy](https://github.com/devinivy)! - Export AtpAgentOptions type to better support extending AtpAgent. + +## 0.13.0 + +### Minor Changes + +- [#2483](https://github.com/bluesky-social/atproto/pull/2483) [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! + + #### Motivation + + The motivation for these changes is the need to make the `@atproto/api` package + compatible with OAuth session management. We don't have OAuth client support + "launched" and documented quite yet, so you can keep using the current app + password authentication system. When we do "launch" OAuth support and begin + encouraging its usage in the near future (see the [OAuth + Roadmap](https://github.com/bluesky-social/atproto/discussions/2656)), these + changes will make it easier to migrate. + + In addition, the redesigned session management system fixes a bug that could + cause the session data to become invalid when Agent clones are created (e.g. + using `agent.withProxy()`). + + #### New Features + + We've restructured the `XrpcClient` HTTP fetch handler to be specified during + the instantiation of the XRPC client, through the constructor, instead of using + a default implementation (which was statically defined). + + With this refactor, the XRPC client is now more modular and reusable. Session + management, retries, cryptographic signing, and other request-specific logic can + be implemented in the fetch handler itself rather than by the calling code. + + A new abstract class named `Agent`, has been added to `@atproto/api`. This class + will be the base class for all Bluesky agents classes in the `@atproto` + ecosystem. It is meant to be extended by implementations that provide session + management and fetch handling. + + As you adapt your code to these changes, make sure to use the `Agent` type + wherever you expect to receive an agent, and use the `AtpAgent` type (class) + only to instantiate your client. The reason for this is to be forward compatible + with the OAuth agent implementation that will also extend `Agent`, and not + `AtpAgent`. + + ```ts + import { Agent, AtpAgent } from "@atproto/api"; + + async function setupAgent( + service: string, + username: string, + password: string, + ): Promise { + const agent = new AtpAgent({ + service, + persistSession: (evt, session) => { + // handle session update + }, + }); + + await agent.login(username, password); + + return agent; + } + ``` + + ```ts + import { Agent } from "@atproto/api"; + + async function doStuffWithAgent(agent: Agent, arg: string) { + return agent.resolveHandle(arg); + } + ``` + + ```ts + import { Agent, AtpAgent } from "@atproto/api"; + + class MyClass { + agent: Agent; + + constructor() { + this.agent = new AtpAgent(); + } + } + ``` + + #### Breaking changes + + Most of the changes introduced in this version are backward-compatible. However, + there are a couple of breaking changes you should be aware of: + + - Customizing `fetch`: The ability to customize the `fetch: FetchHandler` + property of `@atproto/xrpc`'s `Client` and `@atproto/api`'s `AtpAgent` classes + has been removed. Previously, the `fetch` property could be set to a function + that would be used as the fetch handler for that instance, and was initialized + to a default fetch handler. That property is still accessible in a read-only + fashion through the `fetchHandler` property and can only be set during the + instance creation. Attempting to set/get the `fetch` property will now result + in an error. + - The `fetch()` method, as well as WhatWG compliant `Request` and `Headers` + constructors, must be globally available in your environment. Use a polyfill + if necessary. + - The `AtpBaseClient` has been removed. The `AtpServiceClient` has been renamed + `AtpBaseClient`. Any code using either of these classes will need to be + updated. + - Instead of _wrapping_ an `XrpcClient` in its `xrpc` property, the + `AtpBaseClient` (formerly `AtpServiceClient`) class - created through + `lex-cli` - now _extends_ the `XrpcClient` class. This means that a client + instance now passes the `instanceof XrpcClient` check. The `xrpc` property now + returns the instance itself and has been deprecated. + - `setSessionPersistHandler` is no longer available on the `AtpAgent` or + `BskyAgent` classes. The session handler can only be set though the + `persistSession` options of the `AtpAgent` constructor. + - The new class hierarchy is as follows: + - `BskyAgent` extends `AtpAgent`: but add no functionality (hence its + deprecation). + - `AtpAgent` extends `Agent`: adds password based session management. + - `Agent` extends `AtpBaseClient`: this abstract class that adds syntactic sugar + methods `app.bsky` lexicons. It also adds abstract session management + methods and adds atproto specific utilities + (`labelers` & `proxy` headers, cloning capability) + - `AtpBaseClient` extends `XrpcClient`: automatically code that adds fully + typed lexicon defined namespaces (`instance.app.bsky.feed.getPosts()`) to + the `XrpcClient`. + - `XrpcClient` is the base class. + + #### Non-breaking changes + + - The `com.*` and `app.*` namespaces have been made directly available to every + `Agent` instances. + + #### Deprecations + + - The default export of the `@atproto/xrpc` package has been deprecated. Use + named exports instead. + - The `Client` and `ServiceClient` classes are now deprecated. They are replaced by a single `XrpcClient` class. + - The default export of the `@atproto/api` package has been deprecated. Use + named exports instead. + - The `BskyAgent` has been deprecated. Use the `AtpAgent` class instead. + - The `xrpc` property of the `AtpClient` instances has been deprecated. The + instance itself should be used as the XRPC client. + - The `api` property of the `AtpAgent` and `BskyAgent` instances has been + deprecated. Use the instance itself instead. + + #### Migration + + ##### The `@atproto/api` package + + If you were relying on the `AtpBaseClient` solely to perform validation, use + this: + + + + + + + + + +
Before
After
+ + ```ts + import { AtpBaseClient, ComAtprotoSyncSubscribeRepos } from "@atproto/api"; + + const baseClient = new AtpBaseClient(); + + baseClient.xrpc.lex.assertValidXrpcMessage("io.example.doStuff", { + // ... + }); + ``` + + + + ```ts + import { lexicons } from "@atproto/api"; + + lexicons.assertValidXrpcMessage("io.example.doStuff", { + // ... + }); + ``` + +
+ + If you are extending the `BskyAgent` to perform custom `session` manipulation, define your own `Agent` subclass instead: + + + + + + + + + +
Before
After
+ + ```ts + import { BskyAgent } from "@atproto/api"; + + class MyAgent extends BskyAgent { + private accessToken?: string; + + async createOrRefreshSession(identifier: string, password: string) { + // custom logic here + + this.accessToken = "my-access-jwt"; + } + + async doStuff() { + return this.call("io.example.doStuff", { + headers: { + Authorization: this.accessToken && `Bearer ${this.accessToken}`, + }, + }); + } + } + ``` + + + + ```ts + import { Agent } from "@atproto/api"; + + class MyAgent extends Agent { + private accessToken?: string; + public did?: string; + + constructor(private readonly service: string | URL) { + super({ + service, + headers: { + Authorization: () => + this.accessToken ? `Bearer ${this.accessToken}` : null, + }, + }); + } + + clone(): MyAgent { + const agent = new MyAgent(this.service); + agent.accessToken = this.accessToken; + agent.did = this.did; + return this.copyInto(agent); + } + + async createOrRefreshSession(identifier: string, password: string) { + // custom logic here + + this.did = "did:example:123"; + this.accessToken = "my-access-jwt"; + } + } + ``` + +
+ + If you are monkey patching the `xrpc` service client to perform client-side rate limiting, you can now do this in the `FetchHandler` function: + + + + + + + + + +
Before
After
+ + ```ts + import { BskyAgent } from "@atproto/api"; + import { RateLimitThreshold } from "rate-limit-threshold"; + + const agent = new BskyAgent(); + const limiter = new RateLimitThreshold(3000, 300_000); + + const origCall = agent.api.xrpc.call; + agent.api.xrpc.call = async function (...args) { + await limiter.wait(); + return origCall.call(this, ...args); + }; + ``` + + + + ```ts + import { AtpAgent } from "@atproto/api"; + import { RateLimitThreshold } from "rate-limit-threshold"; + + class LimitedAtpAgent extends AtpAgent { + constructor(options: AtpAgentOptions) { + const fetch: typeof globalThis.fetch = options.fetch ?? globalThis.fetch; + const limiter = new RateLimitThreshold(3000, 300_000); + + super({ + ...options, + fetch: async (...args) => { + await limiter.wait(); + return fetch(...args); + }, + }); + } + } + ``` + +
+ + If you configure a static `fetch` handler on the `BskyAgent` class - for example + to modify the headers of every request - you can now do this by providing your + own `fetch` function: + + + + + + + + + +
Before
After
+ + ```ts + import { BskyAgent, defaultFetchHandler } from "@atproto/api"; + + BskyAgent.configure({ + fetch: async (httpUri, httpMethod, httpHeaders, httpReqBody) => { + const ua = httpHeaders["User-Agent"]; + + httpHeaders["User-Agent"] = ua ? `${ua} ${userAgent}` : userAgent; + + return defaultFetchHandler(httpUri, httpMethod, httpHeaders, httpReqBody); + }, + }); + ``` + + + + ```ts + import { AtpAgent } from "@atproto/api"; + + class MyAtpAgent extends AtpAgent { + constructor(options: AtpAgentOptions) { + const fetch = options.fetch ?? globalThis.fetch; + + super({ + ...options, + fetch: async (url, init) => { + const headers = new Headers(init.headers); + + const ua = headersList.get("User-Agent"); + headersList.set("User-Agent", ua ? `${ua} ${userAgent}` : userAgent); + + return fetch(url, { ...init, headers }); + }, + }); + } + } + ``` + +
+ + + + ##### The `@atproto/xrpc` package + + The `Client` and `ServiceClient` classes are now **deprecated**. If you need a + lexicon based client, you should update the code to use the `XrpcClient` class + instead. + + The deprecated `ServiceClient` class now extends the new `XrpcClient` class. + Because of this, the `fetch` `FetchHandler` can no longer be configured on the + `Client` instances (including the default export of the package). If you are not + relying on the `fetch` `FetchHandler`, the new changes should have no impact on + your code. Beware that the deprecated classes will eventually be removed in a + future version. + + Since its use has completely changed, the `FetchHandler` type has also + completely changed. The new `FetchHandler` type is now a function that receives + a `url` pathname and a `RequestInit` object and returns a `Promise`. + This function is responsible for making the actual request to the server. + + ```ts + export type FetchHandler = ( + this: void, + /** + * The URL (pathname + query parameters) to make the request to, without the + * origin. The origin (protocol, hostname, and port) must be added by this + * {@link FetchHandler}, typically based on authentication or other factors. + */ + url: string, + init: RequestInit, + ) => Promise; + ``` + + A noticeable change that has been introduced is that the `uri` field of the + `ServiceClient` class has _not_ been ported to the new `XrpcClient` class. It is + now the responsibility of the `FetchHandler` to determine the full URL to make + the request to. The same goes for the `headers`, which should now be set through + the `FetchHandler` function. + + If you _do_ rely on the legacy `Client.fetch` property to perform custom logic + upon request, you will need to migrate your code to use the new `XrpcClient` + class. The `XrpcClient` class has a similar API to the old `ServiceClient` + class, but with a few differences: + + - The `Client` + `ServiceClient` duality was removed in favor of a single + `XrpcClient` class. This means that: + + - There no longer exists a centralized lexicon registry. If you need a global + lexicon registry, you can maintain one yourself using a `new Lexicons` (from + `@atproto/lexicon`). + - The `FetchHandler` is no longer a statically defined property of the + `Client` class. Instead, it is passed as an argument to the `XrpcClient` + constructor. + + - The `XrpcClient` constructor now requires a `FetchHandler` function as the + first argument, and an optional `Lexicon` instance as the second argument. + - The `setHeader` and `unsetHeader` methods were not ported to the new + `XrpcClient` class. If you need to set or unset headers, you should do so in + the `FetchHandler` function provided in the constructor arg. + + + + + + + + + +
Before
After
+ + ```ts + import client, { defaultFetchHandler } from "@atproto/xrpc"; + + client.fetch = function ( + httpUri: string, + httpMethod: string, + httpHeaders: Headers, + httpReqBody: unknown, + ) { + // Custom logic here + return defaultFetchHandler(httpUri, httpMethod, httpHeaders, httpReqBody); + }; + + client.addLexicon({ + lexicon: 1, + id: "io.example.doStuff", + defs: {}, + }); + + const instance = client.service("http://my-service.com"); + + instance.setHeader("my-header", "my-value"); + + await instance.call("io.example.doStuff"); + ``` + + + + ```ts + import { XrpcClient } from "@atproto/xrpc"; + + const instance = new XrpcClient( + async (url, init) => { + const headers = new Headers(init.headers); + + headers.set("my-header", "my-value"); + + // Custom logic here + + const fullUrl = new URL(url, "http://my-service.com"); + + return fetch(fullUrl, { ...init, headers }); + }, + [ + { + lexicon: 1, + id: "io.example.doStuff", + defs: {}, + }, + ], + ); + + await instance.call("io.example.doStuff"); + ``` + +
+ + If your fetch handler does not require any "custom logic", and all you need is + an `XrpcClient` that makes its HTTP requests towards a static service URL, the + previous example can be simplified to: + + ```ts + import { XrpcClient } from "@atproto/xrpc"; + + const instance = new XrpcClient("http://my-service.com", [ + { + lexicon: 1, + id: "io.example.doStuff", + defs: {}, + }, + ]); + ``` + + If you need to add static headers to all requests, you can instead instantiate + the `XrpcClient` as follows: + + ```ts + import { XrpcClient } from "@atproto/xrpc"; + + const instance = new XrpcClient( + { + service: "http://my-service.com", + headers: { + "my-header": "my-value", + }, + }, + [ + { + lexicon: 1, + id: "io.example.doStuff", + defs: {}, + }, + ], + ); + ``` + + If you need the headers or service url to be dynamic, you can define them using + functions: + + ```ts + import { XrpcClient } from "@atproto/xrpc"; + + const instance = new XrpcClient( + { + service: () => "http://my-service.com", + headers: { + "my-header": () => "my-value", + "my-ignored-header": () => null, // ignored + }, + }, + [ + { + lexicon: 1, + id: "io.example.doStuff", + defs: {}, + }, + ], + ); + ``` + +- [#2483](https://github.com/bluesky-social/atproto/pull/2483) [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add the ability to use `fetch()` compatible `BodyInit` body when making XRPC calls. + +### Patch Changes + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`2bdf75d7a`](https://github.com/bluesky-social/atproto/commit/2bdf75d7a63924c10e7a311f16cb447d595b933e), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd)]: + - @atproto/lexicon@0.4.1 + - @atproto/xrpc@0.6.0 + +## 0.12.29 + +### Patch Changes + +- [#2668](https://github.com/bluesky-social/atproto/pull/2668) [`dc471da26`](https://github.com/bluesky-social/atproto/commit/dc471da267955d0962a8affaf983df60d962d97c) Thanks [@dholms](https://github.com/dholms)! - Add lxm and exp parameters to com.atproto.server.getServiceAuth + +## 0.12.28 + +### Patch Changes + +- [#2676](https://github.com/bluesky-social/atproto/pull/2676) [`951a3df15`](https://github.com/bluesky-social/atproto/commit/951a3df15aa9c1f5b0a2b66cfb0e2eaf6198fe41) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Remove `app.bsky.feed.detach` record, to be replaced by `app.bsky.feed.postgate` record in a future release. + ## 0.12.27 ### Patch Changes diff --git a/packages/api/OAUTH.md b/packages/api/OAUTH.md new file mode 100644 index 00000000000..911e4f8569a --- /dev/null +++ b/packages/api/OAUTH.md @@ -0,0 +1,275 @@ +# OAuth Client Quickstart + +This document describes how to implement OAuth based authentication in a +browser-based Single Page App (SPA), to communicate with +[atproto](https://atproto.com) API services. + +## Prerequisites + +- You need a web server - or at the very least a static file server - to host your SPA. + +> [!TIP] +> +> During development, you can use a local server to host your client metadata. +> You will need to use a tunneling service like [ngrok](https://ngrok.com/) to +> make your local server accessible from the internet. + +> [!TIP] +> +> You can use a service like [GitHub Pages](https://pages.github.com/) to host +> your client metadata and SPA for free. + +- You must be able to build and deploy a SPA to your server. + +## Step 1: Create your client metadata + +Based on your hosting server endpoint, you will first need to choose a +`client_id`. That `client_id` will be used to identify your client to +Authorization Servers. A `client_id` must be a URL pointing to a JSON file +which contains your client metadata. The client metadata **must** contain a +`client_id` that is the URL used to access the metadata. + +Here is an example client metadata. + +```json +{ + "client_id": "https://example.com/client-metadata.json", + "client_name": "Example atproto Browser App", + "client_uri": "https://example.com", + "logo_uri": "https://example.com/logo.png", + "tos_uri": "https://example.com/tos", + "policy_uri": "https://example.com/policy", + "redirect_uris": ["https://example.com/callback"], + "scope": "offline_access", + "grant_types": ["authorization_code", "refresh_token"], + "response_types": ["code"], + "token_endpoint_auth_method": "none", + "application_type": "web", + "dpop_bound_access_tokens": true +} +``` + +- `redirect_uris`: An array of URLs that will be used as the redirect URIs for + the OAuth flow. This should typically contain a single URL that points to a + page on your SPA that will handle the OAuth response. This URL must be HTTPS. + +- `client_id`: The URL where the client metadata is hosted. This field must be + the exact same as the URL used to access the metadata. + +- `client_name`: The name of your client. Will be displayed to the user during + the authentication process. + +- `client_uri`: The URL of your client. Whether or not this value is actually + displayed / used is up to the Authorization Server. + +- `logo_uri`: The URL of your client's logo. Should be displayed to the user + during the authentication process. Whether your logo is actually displayed + during the authentication process or not is up to the Authorization Server. + +- `tos_uri`: The URL of your client's terms of service. Will be displayed to + the user during the authentication process. + +- `policy_uri`: The URL of your client's privacy policy. Will be displayed to + the user during the authentication process. + +- If you don't want or need the user to stay authenticated for long periods + (better for security), you can remove the `offline_access` scope, and + `refresh_token` from the `grant_types`. + +> [!NOTE] +> +> To mitigate phishing attacks, the Authentication Server will typically _not_ +> display the `client_uri` or `logo_uri` to the user. If you don't see your logo +> or client name during the authentication process, don't worry. This is normal. +> The `client_name` _is_ generally displayed for all clients. + +Upload this JSON file so that it is accessible at the URL you chose for your +`client_id`. + +## Step 2: Setup your SPA + +Start by setting up your SPA. You can use any framework you like, or none at +all. In this example, we will use TypeScript and Parcel, with plain JavaScript. + +```bash +npm init -y +npm install --save-dev @atproto/oauth-client-browser +npm install --save-dev @atproto/api +npm install --save-dev parcel +npm install --save-dev parcel-reporter-static-files-copy +mkdir -p src +mkdir -p static +``` + +Create a `.parcelrc` file with the following (exact) content: + +```json +{ + "extends": ["@parcel/config-default"], + "reporters": ["...", "parcel-reporter-static-files-copy"] +} +``` + +Create an `src/index.html` file with the following content: + +```html + + + + + + My First OAuth App + + + + Loading... + + +``` + +And an `src/app.ts` file, with the following content: + +```typescript +console.log('Hello from atproto OAuth example app!') +``` + +Start the app in development mode: + +```bash +npx parcel src/index.html +``` + +In another terminal, open a tunnel to your local server: + +```bash +ngrok http 1234 +``` + +Create a `static/client-metadata.json` file with the client metadata you created +in [Step 1](#step-1-create-your-client-metadata). Use the hostname provided by +ngrok as the `client_id`: + +```json +{ + "client_id": "https://.ngrok.app/client-metadata.json", + "client_name": "My First atproto OAuth App", + "client_uri": "https://.ngrok.app", + "redirect_uris": ["https://.ngrok.app/"], + "grant_types": ["authorization_code"], + "response_types": ["code"], + "token_endpoint_auth_method": "none", + "application_type": "web", + "dpop_bound_access_tokens": true +} +``` + +## Step 3: Implement the OAuth flow + +Replace the content of the `src/app.ts` file, with the following content: + +```typescript +import { BrowserOAuthClient } from '@atproto/oauth-client-browser' + +async function main() { + const oauthClient = await BrowserOAuthClient.load({ + clientId: '', + handleResolver: 'https://bsky.social/', + }) + + // TO BE CONTINUED +} + +document.addEventListener('DOMContentLoaded', main) +``` + +> [!CAUTION] +> +> Using Bluesky-hosted services for handle resolution (eg, the `bsky.social` +> endpoint) will leak both user IP addresses and handle identifier to Bluesky, +> a third party. While Bluesky has a declared privacy policy, both developers +> and users of applications need to be informed of and aware of the privacy +> implications of this arrangement. Application developers are encouraged to +> improve user privacy by operating their own handle resolution service when +> possible. If you are a PDS self-hoster, you can use your PDS's URL for +> `handleResolver`. + +The `oauthClient` is now configured to communicate with the user's +Authorization Service. You can now initialize it in order to detect if the user +is already authenticated. Replace the `// TO BE CONTINUED` comment with the +following code: + +```typescript +const result = await oauthClient.init() +const agent = result?.agent + +// TO BE CONTINUED +``` + +At this point you can detect if the user is already authenticated or not (by +checking if `agent` is `undefined`). + +Let's initiate an authentication flow if the user is not authenticated. Replace +the `// TO BE CONTINUED` comment with the following code: + +```typescript +if (!agent) { + const handle = prompt('Enter your atproto handle to authenticate') + if (!handle) throw new Error('Authentication process canceled by the user') + + const url = await oauthClient.authorize(handle) + + // Redirect the user to the authorization page + window.open(url, '_self', 'noopener') + + // Protect against browser's back-forward cache + await new Promise((resolve, reject) => { + setTimeout( + reject, + 10_000, + new Error('User navigated back from the authorization page'), + ) + }) +} + +// TO BE CONTINUED +``` + +At this point in the script, the user **will** be authenticated. API calls can +be made using the `agent`. The `agent` is an instance of a sub-class of the +`Agent` from `@atproto/api`. Let's make a simple call to the API to retrieve the +user's profile. Replace the `// TO BE CONTINUED` comment with the following +code: + +```typescript +if (agent) { + const fetchProfile = async () => { + const profile = await agent.getProfile({ actor: agent.did }) + return profile.data + } + + // Update the user interface + + document.body.textContent = `Authenticated as ${agent.did}` + + const profileBtn = document.createElement('button') + document.body.appendChild(profileBtn) + profileBtn.textContent = 'Fetch Profile' + profileBtn.onclick = async () => { + const profile = await fetchProfile() + outputPre.textContent = JSON.stringify(profile, null, 2) + } + + const logoutBtn = document.createElement('button') + document.body.appendChild(logoutBtn) + logoutBtn.textContent = 'Logout' + logoutBtn.onclick = async () => { + await oauthAgent.signOut() + window.location.reload() + } + + const outputPre = document.createElement('pre') + document.body.appendChild(outputPre) +} +``` + +[API]: ./README.md diff --git a/packages/api/README.md b/packages/api/README.md index 201bba29f67..fb4607deb1d 100644 --- a/packages/api/README.md +++ b/packages/api/README.md @@ -11,36 +11,53 @@ This API is a client for ATProtocol servers. It communicates using HTTP. It incl First install the package: -``` +```sh yarn add @atproto/api ``` Then in your application: ```typescript -import { BskyAgent } from '@atproto/api' +import { AtpAgent } from '@atproto/api' -const agent = new BskyAgent({ service: 'https://example.com' }) +const agent = new AtpAgent({ service: 'https://example.com' }) ``` ## Usage ### Session management -Log into a server or create accounts using these APIs. You'll need an active session for most methods. +You'll need an authenticated session for most API calls. There are two ways to +manage sessions: + +1. [App password based session management](#app-password-based-session-management) +2. [OAuth based session management](#oauth-based-session-management) + +#### App password based session management + +Username / password based authentication van be performed using the `AtpAgent` +class. + +> [!CAUTION] +> +> This method is deprecated in favor of OAuth based session management. It is +> recommended to use OAuth based session management (through the +> `@atproto/oauth-client-*` packages). ```typescript -import { BskyAgent, AtpSessionEvent, AtpSessionData } from '@atproto/api' +import { AtpAgent, AtpSessionEvent, AtpSessionData } from '@atproto/api' // configure connection to the server, without account authentication -const agent = new BskyAgent({ +const agent = new AtpAgent({ service: 'https://example.com', persistSession: (evt: AtpSessionEvent, sess?: AtpSessionData) => { // store the session-data for reuse }, }) -// create a new account on the server +// Change the agent state to an authenticated state either by: + +// 1) creating a new account on the server. await agent.createAccount({ email: 'alice@mail.com', password: 'hunter2', @@ -48,11 +65,40 @@ await agent.createAccount({ inviteCode: 'some-code-12345-abcde', }) -// if an existing session (accessed with 'agent.session') was securely stored previously, then reuse that +// 2) if an existing session was securely stored previously, then reuse that to resume the session. await agent.resumeSession(savedSessionData) -// if no old session was available, create a new one by logging in with password (App Password) -await agent.login({ identifier: 'alice@mail.com', password: 'hunter2' }) +// 3) if no old session was available, create a new one by logging in with password (App Password) +await agent.login({ + identifier: 'alice@mail.com', + password: 'hunter2', +}) +``` + +#### OAuth based session management + +Depending on the environment used by your application, different OAuth clients +are available: + +- [@atproto/oauth-client-browser](https://www.npmjs.com/package/@atproto/oauth-client-browser): + for the browser. +- [@atproto/oauth-client-node](https://www.npmjs.com/package/@atproto/oauth-client-node): for + Node.js. +- [@atproto/oauth-client](https://www.npmjs.com/package/@atproto/oauth-client): + Lower lever; compatible with most JS engines. + +Every `@atproto/oauth-client-*` implementation has a different way to obtain an +OAuth based API agent instance. Here is an example restoring a previously +saved session: + +```typescript +import { OAuthClient } from '@atproto/oauth-client' + +const oauthClient = new OAuthClient({ + // ... +}) + +const agent = await oauthClient.restore('did:plc:123') ``` ### API calls @@ -105,10 +151,12 @@ await agent.updateSeenNotifications() await agent.resolveHandle(params, opts) await agent.updateHandle(params, opts) -// Session management -await agent.createAccount(params) -await agent.login(params) -await agent.resumeSession(session) +// Session management (OAuth based agent instances have a different set of methods) +if (agent instanceof AtpAgent) { + await agent.createAccount(params) + await agent.login(params) + await agent.resumeSession(session) +} ``` ### Validation and types @@ -279,40 +327,29 @@ const res3 = await agent.app.bsky.feed.post.create( const res4 = await agent.app.bsky.feed.post.list({ repo: alice.did }) ``` -### Generic agent +### Non-browser configuration -If you want a generic AT Protocol agent without methods related to the Bluesky social lexicon, use the `AtpAgent` instead of the `BskyAgent`. +If you environment doesn't have a built-in `fetch` implementation, you'll need +to provide one. This will typically be done through a polyfill. -```typescript -import { AtpAgent } from '@atproto/api' +### Bring your own fetch -const agent = new AtpAgent({ service: 'https://example.com' }) -``` +If you want to provide you own `fetch` implementation, you can do so by +instantiating the sessionManager with a custom fetch implementation: -### Non-browser configuration +```typescript +import { AtpAgent } from '@atproto/api' -In non-browser environments you'll need to specify a fetch polyfill. [See the example react-native polyfill here.](./docs/rn-fetch-handler.ts) +const myFetch = (input: RequestInfo | URL, init?: RequestInit) => { + console.log('requesting', input) + const response = await globalThis.fetch(input, init) + console.log('got response', response) + return response +} -```typescript -import { BskyAgent } from '@atproto/api' - -const agent = new BskyAgent({ service: 'https://example.com' }) - -// provide a custom fetch implementation (shouldn't be needed in node or the browser) -import { - AtpAgentFetchHeaders, - AtpAgentFetchHandlerResponse, -} from '@atproto/api' -BskyAgent.configure({ - async fetch( - httpUri: string, - httpMethod: string, - httpHeaders: AtpAgentFetchHeaders, - httpReqBody: any, - ): Promise { - // insert definition here... - return { status: 200 /*...*/ } - }, +const agent = new AtpAgent({ + service: 'https://example.com', + fetch: myFetch, }) ``` diff --git a/packages/api/docs/moderation.md b/packages/api/docs/moderation.md index 571660d00fa..2deee0ce29f 100644 --- a/packages/api/docs/moderation.md +++ b/packages/api/docs/moderation.md @@ -144,9 +144,9 @@ The label value definition are custom labels which only apply to that labeler. Y Here is how to do this: ```typescript -import { BskyAgent } from '@atproto/api' +import { AtpAgent } from '@atproto/api' -const agent = new BskyAgent() +const agent = new AtpAgent({ service: 'https://example.com' }) // assume `agent` is a signed in session const prefs = await agent.getPreferences() const labelDefs = await agent.getLabelDefinitions(prefs) @@ -182,7 +182,7 @@ const res = moderatePost(post, moderationOptions) The response object provides an API for figuring out what your UI should do in different contexts. ```typescript -res.ui(context) /* => +res.ui(context) /* => ModerationUI { filter: boolean // should the content be removed from the interface? @@ -226,7 +226,7 @@ if (mod.ui('contentList').blur) { } } if (mod.ui('contentMedia').blur) { - // cover the post's embbedded images with the explanation from mod.ui('contentMedia').blurs[0] + // cover the post's embedded images with the explanation from mod.ui('contentMedia').blurs[0] if (mod.ui('contentMedia').noOverride) { // dont allow the cover to be removed } diff --git a/packages/api/docs/rn-fetch-handler.ts b/packages/api/docs/rn-fetch-handler.ts deleted file mode 100644 index 448915fcfa7..00000000000 --- a/packages/api/docs/rn-fetch-handler.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * The following is the react-native fetch handler used currently in the bluesky app - * It's not our prettiest work, but it gets the job done - */ - -import { BskyAgent, stringifyLex, jsonToLex } from '@atproto/api' -import RNFS from 'react-native-fs' - -const GET_TIMEOUT = 15e3 // 15s -const POST_TIMEOUT = 60e3 // 60s - -export function doPolyfill() { - BskyAgent.configure({ fetch: fetchHandler }) -} - -interface FetchHandlerResponse { - status: number - headers: Record - body: ArrayBuffer | undefined -} - -async function fetchHandler( - reqUri: string, - reqMethod: string, - reqHeaders: Record, - reqBody: any, -): Promise { - const reqMimeType = reqHeaders['Content-Type'] || reqHeaders['content-type'] - if (reqMimeType && reqMimeType.startsWith('application/json')) { - reqBody = stringifyLex(reqBody) - } else if ( - typeof reqBody === 'string' && - (reqBody.startsWith('/') || reqBody.startsWith('file:')) - ) { - if (reqBody.endsWith('.jpeg') || reqBody.endsWith('.jpg')) { - // HACK - // React native has a bug that inflates the size of jpegs on upload - // we get around that by renaming the file ext to .bin - // see https://github.com/facebook/react-native/issues/27099 - // -prf - const newPath = reqBody.replace(/\.jpe?g$/, '.bin') - await RNFS.moveFile(reqBody, newPath) - reqBody = newPath - } - // NOTE - // React native treats bodies with {uri: string} as file uploads to pull from cache - // -prf - reqBody = { uri: reqBody } - } - - const controller = new AbortController() - const to = setTimeout( - () => controller.abort(), - reqMethod === 'post' ? POST_TIMEOUT : GET_TIMEOUT, - ) - - const res = await fetch(reqUri, { - method: reqMethod, - headers: reqHeaders, - body: reqBody, - signal: controller.signal, - }) - - const resStatus = res.status - const resHeaders: Record = {} - res.headers.forEach((value: string, key: string) => { - resHeaders[key] = value - }) - const resMimeType = resHeaders['Content-Type'] || resHeaders['content-type'] - let resBody - if (resMimeType) { - if (resMimeType.startsWith('application/json')) { - resBody = jsonToLex(await res.json()) - } else if (resMimeType.startsWith('text/')) { - resBody = await res.text() - } else { - resBody = await res.blob() - } - } - - clearTimeout(to) - - return { - status: resStatus, - headers: resHeaders, - body: resBody, - } -} diff --git a/packages/api/package.json b/packages/api/package.json index 46f1a5edefc..6c804d7cdbe 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/api", - "version": "0.12.27", + "version": "0.13.1", "license": "MIT", "description": "Client library for atproto and Bluesky", "keywords": [ @@ -17,7 +17,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { - "codegen": "node ./scripts/generate-code.mjs && lex gen-api ./src/client ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/* ../../lexicons/chat/bsky/*/* ../../lexicons/tools/ozone/*/*", + "codegen": "node ./scripts/generate-code.mjs && lex gen-api --yes ./src/client ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/* ../../lexicons/chat/bsky/*/* ../../lexicons/tools/ozone/*/*", "build": "tsc --build tsconfig.build.json", "test": "jest" }, diff --git a/packages/api/src/agent.ts b/packages/api/src/agent.ts index 69387c98e53..c2e6d27d75b 100644 --- a/packages/api/src/agent.ts +++ b/packages/api/src/agent.ts @@ -1,473 +1,1614 @@ -import { ErrorResponseBody, errorResponseBody } from '@atproto/xrpc' -import { defaultFetchHandler, XRPCError, ResponseType } from '@atproto/xrpc' -import { isValidDidDoc, getPdsEndpoint } from '@atproto/common-web' +import { TID } from '@atproto/common-web' +import { AtUri, ensureValidDid } from '@atproto/syntax' import { + buildFetchHandler, + FetchHandler, + FetchHandlerOptions, +} from '@atproto/xrpc' +import AwaitLock from 'await-lock' +import { + AppBskyActorDefs, + AppBskyActorProfile, + AppBskyFeedPost, + AppBskyLabelerDefs, AtpBaseClient, - AtpServiceClient, - ComAtprotoServerCreateAccount, - ComAtprotoServerCreateSession, - ComAtprotoServerGetSession, - ComAtprotoServerRefreshSession, -} from './client' + ComAtprotoRepoPutRecord, +} from './client/index' +import { MutedWord } from './client/types/app/bsky/actor/defs' +import { BSKY_LABELER_DID } from './const' +import { interpretLabelValueDefinitions } from './moderation' +import { DEFAULT_LABEL_SETTINGS } from './moderation/const/labels' +import { + InterpretedLabelValueDefinition, + LabelPreference, + ModerationPrefs, +} from './moderation/types' import { - AtpSessionData, - AtpAgentLoginOpts, - AtpAgentFetchHandler, - AtpAgentFetchHandlerResponse, AtpAgentGlobalOpts, - AtpPersistSessionHandler, - AtpAgentOpts, AtprotoServiceType, + BskyFeedViewPreference, + BskyInterestsPreference, + BskyPreferences, + BskyThreadViewPreference, } from './types' -import { BSKY_LABELER_DID } from './const' - -const REFRESH_SESSION = 'com.atproto.server.refreshSession' +import { + asDid, + Did, + getSavedFeedType, + isDid, + sanitizeMutedWordValue, + savedFeedsToUriArrays, + validateSavedFeed, +} from './util' -/** - * An ATP "Agent" - * Manages session token lifecycles and provides convenience methods. - */ -export class AtpAgent { - service: URL - api: AtpServiceClient - session?: AtpSessionData - labelersHeader: string[] = [] - proxyHeader: string | undefined - pdsUrl: URL | undefined // The PDS URL, driven by the did doc. May be undefined. +const FEED_VIEW_PREF_DEFAULTS = { + hideReplies: false, + hideRepliesByUnfollowed: true, + hideRepliesByLikeCount: 0, + hideReposts: false, + hideQuotePosts: false, +} - protected _baseClient: AtpBaseClient - protected _persistSession?: AtpPersistSessionHandler - protected _refreshSessionPromise: Promise | undefined +const THREAD_VIEW_PREF_DEFAULTS = { + sort: 'oldest', + prioritizeFollowedUsers: true, +} - get com() { - return this.api.com +declare global { + interface Array { + findLast( + predicate: (value: T, index: number, obj: T[]) => unknown, + thisArg?: any, + ): T } +} - /** - * The `fetch` implementation; must be implemented for your platform. - */ - static fetch: AtpAgentFetchHandler | undefined = defaultFetchHandler +export type { FetchHandler } + +/** + * An {@link Agent} is an {@link AtpBaseClient} with the following + * additional features: + * - Abstract session management utilities + * - AT Protocol labelers configuration utilities + * - AT Protocol proxy configuration utilities + * - Cloning utilities + * - `app.bsky` syntactic sugar + * - `com.atproto` syntactic sugar + */ +export abstract class Agent extends AtpBaseClient { + //#region Static configuration /** * The labelers to be used across all requests with the takedown capability */ - static appLabelers: string[] = [BSKY_LABELER_DID] + static appLabelers: readonly string[] = [BSKY_LABELER_DID] /** - * Configures the API globally. + * Configures the Agent (or its sub classes) globally. */ static configure(opts: AtpAgentGlobalOpts) { - if (opts.fetch) { - AtpAgent.fetch = opts.fetch - } if (opts.appLabelers) { - AtpAgent.appLabelers = opts.appLabelers + this.appLabelers = opts.appLabelers.map(asDid) // Validate & copy } } - constructor(opts: AtpAgentOpts) { - this.service = - opts.service instanceof URL ? opts.service : new URL(opts.service) - this._persistSession = opts.persistSession + //#endregion - // create an ATP client instance for this agent - this._baseClient = new AtpBaseClient() - this._baseClient.xrpc.fetch = this._fetch.bind(this) // patch its fetch implementation - this.api = this._baseClient.service(opts.service) - } + constructor(fetchHandlerOpts: FetchHandler | FetchHandlerOptions) { + const fetchHandler = buildFetchHandler(fetchHandlerOpts) + + super((url, init) => { + const headers = new Headers(init?.headers) + + if (this.proxy && !headers.has('atproto-proxy')) { + headers.set('atproto-proxy', this.proxy) + } - clone() { - const inst = new AtpAgent({ - service: this.service, + // Merge the labelers header of this particular request with the app & + // instance labelers. + headers.set( + 'atproto-accept-labelers', + [ + ...this.appLabelers.map((l) => `${l};redact`), + ...this.labelers, + headers.get('atproto-accept-labelers')?.trim(), + ] + .filter(Boolean) + .join(', '), + ) + + return fetchHandler(url, { ...init, headers }) }) - this.copyInto(inst) - return inst } - copyInto(inst: AtpAgent) { - inst.session = this.session - inst.labelersHeader = this.labelersHeader - inst.proxyHeader = this.proxyHeader - inst.pdsUrl = this.pdsUrl - inst.api.xrpc.uri = this.pdsUrl || this.service + //#region Cloning utilities + + abstract clone(): Agent + + copyInto(inst: T): T { + inst.configureLabelers(this.labelers) + inst.configureProxy(this.proxy ?? null) + return inst } withProxy(serviceType: AtprotoServiceType, did: string) { const inst = this.clone() - inst.configureProxyHeader(serviceType, did) - return inst + inst.configureProxy(`${asDid(did)}#${serviceType}`) + return inst as ReturnType } + //#endregion + + //#region ATPROTO labelers configuration utilities + /** - * Is there any active session? + * The labelers statically configured on the class of the current instance. */ - get hasSession() { - return !!this.session + get appLabelers() { + return (this.constructor as typeof Agent).appLabelers + } + + labelers: readonly string[] = [] + + configureLabelers(labelerDids: readonly string[]) { + this.labelers = labelerDids.map(asDid) // Validate & copy + } + + /** @deprecated use {@link configureLabelers} instead */ + configureLabelersHeader(labelerDids: readonly string[]) { + // Filtering non-did values for backwards compatibility + this.configureLabelers(labelerDids.filter(isDid)) } + //#endregion + + //#region ATPROTO proxy configuration utilities + + proxy?: `${Did}#${AtprotoServiceType}` + + configureProxy(value: `${Did}#${AtprotoServiceType}` | null) { + if (value === null) this.proxy = undefined + else if (isDid(value)) this.proxy = value + else throw new TypeError('Invalid proxy DID') + } + + /** @deprecated use {@link configureProxy} instead */ + configureProxyHeader(serviceType: AtprotoServiceType, did: string) { + // Ignoring non-did values for backwards compatibility + if (isDid(did)) this.configureProxy(`${did}#${serviceType}`) + } + + //#endregion + + //#region Session management + /** - * Sets the "Persist Session" method which can be used to store access tokens - * as they change. + * Get the authenticated user's DID, if any. */ - setPersistSessionHandler(handler?: AtpPersistSessionHandler) { - this._persistSession = handler - } + abstract readonly did?: string /** - * Configures the moderation services to be applied on requests. - * NOTE: this is called automatically by getPreferences() and the relevant moderation config - * methods in BskyAgent instances. + * Get the authenticated user's DID, or throw an error if not authenticated. */ - configureLabelersHeader(labelerDids: string[]) { - this.labelersHeader = labelerDids + public get accountDid(): string { + this.assertAuthenticated() + return this.did } /** - * Configures the atproto-proxy header to be applied on requests + * Assert that the user is authenticated. */ - configureProxyHeader(serviceType: AtprotoServiceType, did: string) { - if (did.startsWith('did:')) { - this.proxyHeader = `${did}#${serviceType}` - } + public assertAuthenticated(): asserts this is { did: string } { + if (!this.did) throw new Error('Not logged in') } + //#endregion + + /** @deprecated use "this" instead */ + get api() { + return this + } + + //#region "com.atproto" lexicon short hand methods + /** - * Create a new account and hydrate its session in this agent. + * Upload a binary blob to the server */ - async createAccount( - opts: ComAtprotoServerCreateAccount.InputSchema, - ): Promise { - try { - const res = await this.api.com.atproto.server.createAccount(opts) - this.session = { - accessJwt: res.data.accessJwt, - refreshJwt: res.data.refreshJwt, - handle: res.data.handle, - did: res.data.did, - email: opts.email, - emailConfirmed: false, - emailAuthFactor: false, - active: true, - } - this._updateApiEndpoint(res.data.didDoc) - return res - } catch (e) { - this.session = undefined - throw e - } finally { - if (this.session) { - this._persistSession?.('create', this.session) - } else { - this._persistSession?.('create-failed', undefined) - } - } - } + uploadBlob: typeof this.com.atproto.repo.uploadBlob = (data, opts) => + this.com.atproto.repo.uploadBlob(data, opts) /** - * Start a new session with this agent. + * Resolve a handle to a DID */ - async login( - opts: AtpAgentLoginOpts, - ): Promise { - try { - const res = await this.api.com.atproto.server.createSession({ - identifier: opts.identifier, - password: opts.password, - authFactorToken: opts.authFactorToken, - }) - this.session = { - accessJwt: res.data.accessJwt, - refreshJwt: res.data.refreshJwt, - handle: res.data.handle, - did: res.data.did, - email: res.data.email, - emailConfirmed: res.data.emailConfirmed, - emailAuthFactor: res.data.emailAuthFactor, - active: res.data.active ?? true, - status: res.data.status, - } - this._updateApiEndpoint(res.data.didDoc) - return res - } catch (e) { - this.session = undefined - throw e - } finally { - if (this.session) { - this._persistSession?.('create', this.session) - } else { - this._persistSession?.('create-failed', undefined) + resolveHandle: typeof this.com.atproto.identity.resolveHandle = ( + params, + opts, + ) => this.com.atproto.identity.resolveHandle(params, opts) + + /** + * Change the user's handle + */ + updateHandle: typeof this.com.atproto.identity.updateHandle = (data, opts) => + this.com.atproto.identity.updateHandle(data, opts) + + /** + * Create a moderation report + */ + createModerationReport: typeof this.com.atproto.moderation.createReport = ( + data, + opts, + ) => this.com.atproto.moderation.createReport(data, opts) + + //#endregion + + //#region "app.bsky" lexicon short hand methods + + getTimeline: typeof this.app.bsky.feed.getTimeline = (params, opts) => + this.app.bsky.feed.getTimeline(params, opts) + + getAuthorFeed: typeof this.app.bsky.feed.getAuthorFeed = (params, opts) => + this.app.bsky.feed.getAuthorFeed(params, opts) + + getActorLikes: typeof this.app.bsky.feed.getActorLikes = (params, opts) => + this.app.bsky.feed.getActorLikes(params, opts) + + getPostThread: typeof this.app.bsky.feed.getPostThread = (params, opts) => + this.app.bsky.feed.getPostThread(params, opts) + + getPost: typeof this.app.bsky.feed.post.get = (params) => + this.app.bsky.feed.post.get(params) + + getPosts: typeof this.app.bsky.feed.getPosts = (params, opts) => + this.app.bsky.feed.getPosts(params, opts) + + getLikes: typeof this.app.bsky.feed.getLikes = (params, opts) => + this.app.bsky.feed.getLikes(params, opts) + + getRepostedBy: typeof this.app.bsky.feed.getRepostedBy = (params, opts) => + this.app.bsky.feed.getRepostedBy(params, opts) + + getFollows: typeof this.app.bsky.graph.getFollows = (params, opts) => + this.app.bsky.graph.getFollows(params, opts) + + getFollowers: typeof this.app.bsky.graph.getFollowers = (params, opts) => + this.app.bsky.graph.getFollowers(params, opts) + + getProfile: typeof this.app.bsky.actor.getProfile = (params, opts) => + this.app.bsky.actor.getProfile(params, opts) + + getProfiles: typeof this.app.bsky.actor.getProfiles = (params, opts) => + this.app.bsky.actor.getProfiles(params, opts) + + getSuggestions: typeof this.app.bsky.actor.getSuggestions = (params, opts) => + this.app.bsky.actor.getSuggestions(params, opts) + + searchActors: typeof this.app.bsky.actor.searchActors = (params, opts) => + this.app.bsky.actor.searchActors(params, opts) + + searchActorsTypeahead: typeof this.app.bsky.actor.searchActorsTypeahead = ( + params, + opts, + ) => this.app.bsky.actor.searchActorsTypeahead(params, opts) + + listNotifications: typeof this.app.bsky.notification.listNotifications = ( + params, + opts, + ) => this.app.bsky.notification.listNotifications(params, opts) + + countUnreadNotifications: typeof this.app.bsky.notification.getUnreadCount = ( + params, + opts, + ) => this.app.bsky.notification.getUnreadCount(params, opts) + + getLabelers: typeof this.app.bsky.labeler.getServices = (params, opts) => + this.app.bsky.labeler.getServices(params, opts) + + async getLabelDefinitions( + prefs: BskyPreferences | ModerationPrefs | string[], + ): Promise> { + // collect the labeler dids + const dids: string[] = [...this.appLabelers] + if (isBskyPrefs(prefs)) { + dids.push(...prefs.moderationPrefs.labelers.map((l) => l.did)) + } else if (isModPrefs(prefs)) { + dids.push(...prefs.labelers.map((l) => l.did)) + } else { + dids.push(...prefs) + } + + // fetch their definitions + const labelers = await this.getLabelers({ + dids, + detailed: true, + }) + + // assemble a map of labeler dids to the interpreted label value definitions + const labelDefs = {} + if (labelers.data) { + for (const labeler of labelers.data + .views as AppBskyLabelerDefs.LabelerViewDetailed[]) { + labelDefs[labeler.creator.did] = interpretLabelValueDefinitions(labeler) } } + + return labelDefs } - /** - * Resume a pre-existing session with this agent. - */ - async resumeSession( - session: AtpSessionData, - ): Promise { - try { - this.session = session - const res = await this.api.com.atproto.server.getSession() - if (res.data.did !== this.session.did) { - throw new XRPCError( - ResponseType.InvalidRequest, - 'Invalid session', - 'InvalidDID', - ) + async post( + record: Partial & + Omit, + ) { + record.createdAt ||= new Date().toISOString() + return this.app.bsky.feed.post.create( + { repo: this.accountDid }, + record as AppBskyFeedPost.Record, + ) + } + + async deletePost(postUri: string) { + this.assertAuthenticated() + + const postUrip = new AtUri(postUri) + return this.app.bsky.feed.post.delete({ + repo: postUrip.hostname, + rkey: postUrip.rkey, + }) + } + + async like(uri: string, cid: string) { + return this.app.bsky.feed.like.create( + { repo: this.accountDid }, + { + subject: { uri, cid }, + createdAt: new Date().toISOString(), + }, + ) + } + + async deleteLike(likeUri: string) { + this.assertAuthenticated() + + const likeUrip = new AtUri(likeUri) + return this.app.bsky.feed.like.delete({ + repo: likeUrip.hostname, + rkey: likeUrip.rkey, + }) + } + + async repost(uri: string, cid: string) { + return this.app.bsky.feed.repost.create( + { repo: this.accountDid }, + { + subject: { uri, cid }, + createdAt: new Date().toISOString(), + }, + ) + } + + async deleteRepost(repostUri: string) { + this.assertAuthenticated() + + const repostUrip = new AtUri(repostUri) + return this.app.bsky.feed.repost.delete({ + repo: repostUrip.hostname, + rkey: repostUrip.rkey, + }) + } + + async follow(subjectDid: string) { + return this.app.bsky.graph.follow.create( + { repo: this.accountDid }, + { + subject: subjectDid, + createdAt: new Date().toISOString(), + }, + ) + } + + async deleteFollow(followUri: string) { + this.assertAuthenticated() + + const followUrip = new AtUri(followUri) + return this.app.bsky.graph.follow.delete({ + repo: followUrip.hostname, + rkey: followUrip.rkey, + }) + } + + async upsertProfile( + updateFn: ( + existing: AppBskyActorProfile.Record | undefined, + ) => AppBskyActorProfile.Record | Promise, + ) { + const repo = this.accountDid + + let retriesRemaining = 5 + while (retriesRemaining >= 0) { + // fetch existing + const existing = await this.com.atproto.repo + .getRecord({ + repo, + collection: 'app.bsky.actor.profile', + rkey: 'self', + }) + .catch((_) => undefined) + + // run the update + const updated = await updateFn(existing?.data.value) + if (updated) { + updated.$type = 'app.bsky.actor.profile' } - this.session.email = res.data.email - this.session.handle = res.data.handle - this.session.emailConfirmed = res.data.emailConfirmed - this.session.emailAuthFactor = res.data.emailAuthFactor - this.session.active = res.data.active ?? true - this.session.status = res.data.status - this._updateApiEndpoint(res.data.didDoc) - this._persistSession?.('update', this.session) - return res - } catch (e) { - this.session = undefined - - if (e instanceof XRPCError) { - /* - * `ExpiredToken` and `InvalidToken` are handled in - * `this_refreshSession`, and emit an `expired` event there. - * - * Everything else is handled here. - */ + + // validate the record + const validation = AppBskyActorProfile.validateRecord(updated) + if (!validation.success) { + throw validation.error + } + + try { + // attempt the put + await this.com.atproto.repo.putRecord({ + repo, + collection: 'app.bsky.actor.profile', + rkey: 'self', + record: updated, + swapRecord: existing?.data.cid || null, + }) + } catch (e: unknown) { if ( - [1, 408, 425, 429, 500, 502, 503, 504, 522, 524].includes(e.status) + retriesRemaining > 0 && + e instanceof ComAtprotoRepoPutRecord.InvalidSwapError ) { - this._persistSession?.('network-error', undefined) + // try again + retriesRemaining-- + continue } else { - this._persistSession?.('expired', undefined) + throw e } - } else { - this._persistSession?.('network-error', undefined) } + break + } + } + + async mute(actor: string) { + return this.app.bsky.graph.muteActor({ actor }) + } + + async unmute(actor: string) { + return this.app.bsky.graph.unmuteActor({ actor }) + } + + async muteModList(uri: string) { + return this.app.bsky.graph.muteActorList({ list: uri }) + } + + async unmuteModList(uri: string) { + return this.app.bsky.graph.unmuteActorList({ list: uri }) + } - throw e + async blockModList(uri: string) { + return this.app.bsky.graph.listblock.create( + { repo: this.accountDid }, + { + subject: uri, + createdAt: new Date().toISOString(), + }, + ) + } + + async unblockModList(uri: string) { + const repo = this.accountDid + + const listInfo = await this.app.bsky.graph.getList({ + list: uri, + limit: 1, + }) + + const blocked = listInfo.data.list.viewer?.blocked + if (blocked) { + const { rkey } = new AtUri(blocked) + return this.app.bsky.graph.listblock.delete({ + repo, + rkey, + }) } } - /** - * Internal helper to add authorization headers to requests. - */ - private _addHeaders(reqHeaders: Record) { - if (!reqHeaders.authorization && this.session?.accessJwt) { - reqHeaders = { - ...reqHeaders, - authorization: `Bearer ${this.session.accessJwt}`, - } + async updateSeenNotifications(seenAt = new Date().toISOString()) { + return this.app.bsky.notification.updateSeen({ seenAt }) + } + + async getPreferences(): Promise { + const prefs: BskyPreferences = { + feeds: { + saved: undefined, + pinned: undefined, + }, + // @ts-ignore populating below + savedFeeds: undefined, + feedViewPrefs: { + home: { + ...FEED_VIEW_PREF_DEFAULTS, + }, + }, + threadViewPrefs: { ...THREAD_VIEW_PREF_DEFAULTS }, + moderationPrefs: { + adultContentEnabled: false, + labels: { ...DEFAULT_LABEL_SETTINGS }, + labelers: this.appLabelers.map((did) => ({ + did, + labels: {}, + })), + mutedWords: [], + hiddenPosts: [], + }, + birthDate: undefined, + interests: { + tags: [], + }, + bskyAppState: { + queuedNudges: [], + activeProgressGuide: undefined, + }, } - if (this.proxyHeader) { - reqHeaders = { - ...reqHeaders, - 'atproto-proxy': this.proxyHeader, + const res = await this.app.bsky.actor.getPreferences({}) + const labelPrefs: AppBskyActorDefs.ContentLabelPref[] = [] + for (const pref of res.data.preferences) { + if ( + AppBskyActorDefs.isAdultContentPref(pref) && + AppBskyActorDefs.validateAdultContentPref(pref).success + ) { + // adult content preferences + prefs.moderationPrefs.adultContentEnabled = pref.enabled + } else if ( + AppBskyActorDefs.isContentLabelPref(pref) && + AppBskyActorDefs.validateContentLabelPref(pref).success + ) { + // content label preference + const adjustedPref = adjustLegacyContentLabelPref(pref) + labelPrefs.push(adjustedPref) + } else if ( + AppBskyActorDefs.isLabelersPref(pref) && + AppBskyActorDefs.validateLabelersPref(pref).success + ) { + // labelers preferences + prefs.moderationPrefs.labelers = this.appLabelers + .map((did: string) => ({ did, labels: {} })) + .concat( + pref.labelers.map((labeler) => ({ + ...labeler, + labels: {}, + })), + ) + } else if ( + AppBskyActorDefs.isSavedFeedsPrefV2(pref) && + AppBskyActorDefs.validateSavedFeedsPrefV2(pref).success + ) { + prefs.savedFeeds = pref.items + } else if ( + AppBskyActorDefs.isSavedFeedsPref(pref) && + AppBskyActorDefs.validateSavedFeedsPref(pref).success + ) { + // saved and pinned feeds + prefs.feeds.saved = pref.saved + prefs.feeds.pinned = pref.pinned + } else if ( + AppBskyActorDefs.isPersonalDetailsPref(pref) && + AppBskyActorDefs.validatePersonalDetailsPref(pref).success + ) { + // birth date (irl) + if (pref.birthDate) { + prefs.birthDate = new Date(pref.birthDate) + } + } else if ( + AppBskyActorDefs.isFeedViewPref(pref) && + AppBskyActorDefs.validateFeedViewPref(pref).success + ) { + // feed view preferences + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { $type, feed, ...v } = pref + prefs.feedViewPrefs[pref.feed] = { ...FEED_VIEW_PREF_DEFAULTS, ...v } + } else if ( + AppBskyActorDefs.isThreadViewPref(pref) && + AppBskyActorDefs.validateThreadViewPref(pref).success + ) { + // thread view preferences + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { $type, ...v } = pref + prefs.threadViewPrefs = { ...prefs.threadViewPrefs, ...v } + } else if ( + AppBskyActorDefs.isInterestsPref(pref) && + AppBskyActorDefs.validateInterestsPref(pref).success + ) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { $type, ...v } = pref + prefs.interests = { ...prefs.interests, ...v } + } else if ( + AppBskyActorDefs.isMutedWordsPref(pref) && + AppBskyActorDefs.validateMutedWordsPref(pref).success + ) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { $type, ...v } = pref + prefs.moderationPrefs.mutedWords = v.items + + if (prefs.moderationPrefs.mutedWords.length) { + prefs.moderationPrefs.mutedWords = + prefs.moderationPrefs.mutedWords.map((word) => { + word.actorTarget = word.actorTarget || 'all' + return word + }) + } + } else if ( + AppBskyActorDefs.isHiddenPostsPref(pref) && + AppBskyActorDefs.validateHiddenPostsPref(pref).success + ) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { $type, ...v } = pref + prefs.moderationPrefs.hiddenPosts = v.items + } else if ( + AppBskyActorDefs.isBskyAppStatePref(pref) && + AppBskyActorDefs.validateBskyAppStatePref(pref).success + ) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { $type, ...v } = pref + prefs.bskyAppState.queuedNudges = v.queuedNudges || [] + prefs.bskyAppState.activeProgressGuide = v.activeProgressGuide } } - const labelerHeaderName = 'atproto-accept-labelers' - const labelerHeaders = AtpAgent.appLabelers - .map((str) => `${str};redact`) - .concat(this.labelersHeader.filter((str) => str.startsWith('did:'))) - - // Besides labelers configured via appLabelers and labelersHeader - // respect any additional labelers configured via the request headers - if (reqHeaders[labelerHeaderName]) { - labelerHeaders.push( - // Allow for headers to be comma-separated with or without spaces in between by trimming after split - ...reqHeaders[labelerHeaderName].split(',').map((str) => str.trim()), - ) + /* + * If `prefs.savedFeeds` is undefined, no `savedFeedsPrefV2` exists, which + * means we want to try to migrate if needed. + * + * If v1 prefs exist, they will be migrated to v2. + * + * If no v1 prefs exist, the user is either new, or could be old and has + * never edited their feeds. + */ + if (prefs.savedFeeds == null) { + const { saved, pinned } = prefs.feeds + + if (saved && pinned) { + const uniqueMigratedSavedFeeds: Map< + string, + AppBskyActorDefs.SavedFeed + > = new Map() + + // insert Following feed first + uniqueMigratedSavedFeeds.set('timeline', { + id: TID.nextStr(), + type: 'timeline', + value: 'following', + pinned: true, + }) + + // use pinned as source of truth for feed order + for (const uri of pinned) { + const type = getSavedFeedType(uri) + // only want supported types + if (type === 'unknown') continue + uniqueMigratedSavedFeeds.set(uri, { + id: TID.nextStr(), + type, + value: uri, + pinned: true, + }) + } + + for (const uri of saved) { + if (!uniqueMigratedSavedFeeds.has(uri)) { + const type = getSavedFeedType(uri) + // only want supported types + if (type === 'unknown') continue + uniqueMigratedSavedFeeds.set(uri, { + id: TID.nextStr(), + type, + value: uri, + pinned: false, + }) + } + } + + prefs.savedFeeds = Array.from(uniqueMigratedSavedFeeds.values()) + } else { + prefs.savedFeeds = [ + { + id: TID.nextStr(), + type: 'timeline', + value: 'following', + pinned: true, + }, + ] + } + + // save to user preferences so this migration doesn't re-occur + await this.overwriteSavedFeeds(prefs.savedFeeds) } - reqHeaders = { - ...reqHeaders, - [labelerHeaderName]: labelerHeaders.join(', '), + // apply the label prefs + for (const pref of labelPrefs) { + if (pref.labelerDid) { + const labeler = prefs.moderationPrefs.labelers.find( + (labeler) => labeler.did === pref.labelerDid, + ) + if (!labeler) continue + labeler.labels[pref.label] = pref.visibility as LabelPreference + } else { + prefs.moderationPrefs.labels[pref.label] = + pref.visibility as LabelPreference + } } - return reqHeaders - } + prefs.moderationPrefs.labels = remapLegacyLabels( + prefs.moderationPrefs.labels, + ) - /** - * Internal fetch handler which adds access-token management - */ - private async _fetch( - reqUri: string, - reqMethod: string, - reqHeaders: Record, - reqBody: any, - ): Promise { - if (!AtpAgent.fetch) { - throw new Error('AtpAgent fetch() method not configured') - } + // automatically configure the client + this.configureLabelers(prefsArrayToLabelerDids(res.data.preferences)) - // wait for any active session-refreshes to finish - await this._refreshSessionPromise + return prefs + } - // send the request - let res = await AtpAgent.fetch( - reqUri, - reqMethod, - this._addHeaders(reqHeaders), - reqBody, + async overwriteSavedFeeds(savedFeeds: AppBskyActorDefs.SavedFeed[]) { + savedFeeds.forEach(validateSavedFeed) + const uniqueSavedFeeds = new Map() + savedFeeds.forEach((feed) => { + // remove and re-insert to preserve order + if (uniqueSavedFeeds.has(feed.id)) { + uniqueSavedFeeds.delete(feed.id) + } + uniqueSavedFeeds.set(feed.id, feed) + }) + return this.updateSavedFeedsV2Preferences(() => + Array.from(uniqueSavedFeeds.values()), ) + } - // handle session-refreshes as needed - if (isErrorResponse(res, ['ExpiredToken']) && this.session?.refreshJwt) { - // attempt refresh - await this.refreshSession() - - // resend the request with the new access token - res = await AtpAgent.fetch( - reqUri, - reqMethod, - this._addHeaders(reqHeaders), - reqBody, - ) - } + async updateSavedFeeds(savedFeedsToUpdate: AppBskyActorDefs.SavedFeed[]) { + savedFeedsToUpdate.map(validateSavedFeed) + return this.updateSavedFeedsV2Preferences((savedFeeds) => { + return savedFeeds.map((savedFeed) => { + const updatedVersion = savedFeedsToUpdate.find( + (updated) => savedFeed.id === updated.id, + ) + if (updatedVersion) { + return { + ...savedFeed, + // only update pinned + pinned: updatedVersion.pinned, + } + } + return savedFeed + }) + }) + } - return res + async addSavedFeeds( + savedFeeds: Pick[], + ) { + const toSave: AppBskyActorDefs.SavedFeed[] = savedFeeds.map((f) => ({ + ...f, + id: TID.nextStr(), + })) + toSave.forEach(validateSavedFeed) + return this.updateSavedFeedsV2Preferences((savedFeeds) => [ + ...savedFeeds, + ...toSave, + ]) + } + + async removeSavedFeeds(ids: string[]) { + return this.updateSavedFeedsV2Preferences((savedFeeds) => [ + ...savedFeeds.filter((feed) => !ids.find((id) => feed.id === id)), + ]) } /** - * Internal helper to refresh sessions - * - Wraps the actual implementation in a promise-guard to ensure only - * one refresh is attempted at a time. + * @deprecated use `overwriteSavedFeeds` */ - async refreshSession() { - if (this._refreshSessionPromise) { - return this._refreshSessionPromise - } - this._refreshSessionPromise = this._refreshSessionInner() - try { - await this._refreshSessionPromise - } finally { - this._refreshSessionPromise = undefined - } + async setSavedFeeds(saved: string[], pinned: string[]) { + return this.updateFeedPreferences(() => ({ + saved, + pinned, + })) } /** - * Internal helper to refresh sessions (actual behavior) + * @deprecated use `addSavedFeeds` */ - private async _refreshSessionInner() { - if (!AtpAgent.fetch) { - throw new Error('AtpAgent fetch() method not configured') - } - if (!this.session?.refreshJwt) { - return + async addSavedFeed(v: string) { + return this.updateFeedPreferences((saved: string[], pinned: string[]) => ({ + saved: [...saved.filter((uri) => uri !== v), v], + pinned, + })) + } + + /** + * @deprecated use `removeSavedFeeds` + */ + async removeSavedFeed(v: string) { + return this.updateFeedPreferences((saved: string[], pinned: string[]) => ({ + saved: saved.filter((uri) => uri !== v), + pinned: pinned.filter((uri) => uri !== v), + })) + } + + /** + * @deprecated use `addSavedFeeds` or `updateSavedFeeds` + */ + async addPinnedFeed(v: string) { + return this.updateFeedPreferences((saved: string[], pinned: string[]) => ({ + saved: [...saved.filter((uri) => uri !== v), v], + pinned: [...pinned.filter((uri) => uri !== v), v], + })) + } + + /** + * @deprecated use `updateSavedFeeds` or `removeSavedFeeds` + */ + async removePinnedFeed(v: string) { + return this.updateFeedPreferences((saved: string[], pinned: string[]) => ({ + saved, + pinned: pinned.filter((uri) => uri !== v), + })) + } + + async setAdultContentEnabled(v: boolean) { + await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { + let adultContentPref = prefs.findLast( + (pref) => + AppBskyActorDefs.isAdultContentPref(pref) && + AppBskyActorDefs.validateAdultContentPref(pref).success, + ) + if (adultContentPref) { + adultContentPref.enabled = v + } else { + adultContentPref = { + $type: 'app.bsky.actor.defs#adultContentPref', + enabled: v, + } + } + return prefs + .filter((pref) => !AppBskyActorDefs.isAdultContentPref(pref)) + .concat([adultContentPref]) + }) + } + + async setContentLabelPref( + key: string, + value: LabelPreference, + labelerDid?: string, + ) { + if (labelerDid) { + ensureValidDid(labelerDid) } + await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { + let labelPref = prefs.findLast( + (pref) => + AppBskyActorDefs.isContentLabelPref(pref) && + AppBskyActorDefs.validateContentLabelPref(pref).success && + pref.label === key && + pref.labelerDid === labelerDid, + ) + let legacyLabelPref: AppBskyActorDefs.ContentLabelPref | undefined - // send the refresh request - const url = new URL((this.pdsUrl || this.service).origin) - url.pathname = `/xrpc/${REFRESH_SESSION}` - const res = await AtpAgent.fetch( - url.toString(), - 'POST', - { - authorization: `Bearer ${this.session.refreshJwt}`, + if (labelPref) { + labelPref.visibility = value + } else { + labelPref = { + $type: 'app.bsky.actor.defs#contentLabelPref', + label: key, + labelerDid, + visibility: value, + } + } + + if (AppBskyActorDefs.isContentLabelPref(labelPref)) { + // is global + if (!labelPref.labelerDid) { + const legacyLabelValue = { + 'graphic-media': 'gore', + porn: 'nsfw', + sexual: 'suggestive', + }[labelPref.label] + + // if it's a legacy label, double-write the legacy label + if (legacyLabelValue) { + legacyLabelPref = prefs.findLast( + (pref) => + AppBskyActorDefs.isContentLabelPref(pref) && + AppBskyActorDefs.validateContentLabelPref(pref).success && + pref.label === legacyLabelValue && + pref.labelerDid === undefined, + ) as AppBskyActorDefs.ContentLabelPref | undefined + + if (legacyLabelPref) { + legacyLabelPref.visibility = value + } else { + legacyLabelPref = { + $type: 'app.bsky.actor.defs#contentLabelPref', + label: legacyLabelValue, + labelerDid: undefined, + visibility: value, + } + } + } + } + } + + return prefs + .filter( + (pref) => + !AppBskyActorDefs.isContentLabelPref(pref) || + !(pref.label === key && pref.labelerDid === labelerDid), + ) + .concat([labelPref]) + .filter((pref) => { + if (!legacyLabelPref) return true + return ( + !AppBskyActorDefs.isContentLabelPref(pref) || + !( + pref.label === legacyLabelPref.label && + pref.labelerDid === undefined + ) + ) + }) + .concat(legacyLabelPref ? [legacyLabelPref] : []) + }) + } + + async addLabeler(did: string) { + const prefs = await this.updatePreferences( + (prefs: AppBskyActorDefs.Preferences) => { + let labelersPref = prefs.findLast( + (pref) => + AppBskyActorDefs.isLabelersPref(pref) && + AppBskyActorDefs.validateLabelersPref(pref).success, + ) + if (!labelersPref) { + labelersPref = { + $type: 'app.bsky.actor.defs#labelersPref', + labelers: [], + } + } + if (AppBskyActorDefs.isLabelersPref(labelersPref)) { + let labelerPrefItem = labelersPref.labelers.find( + (labeler) => labeler.did === did, + ) + if (!labelerPrefItem) { + labelerPrefItem = { + did, + } + labelersPref.labelers.push(labelerPrefItem) + } + } + return prefs + .filter((pref) => !AppBskyActorDefs.isLabelersPref(pref)) + .concat([labelersPref]) }, - undefined, ) + // automatically configure the client + this.configureLabelers(prefsArrayToLabelerDids(prefs)) + } + + async removeLabeler(did: string) { + const prefs = await this.updatePreferences( + (prefs: AppBskyActorDefs.Preferences) => { + let labelersPref = prefs.findLast( + (pref) => + AppBskyActorDefs.isLabelersPref(pref) && + AppBskyActorDefs.validateLabelersPref(pref).success, + ) + if (!labelersPref) { + labelersPref = { + $type: 'app.bsky.actor.defs#labelersPref', + labelers: [], + } + } + if (AppBskyActorDefs.isLabelersPref(labelersPref)) { + labelersPref.labelers = labelersPref.labelers.filter( + (labeler) => labeler.did !== did, + ) + } + return prefs + .filter((pref) => !AppBskyActorDefs.isLabelersPref(pref)) + .concat([labelersPref]) + }, + ) + // automatically configure the client + this.configureLabelers(prefsArrayToLabelerDids(prefs)) + } + + async setPersonalDetails({ + birthDate, + }: { + birthDate: string | Date | undefined + }) { + birthDate = birthDate instanceof Date ? birthDate.toISOString() : birthDate + await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { + let personalDetailsPref = prefs.findLast( + (pref) => + AppBskyActorDefs.isPersonalDetailsPref(pref) && + AppBskyActorDefs.validatePersonalDetailsPref(pref).success, + ) + if (personalDetailsPref) { + personalDetailsPref.birthDate = birthDate + } else { + personalDetailsPref = { + $type: 'app.bsky.actor.defs#personalDetailsPref', + birthDate, + } + } + return prefs + .filter((pref) => !AppBskyActorDefs.isPersonalDetailsPref(pref)) + .concat([personalDetailsPref]) + }) + } - if (isErrorResponse(res, ['ExpiredToken', 'InvalidToken'])) { - // failed due to a bad refresh token - this.session = undefined - this._persistSession?.('expired', undefined) - } else if (isNewSessionObject(this._baseClient, res.body)) { - // succeeded, update the session - this.session = { - ...(this.session || {}), - accessJwt: res.body.accessJwt, - refreshJwt: res.body.refreshJwt, - handle: res.body.handle, - did: res.body.did, + async setFeedViewPrefs(feed: string, pref: Partial) { + await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { + const existing = prefs.findLast( + (pref) => + AppBskyActorDefs.isFeedViewPref(pref) && + AppBskyActorDefs.validateFeedViewPref(pref).success && + pref.feed === feed, + ) + if (existing) { + pref = { ...existing, ...pref } } - this._updateApiEndpoint(res.body.didDoc) - this._persistSession?.('update', this.session) - } - // else: other failures should be ignored - the issue will - // propagate in the _fetch() handler's second attempt to run - // the request + return prefs + .filter( + (p) => !AppBskyActorDefs.isFeedViewPref(pref) || p.feed !== feed, + ) + .concat([{ ...pref, $type: 'app.bsky.actor.defs#feedViewPref', feed }]) + }) + } + + async setThreadViewPrefs(pref: Partial) { + await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { + const existing = prefs.findLast( + (pref) => + AppBskyActorDefs.isThreadViewPref(pref) && + AppBskyActorDefs.validateThreadViewPref(pref).success, + ) + if (existing) { + pref = { ...existing, ...pref } + } + return prefs + .filter((p) => !AppBskyActorDefs.isThreadViewPref(p)) + .concat([{ ...pref, $type: 'app.bsky.actor.defs#threadViewPref' }]) + }) + } + + async setInterestsPref(pref: Partial) { + await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { + const existing = prefs.findLast( + (pref) => + AppBskyActorDefs.isInterestsPref(pref) && + AppBskyActorDefs.validateInterestsPref(pref).success, + ) + if (existing) { + pref = { ...existing, ...pref } + } + return prefs + .filter((p) => !AppBskyActorDefs.isInterestsPref(p)) + .concat([{ ...pref, $type: 'app.bsky.actor.defs#interestsPref' }]) + }) } /** - * Upload a binary blob to the server + * Add a muted word to user preferences. */ - uploadBlob: typeof this.api.com.atproto.repo.uploadBlob = (data, opts) => - this.api.com.atproto.repo.uploadBlob(data, opts) + async addMutedWord( + mutedWord: Pick< + MutedWord, + 'value' | 'targets' | 'actorTarget' | 'expiresAt' + >, + ) { + const sanitizedValue = sanitizeMutedWordValue(mutedWord.value) + + if (!sanitizedValue) return + + await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { + let mutedWordsPref = prefs.findLast( + (pref) => + AppBskyActorDefs.isMutedWordsPref(pref) && + AppBskyActorDefs.validateMutedWordsPref(pref).success, + ) + + const newMutedWord: AppBskyActorDefs.MutedWord = { + id: TID.nextStr(), + value: sanitizedValue, + targets: mutedWord.targets || [], + actorTarget: mutedWord.actorTarget || 'all', + expiresAt: mutedWord.expiresAt || undefined, + } + + if (mutedWordsPref && AppBskyActorDefs.isMutedWordsPref(mutedWordsPref)) { + mutedWordsPref.items.push(newMutedWord) + + /** + * Migrate any old muted words that don't have an id + */ + mutedWordsPref.items = migrateLegacyMutedWordsItems( + mutedWordsPref.items, + ) + } else { + // if the pref doesn't exist, create it + mutedWordsPref = { + items: [newMutedWord], + } + } + + return prefs + .filter((p) => !AppBskyActorDefs.isMutedWordsPref(p)) + .concat([ + { ...mutedWordsPref, $type: 'app.bsky.actor.defs#mutedWordsPref' }, + ]) + }) + } /** - * Resolve a handle to a DID + * Convenience method to add muted words to user preferences */ - resolveHandle: typeof this.api.com.atproto.identity.resolveHandle = ( - params, - opts, - ) => this.api.com.atproto.identity.resolveHandle(params, opts) + async addMutedWords(newMutedWords: AppBskyActorDefs.MutedWord[]) { + await Promise.all(newMutedWords.map((word) => this.addMutedWord(word))) + } /** - * Change the user's handle + * @deprecated use `addMutedWords` or `addMutedWord` instead */ - updateHandle: typeof this.api.com.atproto.identity.updateHandle = ( - data, - opts, - ) => this.api.com.atproto.identity.updateHandle(data, opts) + async upsertMutedWords( + mutedWords: Pick< + MutedWord, + 'value' | 'targets' | 'actorTarget' | 'expiresAt' + >[], + ) { + await this.addMutedWords(mutedWords) + } /** - * Create a moderation report + * Update a muted word in user preferences. + */ + async updateMutedWord(mutedWord: AppBskyActorDefs.MutedWord) { + await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { + const mutedWordsPref = prefs.findLast( + (pref) => + AppBskyActorDefs.isMutedWordsPref(pref) && + AppBskyActorDefs.validateMutedWordsPref(pref).success, + ) + + if (mutedWordsPref && AppBskyActorDefs.isMutedWordsPref(mutedWordsPref)) { + mutedWordsPref.items = mutedWordsPref.items.map((existingItem) => { + const match = matchMutedWord(existingItem, mutedWord) + + if (match) { + const updated = { + ...existingItem, + ...mutedWord, + } + return { + id: existingItem.id || TID.nextStr(), + value: + sanitizeMutedWordValue(updated.value) || existingItem.value, + targets: updated.targets || [], + actorTarget: updated.actorTarget || 'all', + expiresAt: updated.expiresAt || undefined, + } + } else { + return existingItem + } + }) + + /** + * Migrate any old muted words that don't have an id + */ + mutedWordsPref.items = migrateLegacyMutedWordsItems( + mutedWordsPref.items, + ) + + return prefs + .filter((p) => !AppBskyActorDefs.isMutedWordsPref(p)) + .concat([ + { ...mutedWordsPref, $type: 'app.bsky.actor.defs#mutedWordsPref' }, + ]) + } + + return prefs + }) + } + + /** + * Remove a muted word from user preferences. */ - createModerationReport: typeof this.api.com.atproto.moderation.createReport = - (data, opts) => this.api.com.atproto.moderation.createReport(data, opts) + async removeMutedWord(mutedWord: AppBskyActorDefs.MutedWord) { + await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { + const mutedWordsPref = prefs.findLast( + (pref) => + AppBskyActorDefs.isMutedWordsPref(pref) && + AppBskyActorDefs.validateMutedWordsPref(pref).success, + ) + + if (mutedWordsPref && AppBskyActorDefs.isMutedWordsPref(mutedWordsPref)) { + for (let i = 0; i < mutedWordsPref.items.length; i++) { + const match = matchMutedWord(mutedWordsPref.items[i], mutedWord) + + if (match) { + mutedWordsPref.items.splice(i, 1) + break + } + } + + /** + * Migrate any old muted words that don't have an id + */ + mutedWordsPref.items = migrateLegacyMutedWordsItems( + mutedWordsPref.items, + ) + + return prefs + .filter((p) => !AppBskyActorDefs.isMutedWordsPref(p)) + .concat([ + { ...mutedWordsPref, $type: 'app.bsky.actor.defs#mutedWordsPref' }, + ]) + } + + return prefs + }) + } /** - * Helper to update the pds endpoint dynamically. - * - * The session methods (create, resume, refresh) may respond with the user's - * did document which contains the user's canonical PDS endpoint. That endpoint - * may differ from the endpoint used to contact the server. We capture that - * PDS endpoint and update the client to use that given endpoint for future - * requests. (This helps ensure smooth migrations between PDSes, especially - * when the PDSes are operated by a single org.) + * Convenience method to remove muted words from user preferences */ - private _updateApiEndpoint(didDoc: unknown) { - if (isValidDidDoc(didDoc)) { - const endpoint = getPdsEndpoint(didDoc) - this.pdsUrl = endpoint ? new URL(endpoint) : undefined + async removeMutedWords(mutedWords: AppBskyActorDefs.MutedWord[]) { + await Promise.all(mutedWords.map((word) => this.removeMutedWord(word))) + } + + async hidePost(postUri: string) { + await this.updateHiddenPost(postUri, 'hide') + } + + async unhidePost(postUri: string) { + await this.updateHiddenPost(postUri, 'unhide') + } + + async bskyAppQueueNudges(nudges: string | string[]) { + await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { + let bskyAppStatePref: AppBskyActorDefs.BskyAppStatePref = prefs.findLast( + (pref) => + AppBskyActorDefs.isBskyAppStatePref(pref) && + AppBskyActorDefs.validateBskyAppStatePref(pref).success, + ) + + bskyAppStatePref = bskyAppStatePref || {} + nudges = Array.isArray(nudges) ? nudges : [nudges] + bskyAppStatePref.queuedNudges = ( + bskyAppStatePref.queuedNudges || [] + ).concat(nudges) + + return prefs + .filter((p) => !AppBskyActorDefs.isBskyAppStatePref(p)) + .concat([ + { + ...bskyAppStatePref, + $type: 'app.bsky.actor.defs#bskyAppStatePref', + }, + ]) + }) + } + + async bskyAppDismissNudges(nudges: string | string[]) { + await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { + let bskyAppStatePref: AppBskyActorDefs.BskyAppStatePref = prefs.findLast( + (pref) => + AppBskyActorDefs.isBskyAppStatePref(pref) && + AppBskyActorDefs.validateBskyAppStatePref(pref).success, + ) + + bskyAppStatePref = bskyAppStatePref || {} + nudges = Array.isArray(nudges) ? nudges : [nudges] + bskyAppStatePref.queuedNudges = ( + bskyAppStatePref.queuedNudges || [] + ).filter((nudge) => !nudges.includes(nudge)) + + return prefs + .filter((p) => !AppBskyActorDefs.isBskyAppStatePref(p)) + .concat([ + { + ...bskyAppStatePref, + $type: 'app.bsky.actor.defs#bskyAppStatePref', + }, + ]) + }) + } + + async bskyAppSetActiveProgressGuide( + guide: AppBskyActorDefs.BskyAppProgressGuide | undefined, + ) { + if ( + guide && + !AppBskyActorDefs.validateBskyAppProgressGuide(guide).success + ) { + throw new Error('Invalid progress guide') } - this.api.xrpc.uri = this.pdsUrl || this.service + + await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { + let bskyAppStatePref: AppBskyActorDefs.BskyAppStatePref = prefs.findLast( + (pref) => + AppBskyActorDefs.isBskyAppStatePref(pref) && + AppBskyActorDefs.validateBskyAppStatePref(pref).success, + ) + + bskyAppStatePref = bskyAppStatePref || {} + bskyAppStatePref.activeProgressGuide = guide + + return prefs + .filter((p) => !AppBskyActorDefs.isBskyAppStatePref(p)) + .concat([ + { + ...bskyAppStatePref, + $type: 'app.bsky.actor.defs#bskyAppStatePref', + }, + ]) + }) + } + + //- Private methods + + #prefsLock = new AwaitLock() + + /** + * This function updates the preferences of a user and allows for a callback function to be executed + * before the update. + * @param cb - cb is a callback function that takes in a single parameter of type + * AppBskyActorDefs.Preferences and returns either a boolean or void. This callback function is used to + * update the preferences of the user. The function is called with the current preferences as an + * argument and if the callback returns false, the preferences are not updated. + */ + private async updatePreferences( + cb: ( + prefs: AppBskyActorDefs.Preferences, + ) => AppBskyActorDefs.Preferences | false, + ) { + try { + await this.#prefsLock.acquireAsync() + const res = await this.app.bsky.actor.getPreferences({}) + const newPrefs = cb(res.data.preferences) + if (newPrefs === false) { + return res.data.preferences + } + await this.app.bsky.actor.putPreferences({ + preferences: newPrefs, + }) + return newPrefs + } finally { + this.#prefsLock.release() + } + } + + private async updateHiddenPost(postUri: string, action: 'hide' | 'unhide') { + await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { + let pref = prefs.findLast( + (pref) => + AppBskyActorDefs.isHiddenPostsPref(pref) && + AppBskyActorDefs.validateHiddenPostsPref(pref).success, + ) + if (pref && AppBskyActorDefs.isHiddenPostsPref(pref)) { + pref.items = + action === 'hide' + ? Array.from(new Set([...pref.items, postUri])) + : pref.items.filter((uri) => uri !== postUri) + } else { + if (action === 'hide') { + pref = { + $type: 'app.bsky.actor.defs#hiddenPostsPref', + items: [postUri], + } + } + } + return prefs + .filter((p) => !AppBskyActorDefs.isInterestsPref(p)) + .concat([{ ...pref, $type: 'app.bsky.actor.defs#hiddenPostsPref' }]) + }) } + + /** + * A helper specifically for updating feed preferences + */ + private async updateFeedPreferences( + cb: ( + saved: string[], + pinned: string[], + ) => { saved: string[]; pinned: string[] }, + ): Promise<{ saved: string[]; pinned: string[] }> { + let res + await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { + let feedsPref = prefs.findLast( + (pref) => + AppBskyActorDefs.isSavedFeedsPref(pref) && + AppBskyActorDefs.validateSavedFeedsPref(pref).success, + ) as AppBskyActorDefs.SavedFeedsPref | undefined + if (feedsPref) { + res = cb(feedsPref.saved, feedsPref.pinned) + feedsPref.saved = res.saved + feedsPref.pinned = res.pinned + } else { + res = cb([], []) + feedsPref = { + $type: 'app.bsky.actor.defs#savedFeedsPref', + saved: res.saved, + pinned: res.pinned, + } + } + return prefs + .filter((pref) => !AppBskyActorDefs.isSavedFeedsPref(pref)) + .concat([feedsPref]) + }) + return res + } + + private async updateSavedFeedsV2Preferences( + cb: ( + savedFeedsPref: AppBskyActorDefs.SavedFeed[], + ) => AppBskyActorDefs.SavedFeed[], + ): Promise { + let maybeMutatedSavedFeeds: AppBskyActorDefs.SavedFeed[] = [] + + await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { + let existingV2Pref = prefs.findLast( + (pref) => + AppBskyActorDefs.isSavedFeedsPrefV2(pref) && + AppBskyActorDefs.validateSavedFeedsPrefV2(pref).success, + ) as AppBskyActorDefs.SavedFeedsPrefV2 | undefined + let existingV1Pref = prefs.findLast( + (pref) => + AppBskyActorDefs.isSavedFeedsPref(pref) && + AppBskyActorDefs.validateSavedFeedsPref(pref).success, + ) as AppBskyActorDefs.SavedFeedsPref | undefined + + if (existingV2Pref) { + maybeMutatedSavedFeeds = cb(existingV2Pref.items) + existingV2Pref = { + ...existingV2Pref, + items: maybeMutatedSavedFeeds, + } + } else { + maybeMutatedSavedFeeds = cb([]) + existingV2Pref = { + $type: 'app.bsky.actor.defs#savedFeedsPrefV2', + items: maybeMutatedSavedFeeds, + } + } + + // enforce ordering, pinned then saved + const pinned = existingV2Pref.items.filter((i) => i.pinned) + const saved = existingV2Pref.items.filter((i) => !i.pinned) + existingV2Pref.items = pinned.concat(saved) + + let updatedPrefs = prefs + .filter((pref) => !AppBskyActorDefs.isSavedFeedsPrefV2(pref)) + .concat(existingV2Pref) + + /* + * If there's a v2 pref present, it means this account was migrated from v1 + * to v2. During the transition period, we double write v2 prefs back to + * v1, but NOT the other way around. + */ + if (existingV1Pref) { + const { saved, pinned } = existingV1Pref + const v2Compat = savedFeedsToUriArrays( + // v1 only supports feeds and lists + existingV2Pref.items.filter((i) => ['feed', 'list'].includes(i.type)), + ) + existingV1Pref = { + ...existingV1Pref, + saved: Array.from(new Set([...saved, ...v2Compat.saved])), + pinned: Array.from(new Set([...pinned, ...v2Compat.pinned])), + } + updatedPrefs = updatedPrefs + .filter((pref) => !AppBskyActorDefs.isSavedFeedsPref(pref)) + .concat(existingV1Pref) + } + + return updatedPrefs + }) + + return maybeMutatedSavedFeeds + } + + //#endregion } -function isErrorObject(v: unknown): v is ErrorResponseBody { - return errorResponseBody.safeParse(v).success +/** + * Helper to transform the legacy content preferences. + */ +function adjustLegacyContentLabelPref( + pref: AppBskyActorDefs.ContentLabelPref, +): AppBskyActorDefs.ContentLabelPref { + let visibility = pref.visibility + + // adjust legacy values + if (visibility === 'show') { + visibility = 'ignore' + } + + return { ...pref, visibility } } -function isErrorResponse( - res: AtpAgentFetchHandlerResponse, - errorNames: string[], -): boolean { - if (res.status !== 400) { - return false +/** + * Re-maps legacy labels to new labels on READ. Does not save these changes to + * the user's preferences. + */ +function remapLegacyLabels( + labels: BskyPreferences['moderationPrefs']['labels'], +) { + const _labels = { ...labels } + const legacyToNewMap: Record = { + gore: 'graphic-media', + nsfw: 'porn', + suggestive: 'sexual', } - if (!isErrorObject(res.body)) { - return false + + for (const labelName in _labels) { + const newLabelName = legacyToNewMap[labelName]! + if (newLabelName) { + _labels[newLabelName] = _labels[labelName] + } } - return ( - typeof res.body.error === 'string' && errorNames.includes(res.body.error) - ) + + return _labels } -function isNewSessionObject( - client: AtpBaseClient, - v: unknown, -): v is ComAtprotoServerRefreshSession.OutputSchema { - try { - client.xrpc.lex.assertValidXrpcOutput( - 'com.atproto.server.refreshSession', - v, +/** + * A helper to get the currently enabled labelers from the full preferences array + */ +function prefsArrayToLabelerDids( + prefs: AppBskyActorDefs.Preferences, +): string[] { + const labelersPref = prefs.findLast( + (pref) => + AppBskyActorDefs.isLabelersPref(pref) && + AppBskyActorDefs.validateLabelersPref(pref).success, + ) + let dids: string[] = [] + if (labelersPref) { + dids = (labelersPref as AppBskyActorDefs.LabelersPref).labelers.map( + (labeler) => labeler.did, ) - return true - } catch { - return false } + return dids +} + +function isBskyPrefs(v: any): v is BskyPreferences { + return ( + v && + typeof v === 'object' && + 'moderationPrefs' in v && + isModPrefs(v.moderationPrefs) + ) +} + +function isModPrefs(v: any): v is ModerationPrefs { + return v && typeof v === 'object' && 'labelers' in v +} + +function migrateLegacyMutedWordsItems(items: AppBskyActorDefs.MutedWord[]) { + return items.map((item) => ({ + ...item, + id: item.id || TID.nextStr(), + })) +} + +function matchMutedWord( + existingWord: AppBskyActorDefs.MutedWord, + newWord: AppBskyActorDefs.MutedWord, +): boolean { + // id is undefined in legacy implementation + const existingId = existingWord.id + // prefer matching based on id + const matchById = existingId && existingId === newWord.id + // handle legacy case where id is not set + const legacyMatchByValue = !existingId && existingWord.value === newWord.value + + return matchById || legacyMatchByValue } diff --git a/packages/api/src/atp-agent.ts b/packages/api/src/atp-agent.ts new file mode 100644 index 00000000000..1495c9423e3 --- /dev/null +++ b/packages/api/src/atp-agent.ts @@ -0,0 +1,515 @@ +import { getPdsEndpoint, isValidDidDoc } from '@atproto/common-web' +import { + ErrorResponseBody, + Gettable, + ResponseType, + XRPCError, + combineHeaders, + errorResponseBody, +} from '@atproto/xrpc' +import { Agent } from './agent' +import { + AtpBaseClient, + ComAtprotoServerCreateAccount, + ComAtprotoServerCreateSession, + ComAtprotoServerGetSession, +} from './client' +import { + AtpAgentLoginOpts, + AtpPersistSessionHandler, + AtpSessionData, +} from './types' + +const ReadableStream = globalThis.ReadableStream as + | typeof globalThis.ReadableStream + | undefined + +export type AtpAgentOptions = { + service: string | URL + persistSession?: AtpPersistSessionHandler + fetch?: typeof globalThis.fetch + headers?: Iterable<[string, Gettable]> +} + +/** + * An {@link AtpAgent} extends the {@link Agent} abstract class by + * implementing password based session management. + */ +export class AtpAgent extends Agent { + public readonly headers: Map> + public readonly sessionManager: SessionManager + + constructor(options: AtpAgentOptions | SessionManager) { + super(async (url: string, init?: RequestInit): Promise => { + // wait for any active session-refreshes to finish + await this.sessionManager.refreshSessionPromise + + const initialHeaders = combineHeaders(init?.headers, this.headers) + const reqInit = { ...init, headers: initialHeaders } + + const initialUri = new URL(url, this.dispatchUrl) + const initialReq = new Request(initialUri, reqInit) + + const initialToken = this.session?.accessJwt + if (!initialToken || initialReq.headers.has('authorization')) { + return (0, this.sessionManager.fetch)(initialReq) + } + + initialReq.headers.set('authorization', `Bearer ${initialToken}`) + const initialRes = await (0, this.sessionManager.fetch)(initialReq) + + if (!this.session?.refreshJwt) { + return initialRes + } + const isExpiredToken = await isErrorResponse( + initialRes, + [400], + ['ExpiredToken'], + ) + + if (!isExpiredToken) { + return initialRes + } + + try { + await this.sessionManager.refreshSession() + } catch { + return initialRes + } + + if (reqInit?.signal?.aborted) { + return initialRes + } + + // The stream was already consumed. We cannot retry the request. A solution + // would be to tee() the input stream but that would bufferize the entire + // stream in memory which can lead to memory starvation. Instead, we will + // return the original response and let the calling code handle retries. + if (ReadableStream && reqInit.body instanceof ReadableStream) { + return initialRes + } + + // Return initial "ExpiredToken" response if the session was not refreshed. + const updatedToken = this.session?.accessJwt + if (!updatedToken || updatedToken === initialToken) { + return initialRes + } + + // Make sure the initial request is cancelled to avoid leaking resources + // (NodeJS 👀): https://undici.nodejs.org/#/?id=garbage-collection + await initialRes.body?.cancel() + + // We need to re-compute the URI in case the PDS endpoint has changed + const updatedUri = new URL(url, this.dispatchUrl) + const updatedReq = new Request(updatedUri, reqInit) + + updatedReq.headers.set('authorization', `Bearer ${updatedToken}`) + + return await (0, this.sessionManager.fetch)(updatedReq) + }) + + if (options instanceof SessionManager) { + this.headers = new Map() + this.sessionManager = options + } else { + this.headers = new Map(options.headers) + this.sessionManager = new SessionManager( + new URL(options.service), + options.fetch, + options.persistSession, + ) + } + } + + clone(): AtpAgent { + return this.copyInto(new AtpAgent(this.sessionManager)) + } + + copyInto(inst: T): T { + if (inst instanceof AtpAgent) { + for (const [key] of inst.headers) { + inst.unsetHeader(key) + } + for (const [key, value] of this.headers) { + inst.setHeader(key, value) + } + } + return super.copyInto(inst) + } + + setHeader(key: string, value: Gettable): void { + this.headers.set(key.toLowerCase(), value) + } + + unsetHeader(key: string): void { + this.headers.delete(key.toLowerCase()) + } + + get session() { + return this.sessionManager.session + } + + get hasSession() { + return !!this.session + } + + get did() { + return this.session?.did + } + + get serviceUrl() { + return this.sessionManager.serviceUrl + } + + get pdsUrl() { + return this.sessionManager.pdsUrl + } + + get dispatchUrl() { + return this.pdsUrl || this.serviceUrl + } + + /** @deprecated use {@link serviceUrl} instead */ + get service() { + return this.serviceUrl + } + + get persistSession() { + throw new Error( + 'Cannot set persistSession directly. "persistSession" is defined through the constructor and will be invoked automatically when session data changes.', + ) + } + + set persistSession(v: unknown) { + throw new Error( + 'Cannot set persistSession directly. "persistSession" must be defined in the constructor and can no longer be changed.', + ) + } + + /** @deprecated This will be removed in OAuthAtpAgent */ + getServiceUrl() { + return this.serviceUrl + } + + async resumeSession( + session: AtpSessionData, + ): Promise { + return this.sessionManager.resumeSession(session) + } + + async createAccount( + data: ComAtprotoServerCreateAccount.InputSchema, + opts?: ComAtprotoServerCreateAccount.CallOptions, + ): Promise { + return this.sessionManager.createAccount(data, opts) + } + + async login( + opts: AtpAgentLoginOpts, + ): Promise { + return this.sessionManager.login(opts) + } + + async logout(): Promise { + return this.sessionManager.logout() + } +} + +/** + * Private class meant to be used by clones of {@link AtpAgent} so they can + * share the same session across multiple instances (with different + * proxying/labelers/headers options). + */ +class SessionManager { + public pdsUrl?: URL // The PDS URL, driven by the did doc + public session?: AtpSessionData + public refreshSessionPromise: Promise | undefined + + /** + * Private {@link AtpBaseClient} used to perform session management API + * calls on the service endpoint. Calls performed by this agent will not be + * authenticated using the user's session. + */ + protected api = new AtpBaseClient((url, init) => { + return (0, this.fetch)(new URL(url, this.serviceUrl), init) + }) + + constructor( + public readonly serviceUrl: URL, + public fetch = globalThis.fetch, + protected readonly persistSession?: AtpPersistSessionHandler, + ) {} + + /** + * Sets a WhatWG "fetch()" function to be used for making HTTP requests. + */ + setFetch(fetch = globalThis.fetch) { + this.fetch = fetch + } + + /** + * Create a new account and hydrate its session in this agent. + */ + async createAccount( + data: ComAtprotoServerCreateAccount.InputSchema, + opts?: ComAtprotoServerCreateAccount.CallOptions, + ): Promise { + try { + const res = await this.api.com.atproto.server.createAccount(data, opts) + this.session = { + accessJwt: res.data.accessJwt, + refreshJwt: res.data.refreshJwt, + handle: res.data.handle, + did: res.data.did, + email: data.email, + emailConfirmed: false, + emailAuthFactor: false, + active: true, + } + this.persistSession?.('create', this.session) + this._updateApiEndpoint(res.data.didDoc) + return res + } catch (e) { + this.session = undefined + this.persistSession?.('create-failed', undefined) + throw e + } + } + + /** + * Start a new session with this agent. + */ + async login( + opts: AtpAgentLoginOpts, + ): Promise { + try { + const res = await this.api.com.atproto.server.createSession({ + identifier: opts.identifier, + password: opts.password, + authFactorToken: opts.authFactorToken, + }) + this.session = { + accessJwt: res.data.accessJwt, + refreshJwt: res.data.refreshJwt, + handle: res.data.handle, + did: res.data.did, + email: res.data.email, + emailConfirmed: res.data.emailConfirmed, + emailAuthFactor: res.data.emailAuthFactor, + active: res.data.active ?? true, + status: res.data.status, + } + this._updateApiEndpoint(res.data.didDoc) + this.persistSession?.('create', this.session) + return res + } catch (e) { + this.session = undefined + this.persistSession?.('create-failed', undefined) + throw e + } + } + + async logout(): Promise { + if (this.session) { + try { + await this.api.com.atproto.server.deleteSession(undefined, { + headers: { + authorization: `Bearer ${this.session.accessJwt}`, + }, + }) + } catch { + // Ignore errors + } finally { + this.session = undefined + this.persistSession?.('expired', undefined) + } + } + } + + /** + * Resume a pre-existing session with this agent. + */ + async resumeSession( + session: AtpSessionData, + ): Promise { + this.session = session + + try { + const res = await this.api.com.atproto.server + .getSession(undefined, { + headers: { authorization: `Bearer ${session.accessJwt}` }, + }) + .catch(async (err) => { + if ( + err instanceof XRPCError && + ['ExpiredToken', 'InvalidToken'].includes(err.error) && + session.refreshJwt + ) { + try { + const res = await this.api.com.atproto.server.refreshSession( + undefined, + { headers: { authorization: `Bearer ${session.refreshJwt}` } }, + ) + + session.accessJwt = res.data.accessJwt + session.refreshJwt = res.data.refreshJwt + + return this.api.com.atproto.server.getSession(undefined, { + headers: { authorization: `Bearer ${session.accessJwt}` }, + }) + } catch { + // Noop, we'll throw the original error + } + } + throw err + }) + + if (res.data.did !== session.did) { + throw new XRPCError( + ResponseType.InvalidRequest, + 'Invalid session', + 'InvalidDID', + ) + } + + session.email = res.data.email + session.handle = res.data.handle + session.emailConfirmed = res.data.emailConfirmed + session.emailAuthFactor = res.data.emailAuthFactor + session.active = res.data.active ?? true + session.status = res.data.status + + // protect against concurrent session updates + if (this.session === session) { + this._updateApiEndpoint(res.data.didDoc) + this.persistSession?.('update', session) + } + + return res + } catch (err) { + // protect against concurrent session updates + if (this.session === session) { + this.session = undefined + this.persistSession?.( + err instanceof XRPCError && + ['ExpiredToken', 'InvalidToken'].includes(err.error) + ? 'expired' + : 'network-error', + undefined, + ) + } + + throw err + } + } + + /** + * Internal helper to refresh sessions + * - Wraps the actual implementation in a promise-guard to ensure only + * one refresh is attempted at a time. + */ + async refreshSession(): Promise { + return (this.refreshSessionPromise ||= this._refreshSessionInner().finally( + () => { + this.refreshSessionPromise = undefined + }, + )) + } + + /** + * Internal helper to refresh sessions (actual behavior) + */ + private async _refreshSessionInner() { + if (!this.session?.refreshJwt) { + return + } + + try { + const res = await this.api.com.atproto.server.refreshSession(undefined, { + headers: { authorization: `Bearer ${this.session.refreshJwt}` }, + }) + // succeeded, update the session + this.session = { + ...this.session, + accessJwt: res.data.accessJwt, + refreshJwt: res.data.refreshJwt, + handle: res.data.handle, + did: res.data.did, + } + this._updateApiEndpoint(res.data.didDoc) + this.persistSession?.('update', this.session) + } catch (err) { + if ( + err instanceof XRPCError && + err.error && + ['ExpiredToken', 'InvalidToken'].includes(err.error) + ) { + // failed due to a bad refresh token + this.session = undefined + this.persistSession?.('expired', undefined) + } + // else: other failures should be ignored - the issue will + // propagate in the _dispatch() second attempt to run + // the request + } + } + + /** + * Helper to update the pds endpoint dynamically. + * + * The session methods (create, resume, refresh) may respond with the user's + * did document which contains the user's canonical PDS endpoint. That endpoint + * may differ from the endpoint used to contact the server. We capture that + * PDS endpoint and update the client to use that given endpoint for future + * requests. (This helps ensure smooth migrations between PDSes, especially + * when the PDSes are operated by a single org.) + */ + private _updateApiEndpoint(didDoc: unknown) { + if (isValidDidDoc(didDoc)) { + const endpoint = getPdsEndpoint(didDoc) + this.pdsUrl = endpoint ? new URL(endpoint) : undefined + } else { + // If the did doc is invalid, we clear the pdsUrl (should never happen) + this.pdsUrl = undefined + } + } +} + +function isErrorObject(v: unknown): v is ErrorResponseBody { + return errorResponseBody.safeParse(v).success +} + +async function isErrorResponse( + response: Response, + status: number[], + errorNames: string[], +): Promise { + if (!status.includes(response.status)) return false + // Some engines (react-native 👀) don't expose a response.body property... + // if (!response.body) return false + try { + const json = await peekJson(response, 10 * 1024) + return isErrorObject(json) && (errorNames as any[]).includes(json.error) + } catch (err) { + return false + } +} + +async function peekJson( + response: Response, + maxSize = Infinity, +): Promise { + if (extractType(response) !== 'application/json') throw new Error('Not JSON') + if (extractLength(response) > maxSize) throw new Error('Response too large') + return response.clone().json() +} + +function extractLength({ headers }: Response) { + return headers.get('Content-Length') + ? Number(headers.get('Content-Length')) + : NaN +} + +function extractType({ headers }: Response) { + return headers.get('Content-Type')?.split(';')[0]?.trim() +} diff --git a/packages/api/src/bsky-agent.ts b/packages/api/src/bsky-agent.ts index b32390f6f66..5091101afcf 100644 --- a/packages/api/src/bsky-agent.ts +++ b/packages/api/src/bsky-agent.ts @@ -1,1465 +1,14 @@ -import { AtUri, ensureValidDid } from '@atproto/syntax' -import { TID } from '@atproto/common-web' -import AwaitLock from 'await-lock' -import { AtpAgent } from './agent' -import { - AppBskyFeedPost, - AppBskyActorProfile, - AppBskyActorDefs, - AppBskyLabelerDefs, - ComAtprotoRepoPutRecord, -} from './client' -import { MutedWord } from './client/types/app/bsky/actor/defs' -import { - BskyPreferences, - BskyFeedViewPreference, - BskyThreadViewPreference, - BskyInterestsPreference, -} from './types' -import { - InterpretedLabelValueDefinition, - LabelPreference, - ModerationPrefs, -} from './moderation/types' -import { DEFAULT_LABEL_SETTINGS } from './moderation/const/labels' -import { - sanitizeMutedWordValue, - validateSavedFeed, - savedFeedsToUriArrays, - getSavedFeedType, -} from './util' -import { interpretLabelValueDefinitions } from './moderation' - -const FEED_VIEW_PREF_DEFAULTS = { - hideReplies: false, - hideRepliesByUnfollowed: true, - hideRepliesByLikeCount: 0, - hideReposts: false, - hideQuotePosts: false, -} -const THREAD_VIEW_PREF_DEFAULTS = { - sort: 'oldest', - prioritizeFollowedUsers: true, -} - -declare global { - interface Array { - findLast( - predicate: (value: T, index: number, obj: T[]) => unknown, - thisArg?: any, - ): T - } -} +import { AtpAgent } from './atp-agent' +/** @deprecated use {@link AtpAgent} instead */ export class BskyAgent extends AtpAgent { - _prefsLock = new AwaitLock() - - clone() { - const inst = new BskyAgent({ - service: this.service, - }) - this.copyInto(inst) - return inst - } - - get app() { - return this.api.app - } - - getTimeline: typeof this.api.app.bsky.feed.getTimeline = (params, opts) => - this.api.app.bsky.feed.getTimeline(params, opts) - - getAuthorFeed: typeof this.api.app.bsky.feed.getAuthorFeed = (params, opts) => - this.api.app.bsky.feed.getAuthorFeed(params, opts) - - getActorLikes: typeof this.api.app.bsky.feed.getActorLikes = (params, opts) => - this.api.app.bsky.feed.getActorLikes(params, opts) - - getPostThread: typeof this.api.app.bsky.feed.getPostThread = (params, opts) => - this.api.app.bsky.feed.getPostThread(params, opts) - - getPost: typeof this.api.app.bsky.feed.post.get = (params) => - this.api.app.bsky.feed.post.get(params) - - getPosts: typeof this.api.app.bsky.feed.getPosts = (params, opts) => - this.api.app.bsky.feed.getPosts(params, opts) - - getLikes: typeof this.api.app.bsky.feed.getLikes = (params, opts) => - this.api.app.bsky.feed.getLikes(params, opts) - - getRepostedBy: typeof this.api.app.bsky.feed.getRepostedBy = (params, opts) => - this.api.app.bsky.feed.getRepostedBy(params, opts) - - getFollows: typeof this.api.app.bsky.graph.getFollows = (params, opts) => - this.api.app.bsky.graph.getFollows(params, opts) - - getFollowers: typeof this.api.app.bsky.graph.getFollowers = (params, opts) => - this.api.app.bsky.graph.getFollowers(params, opts) - - getProfile: typeof this.api.app.bsky.actor.getProfile = (params, opts) => - this.api.app.bsky.actor.getProfile(params, opts) - - getProfiles: typeof this.api.app.bsky.actor.getProfiles = (params, opts) => - this.api.app.bsky.actor.getProfiles(params, opts) - - getSuggestions: typeof this.api.app.bsky.actor.getSuggestions = ( - params, - opts, - ) => this.api.app.bsky.actor.getSuggestions(params, opts) - - searchActors: typeof this.api.app.bsky.actor.searchActors = (params, opts) => - this.api.app.bsky.actor.searchActors(params, opts) - - searchActorsTypeahead: typeof this.api.app.bsky.actor.searchActorsTypeahead = - (params, opts) => - this.api.app.bsky.actor.searchActorsTypeahead(params, opts) - - listNotifications: typeof this.api.app.bsky.notification.listNotifications = ( - params, - opts, - ) => this.api.app.bsky.notification.listNotifications(params, opts) - - countUnreadNotifications: typeof this.api.app.bsky.notification.getUnreadCount = - (params, opts) => - this.api.app.bsky.notification.getUnreadCount(params, opts) - - getLabelers: typeof this.api.app.bsky.labeler.getServices = (params, opts) => - this.api.app.bsky.labeler.getServices(params, opts) - - async getLabelDefinitions( - prefs: BskyPreferences | ModerationPrefs | string[], - ): Promise> { - // collect the labeler dids - let dids: string[] = BskyAgent.appLabelers - if (isBskyPrefs(prefs)) { - dids = dids.concat(prefs.moderationPrefs.labelers.map((l) => l.did)) - } else if (isModPrefs(prefs)) { - dids = dids.concat(prefs.labelers.map((l) => l.did)) - } else { - dids = dids.concat(prefs) - } - - // fetch their definitions - const labelers = await this.getLabelers({ - dids, - detailed: true, - }) - - // assemble a map of labeler dids to the interpretted label value definitions - const labelDefs = {} - if (labelers.data) { - for (const labeler of labelers.data - .views as AppBskyLabelerDefs.LabelerViewDetailed[]) { - labelDefs[labeler.creator.did] = interpretLabelValueDefinitions(labeler) - } - } - - return labelDefs - } - - async post( - record: Partial & - Omit, - ) { - if (!this.session) { - throw new Error('Not logged in') - } - record.createdAt = record.createdAt || new Date().toISOString() - return this.api.app.bsky.feed.post.create( - { repo: this.session.did }, - record as AppBskyFeedPost.Record, - ) - } - - async deletePost(postUri: string) { - if (!this.session) { - throw new Error('Not logged in') - } - const postUrip = new AtUri(postUri) - return await this.api.app.bsky.feed.post.delete({ - repo: postUrip.hostname, - rkey: postUrip.rkey, - }) - } - - async like(uri: string, cid: string) { - if (!this.session) { - throw new Error('Not logged in') - } - return await this.api.app.bsky.feed.like.create( - { repo: this.session.did }, - { - subject: { uri, cid }, - createdAt: new Date().toISOString(), - }, - ) - } - - async deleteLike(likeUri: string) { - if (!this.session) { - throw new Error('Not logged in') - } - const likeUrip = new AtUri(likeUri) - return await this.api.app.bsky.feed.like.delete({ - repo: likeUrip.hostname, - rkey: likeUrip.rkey, - }) - } - - async repost(uri: string, cid: string) { - if (!this.session) { - throw new Error('Not logged in') - } - return await this.api.app.bsky.feed.repost.create( - { repo: this.session.did }, - { - subject: { uri, cid }, - createdAt: new Date().toISOString(), - }, - ) - } - - async deleteRepost(repostUri: string) { - if (!this.session) { - throw new Error('Not logged in') - } - const repostUrip = new AtUri(repostUri) - return await this.api.app.bsky.feed.repost.delete({ - repo: repostUrip.hostname, - rkey: repostUrip.rkey, - }) - } - - async follow(subjectDid: string) { - if (!this.session) { - throw new Error('Not logged in') - } - return await this.api.app.bsky.graph.follow.create( - { repo: this.session.did }, - { - subject: subjectDid, - createdAt: new Date().toISOString(), - }, - ) - } - - async deleteFollow(followUri: string) { - if (!this.session) { - throw new Error('Not logged in') - } - const followUrip = new AtUri(followUri) - return await this.api.app.bsky.graph.follow.delete({ - repo: followUrip.hostname, - rkey: followUrip.rkey, - }) - } - - async upsertProfile( - updateFn: ( - existing: AppBskyActorProfile.Record | undefined, - ) => AppBskyActorProfile.Record | Promise, - ) { - if (!this.session) { - throw new Error('Not logged in') - } - - let retriesRemaining = 5 - while (retriesRemaining >= 0) { - // fetch existing - const existing = await this.com.atproto.repo - .getRecord({ - repo: this.session.did, - collection: 'app.bsky.actor.profile', - rkey: 'self', - }) - .catch((_) => undefined) - - // run the update - const updated = await updateFn(existing?.data.value) - if (updated) { - updated.$type = 'app.bsky.actor.profile' - } - - // validate the record - const validation = AppBskyActorProfile.validateRecord(updated) - if (!validation.success) { - throw validation.error - } - - try { - // attempt the put - await this.com.atproto.repo.putRecord({ - repo: this.session.did, - collection: 'app.bsky.actor.profile', - rkey: 'self', - record: updated, - swapRecord: existing?.data.cid || null, - }) - } catch (e: unknown) { - if ( - retriesRemaining > 0 && - e instanceof ComAtprotoRepoPutRecord.InvalidSwapError - ) { - // try again - retriesRemaining-- - continue - } else { - throw e - } - } - break - } - } - - async mute(actor: string) { - return this.api.app.bsky.graph.muteActor({ actor }) - } - - async unmute(actor: string) { - return this.api.app.bsky.graph.unmuteActor({ actor }) - } - - async muteModList(uri: string) { - return this.api.app.bsky.graph.muteActorList({ - list: uri, - }) - } - - async unmuteModList(uri: string) { - return this.api.app.bsky.graph.unmuteActorList({ - list: uri, - }) - } - - async blockModList(uri: string) { - if (!this.session) { - throw new Error('Not logged in') - } - return await this.api.app.bsky.graph.listblock.create( - { repo: this.session.did }, - { - subject: uri, - createdAt: new Date().toISOString(), - }, - ) - } - - async unblockModList(uri: string) { - if (!this.session) { - throw new Error('Not logged in') - } - const listInfo = await this.api.app.bsky.graph.getList({ - list: uri, - limit: 1, - }) - if (!listInfo.data.list.viewer?.blocked) { - return - } - const { rkey } = new AtUri(listInfo.data.list.viewer.blocked) - return await this.api.app.bsky.graph.listblock.delete({ - repo: this.session.did, - rkey, - }) - } - - async updateSeenNotifications(seenAt?: string) { - seenAt = seenAt || new Date().toISOString() - return this.api.app.bsky.notification.updateSeen({ - seenAt, - }) - } - - async getPreferences(): Promise { - const prefs: BskyPreferences = { - feeds: { - saved: undefined, - pinned: undefined, - }, - // @ts-ignore populating below - savedFeeds: undefined, - feedViewPrefs: { - home: { - ...FEED_VIEW_PREF_DEFAULTS, - }, - }, - threadViewPrefs: { ...THREAD_VIEW_PREF_DEFAULTS }, - moderationPrefs: { - adultContentEnabled: false, - labels: { ...DEFAULT_LABEL_SETTINGS }, - labelers: BskyAgent.appLabelers.map((did) => ({ did, labels: {} })), - mutedWords: [], - hiddenPosts: [], - }, - birthDate: undefined, - interests: { - tags: [], - }, - bskyAppState: { - queuedNudges: [], - activeProgressGuide: undefined, - }, - } - const res = await this.app.bsky.actor.getPreferences({}) - const labelPrefs: AppBskyActorDefs.ContentLabelPref[] = [] - for (const pref of res.data.preferences) { - if ( - AppBskyActorDefs.isAdultContentPref(pref) && - AppBskyActorDefs.validateAdultContentPref(pref).success - ) { - // adult content preferences - prefs.moderationPrefs.adultContentEnabled = pref.enabled - } else if ( - AppBskyActorDefs.isContentLabelPref(pref) && - AppBskyActorDefs.validateContentLabelPref(pref).success - ) { - // content label preference - const adjustedPref = adjustLegacyContentLabelPref(pref) - labelPrefs.push(adjustedPref) - } else if ( - AppBskyActorDefs.isLabelersPref(pref) && - AppBskyActorDefs.validateLabelersPref(pref).success - ) { - // labelers preferences - prefs.moderationPrefs.labelers = BskyAgent.appLabelers - .map((did) => ({ did, labels: {} })) - .concat( - pref.labelers.map((labeler) => ({ - ...labeler, - labels: {}, - })), - ) - } else if ( - AppBskyActorDefs.isSavedFeedsPrefV2(pref) && - AppBskyActorDefs.validateSavedFeedsPrefV2(pref).success - ) { - prefs.savedFeeds = pref.items - } else if ( - AppBskyActorDefs.isSavedFeedsPref(pref) && - AppBskyActorDefs.validateSavedFeedsPref(pref).success - ) { - // saved and pinned feeds - prefs.feeds.saved = pref.saved - prefs.feeds.pinned = pref.pinned - } else if ( - AppBskyActorDefs.isPersonalDetailsPref(pref) && - AppBskyActorDefs.validatePersonalDetailsPref(pref).success - ) { - // birth date (irl) - if (pref.birthDate) { - prefs.birthDate = new Date(pref.birthDate) - } - } else if ( - AppBskyActorDefs.isFeedViewPref(pref) && - AppBskyActorDefs.validateFeedViewPref(pref).success - ) { - // feed view preferences - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { $type, feed, ...v } = pref - prefs.feedViewPrefs[pref.feed] = { ...FEED_VIEW_PREF_DEFAULTS, ...v } - } else if ( - AppBskyActorDefs.isThreadViewPref(pref) && - AppBskyActorDefs.validateThreadViewPref(pref).success - ) { - // thread view preferences - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { $type, ...v } = pref - prefs.threadViewPrefs = { ...prefs.threadViewPrefs, ...v } - } else if ( - AppBskyActorDefs.isInterestsPref(pref) && - AppBskyActorDefs.validateInterestsPref(pref).success - ) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { $type, ...v } = pref - prefs.interests = { ...prefs.interests, ...v } - } else if ( - AppBskyActorDefs.isMutedWordsPref(pref) && - AppBskyActorDefs.validateMutedWordsPref(pref).success - ) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { $type, ...v } = pref - prefs.moderationPrefs.mutedWords = v.items - - if (prefs.moderationPrefs.mutedWords.length) { - prefs.moderationPrefs.mutedWords = - prefs.moderationPrefs.mutedWords.map((word) => { - word.actorTarget = word.actorTarget || 'all' - return word - }) - } - } else if ( - AppBskyActorDefs.isHiddenPostsPref(pref) && - AppBskyActorDefs.validateHiddenPostsPref(pref).success - ) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { $type, ...v } = pref - prefs.moderationPrefs.hiddenPosts = v.items - } else if ( - AppBskyActorDefs.isBskyAppStatePref(pref) && - AppBskyActorDefs.validateBskyAppStatePref(pref).success - ) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { $type, ...v } = pref - prefs.bskyAppState.queuedNudges = v.queuedNudges || [] - prefs.bskyAppState.activeProgressGuide = v.activeProgressGuide - } - } - - /* - * If `prefs.savedFeeds` is undefined, no `savedFeedsPrefV2` exists, which - * means we want to try to migrate if needed. - * - * If v1 prefs exist, they will be migrated to v2. - * - * If no v1 prefs exist, the user is either new, or could be old and has - * never edited their feeds. - */ - if (prefs.savedFeeds === undefined) { - const { saved, pinned } = prefs.feeds - - if (saved && pinned) { - const uniqueMigratedSavedFeeds: Map< - string, - AppBskyActorDefs.SavedFeed - > = new Map() - - // insert Following feed first - uniqueMigratedSavedFeeds.set('timeline', { - id: TID.nextStr(), - type: 'timeline', - value: 'following', - pinned: true, - }) - - // use pinned as source of truth for feed order - for (const uri of pinned) { - const type = getSavedFeedType(uri) - // only want supported types - if (type === 'unknown') continue - uniqueMigratedSavedFeeds.set(uri, { - id: TID.nextStr(), - type, - value: uri, - pinned: true, - }) - } - - for (const uri of saved) { - if (!uniqueMigratedSavedFeeds.has(uri)) { - const type = getSavedFeedType(uri) - // only want supported types - if (type === 'unknown') continue - uniqueMigratedSavedFeeds.set(uri, { - id: TID.nextStr(), - type, - value: uri, - pinned: false, - }) - } - } - - prefs.savedFeeds = Array.from(uniqueMigratedSavedFeeds.values()) - } else { - prefs.savedFeeds = [ - { - id: TID.nextStr(), - type: 'timeline', - value: 'following', - pinned: true, - }, - ] - } - - // save to user preferences so this migration doesn't re-occur - await this.overwriteSavedFeeds(prefs.savedFeeds) - } - - // apply the label prefs - for (const pref of labelPrefs) { - if (pref.labelerDid) { - const labeler = prefs.moderationPrefs.labelers.find( - (labeler) => labeler.did === pref.labelerDid, - ) - if (!labeler) continue - labeler.labels[pref.label] = pref.visibility as LabelPreference - } else { - prefs.moderationPrefs.labels[pref.label] = - pref.visibility as LabelPreference - } - } - - prefs.moderationPrefs.labels = remapLegacyLabels( - prefs.moderationPrefs.labels, - ) - - // automatically configure the client - this.configureLabelersHeader(prefsArrayToLabelerDids(res.data.preferences)) - - return prefs - } - - async overwriteSavedFeeds(savedFeeds: AppBskyActorDefs.SavedFeed[]) { - savedFeeds.forEach(validateSavedFeed) - const uniqueSavedFeeds = new Map() - savedFeeds.forEach((feed) => { - // remove and re-insert to preserve order - if (uniqueSavedFeeds.has(feed.id)) { - uniqueSavedFeeds.delete(feed.id) - } - uniqueSavedFeeds.set(feed.id, feed) - }) - return updateSavedFeedsV2Preferences(this, () => - Array.from(uniqueSavedFeeds.values()), - ) - } - - async updateSavedFeeds(savedFeedsToUpdate: AppBskyActorDefs.SavedFeed[]) { - savedFeedsToUpdate.map(validateSavedFeed) - return updateSavedFeedsV2Preferences(this, (savedFeeds) => { - return savedFeeds.map((savedFeed) => { - const updatedVersion = savedFeedsToUpdate.find( - (updated) => savedFeed.id === updated.id, - ) - if (updatedVersion) { - return { - ...savedFeed, - // only update pinned - pinned: updatedVersion.pinned, - } - } - return savedFeed - }) - }) - } - - async addSavedFeeds( - savedFeeds: Pick[], - ) { - const toSave: AppBskyActorDefs.SavedFeed[] = savedFeeds.map((f) => ({ - ...f, - id: TID.nextStr(), - })) - toSave.forEach(validateSavedFeed) - return updateSavedFeedsV2Preferences(this, (savedFeeds) => [ - ...savedFeeds, - ...toSave, - ]) - } - - async removeSavedFeeds(ids: string[]) { - return updateSavedFeedsV2Preferences(this, (savedFeeds) => [ - ...savedFeeds.filter((feed) => !ids.find((id) => feed.id === id)), - ]) - } - - /** - * @deprecated use `overwriteSavedFeeds` - */ - async setSavedFeeds(saved: string[], pinned: string[]) { - return updateFeedPreferences(this, () => ({ - saved, - pinned, - })) - } - - /** - * @deprecated use `addSavedFeeds` - */ - async addSavedFeed(v: string) { - return updateFeedPreferences(this, (saved: string[], pinned: string[]) => ({ - saved: [...saved.filter((uri) => uri !== v), v], - pinned, - })) - } - - /** - * @deprecated use `removeSavedFeeds` - */ - async removeSavedFeed(v: string) { - return updateFeedPreferences(this, (saved: string[], pinned: string[]) => ({ - saved: saved.filter((uri) => uri !== v), - pinned: pinned.filter((uri) => uri !== v), - })) - } - - /** - * @deprecated use `addSavedFeeds` or `updateSavedFeeds` - */ - async addPinnedFeed(v: string) { - return updateFeedPreferences(this, (saved: string[], pinned: string[]) => ({ - saved: [...saved.filter((uri) => uri !== v), v], - pinned: [...pinned.filter((uri) => uri !== v), v], - })) - } - - /** - * @deprecated use `updateSavedFeeds` or `removeSavedFeeds` - */ - async removePinnedFeed(v: string) { - return updateFeedPreferences(this, (saved: string[], pinned: string[]) => ({ - saved, - pinned: pinned.filter((uri) => uri !== v), - })) - } - - async setAdultContentEnabled(v: boolean) { - await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => { - let adultContentPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isAdultContentPref(pref) && - AppBskyActorDefs.validateAdultContentPref(pref).success, - ) - if (adultContentPref) { - adultContentPref.enabled = v - } else { - adultContentPref = { - $type: 'app.bsky.actor.defs#adultContentPref', - enabled: v, - } - } - return prefs - .filter((pref) => !AppBskyActorDefs.isAdultContentPref(pref)) - .concat([adultContentPref]) - }) - } - - async setContentLabelPref( - key: string, - value: LabelPreference, - labelerDid?: string, - ) { - if (labelerDid) { - ensureValidDid(labelerDid) + clone(): this { + if (this.constructor === BskyAgent) { + const agent = new BskyAgent(this.sessionManager) + return this.copyInto(agent as this) } - await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => { - let labelPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isContentLabelPref(pref) && - AppBskyActorDefs.validateContentLabelPref(pref).success && - pref.label === key && - pref.labelerDid === labelerDid, - ) - let legacyLabelPref: AppBskyActorDefs.ContentLabelPref | undefined - - if (labelPref) { - labelPref.visibility = value - } else { - labelPref = { - $type: 'app.bsky.actor.defs#contentLabelPref', - label: key, - labelerDid, - visibility: value, - } - } - - if (AppBskyActorDefs.isContentLabelPref(labelPref)) { - // is global - if (!labelPref.labelerDid) { - const legacyLabelValue = { - 'graphic-media': 'gore', - porn: 'nsfw', - sexual: 'suggestive', - }[labelPref.label] - - // if it's a legacy label, double-write the legacy label - if (legacyLabelValue) { - legacyLabelPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isContentLabelPref(pref) && - AppBskyActorDefs.validateContentLabelPref(pref).success && - pref.label === legacyLabelValue && - pref.labelerDid === undefined, - ) as AppBskyActorDefs.ContentLabelPref | undefined - - if (legacyLabelPref) { - legacyLabelPref.visibility = value - } else { - legacyLabelPref = { - $type: 'app.bsky.actor.defs#contentLabelPref', - label: legacyLabelValue, - labelerDid: undefined, - visibility: value, - } - } - } - } - } - - return prefs - .filter( - (pref) => - !AppBskyActorDefs.isContentLabelPref(pref) || - !(pref.label === key && pref.labelerDid === labelerDid), - ) - .concat([labelPref]) - .filter((pref) => { - if (!legacyLabelPref) return true - return ( - !AppBskyActorDefs.isContentLabelPref(pref) || - !( - pref.label === legacyLabelPref.label && - pref.labelerDid === undefined - ) - ) - }) - .concat(legacyLabelPref ? [legacyLabelPref] : []) - }) - } - - async addLabeler(did: string) { - const prefs = await updatePreferences( - this, - (prefs: AppBskyActorDefs.Preferences) => { - let labelersPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isLabelersPref(pref) && - AppBskyActorDefs.validateLabelersPref(pref).success, - ) - if (!labelersPref) { - labelersPref = { - $type: 'app.bsky.actor.defs#labelersPref', - labelers: [], - } - } - if (AppBskyActorDefs.isLabelersPref(labelersPref)) { - let labelerPrefItem = labelersPref.labelers.find( - (labeler) => labeler.did === did, - ) - if (!labelerPrefItem) { - labelerPrefItem = { - did, - } - labelersPref.labelers.push(labelerPrefItem) - } - } - return prefs - .filter((pref) => !AppBskyActorDefs.isLabelersPref(pref)) - .concat([labelersPref]) - }, - ) - // automatically configure the client - this.configureLabelersHeader(prefsArrayToLabelerDids(prefs)) - } - - async removeLabeler(did: string) { - const prefs = await updatePreferences( - this, - (prefs: AppBskyActorDefs.Preferences) => { - let labelersPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isLabelersPref(pref) && - AppBskyActorDefs.validateLabelersPref(pref).success, - ) - if (!labelersPref) { - labelersPref = { - $type: 'app.bsky.actor.defs#labelersPref', - labelers: [], - } - } - if (AppBskyActorDefs.isLabelersPref(labelersPref)) { - labelersPref.labelers = labelersPref.labelers.filter( - (labeler) => labeler.did !== did, - ) - } - return prefs - .filter((pref) => !AppBskyActorDefs.isLabelersPref(pref)) - .concat([labelersPref]) - }, - ) - // automatically configure the client - this.configureLabelersHeader(prefsArrayToLabelerDids(prefs)) - } - - async setPersonalDetails({ - birthDate, - }: { - birthDate: string | Date | undefined - }) { - birthDate = birthDate instanceof Date ? birthDate.toISOString() : birthDate - await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => { - let personalDetailsPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isPersonalDetailsPref(pref) && - AppBskyActorDefs.validatePersonalDetailsPref(pref).success, - ) - if (personalDetailsPref) { - personalDetailsPref.birthDate = birthDate - } else { - personalDetailsPref = { - $type: 'app.bsky.actor.defs#personalDetailsPref', - birthDate, - } - } - return prefs - .filter((pref) => !AppBskyActorDefs.isPersonalDetailsPref(pref)) - .concat([personalDetailsPref]) - }) - } - - async setFeedViewPrefs(feed: string, pref: Partial) { - await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => { - const existing = prefs.findLast( - (pref) => - AppBskyActorDefs.isFeedViewPref(pref) && - AppBskyActorDefs.validateFeedViewPref(pref).success && - pref.feed === feed, - ) - if (existing) { - pref = { ...existing, ...pref } - } - return prefs - .filter( - (p) => !AppBskyActorDefs.isFeedViewPref(pref) || p.feed !== feed, - ) - .concat([{ ...pref, $type: 'app.bsky.actor.defs#feedViewPref', feed }]) - }) - } - - async setThreadViewPrefs(pref: Partial) { - await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => { - const existing = prefs.findLast( - (pref) => - AppBskyActorDefs.isThreadViewPref(pref) && - AppBskyActorDefs.validateThreadViewPref(pref).success, - ) - if (existing) { - pref = { ...existing, ...pref } - } - return prefs - .filter((p) => !AppBskyActorDefs.isThreadViewPref(p)) - .concat([{ ...pref, $type: 'app.bsky.actor.defs#threadViewPref' }]) - }) - } - - async setInterestsPref(pref: Partial) { - await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => { - const existing = prefs.findLast( - (pref) => - AppBskyActorDefs.isInterestsPref(pref) && - AppBskyActorDefs.validateInterestsPref(pref).success, - ) - if (existing) { - pref = { ...existing, ...pref } - } - return prefs - .filter((p) => !AppBskyActorDefs.isInterestsPref(p)) - .concat([{ ...pref, $type: 'app.bsky.actor.defs#interestsPref' }]) - }) - } - - /** - * Add a muted word to user preferences. - */ - async addMutedWord( - mutedWord: Pick< - MutedWord, - 'value' | 'targets' | 'actorTarget' | 'expiresAt' - >, - ) { - const sanitizedValue = sanitizeMutedWordValue(mutedWord.value) - - if (!sanitizedValue) return - - await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => { - let mutedWordsPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isMutedWordsPref(pref) && - AppBskyActorDefs.validateMutedWordsPref(pref).success, - ) - const newMutedWord: AppBskyActorDefs.MutedWord = { - id: TID.nextStr(), - value: sanitizedValue, - targets: mutedWord.targets || [], - actorTarget: mutedWord.actorTarget || 'all', - expiresAt: mutedWord.expiresAt || undefined, - } - - if (mutedWordsPref && AppBskyActorDefs.isMutedWordsPref(mutedWordsPref)) { - mutedWordsPref.items.push(newMutedWord) - - /** - * Migrate any old muted words that don't have an id - */ - mutedWordsPref.items = migrateLegacyMutedWordsItems( - mutedWordsPref.items, - ) - } else { - // if the pref doesn't exist, create it - mutedWordsPref = { - items: [newMutedWord], - } - } - - return prefs - .filter((p) => !AppBskyActorDefs.isMutedWordsPref(p)) - .concat([ - { ...mutedWordsPref, $type: 'app.bsky.actor.defs#mutedWordsPref' }, - ]) - }) - } - - /** - * Convenience method to add muted words to user preferences - */ - async addMutedWords(newMutedWords: AppBskyActorDefs.MutedWord[]) { - await Promise.all(newMutedWords.map((word) => this.addMutedWord(word))) - } - - /** - * @deprecated use `addMutedWords` or `addMutedWord` instead - */ - async upsertMutedWords( - mutedWords: Pick< - MutedWord, - 'value' | 'targets' | 'actorTarget' | 'expiresAt' - >[], - ) { - await this.addMutedWords(mutedWords) + // sub-classes should override this method + throw new TypeError('Cannot clone a subclass of BskyAgent') } - - /** - * Update a muted word in user preferences. - */ - async updateMutedWord(mutedWord: AppBskyActorDefs.MutedWord) { - await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => { - const mutedWordsPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isMutedWordsPref(pref) && - AppBskyActorDefs.validateMutedWordsPref(pref).success, - ) - - if (mutedWordsPref && AppBskyActorDefs.isMutedWordsPref(mutedWordsPref)) { - mutedWordsPref.items = mutedWordsPref.items.map((existingItem) => { - const match = matchMutedWord(existingItem, mutedWord) - - if (match) { - const updated = { - ...existingItem, - ...mutedWord, - } - return { - id: existingItem.id || TID.nextStr(), - value: - sanitizeMutedWordValue(updated.value) || existingItem.value, - targets: updated.targets || [], - actorTarget: updated.actorTarget || 'all', - expiresAt: updated.expiresAt || undefined, - } - } else { - return existingItem - } - }) - - /** - * Migrate any old muted words that don't have an id - */ - mutedWordsPref.items = migrateLegacyMutedWordsItems( - mutedWordsPref.items, - ) - - return prefs - .filter((p) => !AppBskyActorDefs.isMutedWordsPref(p)) - .concat([ - { ...mutedWordsPref, $type: 'app.bsky.actor.defs#mutedWordsPref' }, - ]) - } - - return prefs - }) - } - - /** - * Remove a muted word from user preferences. - */ - async removeMutedWord(mutedWord: AppBskyActorDefs.MutedWord) { - await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => { - const mutedWordsPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isMutedWordsPref(pref) && - AppBskyActorDefs.validateMutedWordsPref(pref).success, - ) - - if (mutedWordsPref && AppBskyActorDefs.isMutedWordsPref(mutedWordsPref)) { - for (let i = 0; i < mutedWordsPref.items.length; i++) { - const match = matchMutedWord(mutedWordsPref.items[i], mutedWord) - - if (match) { - mutedWordsPref.items.splice(i, 1) - break - } - } - - /** - * Migrate any old muted words that don't have an id - */ - mutedWordsPref.items = migrateLegacyMutedWordsItems( - mutedWordsPref.items, - ) - - return prefs - .filter((p) => !AppBskyActorDefs.isMutedWordsPref(p)) - .concat([ - { ...mutedWordsPref, $type: 'app.bsky.actor.defs#mutedWordsPref' }, - ]) - } - - return prefs - }) - } - - /** - * Convenience method to remove muted words from user preferences - */ - async removeMutedWords(mutedWords: AppBskyActorDefs.MutedWord[]) { - await Promise.all(mutedWords.map((word) => this.removeMutedWord(word))) - } - - async hidePost(postUri: string) { - await updateHiddenPost(this, postUri, 'hide') - } - - async unhidePost(postUri: string) { - await updateHiddenPost(this, postUri, 'unhide') - } - - async bskyAppQueueNudges(nudges: string | string[]) { - await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => { - let bskyAppStatePref: AppBskyActorDefs.BskyAppStatePref = prefs.findLast( - (pref) => - AppBskyActorDefs.isBskyAppStatePref(pref) && - AppBskyActorDefs.validateBskyAppStatePref(pref).success, - ) - - bskyAppStatePref = bskyAppStatePref || {} - nudges = Array.isArray(nudges) ? nudges : [nudges] - bskyAppStatePref.queuedNudges = ( - bskyAppStatePref.queuedNudges || [] - ).concat(nudges) - - return prefs - .filter((p) => !AppBskyActorDefs.isBskyAppStatePref(p)) - .concat([ - { - ...bskyAppStatePref, - $type: 'app.bsky.actor.defs#bskyAppStatePref', - }, - ]) - }) - } - - async bskyAppDismissNudges(nudges: string | string[]) { - await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => { - let bskyAppStatePref: AppBskyActorDefs.BskyAppStatePref = prefs.findLast( - (pref) => - AppBskyActorDefs.isBskyAppStatePref(pref) && - AppBskyActorDefs.validateBskyAppStatePref(pref).success, - ) - - bskyAppStatePref = bskyAppStatePref || {} - nudges = Array.isArray(nudges) ? nudges : [nudges] - bskyAppStatePref.queuedNudges = ( - bskyAppStatePref.queuedNudges || [] - ).filter((nudge) => !nudges.includes(nudge)) - - return prefs - .filter((p) => !AppBskyActorDefs.isBskyAppStatePref(p)) - .concat([ - { - ...bskyAppStatePref, - $type: 'app.bsky.actor.defs#bskyAppStatePref', - }, - ]) - }) - } - - async bskyAppSetActiveProgressGuide( - guide: AppBskyActorDefs.BskyAppProgressGuide | undefined, - ) { - if ( - guide && - !AppBskyActorDefs.validateBskyAppProgressGuide(guide).success - ) { - throw new Error('Invalid progress guide') - } - - await updatePreferences(this, (prefs: AppBskyActorDefs.Preferences) => { - let bskyAppStatePref: AppBskyActorDefs.BskyAppStatePref = prefs.findLast( - (pref) => - AppBskyActorDefs.isBskyAppStatePref(pref) && - AppBskyActorDefs.validateBskyAppStatePref(pref).success, - ) - - bskyAppStatePref = bskyAppStatePref || {} - bskyAppStatePref.activeProgressGuide = guide - - return prefs - .filter((p) => !AppBskyActorDefs.isBskyAppStatePref(p)) - .concat([ - { - ...bskyAppStatePref, - $type: 'app.bsky.actor.defs#bskyAppStatePref', - }, - ]) - }) - } -} - -/** - * This function updates the preferences of a user and allows for a callback function to be executed - * before the update. - * @param cb - cb is a callback function that takes in a single parameter of type - * AppBskyActorDefs.Preferences and returns either a boolean or void. This callback function is used to - * update the preferences of the user. The function is called with the current preferences as an - * argument and if the callback returns false, the preferences are not updated. - */ -async function updatePreferences( - agent: BskyAgent, - cb: ( - prefs: AppBskyActorDefs.Preferences, - ) => AppBskyActorDefs.Preferences | false, -) { - try { - await agent._prefsLock.acquireAsync() - const res = await agent.app.bsky.actor.getPreferences({}) - const newPrefs = cb(res.data.preferences) - if (newPrefs === false) { - return res.data.preferences - } - await agent.app.bsky.actor.putPreferences({ - preferences: newPrefs, - }) - return newPrefs - } finally { - agent._prefsLock.release() - } -} - -/** - * A helper specifically for updating feed preferences - */ -async function updateFeedPreferences( - agent: BskyAgent, - cb: ( - saved: string[], - pinned: string[], - ) => { saved: string[]; pinned: string[] }, -): Promise<{ saved: string[]; pinned: string[] }> { - let res - await updatePreferences(agent, (prefs: AppBskyActorDefs.Preferences) => { - let feedsPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isSavedFeedsPref(pref) && - AppBskyActorDefs.validateSavedFeedsPref(pref).success, - ) as AppBskyActorDefs.SavedFeedsPref | undefined - if (feedsPref) { - res = cb(feedsPref.saved, feedsPref.pinned) - feedsPref.saved = res.saved - feedsPref.pinned = res.pinned - } else { - res = cb([], []) - feedsPref = { - $type: 'app.bsky.actor.defs#savedFeedsPref', - saved: res.saved, - pinned: res.pinned, - } - } - return prefs - .filter((pref) => !AppBskyActorDefs.isSavedFeedsPref(pref)) - .concat([feedsPref]) - }) - return res -} - -async function updateSavedFeedsV2Preferences( - agent: BskyAgent, - cb: ( - savedFeedsPref: AppBskyActorDefs.SavedFeed[], - ) => AppBskyActorDefs.SavedFeed[], -): Promise { - let maybeMutatedSavedFeeds: AppBskyActorDefs.SavedFeed[] = [] - - await updatePreferences(agent, (prefs: AppBskyActorDefs.Preferences) => { - let existingV2Pref = prefs.findLast( - (pref) => - AppBskyActorDefs.isSavedFeedsPrefV2(pref) && - AppBskyActorDefs.validateSavedFeedsPrefV2(pref).success, - ) as AppBskyActorDefs.SavedFeedsPrefV2 | undefined - let existingV1Pref = prefs.findLast( - (pref) => - AppBskyActorDefs.isSavedFeedsPref(pref) && - AppBskyActorDefs.validateSavedFeedsPref(pref).success, - ) as AppBskyActorDefs.SavedFeedsPref | undefined - - if (existingV2Pref) { - maybeMutatedSavedFeeds = cb(existingV2Pref.items) - existingV2Pref = { - ...existingV2Pref, - items: maybeMutatedSavedFeeds, - } - } else { - maybeMutatedSavedFeeds = cb([]) - existingV2Pref = { - $type: 'app.bsky.actor.defs#savedFeedsPrefV2', - items: maybeMutatedSavedFeeds, - } - } - - // enforce ordering, pinned then saved - const pinned = existingV2Pref.items.filter((i) => i.pinned) - const saved = existingV2Pref.items.filter((i) => !i.pinned) - existingV2Pref.items = pinned.concat(saved) - - let updatedPrefs = prefs - .filter((pref) => !AppBskyActorDefs.isSavedFeedsPrefV2(pref)) - .concat(existingV2Pref) - - /* - * If there's a v2 pref present, it means this account was migrated from v1 - * to v2. During the transition period, we double write v2 prefs back to - * v1, but NOT the other way around. - */ - if (existingV1Pref) { - const { saved, pinned } = existingV1Pref - const v2Compat = savedFeedsToUriArrays( - // v1 only supports feeds and lists - existingV2Pref.items.filter((i) => ['feed', 'list'].includes(i.type)), - ) - existingV1Pref = { - ...existingV1Pref, - saved: Array.from(new Set([...saved, ...v2Compat.saved])), - pinned: Array.from(new Set([...pinned, ...v2Compat.pinned])), - } - updatedPrefs = updatedPrefs - .filter((pref) => !AppBskyActorDefs.isSavedFeedsPref(pref)) - .concat(existingV1Pref) - } - - return updatedPrefs - }) - - return maybeMutatedSavedFeeds -} - -/** - * Helper to transform the legacy content preferences. - */ -function adjustLegacyContentLabelPref( - pref: AppBskyActorDefs.ContentLabelPref, -): AppBskyActorDefs.ContentLabelPref { - let visibility = pref.visibility - - // adjust legacy values - if (visibility === 'show') { - visibility = 'ignore' - } - - return { ...pref, visibility } -} - -/** - * Re-maps legacy labels to new labels on READ. Does not save these changes to - * the user's preferences. - */ -function remapLegacyLabels( - labels: BskyPreferences['moderationPrefs']['labels'], -) { - const _labels = { ...labels } - const legacyToNewMap: Record = { - gore: 'graphic-media', - nsfw: 'porn', - suggestive: 'sexual', - } - - for (const labelName in _labels) { - const newLabelName = legacyToNewMap[labelName]! - if (newLabelName) { - _labels[newLabelName] = _labels[labelName] - } - } - - return _labels -} - -/** - * A helper to get the currently enabled labelers from the full preferences array - */ -function prefsArrayToLabelerDids( - prefs: AppBskyActorDefs.Preferences, -): string[] { - const labelersPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isLabelersPref(pref) && - AppBskyActorDefs.validateLabelersPref(pref).success, - ) - let dids: string[] = [] - if (labelersPref) { - dids = (labelersPref as AppBskyActorDefs.LabelersPref).labelers.map( - (labeler) => labeler.did, - ) - } - return dids -} - -async function updateHiddenPost( - agent: BskyAgent, - postUri: string, - action: 'hide' | 'unhide', -) { - await updatePreferences(agent, (prefs: AppBskyActorDefs.Preferences) => { - let pref = prefs.findLast( - (pref) => - AppBskyActorDefs.isHiddenPostsPref(pref) && - AppBskyActorDefs.validateHiddenPostsPref(pref).success, - ) - if (pref && AppBskyActorDefs.isHiddenPostsPref(pref)) { - pref.items = - action === 'hide' - ? Array.from(new Set([...pref.items, postUri])) - : pref.items.filter((uri) => uri !== postUri) - } else { - if (action === 'hide') { - pref = { - $type: 'app.bsky.actor.defs#hiddenPostsPref', - items: [postUri], - } - } - } - return prefs - .filter((p) => !AppBskyActorDefs.isInterestsPref(p)) - .concat([{ ...pref, $type: 'app.bsky.actor.defs#hiddenPostsPref' }]) - }) -} - -function isBskyPrefs(v: any): v is BskyPreferences { - return ( - v && - typeof v === 'object' && - 'moderationPrefs' in v && - isModPrefs(v.moderationPrefs) - ) -} - -function isModPrefs(v: any): v is ModerationPrefs { - return v && typeof v === 'object' && 'labelers' in v -} - -function migrateLegacyMutedWordsItems(items: AppBskyActorDefs.MutedWord[]) { - return items.map((item) => ({ - ...item, - id: item.id || TID.nextStr(), - })) -} - -function matchMutedWord( - existingWord: AppBskyActorDefs.MutedWord, - newWord: AppBskyActorDefs.MutedWord, -): boolean { - // id is undefined in legacy implementation - const existingId = existingWord.id - // prefer matching based on id - const matchById = existingId && existingId === newWord.id - // handle legacy case where id is not set - const legacyMatchByValue = !existingId && existingWord.value === newWord.value - - return matchById || legacyMatchByValue } diff --git a/packages/api/src/client/index.ts b/packages/api/src/client/index.ts index c683a32f2ad..9b6bc1d56b4 100644 --- a/packages/api/src/client/index.ts +++ b/packages/api/src/client/index.ts @@ -1,10 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { - Client as XrpcClient, - ServiceClient as XrpcServiceClient, -} from '@atproto/xrpc' +import { XrpcClient, FetchHandler, FetchHandlerOptions } from '@atproto/xrpc' import { schemas } from './lexicons' import { CID } from 'multiformats/cid' import * as ComAtprotoAdminDefs from './types/com/atproto/admin/defs' @@ -443,52 +440,38 @@ export const TOOLS_OZONE_TEAM = { DefsRoleTriage: 'tools.ozone.team.defs#roleTriage', } -export class AtpBaseClient { - xrpc: XrpcClient = new XrpcClient() - - constructor() { - this.xrpc.addLexicons(schemas) - } - - service(serviceUri: string | URL): AtpServiceClient { - return new AtpServiceClient(this, this.xrpc.service(serviceUri)) - } -} - -export class AtpServiceClient { - _baseClient: AtpBaseClient - xrpc: XrpcServiceClient +export class AtpBaseClient extends XrpcClient { com: ComNS app: AppNS chat: ChatNS tools: ToolsNS - constructor(baseClient: AtpBaseClient, xrpcService: XrpcServiceClient) { - this._baseClient = baseClient - this.xrpc = xrpcService + constructor(options: FetchHandler | FetchHandlerOptions) { + super(options, schemas) this.com = new ComNS(this) this.app = new AppNS(this) this.chat = new ChatNS(this) this.tools = new ToolsNS(this) } - setHeader(key: string, value: string): void { - this.xrpc.setHeader(key, value) + /** @deprecated use `this` instead */ + get xrpc(): XrpcClient { + return this } } export class ComNS { - _service: AtpServiceClient + _client: XrpcClient atproto: ComAtprotoNS - constructor(service: AtpServiceClient) { - this._service = service - this.atproto = new ComAtprotoNS(service) + constructor(client: XrpcClient) { + this._client = client + this.atproto = new ComAtprotoNS(client) } } export class ComAtprotoNS { - _service: AtpServiceClient + _client: XrpcClient admin: ComAtprotoAdminNS identity: ComAtprotoIdentityNS label: ComAtprotoLabelNS @@ -498,315 +481,327 @@ export class ComAtprotoNS { sync: ComAtprotoSyncNS temp: ComAtprotoTempNS - constructor(service: AtpServiceClient) { - this._service = service - this.admin = new ComAtprotoAdminNS(service) - this.identity = new ComAtprotoIdentityNS(service) - this.label = new ComAtprotoLabelNS(service) - this.moderation = new ComAtprotoModerationNS(service) - this.repo = new ComAtprotoRepoNS(service) - this.server = new ComAtprotoServerNS(service) - this.sync = new ComAtprotoSyncNS(service) - this.temp = new ComAtprotoTempNS(service) + constructor(client: XrpcClient) { + this._client = client + this.admin = new ComAtprotoAdminNS(client) + this.identity = new ComAtprotoIdentityNS(client) + this.label = new ComAtprotoLabelNS(client) + this.moderation = new ComAtprotoModerationNS(client) + this.repo = new ComAtprotoRepoNS(client) + this.server = new ComAtprotoServerNS(client) + this.sync = new ComAtprotoSyncNS(client) + this.temp = new ComAtprotoTempNS(client) } } export class ComAtprotoAdminNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } deleteAccount( data?: ComAtprotoAdminDeleteAccount.InputSchema, opts?: ComAtprotoAdminDeleteAccount.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.admin.deleteAccount', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoAdminDeleteAccount.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.admin.deleteAccount', + opts?.qp, + data, + opts, + ) } disableAccountInvites( data?: ComAtprotoAdminDisableAccountInvites.InputSchema, opts?: ComAtprotoAdminDisableAccountInvites.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.admin.disableAccountInvites', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoAdminDisableAccountInvites.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.admin.disableAccountInvites', + opts?.qp, + data, + opts, + ) } disableInviteCodes( data?: ComAtprotoAdminDisableInviteCodes.InputSchema, opts?: ComAtprotoAdminDisableInviteCodes.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.admin.disableInviteCodes', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoAdminDisableInviteCodes.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.admin.disableInviteCodes', + opts?.qp, + data, + opts, + ) } enableAccountInvites( data?: ComAtprotoAdminEnableAccountInvites.InputSchema, opts?: ComAtprotoAdminEnableAccountInvites.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.admin.enableAccountInvites', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoAdminEnableAccountInvites.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.admin.enableAccountInvites', + opts?.qp, + data, + opts, + ) } getAccountInfo( params?: ComAtprotoAdminGetAccountInfo.QueryParams, opts?: ComAtprotoAdminGetAccountInfo.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.admin.getAccountInfo', params, undefined, opts) - .catch((e) => { - throw ComAtprotoAdminGetAccountInfo.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.admin.getAccountInfo', + params, + undefined, + opts, + ) } getAccountInfos( params?: ComAtprotoAdminGetAccountInfos.QueryParams, opts?: ComAtprotoAdminGetAccountInfos.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.admin.getAccountInfos', params, undefined, opts) - .catch((e) => { - throw ComAtprotoAdminGetAccountInfos.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.admin.getAccountInfos', + params, + undefined, + opts, + ) } getInviteCodes( params?: ComAtprotoAdminGetInviteCodes.QueryParams, opts?: ComAtprotoAdminGetInviteCodes.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.admin.getInviteCodes', params, undefined, opts) - .catch((e) => { - throw ComAtprotoAdminGetInviteCodes.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.admin.getInviteCodes', + params, + undefined, + opts, + ) } getSubjectStatus( params?: ComAtprotoAdminGetSubjectStatus.QueryParams, opts?: ComAtprotoAdminGetSubjectStatus.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.admin.getSubjectStatus', params, undefined, opts) - .catch((e) => { - throw ComAtprotoAdminGetSubjectStatus.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.admin.getSubjectStatus', + params, + undefined, + opts, + ) } searchAccounts( params?: ComAtprotoAdminSearchAccounts.QueryParams, opts?: ComAtprotoAdminSearchAccounts.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.admin.searchAccounts', params, undefined, opts) - .catch((e) => { - throw ComAtprotoAdminSearchAccounts.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.admin.searchAccounts', + params, + undefined, + opts, + ) } sendEmail( data?: ComAtprotoAdminSendEmail.InputSchema, opts?: ComAtprotoAdminSendEmail.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.admin.sendEmail', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoAdminSendEmail.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.admin.sendEmail', + opts?.qp, + data, + opts, + ) } updateAccountEmail( data?: ComAtprotoAdminUpdateAccountEmail.InputSchema, opts?: ComAtprotoAdminUpdateAccountEmail.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.admin.updateAccountEmail', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoAdminUpdateAccountEmail.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.admin.updateAccountEmail', + opts?.qp, + data, + opts, + ) } updateAccountHandle( data?: ComAtprotoAdminUpdateAccountHandle.InputSchema, opts?: ComAtprotoAdminUpdateAccountHandle.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.admin.updateAccountHandle', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoAdminUpdateAccountHandle.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.admin.updateAccountHandle', + opts?.qp, + data, + opts, + ) } updateAccountPassword( data?: ComAtprotoAdminUpdateAccountPassword.InputSchema, opts?: ComAtprotoAdminUpdateAccountPassword.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.admin.updateAccountPassword', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoAdminUpdateAccountPassword.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.admin.updateAccountPassword', + opts?.qp, + data, + opts, + ) } updateSubjectStatus( data?: ComAtprotoAdminUpdateSubjectStatus.InputSchema, opts?: ComAtprotoAdminUpdateSubjectStatus.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.admin.updateSubjectStatus', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoAdminUpdateSubjectStatus.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.admin.updateSubjectStatus', + opts?.qp, + data, + opts, + ) } } export class ComAtprotoIdentityNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } getRecommendedDidCredentials( params?: ComAtprotoIdentityGetRecommendedDidCredentials.QueryParams, opts?: ComAtprotoIdentityGetRecommendedDidCredentials.CallOptions, ): Promise { - return this._service.xrpc - .call( - 'com.atproto.identity.getRecommendedDidCredentials', - params, - undefined, - opts, - ) - .catch((e) => { - throw ComAtprotoIdentityGetRecommendedDidCredentials.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.identity.getRecommendedDidCredentials', + params, + undefined, + opts, + ) } requestPlcOperationSignature( data?: ComAtprotoIdentityRequestPlcOperationSignature.InputSchema, opts?: ComAtprotoIdentityRequestPlcOperationSignature.CallOptions, ): Promise { - return this._service.xrpc - .call( - 'com.atproto.identity.requestPlcOperationSignature', - opts?.qp, - data, - opts, - ) - .catch((e) => { - throw ComAtprotoIdentityRequestPlcOperationSignature.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.identity.requestPlcOperationSignature', + opts?.qp, + data, + opts, + ) } resolveHandle( params?: ComAtprotoIdentityResolveHandle.QueryParams, opts?: ComAtprotoIdentityResolveHandle.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.identity.resolveHandle', params, undefined, opts) - .catch((e) => { - throw ComAtprotoIdentityResolveHandle.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.identity.resolveHandle', + params, + undefined, + opts, + ) } signPlcOperation( data?: ComAtprotoIdentitySignPlcOperation.InputSchema, opts?: ComAtprotoIdentitySignPlcOperation.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.identity.signPlcOperation', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoIdentitySignPlcOperation.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.identity.signPlcOperation', + opts?.qp, + data, + opts, + ) } submitPlcOperation( data?: ComAtprotoIdentitySubmitPlcOperation.InputSchema, opts?: ComAtprotoIdentitySubmitPlcOperation.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.identity.submitPlcOperation', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoIdentitySubmitPlcOperation.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.identity.submitPlcOperation', + opts?.qp, + data, + opts, + ) } updateHandle( data?: ComAtprotoIdentityUpdateHandle.InputSchema, opts?: ComAtprotoIdentityUpdateHandle.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.identity.updateHandle', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoIdentityUpdateHandle.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.identity.updateHandle', + opts?.qp, + data, + opts, + ) } } export class ComAtprotoLabelNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } queryLabels( params?: ComAtprotoLabelQueryLabels.QueryParams, opts?: ComAtprotoLabelQueryLabels.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.label.queryLabels', params, undefined, opts) - .catch((e) => { - throw ComAtprotoLabelQueryLabels.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.label.queryLabels', + params, + undefined, + opts, + ) } } export class ComAtprotoModerationNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } createReport( data?: ComAtprotoModerationCreateReport.InputSchema, opts?: ComAtprotoModerationCreateReport.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.moderation.createReport', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoModerationCreateReport.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.moderation.createReport', + opts?.qp, + data, + opts, + ) } } export class ComAtprotoRepoNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } applyWrites( data?: ComAtprotoRepoApplyWrites.InputSchema, opts?: ComAtprotoRepoApplyWrites.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.repo.applyWrites', opts?.qp, data, opts) .catch((e) => { throw ComAtprotoRepoApplyWrites.toKnownErr(e) @@ -817,7 +812,7 @@ export class ComAtprotoRepoNS { data?: ComAtprotoRepoCreateRecord.InputSchema, opts?: ComAtprotoRepoCreateRecord.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.repo.createRecord', opts?.qp, data, opts) .catch((e) => { throw ComAtprotoRepoCreateRecord.toKnownErr(e) @@ -828,7 +823,7 @@ export class ComAtprotoRepoNS { data?: ComAtprotoRepoDeleteRecord.InputSchema, opts?: ComAtprotoRepoDeleteRecord.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.repo.deleteRecord', opts?.qp, data, opts) .catch((e) => { throw ComAtprotoRepoDeleteRecord.toKnownErr(e) @@ -839,62 +834,67 @@ export class ComAtprotoRepoNS { params?: ComAtprotoRepoDescribeRepo.QueryParams, opts?: ComAtprotoRepoDescribeRepo.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.repo.describeRepo', params, undefined, opts) - .catch((e) => { - throw ComAtprotoRepoDescribeRepo.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.repo.describeRepo', + params, + undefined, + opts, + ) } getRecord( params?: ComAtprotoRepoGetRecord.QueryParams, opts?: ComAtprotoRepoGetRecord.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.repo.getRecord', params, undefined, opts) - .catch((e) => { - throw ComAtprotoRepoGetRecord.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.repo.getRecord', + params, + undefined, + opts, + ) } importRepo( data?: ComAtprotoRepoImportRepo.InputSchema, opts?: ComAtprotoRepoImportRepo.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.repo.importRepo', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoRepoImportRepo.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.repo.importRepo', + opts?.qp, + data, + opts, + ) } listMissingBlobs( params?: ComAtprotoRepoListMissingBlobs.QueryParams, opts?: ComAtprotoRepoListMissingBlobs.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.repo.listMissingBlobs', params, undefined, opts) - .catch((e) => { - throw ComAtprotoRepoListMissingBlobs.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.repo.listMissingBlobs', + params, + undefined, + opts, + ) } listRecords( params?: ComAtprotoRepoListRecords.QueryParams, opts?: ComAtprotoRepoListRecords.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.repo.listRecords', params, undefined, opts) - .catch((e) => { - throw ComAtprotoRepoListRecords.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.repo.listRecords', + params, + undefined, + opts, + ) } putRecord( data?: ComAtprotoRepoPutRecord.InputSchema, opts?: ComAtprotoRepoPutRecord.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.repo.putRecord', opts?.qp, data, opts) .catch((e) => { throw ComAtprotoRepoPutRecord.toKnownErr(e) @@ -905,48 +905,51 @@ export class ComAtprotoRepoNS { data?: ComAtprotoRepoUploadBlob.InputSchema, opts?: ComAtprotoRepoUploadBlob.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.repo.uploadBlob', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoRepoUploadBlob.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.repo.uploadBlob', + opts?.qp, + data, + opts, + ) } } export class ComAtprotoServerNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } activateAccount( data?: ComAtprotoServerActivateAccount.InputSchema, opts?: ComAtprotoServerActivateAccount.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.server.activateAccount', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoServerActivateAccount.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.server.activateAccount', + opts?.qp, + data, + opts, + ) } checkAccountStatus( params?: ComAtprotoServerCheckAccountStatus.QueryParams, opts?: ComAtprotoServerCheckAccountStatus.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.server.checkAccountStatus', params, undefined, opts) - .catch((e) => { - throw ComAtprotoServerCheckAccountStatus.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.server.checkAccountStatus', + params, + undefined, + opts, + ) } confirmEmail( data?: ComAtprotoServerConfirmEmail.InputSchema, opts?: ComAtprotoServerConfirmEmail.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.server.confirmEmail', opts?.qp, data, opts) .catch((e) => { throw ComAtprotoServerConfirmEmail.toKnownErr(e) @@ -957,7 +960,7 @@ export class ComAtprotoServerNS { data?: ComAtprotoServerCreateAccount.InputSchema, opts?: ComAtprotoServerCreateAccount.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.server.createAccount', opts?.qp, data, opts) .catch((e) => { throw ComAtprotoServerCreateAccount.toKnownErr(e) @@ -968,7 +971,7 @@ export class ComAtprotoServerNS { data?: ComAtprotoServerCreateAppPassword.InputSchema, opts?: ComAtprotoServerCreateAppPassword.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.server.createAppPassword', opts?.qp, data, opts) .catch((e) => { throw ComAtprotoServerCreateAppPassword.toKnownErr(e) @@ -979,29 +982,31 @@ export class ComAtprotoServerNS { data?: ComAtprotoServerCreateInviteCode.InputSchema, opts?: ComAtprotoServerCreateInviteCode.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.server.createInviteCode', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoServerCreateInviteCode.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.server.createInviteCode', + opts?.qp, + data, + opts, + ) } createInviteCodes( data?: ComAtprotoServerCreateInviteCodes.InputSchema, opts?: ComAtprotoServerCreateInviteCodes.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.server.createInviteCodes', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoServerCreateInviteCodes.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.server.createInviteCodes', + opts?.qp, + data, + opts, + ) } createSession( data?: ComAtprotoServerCreateSession.InputSchema, opts?: ComAtprotoServerCreateSession.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.server.createSession', opts?.qp, data, opts) .catch((e) => { throw ComAtprotoServerCreateSession.toKnownErr(e) @@ -1012,18 +1017,19 @@ export class ComAtprotoServerNS { data?: ComAtprotoServerDeactivateAccount.InputSchema, opts?: ComAtprotoServerDeactivateAccount.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.server.deactivateAccount', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoServerDeactivateAccount.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.server.deactivateAccount', + opts?.qp, + data, + opts, + ) } deleteAccount( data?: ComAtprotoServerDeleteAccount.InputSchema, opts?: ComAtprotoServerDeleteAccount.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.server.deleteAccount', opts?.qp, data, opts) .catch((e) => { throw ComAtprotoServerDeleteAccount.toKnownErr(e) @@ -1034,29 +1040,31 @@ export class ComAtprotoServerNS { data?: ComAtprotoServerDeleteSession.InputSchema, opts?: ComAtprotoServerDeleteSession.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.server.deleteSession', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoServerDeleteSession.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.server.deleteSession', + opts?.qp, + data, + opts, + ) } describeServer( params?: ComAtprotoServerDescribeServer.QueryParams, opts?: ComAtprotoServerDescribeServer.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.server.describeServer', params, undefined, opts) - .catch((e) => { - throw ComAtprotoServerDescribeServer.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.server.describeServer', + params, + undefined, + opts, + ) } getAccountInviteCodes( params?: ComAtprotoServerGetAccountInviteCodes.QueryParams, opts?: ComAtprotoServerGetAccountInviteCodes.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.server.getAccountInviteCodes', params, undefined, opts) .catch((e) => { throw ComAtprotoServerGetAccountInviteCodes.toKnownErr(e) @@ -1067,7 +1075,7 @@ export class ComAtprotoServerNS { params?: ComAtprotoServerGetServiceAuth.QueryParams, opts?: ComAtprotoServerGetServiceAuth.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.server.getServiceAuth', params, undefined, opts) .catch((e) => { throw ComAtprotoServerGetServiceAuth.toKnownErr(e) @@ -1078,18 +1086,19 @@ export class ComAtprotoServerNS { params?: ComAtprotoServerGetSession.QueryParams, opts?: ComAtprotoServerGetSession.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.server.getSession', params, undefined, opts) - .catch((e) => { - throw ComAtprotoServerGetSession.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.server.getSession', + params, + undefined, + opts, + ) } listAppPasswords( params?: ComAtprotoServerListAppPasswords.QueryParams, opts?: ComAtprotoServerListAppPasswords.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.server.listAppPasswords', params, undefined, opts) .catch((e) => { throw ComAtprotoServerListAppPasswords.toKnownErr(e) @@ -1100,7 +1109,7 @@ export class ComAtprotoServerNS { data?: ComAtprotoServerRefreshSession.InputSchema, opts?: ComAtprotoServerRefreshSession.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.server.refreshSession', opts?.qp, data, opts) .catch((e) => { throw ComAtprotoServerRefreshSession.toKnownErr(e) @@ -1111,62 +1120,67 @@ export class ComAtprotoServerNS { data?: ComAtprotoServerRequestAccountDelete.InputSchema, opts?: ComAtprotoServerRequestAccountDelete.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.server.requestAccountDelete', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoServerRequestAccountDelete.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.server.requestAccountDelete', + opts?.qp, + data, + opts, + ) } requestEmailConfirmation( data?: ComAtprotoServerRequestEmailConfirmation.InputSchema, opts?: ComAtprotoServerRequestEmailConfirmation.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.server.requestEmailConfirmation', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoServerRequestEmailConfirmation.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.server.requestEmailConfirmation', + opts?.qp, + data, + opts, + ) } requestEmailUpdate( data?: ComAtprotoServerRequestEmailUpdate.InputSchema, opts?: ComAtprotoServerRequestEmailUpdate.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.server.requestEmailUpdate', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoServerRequestEmailUpdate.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.server.requestEmailUpdate', + opts?.qp, + data, + opts, + ) } requestPasswordReset( data?: ComAtprotoServerRequestPasswordReset.InputSchema, opts?: ComAtprotoServerRequestPasswordReset.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.server.requestPasswordReset', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoServerRequestPasswordReset.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.server.requestPasswordReset', + opts?.qp, + data, + opts, + ) } reserveSigningKey( data?: ComAtprotoServerReserveSigningKey.InputSchema, opts?: ComAtprotoServerReserveSigningKey.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.server.reserveSigningKey', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoServerReserveSigningKey.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.server.reserveSigningKey', + opts?.qp, + data, + opts, + ) } resetPassword( data?: ComAtprotoServerResetPassword.InputSchema, opts?: ComAtprotoServerResetPassword.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.server.resetPassword', opts?.qp, data, opts) .catch((e) => { throw ComAtprotoServerResetPassword.toKnownErr(e) @@ -1177,18 +1191,19 @@ export class ComAtprotoServerNS { data?: ComAtprotoServerRevokeAppPassword.InputSchema, opts?: ComAtprotoServerRevokeAppPassword.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.server.revokeAppPassword', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoServerRevokeAppPassword.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.server.revokeAppPassword', + opts?.qp, + data, + opts, + ) } updateEmail( data?: ComAtprotoServerUpdateEmail.InputSchema, opts?: ComAtprotoServerUpdateEmail.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.server.updateEmail', opts?.qp, data, opts) .catch((e) => { throw ComAtprotoServerUpdateEmail.toKnownErr(e) @@ -1197,17 +1212,17 @@ export class ComAtprotoServerNS { } export class ComAtprotoSyncNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } getBlob( params?: ComAtprotoSyncGetBlob.QueryParams, opts?: ComAtprotoSyncGetBlob.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.sync.getBlob', params, undefined, opts) .catch((e) => { throw ComAtprotoSyncGetBlob.toKnownErr(e) @@ -1218,7 +1233,7 @@ export class ComAtprotoSyncNS { params?: ComAtprotoSyncGetBlocks.QueryParams, opts?: ComAtprotoSyncGetBlocks.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.sync.getBlocks', params, undefined, opts) .catch((e) => { throw ComAtprotoSyncGetBlocks.toKnownErr(e) @@ -1229,18 +1244,19 @@ export class ComAtprotoSyncNS { params?: ComAtprotoSyncGetCheckout.QueryParams, opts?: ComAtprotoSyncGetCheckout.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.sync.getCheckout', params, undefined, opts) - .catch((e) => { - throw ComAtprotoSyncGetCheckout.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.sync.getCheckout', + params, + undefined, + opts, + ) } getHead( params?: ComAtprotoSyncGetHead.QueryParams, opts?: ComAtprotoSyncGetHead.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.sync.getHead', params, undefined, opts) .catch((e) => { throw ComAtprotoSyncGetHead.toKnownErr(e) @@ -1251,7 +1267,7 @@ export class ComAtprotoSyncNS { params?: ComAtprotoSyncGetLatestCommit.QueryParams, opts?: ComAtprotoSyncGetLatestCommit.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.sync.getLatestCommit', params, undefined, opts) .catch((e) => { throw ComAtprotoSyncGetLatestCommit.toKnownErr(e) @@ -1262,7 +1278,7 @@ export class ComAtprotoSyncNS { params?: ComAtprotoSyncGetRecord.QueryParams, opts?: ComAtprotoSyncGetRecord.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.sync.getRecord', params, undefined, opts) .catch((e) => { throw ComAtprotoSyncGetRecord.toKnownErr(e) @@ -1273,7 +1289,7 @@ export class ComAtprotoSyncNS { params?: ComAtprotoSyncGetRepo.QueryParams, opts?: ComAtprotoSyncGetRepo.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.sync.getRepo', params, undefined, opts) .catch((e) => { throw ComAtprotoSyncGetRepo.toKnownErr(e) @@ -1284,7 +1300,7 @@ export class ComAtprotoSyncNS { params?: ComAtprotoSyncGetRepoStatus.QueryParams, opts?: ComAtprotoSyncGetRepoStatus.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.sync.getRepoStatus', params, undefined, opts) .catch((e) => { throw ComAtprotoSyncGetRepoStatus.toKnownErr(e) @@ -1295,7 +1311,7 @@ export class ComAtprotoSyncNS { params?: ComAtprotoSyncListBlobs.QueryParams, opts?: ComAtprotoSyncListBlobs.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('com.atproto.sync.listBlobs', params, undefined, opts) .catch((e) => { throw ComAtprotoSyncListBlobs.toKnownErr(e) @@ -1306,89 +1322,95 @@ export class ComAtprotoSyncNS { params?: ComAtprotoSyncListRepos.QueryParams, opts?: ComAtprotoSyncListRepos.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.sync.listRepos', params, undefined, opts) - .catch((e) => { - throw ComAtprotoSyncListRepos.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.sync.listRepos', + params, + undefined, + opts, + ) } notifyOfUpdate( data?: ComAtprotoSyncNotifyOfUpdate.InputSchema, opts?: ComAtprotoSyncNotifyOfUpdate.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.sync.notifyOfUpdate', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoSyncNotifyOfUpdate.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.sync.notifyOfUpdate', + opts?.qp, + data, + opts, + ) } requestCrawl( data?: ComAtprotoSyncRequestCrawl.InputSchema, opts?: ComAtprotoSyncRequestCrawl.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.sync.requestCrawl', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoSyncRequestCrawl.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.sync.requestCrawl', + opts?.qp, + data, + opts, + ) } } export class ComAtprotoTempNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } checkSignupQueue( params?: ComAtprotoTempCheckSignupQueue.QueryParams, opts?: ComAtprotoTempCheckSignupQueue.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.temp.checkSignupQueue', params, undefined, opts) - .catch((e) => { - throw ComAtprotoTempCheckSignupQueue.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.temp.checkSignupQueue', + params, + undefined, + opts, + ) } fetchLabels( params?: ComAtprotoTempFetchLabels.QueryParams, opts?: ComAtprotoTempFetchLabels.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.temp.fetchLabels', params, undefined, opts) - .catch((e) => { - throw ComAtprotoTempFetchLabels.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.temp.fetchLabels', + params, + undefined, + opts, + ) } requestPhoneVerification( data?: ComAtprotoTempRequestPhoneVerification.InputSchema, opts?: ComAtprotoTempRequestPhoneVerification.CallOptions, ): Promise { - return this._service.xrpc - .call('com.atproto.temp.requestPhoneVerification', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoTempRequestPhoneVerification.toKnownErr(e) - }) + return this._client.call( + 'com.atproto.temp.requestPhoneVerification', + opts?.qp, + data, + opts, + ) } } export class AppNS { - _service: AtpServiceClient + _client: XrpcClient bsky: AppBskyNS - constructor(service: AtpServiceClient) { - this._service = service - this.bsky = new AppBskyNS(service) + constructor(client: XrpcClient) { + this._client = client + this.bsky = new AppBskyNS(client) } } export class AppBskyNS { - _service: AtpServiceClient + _client: XrpcClient actor: AppBskyActorNS embed: AppBskyEmbedNS feed: AppBskyFeedNS @@ -1398,111 +1420,118 @@ export class AppBskyNS { richtext: AppBskyRichtextNS unspecced: AppBskyUnspeccedNS - constructor(service: AtpServiceClient) { - this._service = service - this.actor = new AppBskyActorNS(service) - this.embed = new AppBskyEmbedNS(service) - this.feed = new AppBskyFeedNS(service) - this.graph = new AppBskyGraphNS(service) - this.labeler = new AppBskyLabelerNS(service) - this.notification = new AppBskyNotificationNS(service) - this.richtext = new AppBskyRichtextNS(service) - this.unspecced = new AppBskyUnspeccedNS(service) + constructor(client: XrpcClient) { + this._client = client + this.actor = new AppBskyActorNS(client) + this.embed = new AppBskyEmbedNS(client) + this.feed = new AppBskyFeedNS(client) + this.graph = new AppBskyGraphNS(client) + this.labeler = new AppBskyLabelerNS(client) + this.notification = new AppBskyNotificationNS(client) + this.richtext = new AppBskyRichtextNS(client) + this.unspecced = new AppBskyUnspeccedNS(client) } } export class AppBskyActorNS { - _service: AtpServiceClient + _client: XrpcClient profile: ProfileRecord - constructor(service: AtpServiceClient) { - this._service = service - this.profile = new ProfileRecord(service) + constructor(client: XrpcClient) { + this._client = client + this.profile = new ProfileRecord(client) } getPreferences( params?: AppBskyActorGetPreferences.QueryParams, opts?: AppBskyActorGetPreferences.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.actor.getPreferences', params, undefined, opts) - .catch((e) => { - throw AppBskyActorGetPreferences.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.actor.getPreferences', + params, + undefined, + opts, + ) } getProfile( params?: AppBskyActorGetProfile.QueryParams, opts?: AppBskyActorGetProfile.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.actor.getProfile', params, undefined, opts) - .catch((e) => { - throw AppBskyActorGetProfile.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.actor.getProfile', + params, + undefined, + opts, + ) } getProfiles( params?: AppBskyActorGetProfiles.QueryParams, opts?: AppBskyActorGetProfiles.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.actor.getProfiles', params, undefined, opts) - .catch((e) => { - throw AppBskyActorGetProfiles.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.actor.getProfiles', + params, + undefined, + opts, + ) } getSuggestions( params?: AppBskyActorGetSuggestions.QueryParams, opts?: AppBskyActorGetSuggestions.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.actor.getSuggestions', params, undefined, opts) - .catch((e) => { - throw AppBskyActorGetSuggestions.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.actor.getSuggestions', + params, + undefined, + opts, + ) } putPreferences( data?: AppBskyActorPutPreferences.InputSchema, opts?: AppBskyActorPutPreferences.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.actor.putPreferences', opts?.qp, data, opts) - .catch((e) => { - throw AppBskyActorPutPreferences.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.actor.putPreferences', + opts?.qp, + data, + opts, + ) } searchActors( params?: AppBskyActorSearchActors.QueryParams, opts?: AppBskyActorSearchActors.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.actor.searchActors', params, undefined, opts) - .catch((e) => { - throw AppBskyActorSearchActors.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.actor.searchActors', + params, + undefined, + opts, + ) } searchActorsTypeahead( params?: AppBskyActorSearchActorsTypeahead.QueryParams, opts?: AppBskyActorSearchActorsTypeahead.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.actor.searchActorsTypeahead', params, undefined, opts) - .catch((e) => { - throw AppBskyActorSearchActorsTypeahead.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.actor.searchActorsTypeahead', + params, + undefined, + opts, + ) } } export class ProfileRecord { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } async list( @@ -1511,7 +1540,7 @@ export class ProfileRecord { cursor?: string records: { uri: string; value: AppBskyActorProfile.Record }[] }> { - const res = await this._service.xrpc.call('com.atproto.repo.listRecords', { + const res = await this._client.call('com.atproto.repo.listRecords', { collection: 'app.bsky.actor.profile', ...params, }) @@ -1521,7 +1550,7 @@ export class ProfileRecord { async get( params: Omit, ): Promise<{ uri: string; cid: string; value: AppBskyActorProfile.Record }> { - const res = await this._service.xrpc.call('com.atproto.repo.getRecord', { + const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.actor.profile', ...params, }) @@ -1537,7 +1566,7 @@ export class ProfileRecord { headers?: Record, ): Promise<{ uri: string; cid: string }> { record.$type = 'app.bsky.actor.profile' - const res = await this._service.xrpc.call( + const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, { collection: 'app.bsky.actor.profile', rkey: 'self', ...params, record }, @@ -1550,7 +1579,7 @@ export class ProfileRecord { params: Omit, headers?: Record, ): Promise { - await this._service.xrpc.call( + await this._client.call( 'com.atproto.repo.deleteRecord', undefined, { collection: 'app.bsky.actor.profile', ...params }, @@ -1560,15 +1589,15 @@ export class ProfileRecord { } export class AppBskyEmbedNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } } export class AppBskyFeedNS { - _service: AtpServiceClient + _client: XrpcClient generator: GeneratorRecord like: LikeRecord post: PostRecord @@ -1576,43 +1605,45 @@ export class AppBskyFeedNS { repost: RepostRecord threadgate: ThreadgateRecord - constructor(service: AtpServiceClient) { - this._service = service - this.generator = new GeneratorRecord(service) - this.like = new LikeRecord(service) - this.post = new PostRecord(service) - this.postgate = new PostgateRecord(service) - this.repost = new RepostRecord(service) - this.threadgate = new ThreadgateRecord(service) + constructor(client: XrpcClient) { + this._client = client + this.generator = new GeneratorRecord(client) + this.like = new LikeRecord(client) + this.post = new PostRecord(client) + this.postgate = new PostgateRecord(client) + this.repost = new RepostRecord(client) + this.threadgate = new ThreadgateRecord(client) } describeFeedGenerator( params?: AppBskyFeedDescribeFeedGenerator.QueryParams, opts?: AppBskyFeedDescribeFeedGenerator.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.feed.describeFeedGenerator', params, undefined, opts) - .catch((e) => { - throw AppBskyFeedDescribeFeedGenerator.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.feed.describeFeedGenerator', + params, + undefined, + opts, + ) } getActorFeeds( params?: AppBskyFeedGetActorFeeds.QueryParams, opts?: AppBskyFeedGetActorFeeds.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.feed.getActorFeeds', params, undefined, opts) - .catch((e) => { - throw AppBskyFeedGetActorFeeds.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.feed.getActorFeeds', + params, + undefined, + opts, + ) } getActorLikes( params?: AppBskyFeedGetActorLikes.QueryParams, opts?: AppBskyFeedGetActorLikes.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('app.bsky.feed.getActorLikes', params, undefined, opts) .catch((e) => { throw AppBskyFeedGetActorLikes.toKnownErr(e) @@ -1623,7 +1654,7 @@ export class AppBskyFeedNS { params?: AppBskyFeedGetAuthorFeed.QueryParams, opts?: AppBskyFeedGetAuthorFeed.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('app.bsky.feed.getAuthorFeed', params, undefined, opts) .catch((e) => { throw AppBskyFeedGetAuthorFeed.toKnownErr(e) @@ -1634,7 +1665,7 @@ export class AppBskyFeedNS { params?: AppBskyFeedGetFeed.QueryParams, opts?: AppBskyFeedGetFeed.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('app.bsky.feed.getFeed', params, undefined, opts) .catch((e) => { throw AppBskyFeedGetFeed.toKnownErr(e) @@ -1645,29 +1676,31 @@ export class AppBskyFeedNS { params?: AppBskyFeedGetFeedGenerator.QueryParams, opts?: AppBskyFeedGetFeedGenerator.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.feed.getFeedGenerator', params, undefined, opts) - .catch((e) => { - throw AppBskyFeedGetFeedGenerator.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.feed.getFeedGenerator', + params, + undefined, + opts, + ) } getFeedGenerators( params?: AppBskyFeedGetFeedGenerators.QueryParams, opts?: AppBskyFeedGetFeedGenerators.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.feed.getFeedGenerators', params, undefined, opts) - .catch((e) => { - throw AppBskyFeedGetFeedGenerators.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.feed.getFeedGenerators', + params, + undefined, + opts, + ) } getFeedSkeleton( params?: AppBskyFeedGetFeedSkeleton.QueryParams, opts?: AppBskyFeedGetFeedSkeleton.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('app.bsky.feed.getFeedSkeleton', params, undefined, opts) .catch((e) => { throw AppBskyFeedGetFeedSkeleton.toKnownErr(e) @@ -1678,18 +1711,14 @@ export class AppBskyFeedNS { params?: AppBskyFeedGetLikes.QueryParams, opts?: AppBskyFeedGetLikes.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.feed.getLikes', params, undefined, opts) - .catch((e) => { - throw AppBskyFeedGetLikes.toKnownErr(e) - }) + return this._client.call('app.bsky.feed.getLikes', params, undefined, opts) } getListFeed( params?: AppBskyFeedGetListFeed.QueryParams, opts?: AppBskyFeedGetListFeed.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('app.bsky.feed.getListFeed', params, undefined, opts) .catch((e) => { throw AppBskyFeedGetListFeed.toKnownErr(e) @@ -1700,7 +1729,7 @@ export class AppBskyFeedNS { params?: AppBskyFeedGetPostThread.QueryParams, opts?: AppBskyFeedGetPostThread.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('app.bsky.feed.getPostThread', params, undefined, opts) .catch((e) => { throw AppBskyFeedGetPostThread.toKnownErr(e) @@ -1711,51 +1740,50 @@ export class AppBskyFeedNS { params?: AppBskyFeedGetPosts.QueryParams, opts?: AppBskyFeedGetPosts.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.feed.getPosts', params, undefined, opts) - .catch((e) => { - throw AppBskyFeedGetPosts.toKnownErr(e) - }) + return this._client.call('app.bsky.feed.getPosts', params, undefined, opts) } getRepostedBy( params?: AppBskyFeedGetRepostedBy.QueryParams, opts?: AppBskyFeedGetRepostedBy.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.feed.getRepostedBy', params, undefined, opts) - .catch((e) => { - throw AppBskyFeedGetRepostedBy.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.feed.getRepostedBy', + params, + undefined, + opts, + ) } getSuggestedFeeds( params?: AppBskyFeedGetSuggestedFeeds.QueryParams, opts?: AppBskyFeedGetSuggestedFeeds.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.feed.getSuggestedFeeds', params, undefined, opts) - .catch((e) => { - throw AppBskyFeedGetSuggestedFeeds.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.feed.getSuggestedFeeds', + params, + undefined, + opts, + ) } getTimeline( params?: AppBskyFeedGetTimeline.QueryParams, opts?: AppBskyFeedGetTimeline.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.feed.getTimeline', params, undefined, opts) - .catch((e) => { - throw AppBskyFeedGetTimeline.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.feed.getTimeline', + params, + undefined, + opts, + ) } searchPosts( params?: AppBskyFeedSearchPosts.QueryParams, opts?: AppBskyFeedSearchPosts.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('app.bsky.feed.searchPosts', params, undefined, opts) .catch((e) => { throw AppBskyFeedSearchPosts.toKnownErr(e) @@ -1766,19 +1794,20 @@ export class AppBskyFeedNS { data?: AppBskyFeedSendInteractions.InputSchema, opts?: AppBskyFeedSendInteractions.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.feed.sendInteractions', opts?.qp, data, opts) - .catch((e) => { - throw AppBskyFeedSendInteractions.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.feed.sendInteractions', + opts?.qp, + data, + opts, + ) } } export class GeneratorRecord { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } async list( @@ -1787,7 +1816,7 @@ export class GeneratorRecord { cursor?: string records: { uri: string; value: AppBskyFeedGenerator.Record }[] }> { - const res = await this._service.xrpc.call('com.atproto.repo.listRecords', { + const res = await this._client.call('com.atproto.repo.listRecords', { collection: 'app.bsky.feed.generator', ...params, }) @@ -1797,7 +1826,7 @@ export class GeneratorRecord { async get( params: Omit, ): Promise<{ uri: string; cid: string; value: AppBskyFeedGenerator.Record }> { - const res = await this._service.xrpc.call('com.atproto.repo.getRecord', { + const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.feed.generator', ...params, }) @@ -1813,7 +1842,7 @@ export class GeneratorRecord { headers?: Record, ): Promise<{ uri: string; cid: string }> { record.$type = 'app.bsky.feed.generator' - const res = await this._service.xrpc.call( + const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, { collection: 'app.bsky.feed.generator', ...params, record }, @@ -1826,7 +1855,7 @@ export class GeneratorRecord { params: Omit, headers?: Record, ): Promise { - await this._service.xrpc.call( + await this._client.call( 'com.atproto.repo.deleteRecord', undefined, { collection: 'app.bsky.feed.generator', ...params }, @@ -1836,10 +1865,10 @@ export class GeneratorRecord { } export class LikeRecord { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } async list( @@ -1848,7 +1877,7 @@ export class LikeRecord { cursor?: string records: { uri: string; value: AppBskyFeedLike.Record }[] }> { - const res = await this._service.xrpc.call('com.atproto.repo.listRecords', { + const res = await this._client.call('com.atproto.repo.listRecords', { collection: 'app.bsky.feed.like', ...params, }) @@ -1858,7 +1887,7 @@ export class LikeRecord { async get( params: Omit, ): Promise<{ uri: string; cid: string; value: AppBskyFeedLike.Record }> { - const res = await this._service.xrpc.call('com.atproto.repo.getRecord', { + const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.feed.like', ...params, }) @@ -1874,7 +1903,7 @@ export class LikeRecord { headers?: Record, ): Promise<{ uri: string; cid: string }> { record.$type = 'app.bsky.feed.like' - const res = await this._service.xrpc.call( + const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, { collection: 'app.bsky.feed.like', ...params, record }, @@ -1887,7 +1916,7 @@ export class LikeRecord { params: Omit, headers?: Record, ): Promise { - await this._service.xrpc.call( + await this._client.call( 'com.atproto.repo.deleteRecord', undefined, { collection: 'app.bsky.feed.like', ...params }, @@ -1897,10 +1926,10 @@ export class LikeRecord { } export class PostRecord { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } async list( @@ -1909,7 +1938,7 @@ export class PostRecord { cursor?: string records: { uri: string; value: AppBskyFeedPost.Record }[] }> { - const res = await this._service.xrpc.call('com.atproto.repo.listRecords', { + const res = await this._client.call('com.atproto.repo.listRecords', { collection: 'app.bsky.feed.post', ...params, }) @@ -1919,7 +1948,7 @@ export class PostRecord { async get( params: Omit, ): Promise<{ uri: string; cid: string; value: AppBskyFeedPost.Record }> { - const res = await this._service.xrpc.call('com.atproto.repo.getRecord', { + const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.feed.post', ...params, }) @@ -1935,7 +1964,7 @@ export class PostRecord { headers?: Record, ): Promise<{ uri: string; cid: string }> { record.$type = 'app.bsky.feed.post' - const res = await this._service.xrpc.call( + const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, { collection: 'app.bsky.feed.post', ...params, record }, @@ -1948,7 +1977,7 @@ export class PostRecord { params: Omit, headers?: Record, ): Promise { - await this._service.xrpc.call( + await this._client.call( 'com.atproto.repo.deleteRecord', undefined, { collection: 'app.bsky.feed.post', ...params }, @@ -1958,10 +1987,10 @@ export class PostRecord { } export class PostgateRecord { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } async list( @@ -1970,7 +1999,7 @@ export class PostgateRecord { cursor?: string records: { uri: string; value: AppBskyFeedPostgate.Record }[] }> { - const res = await this._service.xrpc.call('com.atproto.repo.listRecords', { + const res = await this._client.call('com.atproto.repo.listRecords', { collection: 'app.bsky.feed.postgate', ...params, }) @@ -1980,7 +2009,7 @@ export class PostgateRecord { async get( params: Omit, ): Promise<{ uri: string; cid: string; value: AppBskyFeedPostgate.Record }> { - const res = await this._service.xrpc.call('com.atproto.repo.getRecord', { + const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.feed.postgate', ...params, }) @@ -1996,7 +2025,7 @@ export class PostgateRecord { headers?: Record, ): Promise<{ uri: string; cid: string }> { record.$type = 'app.bsky.feed.postgate' - const res = await this._service.xrpc.call( + const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, { collection: 'app.bsky.feed.postgate', ...params, record }, @@ -2009,7 +2038,7 @@ export class PostgateRecord { params: Omit, headers?: Record, ): Promise { - await this._service.xrpc.call( + await this._client.call( 'com.atproto.repo.deleteRecord', undefined, { collection: 'app.bsky.feed.postgate', ...params }, @@ -2019,10 +2048,10 @@ export class PostgateRecord { } export class RepostRecord { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } async list( @@ -2031,7 +2060,7 @@ export class RepostRecord { cursor?: string records: { uri: string; value: AppBskyFeedRepost.Record }[] }> { - const res = await this._service.xrpc.call('com.atproto.repo.listRecords', { + const res = await this._client.call('com.atproto.repo.listRecords', { collection: 'app.bsky.feed.repost', ...params, }) @@ -2041,7 +2070,7 @@ export class RepostRecord { async get( params: Omit, ): Promise<{ uri: string; cid: string; value: AppBskyFeedRepost.Record }> { - const res = await this._service.xrpc.call('com.atproto.repo.getRecord', { + const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.feed.repost', ...params, }) @@ -2057,7 +2086,7 @@ export class RepostRecord { headers?: Record, ): Promise<{ uri: string; cid: string }> { record.$type = 'app.bsky.feed.repost' - const res = await this._service.xrpc.call( + const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, { collection: 'app.bsky.feed.repost', ...params, record }, @@ -2070,7 +2099,7 @@ export class RepostRecord { params: Omit, headers?: Record, ): Promise { - await this._service.xrpc.call( + await this._client.call( 'com.atproto.repo.deleteRecord', undefined, { collection: 'app.bsky.feed.repost', ...params }, @@ -2080,10 +2109,10 @@ export class RepostRecord { } export class ThreadgateRecord { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } async list( @@ -2092,7 +2121,7 @@ export class ThreadgateRecord { cursor?: string records: { uri: string; value: AppBskyFeedThreadgate.Record }[] }> { - const res = await this._service.xrpc.call('com.atproto.repo.listRecords', { + const res = await this._client.call('com.atproto.repo.listRecords', { collection: 'app.bsky.feed.threadgate', ...params, }) @@ -2106,7 +2135,7 @@ export class ThreadgateRecord { cid: string value: AppBskyFeedThreadgate.Record }> { - const res = await this._service.xrpc.call('com.atproto.repo.getRecord', { + const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.feed.threadgate', ...params, }) @@ -2122,7 +2151,7 @@ export class ThreadgateRecord { headers?: Record, ): Promise<{ uri: string; cid: string }> { record.$type = 'app.bsky.feed.threadgate' - const res = await this._service.xrpc.call( + const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, { collection: 'app.bsky.feed.threadgate', ...params, record }, @@ -2135,7 +2164,7 @@ export class ThreadgateRecord { params: Omit, headers?: Record, ): Promise { - await this._service.xrpc.call( + await this._client.call( 'com.atproto.repo.deleteRecord', undefined, { collection: 'app.bsky.feed.threadgate', ...params }, @@ -2145,7 +2174,7 @@ export class ThreadgateRecord { } export class AppBskyGraphNS { - _service: AtpServiceClient + _client: XrpcClient block: BlockRecord follow: FollowRecord list: ListRecord @@ -2153,131 +2182,126 @@ export class AppBskyGraphNS { listitem: ListitemRecord starterpack: StarterpackRecord - constructor(service: AtpServiceClient) { - this._service = service - this.block = new BlockRecord(service) - this.follow = new FollowRecord(service) - this.list = new ListRecord(service) - this.listblock = new ListblockRecord(service) - this.listitem = new ListitemRecord(service) - this.starterpack = new StarterpackRecord(service) + constructor(client: XrpcClient) { + this._client = client + this.block = new BlockRecord(client) + this.follow = new FollowRecord(client) + this.list = new ListRecord(client) + this.listblock = new ListblockRecord(client) + this.listitem = new ListitemRecord(client) + this.starterpack = new StarterpackRecord(client) } getActorStarterPacks( params?: AppBskyGraphGetActorStarterPacks.QueryParams, opts?: AppBskyGraphGetActorStarterPacks.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.getActorStarterPacks', params, undefined, opts) - .catch((e) => { - throw AppBskyGraphGetActorStarterPacks.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.graph.getActorStarterPacks', + params, + undefined, + opts, + ) } getBlocks( params?: AppBskyGraphGetBlocks.QueryParams, opts?: AppBskyGraphGetBlocks.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.getBlocks', params, undefined, opts) - .catch((e) => { - throw AppBskyGraphGetBlocks.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.graph.getBlocks', + params, + undefined, + opts, + ) } getFollowers( params?: AppBskyGraphGetFollowers.QueryParams, opts?: AppBskyGraphGetFollowers.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.getFollowers', params, undefined, opts) - .catch((e) => { - throw AppBskyGraphGetFollowers.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.graph.getFollowers', + params, + undefined, + opts, + ) } getFollows( params?: AppBskyGraphGetFollows.QueryParams, opts?: AppBskyGraphGetFollows.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.getFollows', params, undefined, opts) - .catch((e) => { - throw AppBskyGraphGetFollows.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.graph.getFollows', + params, + undefined, + opts, + ) } getKnownFollowers( params?: AppBskyGraphGetKnownFollowers.QueryParams, opts?: AppBskyGraphGetKnownFollowers.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.getKnownFollowers', params, undefined, opts) - .catch((e) => { - throw AppBskyGraphGetKnownFollowers.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.graph.getKnownFollowers', + params, + undefined, + opts, + ) } getList( params?: AppBskyGraphGetList.QueryParams, opts?: AppBskyGraphGetList.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.getList', params, undefined, opts) - .catch((e) => { - throw AppBskyGraphGetList.toKnownErr(e) - }) + return this._client.call('app.bsky.graph.getList', params, undefined, opts) } getListBlocks( params?: AppBskyGraphGetListBlocks.QueryParams, opts?: AppBskyGraphGetListBlocks.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.getListBlocks', params, undefined, opts) - .catch((e) => { - throw AppBskyGraphGetListBlocks.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.graph.getListBlocks', + params, + undefined, + opts, + ) } getListMutes( params?: AppBskyGraphGetListMutes.QueryParams, opts?: AppBskyGraphGetListMutes.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.getListMutes', params, undefined, opts) - .catch((e) => { - throw AppBskyGraphGetListMutes.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.graph.getListMutes', + params, + undefined, + opts, + ) } getLists( params?: AppBskyGraphGetLists.QueryParams, opts?: AppBskyGraphGetLists.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.getLists', params, undefined, opts) - .catch((e) => { - throw AppBskyGraphGetLists.toKnownErr(e) - }) + return this._client.call('app.bsky.graph.getLists', params, undefined, opts) } getMutes( params?: AppBskyGraphGetMutes.QueryParams, opts?: AppBskyGraphGetMutes.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.getMutes', params, undefined, opts) - .catch((e) => { - throw AppBskyGraphGetMutes.toKnownErr(e) - }) + return this._client.call('app.bsky.graph.getMutes', params, undefined, opts) } getRelationships( params?: AppBskyGraphGetRelationships.QueryParams, opts?: AppBskyGraphGetRelationships.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('app.bsky.graph.getRelationships', params, undefined, opts) .catch((e) => { throw AppBskyGraphGetRelationships.toKnownErr(e) @@ -2288,112 +2312,101 @@ export class AppBskyGraphNS { params?: AppBskyGraphGetStarterPack.QueryParams, opts?: AppBskyGraphGetStarterPack.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.getStarterPack', params, undefined, opts) - .catch((e) => { - throw AppBskyGraphGetStarterPack.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.graph.getStarterPack', + params, + undefined, + opts, + ) } getStarterPacks( params?: AppBskyGraphGetStarterPacks.QueryParams, opts?: AppBskyGraphGetStarterPacks.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.getStarterPacks', params, undefined, opts) - .catch((e) => { - throw AppBskyGraphGetStarterPacks.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.graph.getStarterPacks', + params, + undefined, + opts, + ) } getSuggestedFollowsByActor( params?: AppBskyGraphGetSuggestedFollowsByActor.QueryParams, opts?: AppBskyGraphGetSuggestedFollowsByActor.CallOptions, ): Promise { - return this._service.xrpc - .call( - 'app.bsky.graph.getSuggestedFollowsByActor', - params, - undefined, - opts, - ) - .catch((e) => { - throw AppBskyGraphGetSuggestedFollowsByActor.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.graph.getSuggestedFollowsByActor', + params, + undefined, + opts, + ) } muteActor( data?: AppBskyGraphMuteActor.InputSchema, opts?: AppBskyGraphMuteActor.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.muteActor', opts?.qp, data, opts) - .catch((e) => { - throw AppBskyGraphMuteActor.toKnownErr(e) - }) + return this._client.call('app.bsky.graph.muteActor', opts?.qp, data, opts) } muteActorList( data?: AppBskyGraphMuteActorList.InputSchema, opts?: AppBskyGraphMuteActorList.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.muteActorList', opts?.qp, data, opts) - .catch((e) => { - throw AppBskyGraphMuteActorList.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.graph.muteActorList', + opts?.qp, + data, + opts, + ) } muteThread( data?: AppBskyGraphMuteThread.InputSchema, opts?: AppBskyGraphMuteThread.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.muteThread', opts?.qp, data, opts) - .catch((e) => { - throw AppBskyGraphMuteThread.toKnownErr(e) - }) + return this._client.call('app.bsky.graph.muteThread', opts?.qp, data, opts) } unmuteActor( data?: AppBskyGraphUnmuteActor.InputSchema, opts?: AppBskyGraphUnmuteActor.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.unmuteActor', opts?.qp, data, opts) - .catch((e) => { - throw AppBskyGraphUnmuteActor.toKnownErr(e) - }) + return this._client.call('app.bsky.graph.unmuteActor', opts?.qp, data, opts) } unmuteActorList( data?: AppBskyGraphUnmuteActorList.InputSchema, opts?: AppBskyGraphUnmuteActorList.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.unmuteActorList', opts?.qp, data, opts) - .catch((e) => { - throw AppBskyGraphUnmuteActorList.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.graph.unmuteActorList', + opts?.qp, + data, + opts, + ) } unmuteThread( data?: AppBskyGraphUnmuteThread.InputSchema, opts?: AppBskyGraphUnmuteThread.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.graph.unmuteThread', opts?.qp, data, opts) - .catch((e) => { - throw AppBskyGraphUnmuteThread.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.graph.unmuteThread', + opts?.qp, + data, + opts, + ) } } export class BlockRecord { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } async list( @@ -2402,7 +2415,7 @@ export class BlockRecord { cursor?: string records: { uri: string; value: AppBskyGraphBlock.Record }[] }> { - const res = await this._service.xrpc.call('com.atproto.repo.listRecords', { + const res = await this._client.call('com.atproto.repo.listRecords', { collection: 'app.bsky.graph.block', ...params, }) @@ -2412,7 +2425,7 @@ export class BlockRecord { async get( params: Omit, ): Promise<{ uri: string; cid: string; value: AppBskyGraphBlock.Record }> { - const res = await this._service.xrpc.call('com.atproto.repo.getRecord', { + const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.graph.block', ...params, }) @@ -2428,7 +2441,7 @@ export class BlockRecord { headers?: Record, ): Promise<{ uri: string; cid: string }> { record.$type = 'app.bsky.graph.block' - const res = await this._service.xrpc.call( + const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, { collection: 'app.bsky.graph.block', ...params, record }, @@ -2441,7 +2454,7 @@ export class BlockRecord { params: Omit, headers?: Record, ): Promise { - await this._service.xrpc.call( + await this._client.call( 'com.atproto.repo.deleteRecord', undefined, { collection: 'app.bsky.graph.block', ...params }, @@ -2451,10 +2464,10 @@ export class BlockRecord { } export class FollowRecord { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } async list( @@ -2463,7 +2476,7 @@ export class FollowRecord { cursor?: string records: { uri: string; value: AppBskyGraphFollow.Record }[] }> { - const res = await this._service.xrpc.call('com.atproto.repo.listRecords', { + const res = await this._client.call('com.atproto.repo.listRecords', { collection: 'app.bsky.graph.follow', ...params, }) @@ -2473,7 +2486,7 @@ export class FollowRecord { async get( params: Omit, ): Promise<{ uri: string; cid: string; value: AppBskyGraphFollow.Record }> { - const res = await this._service.xrpc.call('com.atproto.repo.getRecord', { + const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.graph.follow', ...params, }) @@ -2489,7 +2502,7 @@ export class FollowRecord { headers?: Record, ): Promise<{ uri: string; cid: string }> { record.$type = 'app.bsky.graph.follow' - const res = await this._service.xrpc.call( + const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, { collection: 'app.bsky.graph.follow', ...params, record }, @@ -2502,7 +2515,7 @@ export class FollowRecord { params: Omit, headers?: Record, ): Promise { - await this._service.xrpc.call( + await this._client.call( 'com.atproto.repo.deleteRecord', undefined, { collection: 'app.bsky.graph.follow', ...params }, @@ -2512,10 +2525,10 @@ export class FollowRecord { } export class ListRecord { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } async list( @@ -2524,7 +2537,7 @@ export class ListRecord { cursor?: string records: { uri: string; value: AppBskyGraphList.Record }[] }> { - const res = await this._service.xrpc.call('com.atproto.repo.listRecords', { + const res = await this._client.call('com.atproto.repo.listRecords', { collection: 'app.bsky.graph.list', ...params, }) @@ -2534,7 +2547,7 @@ export class ListRecord { async get( params: Omit, ): Promise<{ uri: string; cid: string; value: AppBskyGraphList.Record }> { - const res = await this._service.xrpc.call('com.atproto.repo.getRecord', { + const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.graph.list', ...params, }) @@ -2550,7 +2563,7 @@ export class ListRecord { headers?: Record, ): Promise<{ uri: string; cid: string }> { record.$type = 'app.bsky.graph.list' - const res = await this._service.xrpc.call( + const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, { collection: 'app.bsky.graph.list', ...params, record }, @@ -2563,7 +2576,7 @@ export class ListRecord { params: Omit, headers?: Record, ): Promise { - await this._service.xrpc.call( + await this._client.call( 'com.atproto.repo.deleteRecord', undefined, { collection: 'app.bsky.graph.list', ...params }, @@ -2573,10 +2586,10 @@ export class ListRecord { } export class ListblockRecord { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } async list( @@ -2585,7 +2598,7 @@ export class ListblockRecord { cursor?: string records: { uri: string; value: AppBskyGraphListblock.Record }[] }> { - const res = await this._service.xrpc.call('com.atproto.repo.listRecords', { + const res = await this._client.call('com.atproto.repo.listRecords', { collection: 'app.bsky.graph.listblock', ...params, }) @@ -2599,7 +2612,7 @@ export class ListblockRecord { cid: string value: AppBskyGraphListblock.Record }> { - const res = await this._service.xrpc.call('com.atproto.repo.getRecord', { + const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.graph.listblock', ...params, }) @@ -2615,7 +2628,7 @@ export class ListblockRecord { headers?: Record, ): Promise<{ uri: string; cid: string }> { record.$type = 'app.bsky.graph.listblock' - const res = await this._service.xrpc.call( + const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, { collection: 'app.bsky.graph.listblock', ...params, record }, @@ -2628,7 +2641,7 @@ export class ListblockRecord { params: Omit, headers?: Record, ): Promise { - await this._service.xrpc.call( + await this._client.call( 'com.atproto.repo.deleteRecord', undefined, { collection: 'app.bsky.graph.listblock', ...params }, @@ -2638,10 +2651,10 @@ export class ListblockRecord { } export class ListitemRecord { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } async list( @@ -2650,7 +2663,7 @@ export class ListitemRecord { cursor?: string records: { uri: string; value: AppBskyGraphListitem.Record }[] }> { - const res = await this._service.xrpc.call('com.atproto.repo.listRecords', { + const res = await this._client.call('com.atproto.repo.listRecords', { collection: 'app.bsky.graph.listitem', ...params, }) @@ -2660,7 +2673,7 @@ export class ListitemRecord { async get( params: Omit, ): Promise<{ uri: string; cid: string; value: AppBskyGraphListitem.Record }> { - const res = await this._service.xrpc.call('com.atproto.repo.getRecord', { + const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.graph.listitem', ...params, }) @@ -2676,7 +2689,7 @@ export class ListitemRecord { headers?: Record, ): Promise<{ uri: string; cid: string }> { record.$type = 'app.bsky.graph.listitem' - const res = await this._service.xrpc.call( + const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, { collection: 'app.bsky.graph.listitem', ...params, record }, @@ -2689,7 +2702,7 @@ export class ListitemRecord { params: Omit, headers?: Record, ): Promise { - await this._service.xrpc.call( + await this._client.call( 'com.atproto.repo.deleteRecord', undefined, { collection: 'app.bsky.graph.listitem', ...params }, @@ -2699,10 +2712,10 @@ export class ListitemRecord { } export class StarterpackRecord { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } async list( @@ -2711,7 +2724,7 @@ export class StarterpackRecord { cursor?: string records: { uri: string; value: AppBskyGraphStarterpack.Record }[] }> { - const res = await this._service.xrpc.call('com.atproto.repo.listRecords', { + const res = await this._client.call('com.atproto.repo.listRecords', { collection: 'app.bsky.graph.starterpack', ...params, }) @@ -2725,7 +2738,7 @@ export class StarterpackRecord { cid: string value: AppBskyGraphStarterpack.Record }> { - const res = await this._service.xrpc.call('com.atproto.repo.getRecord', { + const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.graph.starterpack', ...params, }) @@ -2741,7 +2754,7 @@ export class StarterpackRecord { headers?: Record, ): Promise<{ uri: string; cid: string }> { record.$type = 'app.bsky.graph.starterpack' - const res = await this._service.xrpc.call( + const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, { collection: 'app.bsky.graph.starterpack', ...params, record }, @@ -2754,7 +2767,7 @@ export class StarterpackRecord { params: Omit, headers?: Record, ): Promise { - await this._service.xrpc.call( + await this._client.call( 'com.atproto.repo.deleteRecord', undefined, { collection: 'app.bsky.graph.starterpack', ...params }, @@ -2764,31 +2777,32 @@ export class StarterpackRecord { } export class AppBskyLabelerNS { - _service: AtpServiceClient + _client: XrpcClient service: ServiceRecord - constructor(service: AtpServiceClient) { - this._service = service - this.service = new ServiceRecord(service) + constructor(client: XrpcClient) { + this._client = client + this.service = new ServiceRecord(client) } getServices( params?: AppBskyLabelerGetServices.QueryParams, opts?: AppBskyLabelerGetServices.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.labeler.getServices', params, undefined, opts) - .catch((e) => { - throw AppBskyLabelerGetServices.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.labeler.getServices', + params, + undefined, + opts, + ) } } export class ServiceRecord { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } async list( @@ -2797,7 +2811,7 @@ export class ServiceRecord { cursor?: string records: { uri: string; value: AppBskyLabelerService.Record }[] }> { - const res = await this._service.xrpc.call('com.atproto.repo.listRecords', { + const res = await this._client.call('com.atproto.repo.listRecords', { collection: 'app.bsky.labeler.service', ...params, }) @@ -2811,7 +2825,7 @@ export class ServiceRecord { cid: string value: AppBskyLabelerService.Record }> { - const res = await this._service.xrpc.call('com.atproto.repo.getRecord', { + const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.labeler.service', ...params, }) @@ -2827,7 +2841,7 @@ export class ServiceRecord { headers?: Record, ): Promise<{ uri: string; cid: string }> { record.$type = 'app.bsky.labeler.service' - const res = await this._service.xrpc.call( + const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, { @@ -2845,7 +2859,7 @@ export class ServiceRecord { params: Omit, headers?: Record, ): Promise { - await this._service.xrpc.call( + await this._client.call( 'com.atproto.repo.deleteRecord', undefined, { collection: 'app.bsky.labeler.service', ...params }, @@ -2855,131 +2869,129 @@ export class ServiceRecord { } export class AppBskyNotificationNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } getUnreadCount( params?: AppBskyNotificationGetUnreadCount.QueryParams, opts?: AppBskyNotificationGetUnreadCount.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.notification.getUnreadCount', params, undefined, opts) - .catch((e) => { - throw AppBskyNotificationGetUnreadCount.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.notification.getUnreadCount', + params, + undefined, + opts, + ) } listNotifications( params?: AppBskyNotificationListNotifications.QueryParams, opts?: AppBskyNotificationListNotifications.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.notification.listNotifications', params, undefined, opts) - .catch((e) => { - throw AppBskyNotificationListNotifications.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.notification.listNotifications', + params, + undefined, + opts, + ) } putPreferences( data?: AppBskyNotificationPutPreferences.InputSchema, opts?: AppBskyNotificationPutPreferences.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.notification.putPreferences', opts?.qp, data, opts) - .catch((e) => { - throw AppBskyNotificationPutPreferences.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.notification.putPreferences', + opts?.qp, + data, + opts, + ) } registerPush( data?: AppBskyNotificationRegisterPush.InputSchema, opts?: AppBskyNotificationRegisterPush.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.notification.registerPush', opts?.qp, data, opts) - .catch((e) => { - throw AppBskyNotificationRegisterPush.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.notification.registerPush', + opts?.qp, + data, + opts, + ) } updateSeen( data?: AppBskyNotificationUpdateSeen.InputSchema, opts?: AppBskyNotificationUpdateSeen.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.notification.updateSeen', opts?.qp, data, opts) - .catch((e) => { - throw AppBskyNotificationUpdateSeen.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.notification.updateSeen', + opts?.qp, + data, + opts, + ) } } export class AppBskyRichtextNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } } export class AppBskyUnspeccedNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } getPopularFeedGenerators( params?: AppBskyUnspeccedGetPopularFeedGenerators.QueryParams, opts?: AppBskyUnspeccedGetPopularFeedGenerators.CallOptions, ): Promise { - return this._service.xrpc - .call( - 'app.bsky.unspecced.getPopularFeedGenerators', - params, - undefined, - opts, - ) - .catch((e) => { - throw AppBskyUnspeccedGetPopularFeedGenerators.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.unspecced.getPopularFeedGenerators', + params, + undefined, + opts, + ) } getSuggestionsSkeleton( params?: AppBskyUnspeccedGetSuggestionsSkeleton.QueryParams, opts?: AppBskyUnspeccedGetSuggestionsSkeleton.CallOptions, ): Promise { - return this._service.xrpc - .call( - 'app.bsky.unspecced.getSuggestionsSkeleton', - params, - undefined, - opts, - ) - .catch((e) => { - throw AppBskyUnspeccedGetSuggestionsSkeleton.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.unspecced.getSuggestionsSkeleton', + params, + undefined, + opts, + ) } getTaggedSuggestions( params?: AppBskyUnspeccedGetTaggedSuggestions.QueryParams, opts?: AppBskyUnspeccedGetTaggedSuggestions.CallOptions, ): Promise { - return this._service.xrpc - .call('app.bsky.unspecced.getTaggedSuggestions', params, undefined, opts) - .catch((e) => { - throw AppBskyUnspeccedGetTaggedSuggestions.toKnownErr(e) - }) + return this._client.call( + 'app.bsky.unspecced.getTaggedSuggestions', + params, + undefined, + opts, + ) } searchActorsSkeleton( params?: AppBskyUnspeccedSearchActorsSkeleton.QueryParams, opts?: AppBskyUnspeccedSearchActorsSkeleton.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('app.bsky.unspecced.searchActorsSkeleton', params, undefined, opts) .catch((e) => { throw AppBskyUnspeccedSearchActorsSkeleton.toKnownErr(e) @@ -2990,7 +3002,7 @@ export class AppBskyUnspeccedNS { params?: AppBskyUnspeccedSearchPostsSkeleton.QueryParams, opts?: AppBskyUnspeccedSearchPostsSkeleton.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('app.bsky.unspecced.searchPostsSkeleton', params, undefined, opts) .catch((e) => { throw AppBskyUnspeccedSearchPostsSkeleton.toKnownErr(e) @@ -2999,66 +3011,68 @@ export class AppBskyUnspeccedNS { } export class ChatNS { - _service: AtpServiceClient + _client: XrpcClient bsky: ChatBskyNS - constructor(service: AtpServiceClient) { - this._service = service - this.bsky = new ChatBskyNS(service) + constructor(client: XrpcClient) { + this._client = client + this.bsky = new ChatBskyNS(client) } } export class ChatBskyNS { - _service: AtpServiceClient + _client: XrpcClient actor: ChatBskyActorNS convo: ChatBskyConvoNS moderation: ChatBskyModerationNS - constructor(service: AtpServiceClient) { - this._service = service - this.actor = new ChatBskyActorNS(service) - this.convo = new ChatBskyConvoNS(service) - this.moderation = new ChatBskyModerationNS(service) + constructor(client: XrpcClient) { + this._client = client + this.actor = new ChatBskyActorNS(client) + this.convo = new ChatBskyConvoNS(client) + this.moderation = new ChatBskyModerationNS(client) } } export class ChatBskyActorNS { - _service: AtpServiceClient + _client: XrpcClient declaration: DeclarationRecord - constructor(service: AtpServiceClient) { - this._service = service - this.declaration = new DeclarationRecord(service) + constructor(client: XrpcClient) { + this._client = client + this.declaration = new DeclarationRecord(client) } deleteAccount( data?: ChatBskyActorDeleteAccount.InputSchema, opts?: ChatBskyActorDeleteAccount.CallOptions, ): Promise { - return this._service.xrpc - .call('chat.bsky.actor.deleteAccount', opts?.qp, data, opts) - .catch((e) => { - throw ChatBskyActorDeleteAccount.toKnownErr(e) - }) + return this._client.call( + 'chat.bsky.actor.deleteAccount', + opts?.qp, + data, + opts, + ) } exportAccountData( params?: ChatBskyActorExportAccountData.QueryParams, opts?: ChatBskyActorExportAccountData.CallOptions, ): Promise { - return this._service.xrpc - .call('chat.bsky.actor.exportAccountData', params, undefined, opts) - .catch((e) => { - throw ChatBskyActorExportAccountData.toKnownErr(e) - }) + return this._client.call( + 'chat.bsky.actor.exportAccountData', + params, + undefined, + opts, + ) } } export class DeclarationRecord { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } async list( @@ -3067,7 +3081,7 @@ export class DeclarationRecord { cursor?: string records: { uri: string; value: ChatBskyActorDeclaration.Record }[] }> { - const res = await this._service.xrpc.call('com.atproto.repo.listRecords', { + const res = await this._client.call('com.atproto.repo.listRecords', { collection: 'chat.bsky.actor.declaration', ...params, }) @@ -3081,7 +3095,7 @@ export class DeclarationRecord { cid: string value: ChatBskyActorDeclaration.Record }> { - const res = await this._service.xrpc.call('com.atproto.repo.getRecord', { + const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'chat.bsky.actor.declaration', ...params, }) @@ -3097,7 +3111,7 @@ export class DeclarationRecord { headers?: Record, ): Promise<{ uri: string; cid: string }> { record.$type = 'chat.bsky.actor.declaration' - const res = await this._service.xrpc.call( + const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, { @@ -3115,7 +3129,7 @@ export class DeclarationRecord { params: Omit, headers?: Record, ): Promise { - await this._service.xrpc.call( + await this._client.call( 'com.atproto.repo.deleteRecord', undefined, { collection: 'chat.bsky.actor.declaration', ...params }, @@ -3125,276 +3139,275 @@ export class DeclarationRecord { } export class ChatBskyConvoNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } deleteMessageForSelf( data?: ChatBskyConvoDeleteMessageForSelf.InputSchema, opts?: ChatBskyConvoDeleteMessageForSelf.CallOptions, ): Promise { - return this._service.xrpc - .call('chat.bsky.convo.deleteMessageForSelf', opts?.qp, data, opts) - .catch((e) => { - throw ChatBskyConvoDeleteMessageForSelf.toKnownErr(e) - }) + return this._client.call( + 'chat.bsky.convo.deleteMessageForSelf', + opts?.qp, + data, + opts, + ) } getConvo( params?: ChatBskyConvoGetConvo.QueryParams, opts?: ChatBskyConvoGetConvo.CallOptions, ): Promise { - return this._service.xrpc - .call('chat.bsky.convo.getConvo', params, undefined, opts) - .catch((e) => { - throw ChatBskyConvoGetConvo.toKnownErr(e) - }) + return this._client.call( + 'chat.bsky.convo.getConvo', + params, + undefined, + opts, + ) } getConvoForMembers( params?: ChatBskyConvoGetConvoForMembers.QueryParams, opts?: ChatBskyConvoGetConvoForMembers.CallOptions, ): Promise { - return this._service.xrpc - .call('chat.bsky.convo.getConvoForMembers', params, undefined, opts) - .catch((e) => { - throw ChatBskyConvoGetConvoForMembers.toKnownErr(e) - }) + return this._client.call( + 'chat.bsky.convo.getConvoForMembers', + params, + undefined, + opts, + ) } getLog( params?: ChatBskyConvoGetLog.QueryParams, opts?: ChatBskyConvoGetLog.CallOptions, ): Promise { - return this._service.xrpc - .call('chat.bsky.convo.getLog', params, undefined, opts) - .catch((e) => { - throw ChatBskyConvoGetLog.toKnownErr(e) - }) + return this._client.call('chat.bsky.convo.getLog', params, undefined, opts) } getMessages( params?: ChatBskyConvoGetMessages.QueryParams, opts?: ChatBskyConvoGetMessages.CallOptions, ): Promise { - return this._service.xrpc - .call('chat.bsky.convo.getMessages', params, undefined, opts) - .catch((e) => { - throw ChatBskyConvoGetMessages.toKnownErr(e) - }) + return this._client.call( + 'chat.bsky.convo.getMessages', + params, + undefined, + opts, + ) } leaveConvo( data?: ChatBskyConvoLeaveConvo.InputSchema, opts?: ChatBskyConvoLeaveConvo.CallOptions, ): Promise { - return this._service.xrpc - .call('chat.bsky.convo.leaveConvo', opts?.qp, data, opts) - .catch((e) => { - throw ChatBskyConvoLeaveConvo.toKnownErr(e) - }) + return this._client.call('chat.bsky.convo.leaveConvo', opts?.qp, data, opts) } listConvos( params?: ChatBskyConvoListConvos.QueryParams, opts?: ChatBskyConvoListConvos.CallOptions, ): Promise { - return this._service.xrpc - .call('chat.bsky.convo.listConvos', params, undefined, opts) - .catch((e) => { - throw ChatBskyConvoListConvos.toKnownErr(e) - }) + return this._client.call( + 'chat.bsky.convo.listConvos', + params, + undefined, + opts, + ) } muteConvo( data?: ChatBskyConvoMuteConvo.InputSchema, opts?: ChatBskyConvoMuteConvo.CallOptions, ): Promise { - return this._service.xrpc - .call('chat.bsky.convo.muteConvo', opts?.qp, data, opts) - .catch((e) => { - throw ChatBskyConvoMuteConvo.toKnownErr(e) - }) + return this._client.call('chat.bsky.convo.muteConvo', opts?.qp, data, opts) } sendMessage( data?: ChatBskyConvoSendMessage.InputSchema, opts?: ChatBskyConvoSendMessage.CallOptions, ): Promise { - return this._service.xrpc - .call('chat.bsky.convo.sendMessage', opts?.qp, data, opts) - .catch((e) => { - throw ChatBskyConvoSendMessage.toKnownErr(e) - }) + return this._client.call( + 'chat.bsky.convo.sendMessage', + opts?.qp, + data, + opts, + ) } sendMessageBatch( data?: ChatBskyConvoSendMessageBatch.InputSchema, opts?: ChatBskyConvoSendMessageBatch.CallOptions, ): Promise { - return this._service.xrpc - .call('chat.bsky.convo.sendMessageBatch', opts?.qp, data, opts) - .catch((e) => { - throw ChatBskyConvoSendMessageBatch.toKnownErr(e) - }) + return this._client.call( + 'chat.bsky.convo.sendMessageBatch', + opts?.qp, + data, + opts, + ) } unmuteConvo( data?: ChatBskyConvoUnmuteConvo.InputSchema, opts?: ChatBskyConvoUnmuteConvo.CallOptions, ): Promise { - return this._service.xrpc - .call('chat.bsky.convo.unmuteConvo', opts?.qp, data, opts) - .catch((e) => { - throw ChatBskyConvoUnmuteConvo.toKnownErr(e) - }) + return this._client.call( + 'chat.bsky.convo.unmuteConvo', + opts?.qp, + data, + opts, + ) } updateRead( data?: ChatBskyConvoUpdateRead.InputSchema, opts?: ChatBskyConvoUpdateRead.CallOptions, ): Promise { - return this._service.xrpc - .call('chat.bsky.convo.updateRead', opts?.qp, data, opts) - .catch((e) => { - throw ChatBskyConvoUpdateRead.toKnownErr(e) - }) + return this._client.call('chat.bsky.convo.updateRead', opts?.qp, data, opts) } } export class ChatBskyModerationNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } getActorMetadata( params?: ChatBskyModerationGetActorMetadata.QueryParams, opts?: ChatBskyModerationGetActorMetadata.CallOptions, ): Promise { - return this._service.xrpc - .call('chat.bsky.moderation.getActorMetadata', params, undefined, opts) - .catch((e) => { - throw ChatBskyModerationGetActorMetadata.toKnownErr(e) - }) + return this._client.call( + 'chat.bsky.moderation.getActorMetadata', + params, + undefined, + opts, + ) } getMessageContext( params?: ChatBskyModerationGetMessageContext.QueryParams, opts?: ChatBskyModerationGetMessageContext.CallOptions, ): Promise { - return this._service.xrpc - .call('chat.bsky.moderation.getMessageContext', params, undefined, opts) - .catch((e) => { - throw ChatBskyModerationGetMessageContext.toKnownErr(e) - }) + return this._client.call( + 'chat.bsky.moderation.getMessageContext', + params, + undefined, + opts, + ) } updateActorAccess( data?: ChatBskyModerationUpdateActorAccess.InputSchema, opts?: ChatBskyModerationUpdateActorAccess.CallOptions, ): Promise { - return this._service.xrpc - .call('chat.bsky.moderation.updateActorAccess', opts?.qp, data, opts) - .catch((e) => { - throw ChatBskyModerationUpdateActorAccess.toKnownErr(e) - }) + return this._client.call( + 'chat.bsky.moderation.updateActorAccess', + opts?.qp, + data, + opts, + ) } } export class ToolsNS { - _service: AtpServiceClient + _client: XrpcClient ozone: ToolsOzoneNS - constructor(service: AtpServiceClient) { - this._service = service - this.ozone = new ToolsOzoneNS(service) + constructor(client: XrpcClient) { + this._client = client + this.ozone = new ToolsOzoneNS(client) } } export class ToolsOzoneNS { - _service: AtpServiceClient + _client: XrpcClient communication: ToolsOzoneCommunicationNS moderation: ToolsOzoneModerationNS server: ToolsOzoneServerNS team: ToolsOzoneTeamNS - constructor(service: AtpServiceClient) { - this._service = service - this.communication = new ToolsOzoneCommunicationNS(service) - this.moderation = new ToolsOzoneModerationNS(service) - this.server = new ToolsOzoneServerNS(service) - this.team = new ToolsOzoneTeamNS(service) + constructor(client: XrpcClient) { + this._client = client + this.communication = new ToolsOzoneCommunicationNS(client) + this.moderation = new ToolsOzoneModerationNS(client) + this.server = new ToolsOzoneServerNS(client) + this.team = new ToolsOzoneTeamNS(client) } } export class ToolsOzoneCommunicationNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } createTemplate( data?: ToolsOzoneCommunicationCreateTemplate.InputSchema, opts?: ToolsOzoneCommunicationCreateTemplate.CallOptions, ): Promise { - return this._service.xrpc - .call('tools.ozone.communication.createTemplate', opts?.qp, data, opts) - .catch((e) => { - throw ToolsOzoneCommunicationCreateTemplate.toKnownErr(e) - }) + return this._client.call( + 'tools.ozone.communication.createTemplate', + opts?.qp, + data, + opts, + ) } deleteTemplate( data?: ToolsOzoneCommunicationDeleteTemplate.InputSchema, opts?: ToolsOzoneCommunicationDeleteTemplate.CallOptions, ): Promise { - return this._service.xrpc - .call('tools.ozone.communication.deleteTemplate', opts?.qp, data, opts) - .catch((e) => { - throw ToolsOzoneCommunicationDeleteTemplate.toKnownErr(e) - }) + return this._client.call( + 'tools.ozone.communication.deleteTemplate', + opts?.qp, + data, + opts, + ) } listTemplates( params?: ToolsOzoneCommunicationListTemplates.QueryParams, opts?: ToolsOzoneCommunicationListTemplates.CallOptions, ): Promise { - return this._service.xrpc - .call('tools.ozone.communication.listTemplates', params, undefined, opts) - .catch((e) => { - throw ToolsOzoneCommunicationListTemplates.toKnownErr(e) - }) + return this._client.call( + 'tools.ozone.communication.listTemplates', + params, + undefined, + opts, + ) } updateTemplate( data?: ToolsOzoneCommunicationUpdateTemplate.InputSchema, opts?: ToolsOzoneCommunicationUpdateTemplate.CallOptions, ): Promise { - return this._service.xrpc - .call('tools.ozone.communication.updateTemplate', opts?.qp, data, opts) - .catch((e) => { - throw ToolsOzoneCommunicationUpdateTemplate.toKnownErr(e) - }) + return this._client.call( + 'tools.ozone.communication.updateTemplate', + opts?.qp, + data, + opts, + ) } } export class ToolsOzoneModerationNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } emitEvent( data?: ToolsOzoneModerationEmitEvent.InputSchema, opts?: ToolsOzoneModerationEmitEvent.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('tools.ozone.moderation.emitEvent', opts?.qp, data, opts) .catch((e) => { throw ToolsOzoneModerationEmitEvent.toKnownErr(e) @@ -3405,18 +3418,19 @@ export class ToolsOzoneModerationNS { params?: ToolsOzoneModerationGetEvent.QueryParams, opts?: ToolsOzoneModerationGetEvent.CallOptions, ): Promise { - return this._service.xrpc - .call('tools.ozone.moderation.getEvent', params, undefined, opts) - .catch((e) => { - throw ToolsOzoneModerationGetEvent.toKnownErr(e) - }) + return this._client.call( + 'tools.ozone.moderation.getEvent', + params, + undefined, + opts, + ) } getRecord( params?: ToolsOzoneModerationGetRecord.QueryParams, opts?: ToolsOzoneModerationGetRecord.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('tools.ozone.moderation.getRecord', params, undefined, opts) .catch((e) => { throw ToolsOzoneModerationGetRecord.toKnownErr(e) @@ -3427,7 +3441,7 @@ export class ToolsOzoneModerationNS { params?: ToolsOzoneModerationGetRepo.QueryParams, opts?: ToolsOzoneModerationGetRepo.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('tools.ozone.moderation.getRepo', params, undefined, opts) .catch((e) => { throw ToolsOzoneModerationGetRepo.toKnownErr(e) @@ -3438,67 +3452,71 @@ export class ToolsOzoneModerationNS { params?: ToolsOzoneModerationQueryEvents.QueryParams, opts?: ToolsOzoneModerationQueryEvents.CallOptions, ): Promise { - return this._service.xrpc - .call('tools.ozone.moderation.queryEvents', params, undefined, opts) - .catch((e) => { - throw ToolsOzoneModerationQueryEvents.toKnownErr(e) - }) + return this._client.call( + 'tools.ozone.moderation.queryEvents', + params, + undefined, + opts, + ) } queryStatuses( params?: ToolsOzoneModerationQueryStatuses.QueryParams, opts?: ToolsOzoneModerationQueryStatuses.CallOptions, ): Promise { - return this._service.xrpc - .call('tools.ozone.moderation.queryStatuses', params, undefined, opts) - .catch((e) => { - throw ToolsOzoneModerationQueryStatuses.toKnownErr(e) - }) + return this._client.call( + 'tools.ozone.moderation.queryStatuses', + params, + undefined, + opts, + ) } searchRepos( params?: ToolsOzoneModerationSearchRepos.QueryParams, opts?: ToolsOzoneModerationSearchRepos.CallOptions, ): Promise { - return this._service.xrpc - .call('tools.ozone.moderation.searchRepos', params, undefined, opts) - .catch((e) => { - throw ToolsOzoneModerationSearchRepos.toKnownErr(e) - }) + return this._client.call( + 'tools.ozone.moderation.searchRepos', + params, + undefined, + opts, + ) } } export class ToolsOzoneServerNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } getConfig( params?: ToolsOzoneServerGetConfig.QueryParams, opts?: ToolsOzoneServerGetConfig.CallOptions, ): Promise { - return this._service.xrpc - .call('tools.ozone.server.getConfig', params, undefined, opts) - .catch((e) => { - throw ToolsOzoneServerGetConfig.toKnownErr(e) - }) + return this._client.call( + 'tools.ozone.server.getConfig', + params, + undefined, + opts, + ) } } export class ToolsOzoneTeamNS { - _service: AtpServiceClient + _client: XrpcClient - constructor(service: AtpServiceClient) { - this._service = service + constructor(client: XrpcClient) { + this._client = client } addMember( data?: ToolsOzoneTeamAddMember.InputSchema, opts?: ToolsOzoneTeamAddMember.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('tools.ozone.team.addMember', opts?.qp, data, opts) .catch((e) => { throw ToolsOzoneTeamAddMember.toKnownErr(e) @@ -3509,7 +3527,7 @@ export class ToolsOzoneTeamNS { data?: ToolsOzoneTeamDeleteMember.InputSchema, opts?: ToolsOzoneTeamDeleteMember.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('tools.ozone.team.deleteMember', opts?.qp, data, opts) .catch((e) => { throw ToolsOzoneTeamDeleteMember.toKnownErr(e) @@ -3520,18 +3538,19 @@ export class ToolsOzoneTeamNS { params?: ToolsOzoneTeamListMembers.QueryParams, opts?: ToolsOzoneTeamListMembers.CallOptions, ): Promise { - return this._service.xrpc - .call('tools.ozone.team.listMembers', params, undefined, opts) - .catch((e) => { - throw ToolsOzoneTeamListMembers.toKnownErr(e) - }) + return this._client.call( + 'tools.ozone.team.listMembers', + params, + undefined, + opts, + ) } updateMember( data?: ToolsOzoneTeamUpdateMember.InputSchema, opts?: ToolsOzoneTeamUpdateMember.CallOptions, ): Promise { - return this._service.xrpc + return this._client .call('tools.ozone.team.updateMember', opts?.qp, data, opts) .catch((e) => { throw ToolsOzoneTeamUpdateMember.toKnownErr(e) diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index 5c1068f0022..edb97f078f6 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -891,7 +891,7 @@ export const schemaDict = { labelValueDefinition: { type: 'object', description: - 'Declares a label value and its expected interpertations and behaviors.', + 'Declares a label value and its expected interpretations and behaviors.', required: ['identifier', 'severity', 'blurs', 'locales'], properties: { identifier: { @@ -2607,6 +2607,17 @@ export const schemaDict = { description: 'The DID of the service that the token will be used to authenticate with', }, + exp: { + type: 'integer', + description: + 'The time in Unix Epoch seconds that the JWT expires. Defaults to 60 seconds in the future. The service may enforce certain time bounds on tokens depending on the requested scope.', + }, + lxm: { + type: 'string', + format: 'nsid', + description: + 'Lexicon (XRPC) method to bind the requested token to', + }, }, }, output: { @@ -2621,6 +2632,13 @@ export const schemaDict = { }, }, }, + errors: [ + { + name: 'BadExpiration', + description: + 'Indicates that the requested expiration date is not a valid. May be in the past or may be reliant on the requested scopes.', + }, + ], }, }, }, @@ -5500,7 +5518,7 @@ export const schemaDict = { feedContext: { type: 'string', description: - 'Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton.', + 'Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton.', maxLength: 2000, }, }, diff --git a/packages/api/src/client/types/app/bsky/actor/getPreferences.ts b/packages/api/src/client/types/app/bsky/actor/getPreferences.ts index 94e479b8515..6cc35cc3c62 100644 --- a/packages/api/src/client/types/app/bsky/actor/getPreferences.ts +++ b/packages/api/src/client/types/app/bsky/actor/getPreferences.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -18,17 +18,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/actor/getProfile.ts b/packages/api/src/client/types/app/bsky/actor/getProfile.ts index bbd88c30a7b..13618b6c2f7 100644 --- a/packages/api/src/client/types/app/bsky/actor/getProfile.ts +++ b/packages/api/src/client/types/app/bsky/actor/getProfile.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -17,17 +17,16 @@ export type InputSchema = undefined export type OutputSchema = AppBskyActorDefs.ProfileViewDetailed export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/actor/getProfiles.ts b/packages/api/src/client/types/app/bsky/actor/getProfiles.ts index e346f12cac6..2207218ac69 100644 --- a/packages/api/src/client/types/app/bsky/actor/getProfiles.ts +++ b/packages/api/src/client/types/app/bsky/actor/getProfiles.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -20,17 +20,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/actor/getSuggestions.ts b/packages/api/src/client/types/app/bsky/actor/getSuggestions.ts index 3e974a29a05..48553fd1b85 100644 --- a/packages/api/src/client/types/app/bsky/actor/getSuggestions.ts +++ b/packages/api/src/client/types/app/bsky/actor/getSuggestions.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -22,17 +22,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/actor/putPreferences.ts b/packages/api/src/client/types/app/bsky/actor/putPreferences.ts index 372154d797e..834b1bd247c 100644 --- a/packages/api/src/client/types/app/bsky/actor/putPreferences.ts +++ b/packages/api/src/client/types/app/bsky/actor/putPreferences.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -16,18 +16,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/actor/searchActors.ts b/packages/api/src/client/types/app/bsky/actor/searchActors.ts index 63395418e11..bcbde2b7bfb 100644 --- a/packages/api/src/client/types/app/bsky/actor/searchActors.ts +++ b/packages/api/src/client/types/app/bsky/actor/searchActors.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -26,17 +26,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/actor/searchActorsTypeahead.ts b/packages/api/src/client/types/app/bsky/actor/searchActorsTypeahead.ts index a91e0ce7dcd..abed0289f27 100644 --- a/packages/api/src/client/types/app/bsky/actor/searchActorsTypeahead.ts +++ b/packages/api/src/client/types/app/bsky/actor/searchActorsTypeahead.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -24,17 +24,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/feed/defs.ts b/packages/api/src/client/types/app/bsky/feed/defs.ts index c4671fbb4a1..bc0f4b03c19 100644 --- a/packages/api/src/client/types/app/bsky/feed/defs.ts +++ b/packages/api/src/client/types/app/bsky/feed/defs.ts @@ -333,7 +333,7 @@ export interface Interaction { | 'app.bsky.feed.defs#interactionQuote' | 'app.bsky.feed.defs#interactionShare' | (string & {}) - /** Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton. */ + /** Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton. */ feedContext?: string [k: string]: unknown } diff --git a/packages/api/src/client/types/app/bsky/feed/describeFeedGenerator.ts b/packages/api/src/client/types/app/bsky/feed/describeFeedGenerator.ts index f3fb1f24d18..b974f496144 100644 --- a/packages/api/src/client/types/app/bsky/feed/describeFeedGenerator.ts +++ b/packages/api/src/client/types/app/bsky/feed/describeFeedGenerator.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -19,18 +19,17 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/feed/getActorFeeds.ts b/packages/api/src/client/types/app/bsky/feed/getActorFeeds.ts index e5fbf2305d8..e8a0cb94bd3 100644 --- a/packages/api/src/client/types/app/bsky/feed/getActorFeeds.ts +++ b/packages/api/src/client/types/app/bsky/feed/getActorFeeds.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -23,17 +23,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/feed/getActorLikes.ts b/packages/api/src/client/types/app/bsky/feed/getActorLikes.ts index 0f101ca4c3b..1da239d96f3 100644 --- a/packages/api/src/client/types/app/bsky/feed/getActorLikes.ts +++ b/packages/api/src/client/types/app/bsky/feed/getActorLikes.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -23,24 +23,25 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class BlockedActorError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class BlockedByActorError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -49,5 +50,6 @@ export function toKnownErr(e: any) { if (e.error === 'BlockedActor') return new BlockedActorError(e) if (e.error === 'BlockedByActor') return new BlockedByActorError(e) } + return e } diff --git a/packages/api/src/client/types/app/bsky/feed/getAuthorFeed.ts b/packages/api/src/client/types/app/bsky/feed/getAuthorFeed.ts index 3f498e49514..63f930e857c 100644 --- a/packages/api/src/client/types/app/bsky/feed/getAuthorFeed.ts +++ b/packages/api/src/client/types/app/bsky/feed/getAuthorFeed.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -30,24 +30,25 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class BlockedActorError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class BlockedByActorError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -56,5 +57,6 @@ export function toKnownErr(e: any) { if (e.error === 'BlockedActor') return new BlockedActorError(e) if (e.error === 'BlockedByActor') return new BlockedByActorError(e) } + return e } diff --git a/packages/api/src/client/types/app/bsky/feed/getFeed.ts b/packages/api/src/client/types/app/bsky/feed/getFeed.ts index 65ede1fd2a4..b61a1248e2a 100644 --- a/packages/api/src/client/types/app/bsky/feed/getFeed.ts +++ b/packages/api/src/client/types/app/bsky/feed/getFeed.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -23,18 +23,19 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class UnknownFeedError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -42,5 +43,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'UnknownFeed') return new UnknownFeedError(e) } + return e } diff --git a/packages/api/src/client/types/app/bsky/feed/getFeedGenerator.ts b/packages/api/src/client/types/app/bsky/feed/getFeedGenerator.ts index f08c9b59340..8d222f8d521 100644 --- a/packages/api/src/client/types/app/bsky/feed/getFeedGenerator.ts +++ b/packages/api/src/client/types/app/bsky/feed/getFeedGenerator.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -25,17 +25,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/feed/getFeedGenerators.ts b/packages/api/src/client/types/app/bsky/feed/getFeedGenerators.ts index 40e8bf0e911..1d99fd6608c 100644 --- a/packages/api/src/client/types/app/bsky/feed/getFeedGenerators.ts +++ b/packages/api/src/client/types/app/bsky/feed/getFeedGenerators.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -20,17 +20,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/feed/getFeedSkeleton.ts b/packages/api/src/client/types/app/bsky/feed/getFeedSkeleton.ts index 1426469c84d..bf859cc1e87 100644 --- a/packages/api/src/client/types/app/bsky/feed/getFeedSkeleton.ts +++ b/packages/api/src/client/types/app/bsky/feed/getFeedSkeleton.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -24,18 +24,19 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class UnknownFeedError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -43,5 +44,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'UnknownFeed') return new UnknownFeedError(e) } + return e } diff --git a/packages/api/src/client/types/app/bsky/feed/getLikes.ts b/packages/api/src/client/types/app/bsky/feed/getLikes.ts index 9725ef065d9..35af1b3aae5 100644 --- a/packages/api/src/client/types/app/bsky/feed/getLikes.ts +++ b/packages/api/src/client/types/app/bsky/feed/getLikes.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -28,18 +28,17 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/feed/getListFeed.ts b/packages/api/src/client/types/app/bsky/feed/getListFeed.ts index 6b4156ddda9..4e47e597afd 100644 --- a/packages/api/src/client/types/app/bsky/feed/getListFeed.ts +++ b/packages/api/src/client/types/app/bsky/feed/getListFeed.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -24,18 +24,19 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class UnknownListError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -43,5 +44,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'UnknownList') return new UnknownListError(e) } + return e } diff --git a/packages/api/src/client/types/app/bsky/feed/getPostThread.ts b/packages/api/src/client/types/app/bsky/feed/getPostThread.ts index d03ad7de127..930db4c1766 100644 --- a/packages/api/src/client/types/app/bsky/feed/getPostThread.ts +++ b/packages/api/src/client/types/app/bsky/feed/getPostThread.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -29,18 +29,19 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class NotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -48,5 +49,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'NotFound') return new NotFoundError(e) } + return e } diff --git a/packages/api/src/client/types/app/bsky/feed/getPosts.ts b/packages/api/src/client/types/app/bsky/feed/getPosts.ts index cd932d88047..0ae8657de94 100644 --- a/packages/api/src/client/types/app/bsky/feed/getPosts.ts +++ b/packages/api/src/client/types/app/bsky/feed/getPosts.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -21,17 +21,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/feed/getRepostedBy.ts b/packages/api/src/client/types/app/bsky/feed/getRepostedBy.ts index d27aa1dec0a..dfbac97453b 100644 --- a/packages/api/src/client/types/app/bsky/feed/getRepostedBy.ts +++ b/packages/api/src/client/types/app/bsky/feed/getRepostedBy.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -28,17 +28,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/feed/getSuggestedFeeds.ts b/packages/api/src/client/types/app/bsky/feed/getSuggestedFeeds.ts index ef4fe1cfefe..2e9495d8c19 100644 --- a/packages/api/src/client/types/app/bsky/feed/getSuggestedFeeds.ts +++ b/packages/api/src/client/types/app/bsky/feed/getSuggestedFeeds.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -22,17 +22,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/feed/getTimeline.ts b/packages/api/src/client/types/app/bsky/feed/getTimeline.ts index 5ab2c7c4b1f..6c5280443a9 100644 --- a/packages/api/src/client/types/app/bsky/feed/getTimeline.ts +++ b/packages/api/src/client/types/app/bsky/feed/getTimeline.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -24,17 +24,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/feed/searchPosts.ts b/packages/api/src/client/types/app/bsky/feed/searchPosts.ts index 7e04d8eeca5..1a2ec64dcd2 100644 --- a/packages/api/src/client/types/app/bsky/feed/searchPosts.ts +++ b/packages/api/src/client/types/app/bsky/feed/searchPosts.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -45,18 +45,19 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class BadQueryStringError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -64,5 +65,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'BadQueryString') return new BadQueryStringError(e) } + return e } diff --git a/packages/api/src/client/types/app/bsky/feed/sendInteractions.ts b/packages/api/src/client/types/app/bsky/feed/sendInteractions.ts index 63ea0cc6a82..4d43e26f3dd 100644 --- a/packages/api/src/client/types/app/bsky/feed/sendInteractions.ts +++ b/packages/api/src/client/types/app/bsky/feed/sendInteractions.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -20,19 +20,18 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/getActorStarterPacks.ts b/packages/api/src/client/types/app/bsky/graph/getActorStarterPacks.ts index 2264aa1bd71..bb374e2318d 100644 --- a/packages/api/src/client/types/app/bsky/graph/getActorStarterPacks.ts +++ b/packages/api/src/client/types/app/bsky/graph/getActorStarterPacks.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -23,17 +23,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/getBlocks.ts b/packages/api/src/client/types/app/bsky/graph/getBlocks.ts index eaa2ca09b01..f0e4bd96bc8 100644 --- a/packages/api/src/client/types/app/bsky/graph/getBlocks.ts +++ b/packages/api/src/client/types/app/bsky/graph/getBlocks.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -22,17 +22,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/getFollowers.ts b/packages/api/src/client/types/app/bsky/graph/getFollowers.ts index fc8fb7fc6ed..f55649dc769 100644 --- a/packages/api/src/client/types/app/bsky/graph/getFollowers.ts +++ b/packages/api/src/client/types/app/bsky/graph/getFollowers.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -24,17 +24,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/getFollows.ts b/packages/api/src/client/types/app/bsky/graph/getFollows.ts index fbba5529dab..8570c4a14c0 100644 --- a/packages/api/src/client/types/app/bsky/graph/getFollows.ts +++ b/packages/api/src/client/types/app/bsky/graph/getFollows.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -24,17 +24,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/getKnownFollowers.ts b/packages/api/src/client/types/app/bsky/graph/getKnownFollowers.ts index fc8fb7fc6ed..f55649dc769 100644 --- a/packages/api/src/client/types/app/bsky/graph/getKnownFollowers.ts +++ b/packages/api/src/client/types/app/bsky/graph/getKnownFollowers.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -24,17 +24,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/getList.ts b/packages/api/src/client/types/app/bsky/graph/getList.ts index 36c4cf0aa86..bebde2b49c3 100644 --- a/packages/api/src/client/types/app/bsky/graph/getList.ts +++ b/packages/api/src/client/types/app/bsky/graph/getList.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -25,17 +25,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/getListBlocks.ts b/packages/api/src/client/types/app/bsky/graph/getListBlocks.ts index 052587c603e..d3c7ad5ed7b 100644 --- a/packages/api/src/client/types/app/bsky/graph/getListBlocks.ts +++ b/packages/api/src/client/types/app/bsky/graph/getListBlocks.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -22,17 +22,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/getListMutes.ts b/packages/api/src/client/types/app/bsky/graph/getListMutes.ts index 052587c603e..d3c7ad5ed7b 100644 --- a/packages/api/src/client/types/app/bsky/graph/getListMutes.ts +++ b/packages/api/src/client/types/app/bsky/graph/getListMutes.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -22,17 +22,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/getLists.ts b/packages/api/src/client/types/app/bsky/graph/getLists.ts index 644aeea3b4b..89a4bfd58df 100644 --- a/packages/api/src/client/types/app/bsky/graph/getLists.ts +++ b/packages/api/src/client/types/app/bsky/graph/getLists.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -24,17 +24,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/getMutes.ts b/packages/api/src/client/types/app/bsky/graph/getMutes.ts index c2c8883af2b..0ee441cf9eb 100644 --- a/packages/api/src/client/types/app/bsky/graph/getMutes.ts +++ b/packages/api/src/client/types/app/bsky/graph/getMutes.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -22,17 +22,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/getRelationships.ts b/packages/api/src/client/types/app/bsky/graph/getRelationships.ts index 9aa58ad2699..20194662b71 100644 --- a/packages/api/src/client/types/app/bsky/graph/getRelationships.ts +++ b/packages/api/src/client/types/app/bsky/graph/getRelationships.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -28,18 +28,19 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class ActorNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -47,5 +48,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'ActorNotFound') return new ActorNotFoundError(e) } + return e } diff --git a/packages/api/src/client/types/app/bsky/graph/getStarterPack.ts b/packages/api/src/client/types/app/bsky/graph/getStarterPack.ts index 6abed01fef9..7eafb2d228d 100644 --- a/packages/api/src/client/types/app/bsky/graph/getStarterPack.ts +++ b/packages/api/src/client/types/app/bsky/graph/getStarterPack.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -21,17 +21,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/getStarterPacks.ts b/packages/api/src/client/types/app/bsky/graph/getStarterPacks.ts index 9c960d48b90..50dbfdbaeda 100644 --- a/packages/api/src/client/types/app/bsky/graph/getStarterPacks.ts +++ b/packages/api/src/client/types/app/bsky/graph/getStarterPacks.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -20,17 +20,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/getSuggestedFollowsByActor.ts b/packages/api/src/client/types/app/bsky/graph/getSuggestedFollowsByActor.ts index 8ff7ed414cb..94156e8134b 100644 --- a/packages/api/src/client/types/app/bsky/graph/getSuggestedFollowsByActor.ts +++ b/packages/api/src/client/types/app/bsky/graph/getSuggestedFollowsByActor.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -20,17 +20,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/muteActor.ts b/packages/api/src/client/types/app/bsky/graph/muteActor.ts index dbae811dbce..c21c00b2069 100644 --- a/packages/api/src/client/types/app/bsky/graph/muteActor.ts +++ b/packages/api/src/client/types/app/bsky/graph/muteActor.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -15,18 +15,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/muteActorList.ts b/packages/api/src/client/types/app/bsky/graph/muteActorList.ts index 0e458e1d534..8a0e8586deb 100644 --- a/packages/api/src/client/types/app/bsky/graph/muteActorList.ts +++ b/packages/api/src/client/types/app/bsky/graph/muteActorList.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -15,18 +15,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/muteThread.ts b/packages/api/src/client/types/app/bsky/graph/muteThread.ts index 3ac7401af65..275ba7a0f22 100644 --- a/packages/api/src/client/types/app/bsky/graph/muteThread.ts +++ b/packages/api/src/client/types/app/bsky/graph/muteThread.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -15,18 +15,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/unmuteActor.ts b/packages/api/src/client/types/app/bsky/graph/unmuteActor.ts index dbae811dbce..c21c00b2069 100644 --- a/packages/api/src/client/types/app/bsky/graph/unmuteActor.ts +++ b/packages/api/src/client/types/app/bsky/graph/unmuteActor.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -15,18 +15,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/unmuteActorList.ts b/packages/api/src/client/types/app/bsky/graph/unmuteActorList.ts index 0e458e1d534..8a0e8586deb 100644 --- a/packages/api/src/client/types/app/bsky/graph/unmuteActorList.ts +++ b/packages/api/src/client/types/app/bsky/graph/unmuteActorList.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -15,18 +15,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/graph/unmuteThread.ts b/packages/api/src/client/types/app/bsky/graph/unmuteThread.ts index 3ac7401af65..275ba7a0f22 100644 --- a/packages/api/src/client/types/app/bsky/graph/unmuteThread.ts +++ b/packages/api/src/client/types/app/bsky/graph/unmuteThread.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -15,18 +15,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/labeler/getServices.ts b/packages/api/src/client/types/app/bsky/labeler/getServices.ts index 8a7db1adbf3..688f847d508 100644 --- a/packages/api/src/client/types/app/bsky/labeler/getServices.ts +++ b/packages/api/src/client/types/app/bsky/labeler/getServices.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -25,17 +25,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/notification/getUnreadCount.ts b/packages/api/src/client/types/app/bsky/notification/getUnreadCount.ts index 9e705c23ecf..00600ea54e7 100644 --- a/packages/api/src/client/types/app/bsky/notification/getUnreadCount.ts +++ b/packages/api/src/client/types/app/bsky/notification/getUnreadCount.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -20,17 +20,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/notification/listNotifications.ts b/packages/api/src/client/types/app/bsky/notification/listNotifications.ts index 9fc497330ed..10a5b7148f2 100644 --- a/packages/api/src/client/types/app/bsky/notification/listNotifications.ts +++ b/packages/api/src/client/types/app/bsky/notification/listNotifications.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -27,18 +27,17 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/notification/putPreferences.ts b/packages/api/src/client/types/app/bsky/notification/putPreferences.ts index 49865ef3943..bc79909f459 100644 --- a/packages/api/src/client/types/app/bsky/notification/putPreferences.ts +++ b/packages/api/src/client/types/app/bsky/notification/putPreferences.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -15,18 +15,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/notification/registerPush.ts b/packages/api/src/client/types/app/bsky/notification/registerPush.ts index 9354ef76766..ddc9d438537 100644 --- a/packages/api/src/client/types/app/bsky/notification/registerPush.ts +++ b/packages/api/src/client/types/app/bsky/notification/registerPush.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -18,18 +18,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/notification/updateSeen.ts b/packages/api/src/client/types/app/bsky/notification/updateSeen.ts index 1712935cf10..7151e9b0cd4 100644 --- a/packages/api/src/client/types/app/bsky/notification/updateSeen.ts +++ b/packages/api/src/client/types/app/bsky/notification/updateSeen.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -15,18 +15,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/unspecced/getPopularFeedGenerators.ts b/packages/api/src/client/types/app/bsky/unspecced/getPopularFeedGenerators.ts index 19ca5cbc597..780b4b6641c 100644 --- a/packages/api/src/client/types/app/bsky/unspecced/getPopularFeedGenerators.ts +++ b/packages/api/src/client/types/app/bsky/unspecced/getPopularFeedGenerators.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -23,17 +23,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/unspecced/getSuggestionsSkeleton.ts b/packages/api/src/client/types/app/bsky/unspecced/getSuggestionsSkeleton.ts index 7a076e1fa6c..6900ec882ea 100644 --- a/packages/api/src/client/types/app/bsky/unspecced/getSuggestionsSkeleton.ts +++ b/packages/api/src/client/types/app/bsky/unspecced/getSuggestionsSkeleton.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -26,17 +26,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/unspecced/getTaggedSuggestions.ts b/packages/api/src/client/types/app/bsky/unspecced/getTaggedSuggestions.ts index a35e2411756..0ee04b8e85d 100644 --- a/packages/api/src/client/types/app/bsky/unspecced/getTaggedSuggestions.ts +++ b/packages/api/src/client/types/app/bsky/unspecced/getTaggedSuggestions.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -17,18 +17,17 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/app/bsky/unspecced/searchActorsSkeleton.ts b/packages/api/src/client/types/app/bsky/unspecced/searchActorsSkeleton.ts index 935bba95a3b..d9fe6737d37 100644 --- a/packages/api/src/client/types/app/bsky/unspecced/searchActorsSkeleton.ts +++ b/packages/api/src/client/types/app/bsky/unspecced/searchActorsSkeleton.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -31,18 +31,19 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class BadQueryStringError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -50,5 +51,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'BadQueryString') return new BadQueryStringError(e) } + return e } diff --git a/packages/api/src/client/types/app/bsky/unspecced/searchPostsSkeleton.ts b/packages/api/src/client/types/app/bsky/unspecced/searchPostsSkeleton.ts index 71cd03078fa..ea0135dde83 100644 --- a/packages/api/src/client/types/app/bsky/unspecced/searchPostsSkeleton.ts +++ b/packages/api/src/client/types/app/bsky/unspecced/searchPostsSkeleton.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -47,18 +47,19 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class BadQueryStringError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -66,5 +67,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'BadQueryString') return new BadQueryStringError(e) } + return e } diff --git a/packages/api/src/client/types/chat/bsky/actor/deleteAccount.ts b/packages/api/src/client/types/chat/bsky/actor/deleteAccount.ts index 1e1e7237f51..1a045c21c29 100644 --- a/packages/api/src/client/types/chat/bsky/actor/deleteAccount.ts +++ b/packages/api/src/client/types/chat/bsky/actor/deleteAccount.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -16,18 +16,17 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/chat/bsky/actor/exportAccountData.ts b/packages/api/src/client/types/chat/bsky/actor/exportAccountData.ts index c57f48b285f..0142436b05e 100644 --- a/packages/api/src/client/types/chat/bsky/actor/exportAccountData.ts +++ b/packages/api/src/client/types/chat/bsky/actor/exportAccountData.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -12,17 +12,16 @@ export interface QueryParams {} export type InputSchema = undefined export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: Uint8Array } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/chat/bsky/convo/deleteMessageForSelf.ts b/packages/api/src/client/types/chat/bsky/convo/deleteMessageForSelf.ts index 513c30984e7..863d2208cb8 100644 --- a/packages/api/src/client/types/chat/bsky/convo/deleteMessageForSelf.ts +++ b/packages/api/src/client/types/chat/bsky/convo/deleteMessageForSelf.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -19,19 +19,18 @@ export interface InputSchema { export type OutputSchema = ChatBskyConvoDefs.DeletedMessageView export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/chat/bsky/convo/getConvo.ts b/packages/api/src/client/types/chat/bsky/convo/getConvo.ts index 245788e572c..b3834234b5f 100644 --- a/packages/api/src/client/types/chat/bsky/convo/getConvo.ts +++ b/packages/api/src/client/types/chat/bsky/convo/getConvo.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -20,17 +20,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/chat/bsky/convo/getConvoForMembers.ts b/packages/api/src/client/types/chat/bsky/convo/getConvoForMembers.ts index 9c8f45cef47..9db44887410 100644 --- a/packages/api/src/client/types/chat/bsky/convo/getConvoForMembers.ts +++ b/packages/api/src/client/types/chat/bsky/convo/getConvoForMembers.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -20,17 +20,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/chat/bsky/convo/getLog.ts b/packages/api/src/client/types/chat/bsky/convo/getLog.ts index df55da402ca..f1470fd89ba 100644 --- a/packages/api/src/client/types/chat/bsky/convo/getLog.ts +++ b/packages/api/src/client/types/chat/bsky/convo/getLog.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -27,17 +27,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/chat/bsky/convo/getMessages.ts b/packages/api/src/client/types/chat/bsky/convo/getMessages.ts index d19669f1913..eea7cba82e1 100644 --- a/packages/api/src/client/types/chat/bsky/convo/getMessages.ts +++ b/packages/api/src/client/types/chat/bsky/convo/getMessages.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -27,17 +27,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/chat/bsky/convo/leaveConvo.ts b/packages/api/src/client/types/chat/bsky/convo/leaveConvo.ts index 69db762a447..4124db1855d 100644 --- a/packages/api/src/client/types/chat/bsky/convo/leaveConvo.ts +++ b/packages/api/src/client/types/chat/bsky/convo/leaveConvo.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -21,19 +21,18 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/chat/bsky/convo/listConvos.ts b/packages/api/src/client/types/chat/bsky/convo/listConvos.ts index 23cc6f740b7..3cd1ad68516 100644 --- a/packages/api/src/client/types/chat/bsky/convo/listConvos.ts +++ b/packages/api/src/client/types/chat/bsky/convo/listConvos.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -22,17 +22,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/chat/bsky/convo/muteConvo.ts b/packages/api/src/client/types/chat/bsky/convo/muteConvo.ts index c2d2e3628cc..93ac8785db7 100644 --- a/packages/api/src/client/types/chat/bsky/convo/muteConvo.ts +++ b/packages/api/src/client/types/chat/bsky/convo/muteConvo.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -21,19 +21,18 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/chat/bsky/convo/sendMessage.ts b/packages/api/src/client/types/chat/bsky/convo/sendMessage.ts index 937478d3f9f..e260ba6eee0 100644 --- a/packages/api/src/client/types/chat/bsky/convo/sendMessage.ts +++ b/packages/api/src/client/types/chat/bsky/convo/sendMessage.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -19,19 +19,18 @@ export interface InputSchema { export type OutputSchema = ChatBskyConvoDefs.MessageView export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/chat/bsky/convo/sendMessageBatch.ts b/packages/api/src/client/types/chat/bsky/convo/sendMessageBatch.ts index 0db0921f26f..68ff2711df5 100644 --- a/packages/api/src/client/types/chat/bsky/convo/sendMessageBatch.ts +++ b/packages/api/src/client/types/chat/bsky/convo/sendMessageBatch.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -21,20 +21,19 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/chat/bsky/convo/unmuteConvo.ts b/packages/api/src/client/types/chat/bsky/convo/unmuteConvo.ts index c2d2e3628cc..93ac8785db7 100644 --- a/packages/api/src/client/types/chat/bsky/convo/unmuteConvo.ts +++ b/packages/api/src/client/types/chat/bsky/convo/unmuteConvo.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -21,19 +21,18 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/chat/bsky/convo/updateRead.ts b/packages/api/src/client/types/chat/bsky/convo/updateRead.ts index 93858ea7989..b7a8041dbd7 100644 --- a/packages/api/src/client/types/chat/bsky/convo/updateRead.ts +++ b/packages/api/src/client/types/chat/bsky/convo/updateRead.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -22,19 +22,18 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/chat/bsky/moderation/getActorMetadata.ts b/packages/api/src/client/types/chat/bsky/moderation/getActorMetadata.ts index 99807e447ee..6761c9939b5 100644 --- a/packages/api/src/client/types/chat/bsky/moderation/getActorMetadata.ts +++ b/packages/api/src/client/types/chat/bsky/moderation/getActorMetadata.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -21,18 +21,17 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/chat/bsky/moderation/getMessageContext.ts b/packages/api/src/client/types/chat/bsky/moderation/getMessageContext.ts index d6d4cd6b1c7..2315bfd4ad8 100644 --- a/packages/api/src/client/types/chat/bsky/moderation/getMessageContext.ts +++ b/packages/api/src/client/types/chat/bsky/moderation/getMessageContext.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -28,17 +28,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/chat/bsky/moderation/updateActorAccess.ts b/packages/api/src/client/types/chat/bsky/moderation/updateActorAccess.ts index 124705cbd4a..d6f0205672e 100644 --- a/packages/api/src/client/types/chat/bsky/moderation/updateActorAccess.ts +++ b/packages/api/src/client/types/chat/bsky/moderation/updateActorAccess.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -17,18 +17,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/admin/deleteAccount.ts b/packages/api/src/client/types/com/atproto/admin/deleteAccount.ts index b8b5aa511b8..72066a656b7 100644 --- a/packages/api/src/client/types/com/atproto/admin/deleteAccount.ts +++ b/packages/api/src/client/types/com/atproto/admin/deleteAccount.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -15,18 +15,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/admin/disableAccountInvites.ts b/packages/api/src/client/types/com/atproto/admin/disableAccountInvites.ts index 7d4a59e503f..8df420239de 100644 --- a/packages/api/src/client/types/com/atproto/admin/disableAccountInvites.ts +++ b/packages/api/src/client/types/com/atproto/admin/disableAccountInvites.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -17,18 +17,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/admin/disableInviteCodes.ts b/packages/api/src/client/types/com/atproto/admin/disableInviteCodes.ts index 7ceed97f912..c264b65dbc2 100644 --- a/packages/api/src/client/types/com/atproto/admin/disableInviteCodes.ts +++ b/packages/api/src/client/types/com/atproto/admin/disableInviteCodes.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -16,18 +16,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/admin/enableAccountInvites.ts b/packages/api/src/client/types/com/atproto/admin/enableAccountInvites.ts index c39d1990b26..094b3dfe0af 100644 --- a/packages/api/src/client/types/com/atproto/admin/enableAccountInvites.ts +++ b/packages/api/src/client/types/com/atproto/admin/enableAccountInvites.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -17,18 +17,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/admin/getAccountInfo.ts b/packages/api/src/client/types/com/atproto/admin/getAccountInfo.ts index a6d2b97bb63..645b9e613ff 100644 --- a/packages/api/src/client/types/com/atproto/admin/getAccountInfo.ts +++ b/packages/api/src/client/types/com/atproto/admin/getAccountInfo.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -16,17 +16,16 @@ export type InputSchema = undefined export type OutputSchema = ComAtprotoAdminDefs.AccountView export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/admin/getAccountInfos.ts b/packages/api/src/client/types/com/atproto/admin/getAccountInfos.ts index 353f3150854..9c1c273b9d9 100644 --- a/packages/api/src/client/types/com/atproto/admin/getAccountInfos.ts +++ b/packages/api/src/client/types/com/atproto/admin/getAccountInfos.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -20,17 +20,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/admin/getInviteCodes.ts b/packages/api/src/client/types/com/atproto/admin/getInviteCodes.ts index eb5604839d7..faa4c8bed25 100644 --- a/packages/api/src/client/types/com/atproto/admin/getInviteCodes.ts +++ b/packages/api/src/client/types/com/atproto/admin/getInviteCodes.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -23,17 +23,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/admin/getSubjectStatus.ts b/packages/api/src/client/types/com/atproto/admin/getSubjectStatus.ts index acd7a0ea2d7..f11b514507d 100644 --- a/packages/api/src/client/types/com/atproto/admin/getSubjectStatus.ts +++ b/packages/api/src/client/types/com/atproto/admin/getSubjectStatus.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -29,17 +29,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/admin/searchAccounts.ts b/packages/api/src/client/types/com/atproto/admin/searchAccounts.ts index 2abe5d21cdd..2d22806238c 100644 --- a/packages/api/src/client/types/com/atproto/admin/searchAccounts.ts +++ b/packages/api/src/client/types/com/atproto/admin/searchAccounts.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -23,17 +23,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/admin/sendEmail.ts b/packages/api/src/client/types/com/atproto/admin/sendEmail.ts index 4768fc75ca5..3f7c06d7070 100644 --- a/packages/api/src/client/types/com/atproto/admin/sendEmail.ts +++ b/packages/api/src/client/types/com/atproto/admin/sendEmail.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -25,19 +25,18 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/admin/updateAccountEmail.ts b/packages/api/src/client/types/com/atproto/admin/updateAccountEmail.ts index f023c9aeb0b..d13878711a6 100644 --- a/packages/api/src/client/types/com/atproto/admin/updateAccountEmail.ts +++ b/packages/api/src/client/types/com/atproto/admin/updateAccountEmail.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -17,18 +17,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/admin/updateAccountHandle.ts b/packages/api/src/client/types/com/atproto/admin/updateAccountHandle.ts index 02e1bc1d6af..38fbcae1681 100644 --- a/packages/api/src/client/types/com/atproto/admin/updateAccountHandle.ts +++ b/packages/api/src/client/types/com/atproto/admin/updateAccountHandle.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -16,18 +16,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/admin/updateAccountPassword.ts b/packages/api/src/client/types/com/atproto/admin/updateAccountPassword.ts index 99ef3881c37..412f0facca2 100644 --- a/packages/api/src/client/types/com/atproto/admin/updateAccountPassword.ts +++ b/packages/api/src/client/types/com/atproto/admin/updateAccountPassword.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -16,18 +16,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/admin/updateSubjectStatus.ts b/packages/api/src/client/types/com/atproto/admin/updateSubjectStatus.ts index c4ba46da792..d890993cf3a 100644 --- a/packages/api/src/client/types/com/atproto/admin/updateSubjectStatus.ts +++ b/packages/api/src/client/types/com/atproto/admin/updateSubjectStatus.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -33,19 +33,18 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/identity/getRecommendedDidCredentials.ts b/packages/api/src/client/types/com/atproto/identity/getRecommendedDidCredentials.ts index dfa143e4ab3..a5e4a0296de 100644 --- a/packages/api/src/client/types/com/atproto/identity/getRecommendedDidCredentials.ts +++ b/packages/api/src/client/types/com/atproto/identity/getRecommendedDidCredentials.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -21,17 +21,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/identity/requestPlcOperationSignature.ts b/packages/api/src/client/types/com/atproto/identity/requestPlcOperationSignature.ts index ef2ed1ac47c..dcab71e2558 100644 --- a/packages/api/src/client/types/com/atproto/identity/requestPlcOperationSignature.ts +++ b/packages/api/src/client/types/com/atproto/identity/requestPlcOperationSignature.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -12,17 +12,16 @@ export interface QueryParams {} export type InputSchema = undefined export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/identity/resolveHandle.ts b/packages/api/src/client/types/com/atproto/identity/resolveHandle.ts index 4965f9470c0..32db72138f7 100644 --- a/packages/api/src/client/types/com/atproto/identity/resolveHandle.ts +++ b/packages/api/src/client/types/com/atproto/identity/resolveHandle.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -20,17 +20,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/identity/signPlcOperation.ts b/packages/api/src/client/types/com/atproto/identity/signPlcOperation.ts index 3060c1e3f4d..88c04c5993c 100644 --- a/packages/api/src/client/types/com/atproto/identity/signPlcOperation.ts +++ b/packages/api/src/client/types/com/atproto/identity/signPlcOperation.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -26,19 +26,18 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/identity/submitPlcOperation.ts b/packages/api/src/client/types/com/atproto/identity/submitPlcOperation.ts index 4ba52f74fc7..74dea9f196d 100644 --- a/packages/api/src/client/types/com/atproto/identity/submitPlcOperation.ts +++ b/packages/api/src/client/types/com/atproto/identity/submitPlcOperation.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -15,18 +15,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/identity/updateHandle.ts b/packages/api/src/client/types/com/atproto/identity/updateHandle.ts index 2bd2c4c9d6a..c01d4887a4a 100644 --- a/packages/api/src/client/types/com/atproto/identity/updateHandle.ts +++ b/packages/api/src/client/types/com/atproto/identity/updateHandle.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -16,18 +16,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/label/defs.ts b/packages/api/src/client/types/com/atproto/label/defs.ts index 34009a39b03..131682e550c 100644 --- a/packages/api/src/client/types/com/atproto/label/defs.ts +++ b/packages/api/src/client/types/com/atproto/label/defs.ts @@ -78,7 +78,7 @@ export function validateSelfLabel(v: unknown): ValidationResult { return lexicons.validate('com.atproto.label.defs#selfLabel', v) } -/** Declares a label value and its expected interpertations and behaviors. */ +/** Declares a label value and its expected interpretations and behaviors. */ export interface LabelValueDefinition { /** The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). */ identifier: string diff --git a/packages/api/src/client/types/com/atproto/label/queryLabels.ts b/packages/api/src/client/types/com/atproto/label/queryLabels.ts index 3fd08d1b2b4..615770ec978 100644 --- a/packages/api/src/client/types/com/atproto/label/queryLabels.ts +++ b/packages/api/src/client/types/com/atproto/label/queryLabels.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -26,17 +26,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/label/subscribeLabels.ts b/packages/api/src/client/types/com/atproto/label/subscribeLabels.ts index df2f32aac64..044d4d83b30 100644 --- a/packages/api/src/client/types/com/atproto/label/subscribeLabels.ts +++ b/packages/api/src/client/types/com/atproto/label/subscribeLabels.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' diff --git a/packages/api/src/client/types/com/atproto/moderation/createReport.ts b/packages/api/src/client/types/com/atproto/moderation/createReport.ts index 7bf3cc1a380..16316f585d4 100644 --- a/packages/api/src/client/types/com/atproto/moderation/createReport.ts +++ b/packages/api/src/client/types/com/atproto/moderation/createReport.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -37,19 +37,18 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/repo/applyWrites.ts b/packages/api/src/client/types/com/atproto/repo/applyWrites.ts index df35ec7dcbf..23023fa8482 100644 --- a/packages/api/src/client/types/com/atproto/repo/applyWrites.ts +++ b/packages/api/src/client/types/com/atproto/repo/applyWrites.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -21,19 +21,20 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export class InvalidSwapError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -41,6 +42,7 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'InvalidSwap') return new InvalidSwapError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/repo/createRecord.ts b/packages/api/src/client/types/com/atproto/repo/createRecord.ts index 6b13f67db7f..18bd74e0f5d 100644 --- a/packages/api/src/client/types/com/atproto/repo/createRecord.ts +++ b/packages/api/src/client/types/com/atproto/repo/createRecord.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -32,20 +32,21 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class InvalidSwapError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -53,5 +54,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'InvalidSwap') return new InvalidSwapError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/repo/deleteRecord.ts b/packages/api/src/client/types/com/atproto/repo/deleteRecord.ts index 54109b62f31..5ce29976c95 100644 --- a/packages/api/src/client/types/com/atproto/repo/deleteRecord.ts +++ b/packages/api/src/client/types/com/atproto/repo/deleteRecord.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -24,19 +24,20 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export class InvalidSwapError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -44,5 +45,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'InvalidSwap') return new InvalidSwapError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/repo/describeRepo.ts b/packages/api/src/client/types/com/atproto/repo/describeRepo.ts index f17a8410782..9b3eb337b5c 100644 --- a/packages/api/src/client/types/com/atproto/repo/describeRepo.ts +++ b/packages/api/src/client/types/com/atproto/repo/describeRepo.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -27,17 +27,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/repo/getRecord.ts b/packages/api/src/client/types/com/atproto/repo/getRecord.ts index a6d2bd39e8c..765a229408b 100644 --- a/packages/api/src/client/types/com/atproto/repo/getRecord.ts +++ b/packages/api/src/client/types/com/atproto/repo/getRecord.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -28,17 +28,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/repo/importRepo.ts b/packages/api/src/client/types/com/atproto/repo/importRepo.ts index 040cca671bf..ad093e6ce93 100644 --- a/packages/api/src/client/types/com/atproto/repo/importRepo.ts +++ b/packages/api/src/client/types/com/atproto/repo/importRepo.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -9,21 +9,20 @@ import { CID } from 'multiformats/cid' export interface QueryParams {} -export type InputSchema = string | Uint8Array +export type InputSchema = string | Uint8Array | Blob export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/vnd.ipld.car' + encoding?: 'application/vnd.ipld.car' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/repo/listMissingBlobs.ts b/packages/api/src/client/types/com/atproto/repo/listMissingBlobs.ts index b66f617eea7..9043fd4e1fe 100644 --- a/packages/api/src/client/types/com/atproto/repo/listMissingBlobs.ts +++ b/packages/api/src/client/types/com/atproto/repo/listMissingBlobs.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -21,18 +21,17 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/repo/listRecords.ts b/packages/api/src/client/types/com/atproto/repo/listRecords.ts index 6322c782008..4a97572f74d 100644 --- a/packages/api/src/client/types/com/atproto/repo/listRecords.ts +++ b/packages/api/src/client/types/com/atproto/repo/listRecords.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -32,18 +32,17 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/repo/putRecord.ts b/packages/api/src/client/types/com/atproto/repo/putRecord.ts index 7421ee19780..deb7a65d257 100644 --- a/packages/api/src/client/types/com/atproto/repo/putRecord.ts +++ b/packages/api/src/client/types/com/atproto/repo/putRecord.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -34,20 +34,21 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class InvalidSwapError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -55,5 +56,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'InvalidSwap') return new InvalidSwapError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/repo/uploadBlob.ts b/packages/api/src/client/types/com/atproto/repo/uploadBlob.ts index d0f36edb10f..9931cc61e9d 100644 --- a/packages/api/src/client/types/com/atproto/repo/uploadBlob.ts +++ b/packages/api/src/client/types/com/atproto/repo/uploadBlob.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -9,7 +9,7 @@ import { CID } from 'multiformats/cid' export interface QueryParams {} -export type InputSchema = string | Uint8Array +export type InputSchema = string | Uint8Array | Blob export interface OutputSchema { blob: BlobRef @@ -17,19 +17,18 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: string + encoding?: string } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/server/activateAccount.ts b/packages/api/src/client/types/com/atproto/server/activateAccount.ts index ef2ed1ac47c..dcab71e2558 100644 --- a/packages/api/src/client/types/com/atproto/server/activateAccount.ts +++ b/packages/api/src/client/types/com/atproto/server/activateAccount.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -12,17 +12,16 @@ export interface QueryParams {} export type InputSchema = undefined export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/server/checkAccountStatus.ts b/packages/api/src/client/types/com/atproto/server/checkAccountStatus.ts index 86a942f81f3..b527cca2b67 100644 --- a/packages/api/src/client/types/com/atproto/server/checkAccountStatus.ts +++ b/packages/api/src/client/types/com/atproto/server/checkAccountStatus.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -25,17 +25,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/server/confirmEmail.ts b/packages/api/src/client/types/com/atproto/server/confirmEmail.ts index eb53dc5a0dc..488de314fd6 100644 --- a/packages/api/src/client/types/com/atproto/server/confirmEmail.ts +++ b/packages/api/src/client/types/com/atproto/server/confirmEmail.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -16,37 +16,38 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export class AccountNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class ExpiredTokenError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class InvalidTokenError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class InvalidEmailError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -57,5 +58,6 @@ export function toKnownErr(e: any) { if (e.error === 'InvalidToken') return new InvalidTokenError(e) if (e.error === 'InvalidEmail') return new InvalidEmailError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/server/createAccount.ts b/packages/api/src/client/types/com/atproto/server/createAccount.ts index 5e36eca0ee3..5f0a8d6ad0f 100644 --- a/packages/api/src/client/types/com/atproto/server/createAccount.ts +++ b/packages/api/src/client/types/com/atproto/server/createAccount.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -40,56 +40,57 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class InvalidHandleError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class InvalidPasswordError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class InvalidInviteCodeError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class HandleNotAvailableError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class UnsupportedDomainError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class UnresolvableDidError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class IncompatibleDidDocError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -103,5 +104,6 @@ export function toKnownErr(e: any) { if (e.error === 'UnresolvableDid') return new UnresolvableDidError(e) if (e.error === 'IncompatibleDidDoc') return new IncompatibleDidDocError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/server/createAppPassword.ts b/packages/api/src/client/types/com/atproto/server/createAppPassword.ts index 7300e0623e1..05ee6b1314e 100644 --- a/packages/api/src/client/types/com/atproto/server/createAppPassword.ts +++ b/packages/api/src/client/types/com/atproto/server/createAppPassword.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -20,20 +20,21 @@ export interface InputSchema { export type OutputSchema = AppPassword export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class AccountTakedownError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -41,6 +42,7 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'AccountTakedown') return new AccountTakedownError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/server/createInviteCode.ts b/packages/api/src/client/types/com/atproto/server/createInviteCode.ts index a1ce922dd79..426fb538fb3 100644 --- a/packages/api/src/client/types/com/atproto/server/createInviteCode.ts +++ b/packages/api/src/client/types/com/atproto/server/createInviteCode.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -21,19 +21,18 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/server/createInviteCodes.ts b/packages/api/src/client/types/com/atproto/server/createInviteCodes.ts index 1750b0d79b0..f7908697a21 100644 --- a/packages/api/src/client/types/com/atproto/server/createInviteCodes.ts +++ b/packages/api/src/client/types/com/atproto/server/createInviteCodes.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -22,20 +22,19 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/server/createSession.ts b/packages/api/src/client/types/com/atproto/server/createSession.ts index a543b83c489..3ac1194b36d 100644 --- a/packages/api/src/client/types/com/atproto/server/createSession.ts +++ b/packages/api/src/client/types/com/atproto/server/createSession.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -33,26 +33,27 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class AccountTakedownError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class AuthFactorTokenRequiredError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -62,5 +63,6 @@ export function toKnownErr(e: any) { if (e.error === 'AuthFactorTokenRequired') return new AuthFactorTokenRequiredError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/server/deactivateAccount.ts b/packages/api/src/client/types/com/atproto/server/deactivateAccount.ts index c88bd548243..592380aa9cc 100644 --- a/packages/api/src/client/types/com/atproto/server/deactivateAccount.ts +++ b/packages/api/src/client/types/com/atproto/server/deactivateAccount.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -16,18 +16,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/server/deleteAccount.ts b/packages/api/src/client/types/com/atproto/server/deleteAccount.ts index 403de18a345..1e76ce2972f 100644 --- a/packages/api/src/client/types/com/atproto/server/deleteAccount.ts +++ b/packages/api/src/client/types/com/atproto/server/deleteAccount.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -17,25 +17,26 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export class ExpiredTokenError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class InvalidTokenError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -44,5 +45,6 @@ export function toKnownErr(e: any) { if (e.error === 'ExpiredToken') return new ExpiredTokenError(e) if (e.error === 'InvalidToken') return new InvalidTokenError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/server/deleteSession.ts b/packages/api/src/client/types/com/atproto/server/deleteSession.ts index ef2ed1ac47c..dcab71e2558 100644 --- a/packages/api/src/client/types/com/atproto/server/deleteSession.ts +++ b/packages/api/src/client/types/com/atproto/server/deleteSession.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -12,17 +12,16 @@ export interface QueryParams {} export type InputSchema = undefined export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/server/describeServer.ts b/packages/api/src/client/types/com/atproto/server/describeServer.ts index 610aa12ce5d..b2936e18125 100644 --- a/packages/api/src/client/types/com/atproto/server/describeServer.ts +++ b/packages/api/src/client/types/com/atproto/server/describeServer.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -25,18 +25,17 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/server/getAccountInviteCodes.ts b/packages/api/src/client/types/com/atproto/server/getAccountInviteCodes.ts index 5438cbc96d6..e6b688cea66 100644 --- a/packages/api/src/client/types/com/atproto/server/getAccountInviteCodes.ts +++ b/packages/api/src/client/types/com/atproto/server/getAccountInviteCodes.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -22,18 +22,19 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class DuplicateCreateError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -41,5 +42,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'DuplicateCreate') return new DuplicateCreateError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/server/getServiceAuth.ts b/packages/api/src/client/types/com/atproto/server/getServiceAuth.ts index 6056960effc..41ea36e453e 100644 --- a/packages/api/src/client/types/com/atproto/server/getServiceAuth.ts +++ b/packages/api/src/client/types/com/atproto/server/getServiceAuth.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -10,6 +10,10 @@ import { CID } from 'multiformats/cid' export interface QueryParams { /** The DID of the service that the token will be used to authenticate with */ aud: string + /** The time in Unix Epoch seconds that the JWT expires. Defaults to 60 seconds in the future. The service may enforce certain time bounds on tokens depending on the requested scope. */ + exp?: number + /** Lexicon (XRPC) method to bind the requested token to */ + lxm?: string } export type InputSchema = undefined @@ -20,17 +24,26 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } +export class BadExpirationError extends XRPCError { + constructor(src: XRPCError) { + super(src.status, src.error, src.message, src.headers, { cause: src }) + } +} + export function toKnownErr(e: any) { if (e instanceof XRPCError) { + if (e.error === 'BadExpiration') return new BadExpirationError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/server/getSession.ts b/packages/api/src/client/types/com/atproto/server/getSession.ts index bee0197d7cb..1e0837924cc 100644 --- a/packages/api/src/client/types/com/atproto/server/getSession.ts +++ b/packages/api/src/client/types/com/atproto/server/getSession.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -25,17 +25,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/server/listAppPasswords.ts b/packages/api/src/client/types/com/atproto/server/listAppPasswords.ts index 1d7301642af..a6cf8dca707 100644 --- a/packages/api/src/client/types/com/atproto/server/listAppPasswords.ts +++ b/packages/api/src/client/types/com/atproto/server/listAppPasswords.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -17,18 +17,19 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class AccountTakedownError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -36,6 +37,7 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'AccountTakedown') return new AccountTakedownError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/server/refreshSession.ts b/packages/api/src/client/types/com/atproto/server/refreshSession.ts index 7dcaedc7627..24d0e9e0ccc 100644 --- a/packages/api/src/client/types/com/atproto/server/refreshSession.ts +++ b/packages/api/src/client/types/com/atproto/server/refreshSession.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -24,19 +24,20 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class AccountTakedownError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -44,5 +45,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'AccountTakedown') return new AccountTakedownError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/server/requestAccountDelete.ts b/packages/api/src/client/types/com/atproto/server/requestAccountDelete.ts index ef2ed1ac47c..dcab71e2558 100644 --- a/packages/api/src/client/types/com/atproto/server/requestAccountDelete.ts +++ b/packages/api/src/client/types/com/atproto/server/requestAccountDelete.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -12,17 +12,16 @@ export interface QueryParams {} export type InputSchema = undefined export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/server/requestEmailConfirmation.ts b/packages/api/src/client/types/com/atproto/server/requestEmailConfirmation.ts index ef2ed1ac47c..dcab71e2558 100644 --- a/packages/api/src/client/types/com/atproto/server/requestEmailConfirmation.ts +++ b/packages/api/src/client/types/com/atproto/server/requestEmailConfirmation.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -12,17 +12,16 @@ export interface QueryParams {} export type InputSchema = undefined export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/server/requestEmailUpdate.ts b/packages/api/src/client/types/com/atproto/server/requestEmailUpdate.ts index 30d84002cf2..1d51a212117 100644 --- a/packages/api/src/client/types/com/atproto/server/requestEmailUpdate.ts +++ b/packages/api/src/client/types/com/atproto/server/requestEmailUpdate.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -17,18 +17,17 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/server/requestPasswordReset.ts b/packages/api/src/client/types/com/atproto/server/requestPasswordReset.ts index 10031827325..c33e4f4b604 100644 --- a/packages/api/src/client/types/com/atproto/server/requestPasswordReset.ts +++ b/packages/api/src/client/types/com/atproto/server/requestPasswordReset.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -15,18 +15,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/server/reserveSigningKey.ts b/packages/api/src/client/types/com/atproto/server/reserveSigningKey.ts index 324dee9665a..05867a62537 100644 --- a/packages/api/src/client/types/com/atproto/server/reserveSigningKey.ts +++ b/packages/api/src/client/types/com/atproto/server/reserveSigningKey.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -22,19 +22,18 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/server/resetPassword.ts b/packages/api/src/client/types/com/atproto/server/resetPassword.ts index f0a3a68e50b..fb9f38e8e91 100644 --- a/packages/api/src/client/types/com/atproto/server/resetPassword.ts +++ b/packages/api/src/client/types/com/atproto/server/resetPassword.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -16,25 +16,26 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export class ExpiredTokenError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class InvalidTokenError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -43,5 +44,6 @@ export function toKnownErr(e: any) { if (e.error === 'ExpiredToken') return new ExpiredTokenError(e) if (e.error === 'InvalidToken') return new InvalidTokenError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/server/revokeAppPassword.ts b/packages/api/src/client/types/com/atproto/server/revokeAppPassword.ts index 5ee78d8d42b..2d77e055cb6 100644 --- a/packages/api/src/client/types/com/atproto/server/revokeAppPassword.ts +++ b/packages/api/src/client/types/com/atproto/server/revokeAppPassword.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -15,18 +15,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/server/updateEmail.ts b/packages/api/src/client/types/com/atproto/server/updateEmail.ts index 0f630ec8a97..1213f63c724 100644 --- a/packages/api/src/client/types/com/atproto/server/updateEmail.ts +++ b/packages/api/src/client/types/com/atproto/server/updateEmail.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -18,31 +18,32 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export class ExpiredTokenError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class InvalidTokenError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class TokenRequiredError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -52,5 +53,6 @@ export function toKnownErr(e: any) { if (e.error === 'InvalidToken') return new InvalidTokenError(e) if (e.error === 'TokenRequired') return new TokenRequiredError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/sync/getBlob.ts b/packages/api/src/client/types/com/atproto/sync/getBlob.ts index 4ea99759d57..03318cf2fb0 100644 --- a/packages/api/src/client/types/com/atproto/sync/getBlob.ts +++ b/packages/api/src/client/types/com/atproto/sync/getBlob.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -17,42 +17,43 @@ export interface QueryParams { export type InputSchema = undefined export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: Uint8Array } export class BlobNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoTakendownError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoSuspendedError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoDeactivatedError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -64,5 +65,6 @@ export function toKnownErr(e: any) { if (e.error === 'RepoSuspended') return new RepoSuspendedError(e) if (e.error === 'RepoDeactivated') return new RepoDeactivatedError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/sync/getBlocks.ts b/packages/api/src/client/types/com/atproto/sync/getBlocks.ts index 3253a46a43a..20829a8075c 100644 --- a/packages/api/src/client/types/com/atproto/sync/getBlocks.ts +++ b/packages/api/src/client/types/com/atproto/sync/getBlocks.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -16,42 +16,43 @@ export interface QueryParams { export type InputSchema = undefined export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: Uint8Array } export class BlockNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoTakendownError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoSuspendedError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoDeactivatedError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -63,5 +64,6 @@ export function toKnownErr(e: any) { if (e.error === 'RepoSuspended') return new RepoSuspendedError(e) if (e.error === 'RepoDeactivated') return new RepoDeactivatedError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/sync/getCheckout.ts b/packages/api/src/client/types/com/atproto/sync/getCheckout.ts index 4210840ec1c..86538474ed2 100644 --- a/packages/api/src/client/types/com/atproto/sync/getCheckout.ts +++ b/packages/api/src/client/types/com/atproto/sync/getCheckout.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -15,17 +15,16 @@ export interface QueryParams { export type InputSchema = undefined export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: Uint8Array } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/sync/getHead.ts b/packages/api/src/client/types/com/atproto/sync/getHead.ts index c71de962b01..0f273053833 100644 --- a/packages/api/src/client/types/com/atproto/sync/getHead.ts +++ b/packages/api/src/client/types/com/atproto/sync/getHead.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -20,18 +20,19 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class HeadNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -39,5 +40,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'HeadNotFound') return new HeadNotFoundError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/sync/getLatestCommit.ts b/packages/api/src/client/types/com/atproto/sync/getLatestCommit.ts index 8fb354e392e..b27f350430a 100644 --- a/packages/api/src/client/types/com/atproto/sync/getLatestCommit.ts +++ b/packages/api/src/client/types/com/atproto/sync/getLatestCommit.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -21,36 +21,37 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class RepoNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoTakendownError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoSuspendedError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoDeactivatedError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -61,5 +62,6 @@ export function toKnownErr(e: any) { if (e.error === 'RepoSuspended') return new RepoSuspendedError(e) if (e.error === 'RepoDeactivated') return new RepoDeactivatedError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/sync/getRecord.ts b/packages/api/src/client/types/com/atproto/sync/getRecord.ts index dcb0663c4d6..f88eb6ee70a 100644 --- a/packages/api/src/client/types/com/atproto/sync/getRecord.ts +++ b/packages/api/src/client/types/com/atproto/sync/getRecord.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -20,42 +20,43 @@ export interface QueryParams { export type InputSchema = undefined export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: Uint8Array } export class RecordNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoTakendownError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoSuspendedError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoDeactivatedError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -67,5 +68,6 @@ export function toKnownErr(e: any) { if (e.error === 'RepoSuspended') return new RepoSuspendedError(e) if (e.error === 'RepoDeactivated') return new RepoDeactivatedError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/sync/getRepo.ts b/packages/api/src/client/types/com/atproto/sync/getRepo.ts index cd9296fa9d6..52c43f7de93 100644 --- a/packages/api/src/client/types/com/atproto/sync/getRepo.ts +++ b/packages/api/src/client/types/com/atproto/sync/getRepo.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -17,36 +17,37 @@ export interface QueryParams { export type InputSchema = undefined export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: Uint8Array } export class RepoNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoTakendownError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoSuspendedError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoDeactivatedError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -57,5 +58,6 @@ export function toKnownErr(e: any) { if (e.error === 'RepoSuspended') return new RepoSuspendedError(e) if (e.error === 'RepoDeactivated') return new RepoDeactivatedError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/sync/getRepoStatus.ts b/packages/api/src/client/types/com/atproto/sync/getRepoStatus.ts index a3c2154ec1d..619d65eb558 100644 --- a/packages/api/src/client/types/com/atproto/sync/getRepoStatus.ts +++ b/packages/api/src/client/types/com/atproto/sync/getRepoStatus.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -25,18 +25,19 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class RepoNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -44,5 +45,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'RepoNotFound') return new RepoNotFoundError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/sync/listBlobs.ts b/packages/api/src/client/types/com/atproto/sync/listBlobs.ts index 83e1f4406d6..bf5a8f1f950 100644 --- a/packages/api/src/client/types/com/atproto/sync/listBlobs.ts +++ b/packages/api/src/client/types/com/atproto/sync/listBlobs.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -25,36 +25,37 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class RepoNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoTakendownError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoSuspendedError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class RepoDeactivatedError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -65,5 +66,6 @@ export function toKnownErr(e: any) { if (e.error === 'RepoSuspended') return new RepoSuspendedError(e) if (e.error === 'RepoDeactivated') return new RepoDeactivatedError(e) } + return e } diff --git a/packages/api/src/client/types/com/atproto/sync/listRepos.ts b/packages/api/src/client/types/com/atproto/sync/listRepos.ts index 707de444ea0..84a44654f0b 100644 --- a/packages/api/src/client/types/com/atproto/sync/listRepos.ts +++ b/packages/api/src/client/types/com/atproto/sync/listRepos.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -21,18 +21,17 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/sync/notifyOfUpdate.ts b/packages/api/src/client/types/com/atproto/sync/notifyOfUpdate.ts index f53e4a55385..30e24f8b5b9 100644 --- a/packages/api/src/client/types/com/atproto/sync/notifyOfUpdate.ts +++ b/packages/api/src/client/types/com/atproto/sync/notifyOfUpdate.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -16,18 +16,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/sync/requestCrawl.ts b/packages/api/src/client/types/com/atproto/sync/requestCrawl.ts index 089eb84e089..0f5d78560cf 100644 --- a/packages/api/src/client/types/com/atproto/sync/requestCrawl.ts +++ b/packages/api/src/client/types/com/atproto/sync/requestCrawl.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -16,18 +16,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/sync/subscribeRepos.ts b/packages/api/src/client/types/com/atproto/sync/subscribeRepos.ts index 11ebfcda91f..eac07c9acc1 100644 --- a/packages/api/src/client/types/com/atproto/sync/subscribeRepos.ts +++ b/packages/api/src/client/types/com/atproto/sync/subscribeRepos.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' diff --git a/packages/api/src/client/types/com/atproto/temp/checkSignupQueue.ts b/packages/api/src/client/types/com/atproto/temp/checkSignupQueue.ts index 2f80322c82e..414e6ca565c 100644 --- a/packages/api/src/client/types/com/atproto/temp/checkSignupQueue.ts +++ b/packages/api/src/client/types/com/atproto/temp/checkSignupQueue.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -19,17 +19,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/temp/fetchLabels.ts b/packages/api/src/client/types/com/atproto/temp/fetchLabels.ts index 408d39cdab3..0b1d2399f4b 100644 --- a/packages/api/src/client/types/com/atproto/temp/fetchLabels.ts +++ b/packages/api/src/client/types/com/atproto/temp/fetchLabels.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -21,17 +21,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/com/atproto/temp/requestPhoneVerification.ts b/packages/api/src/client/types/com/atproto/temp/requestPhoneVerification.ts index 06a8972599d..5e0699cd374 100644 --- a/packages/api/src/client/types/com/atproto/temp/requestPhoneVerification.ts +++ b/packages/api/src/client/types/com/atproto/temp/requestPhoneVerification.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -15,18 +15,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/tools/ozone/communication/createTemplate.ts b/packages/api/src/client/types/tools/ozone/communication/createTemplate.ts index 0aba76123c6..6931479a734 100644 --- a/packages/api/src/client/types/tools/ozone/communication/createTemplate.ts +++ b/packages/api/src/client/types/tools/ozone/communication/createTemplate.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -25,19 +25,18 @@ export interface InputSchema { export type OutputSchema = ToolsOzoneCommunicationDefs.TemplateView export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/tools/ozone/communication/deleteTemplate.ts b/packages/api/src/client/types/tools/ozone/communication/deleteTemplate.ts index a5c4d55fdeb..0fc74571563 100644 --- a/packages/api/src/client/types/tools/ozone/communication/deleteTemplate.ts +++ b/packages/api/src/client/types/tools/ozone/communication/deleteTemplate.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -15,18 +15,17 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/tools/ozone/communication/listTemplates.ts b/packages/api/src/client/types/tools/ozone/communication/listTemplates.ts index 116f79f1c9c..c10f1ee56ba 100644 --- a/packages/api/src/client/types/tools/ozone/communication/listTemplates.ts +++ b/packages/api/src/client/types/tools/ozone/communication/listTemplates.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -18,17 +18,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/tools/ozone/communication/updateTemplate.ts b/packages/api/src/client/types/tools/ozone/communication/updateTemplate.ts index 2f39d9e9b32..6ddf4741017 100644 --- a/packages/api/src/client/types/tools/ozone/communication/updateTemplate.ts +++ b/packages/api/src/client/types/tools/ozone/communication/updateTemplate.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -28,19 +28,18 @@ export interface InputSchema { export type OutputSchema = ToolsOzoneCommunicationDefs.TemplateView export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/tools/ozone/moderation/emitEvent.ts b/packages/api/src/client/types/tools/ozone/moderation/emitEvent.ts index edd8a2731ec..0781bef97f5 100644 --- a/packages/api/src/client/types/tools/ozone/moderation/emitEvent.ts +++ b/packages/api/src/client/types/tools/ozone/moderation/emitEvent.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -40,20 +40,21 @@ export interface InputSchema { export type OutputSchema = ToolsOzoneModerationDefs.ModEventView export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class SubjectHasActionError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -61,5 +62,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'SubjectHasAction') return new SubjectHasActionError(e) } + return e } diff --git a/packages/api/src/client/types/tools/ozone/moderation/getEvent.ts b/packages/api/src/client/types/tools/ozone/moderation/getEvent.ts index fef7068170e..7b1206d339f 100644 --- a/packages/api/src/client/types/tools/ozone/moderation/getEvent.ts +++ b/packages/api/src/client/types/tools/ozone/moderation/getEvent.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -16,17 +16,16 @@ export type InputSchema = undefined export type OutputSchema = ToolsOzoneModerationDefs.ModEventViewDetail export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/tools/ozone/moderation/getRecord.ts b/packages/api/src/client/types/tools/ozone/moderation/getRecord.ts index 9a1168033c5..fe1fc0b32c0 100644 --- a/packages/api/src/client/types/tools/ozone/moderation/getRecord.ts +++ b/packages/api/src/client/types/tools/ozone/moderation/getRecord.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -17,18 +17,19 @@ export type InputSchema = undefined export type OutputSchema = ToolsOzoneModerationDefs.RecordViewDetail export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class RecordNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -36,5 +37,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'RecordNotFound') return new RecordNotFoundError(e) } + return e } diff --git a/packages/api/src/client/types/tools/ozone/moderation/getRepo.ts b/packages/api/src/client/types/tools/ozone/moderation/getRepo.ts index 6df65f90aa7..ce26b50acf1 100644 --- a/packages/api/src/client/types/tools/ozone/moderation/getRepo.ts +++ b/packages/api/src/client/types/tools/ozone/moderation/getRepo.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -16,18 +16,19 @@ export type InputSchema = undefined export type OutputSchema = ToolsOzoneModerationDefs.RepoViewDetail export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class RepoNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -35,5 +36,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'RepoNotFound') return new RepoNotFoundError(e) } + return e } diff --git a/packages/api/src/client/types/tools/ozone/moderation/queryEvents.ts b/packages/api/src/client/types/tools/ozone/moderation/queryEvents.ts index 268c570e607..c2ca479f07a 100644 --- a/packages/api/src/client/types/tools/ozone/moderation/queryEvents.ts +++ b/packages/api/src/client/types/tools/ozone/moderation/queryEvents.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -47,17 +47,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/tools/ozone/moderation/queryStatuses.ts b/packages/api/src/client/types/tools/ozone/moderation/queryStatuses.ts index 10453220a33..49cbc21f6e8 100644 --- a/packages/api/src/client/types/tools/ozone/moderation/queryStatuses.ts +++ b/packages/api/src/client/types/tools/ozone/moderation/queryStatuses.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -50,17 +50,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/tools/ozone/moderation/searchRepos.ts b/packages/api/src/client/types/tools/ozone/moderation/searchRepos.ts index 3d56a3876b0..060b2b96331 100644 --- a/packages/api/src/client/types/tools/ozone/moderation/searchRepos.ts +++ b/packages/api/src/client/types/tools/ozone/moderation/searchRepos.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -25,17 +25,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/tools/ozone/server/getConfig.ts b/packages/api/src/client/types/tools/ozone/server/getConfig.ts index 540214a2208..d0127531450 100644 --- a/packages/api/src/client/types/tools/ozone/server/getConfig.ts +++ b/packages/api/src/client/types/tools/ozone/server/getConfig.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -21,18 +21,17 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/tools/ozone/team/addMember.ts b/packages/api/src/client/types/tools/ozone/team/addMember.ts index 2c1dec7ca3c..c323687ff0a 100644 --- a/packages/api/src/client/types/tools/ozone/team/addMember.ts +++ b/packages/api/src/client/types/tools/ozone/team/addMember.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -23,20 +23,21 @@ export interface InputSchema { export type OutputSchema = ToolsOzoneTeamDefs.Member export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class MemberAlreadyExistsError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -45,5 +46,6 @@ export function toKnownErr(e: any) { if (e.error === 'MemberAlreadyExists') return new MemberAlreadyExistsError(e) } + return e } diff --git a/packages/api/src/client/types/tools/ozone/team/deleteMember.ts b/packages/api/src/client/types/tools/ozone/team/deleteMember.ts index 4a1beff59b2..b456035e5d8 100644 --- a/packages/api/src/client/types/tools/ozone/team/deleteMember.ts +++ b/packages/api/src/client/types/tools/ozone/team/deleteMember.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -15,25 +15,26 @@ export interface InputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap } export class MemberNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } export class CannotDeleteSelfError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -42,5 +43,6 @@ export function toKnownErr(e: any) { if (e.error === 'MemberNotFound') return new MemberNotFoundError(e) if (e.error === 'CannotDeleteSelf') return new CannotDeleteSelfError(e) } + return e } diff --git a/packages/api/src/client/types/tools/ozone/team/listMembers.ts b/packages/api/src/client/types/tools/ozone/team/listMembers.ts index 3d3036b3ec5..a0275d0e838 100644 --- a/packages/api/src/client/types/tools/ozone/team/listMembers.ts +++ b/packages/api/src/client/types/tools/ozone/team/listMembers.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -22,17 +22,16 @@ export interface OutputSchema { } export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } return e } diff --git a/packages/api/src/client/types/tools/ozone/team/updateMember.ts b/packages/api/src/client/types/tools/ozone/team/updateMember.ts index f0795be9d99..71a96d12fa2 100644 --- a/packages/api/src/client/types/tools/ozone/team/updateMember.ts +++ b/packages/api/src/client/types/tools/ozone/team/updateMember.ts @@ -1,7 +1,7 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { Headers, XRPCError } from '@atproto/xrpc' +import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' @@ -24,20 +24,21 @@ export interface InputSchema { export type OutputSchema = ToolsOzoneTeamDefs.Member export interface CallOptions { - headers?: Headers + signal?: AbortSignal + headers?: HeadersMap qp?: QueryParams - encoding: 'application/json' + encoding?: 'application/json' } export interface Response { success: boolean - headers: Headers + headers: HeadersMap data: OutputSchema } export class MemberNotFoundError extends XRPCError { constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) + super(src.status, src.error, src.message, src.headers, { cause: src }) } } @@ -45,5 +46,6 @@ export function toKnownErr(e: any) { if (e instanceof XRPCError) { if (e.error === 'MemberNotFound') return new MemberNotFoundError(e) } + return e } diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 64fbb557f43..b482f66baee 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,3 +1,6 @@ +import { Lexicons } from '@atproto/lexicon' +import { lexicons as internalLexicons } from './client/lexicons' + export { AtUri } from '@atproto/syntax' export { BlobRef, @@ -11,7 +14,6 @@ export * from './types' export * from './const' export * from './util' export * from './client' -export * from './agent' export * from './rich-text/rich-text' export * from './rich-text/sanitization' export * from './rich-text/unicode' @@ -20,5 +22,14 @@ export * from './moderation' export * from './moderation/types' export * from './mocker' export { LABELS, DEFAULT_LABEL_SETTINGS } from './moderation/const/labels' +export { Agent } from './agent' + +export { AtpAgent, type AtpAgentOptions } from './atp-agent' export { BskyAgent } from './bsky-agent' -export { AtpAgent as default } from './agent' + +/** @deprecated */ +export { AtpAgent as default } from './atp-agent' + +// Expose a copy to prevent alteration of the internal Lexicon instance used by +// the AtpBaseClient class. +export const lexicons = new Lexicons(internalLexicons) diff --git a/packages/api/src/moderation/decision.ts b/packages/api/src/moderation/decision.ts index 92cc8ad51c1..042a90bbe45 100644 --- a/packages/api/src/moderation/decision.ts +++ b/packages/api/src/moderation/decision.ts @@ -271,7 +271,7 @@ export class ModerationDecision { return // skip labelers not configured by the user } if (isSelf && labelDef.flags.includes('no-self')) { - return // skip self-labels that arent supported + return // skip self-labels that aren't supported } // establish the label preference for interpretation diff --git a/packages/api/src/rich-text/rich-text.ts b/packages/api/src/rich-text/rich-text.ts index 8e7a9465146..261ea7e4e68 100644 --- a/packages/api/src/rich-text/rich-text.ts +++ b/packages/api/src/rich-text/rich-text.ts @@ -91,8 +91,7 @@ F: 0 1 2 3 4 5 6 7 8 910 // string indices ^-------^ // target slice {start: 0, end: 5} */ -import { AtpAgent } from '../agent' -import { AppBskyFeedPost, AppBskyRichtextFacet } from '../client' +import { AppBskyFeedPost, AppBskyRichtextFacet, AtpBaseClient } from '../client' import { UnicodeString } from './unicode' import { sanitizeRichText } from './sanitization' import { detectFacets } from './detection' @@ -348,13 +347,13 @@ export class RichText { * Detects facets such as links and mentions * Note: Overwrites the existing facets with auto-detected facets */ - async detectFacets(agent: AtpAgent) { + async detectFacets(agent: AtpBaseClient) { this.facets = detectFacets(this.unicodeText) if (this.facets) { for (const facet of this.facets) { for (const feature of facet.features) { if (AppBskyRichtextFacet.isMention(feature)) { - const did = await agent + const did = await agent.com.atproto.identity .resolveHandle({ handle: feature.did }) .catch((_) => undefined) .then((res) => res?.data.did) diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts index 7638b4d5ca6..f8878ec1581 100644 --- a/packages/api/src/types.ts +++ b/packages/api/src/types.ts @@ -4,7 +4,8 @@ import { ModerationPrefs } from './moderation/types' /** * Supported proxy targets */ -export type AtprotoServiceType = 'atproto_labeler' +type UnknownServiceType = string & NonNullable +export type AtprotoServiceType = 'atproto_labeler' | UnknownServiceType /** * Used by the PersistSessionHandler to indicate what change occurred @@ -39,14 +40,6 @@ export type AtpPersistSessionHandler = ( session: AtpSessionData | undefined, ) => void | Promise -/** - * AtpAgent constructor() opts - */ -export interface AtpAgentOpts { - service: string | URL - persistSession?: AtpPersistSessionHandler -} - /** * AtpAgent login() opts */ @@ -56,27 +49,10 @@ export interface AtpAgentLoginOpts { authFactorToken?: string | undefined } -/** - * AtpAgent global fetch handler - */ -type AtpAgentFetchHeaders = Record -export interface AtpAgentFetchHandlerResponse { - status: number - headers: Record - body: any -} -export type AtpAgentFetchHandler = ( - httpUri: string, - httpMethod: string, - httpHeaders: AtpAgentFetchHeaders, - httpReqBody: any, -) => Promise - /** * AtpAgent global config opts */ export interface AtpAgentGlobalOpts { - fetch?: AtpAgentFetchHandler appLabelers?: string[] } diff --git a/packages/api/src/util.ts b/packages/api/src/util.ts index 0175a00158f..196952ff557 100644 --- a/packages/api/src/util.ts +++ b/packages/api/src/util.ts @@ -79,3 +79,18 @@ export function validateSavedFeed(savedFeed: AppBskyActorDefs.SavedFeed) { } } } + +export type Did = `did:${string}` + +// @TODO use tools from @atproto/did +export const isDid = (str: unknown): str is Did => + typeof str === 'string' && + str.startsWith('did:') && + str.includes(':', 4) && + str.length > 8 && + str.length <= 2048 + +export const asDid = (value: string): Did => { + if (isDid(value)) return value + throw new TypeError(`Invalid DID: ${value}`) +} diff --git a/packages/api/tests/bsky-agent.test.ts b/packages/api/tests/bsky-agent.test.ts index 925c06e542a..491ade3545f 100644 --- a/packages/api/tests/bsky-agent.test.ts +++ b/packages/api/tests/bsky-agent.test.ts @@ -1,17 +1,17 @@ import { TestNetworkNoAppView } from '@atproto/dev-env' import { TID } from '@atproto/common-web' import { + AppBskyActorDefs, + AppBskyActorProfile, BskyAgent, ComAtprotoRepoPutRecord, - AppBskyActorProfile, DEFAULT_LABEL_SETTINGS, } from '../src' import { - savedFeedsToUriArrays, getSavedFeedType, + savedFeedsToUriArrays, validateSavedFeed, } from '../src/util' -import { AppBskyActorDefs } from '../dist' describe('agent', () => { let network: TestNetworkNoAppView @@ -30,8 +30,8 @@ describe('agent', () => { agent: BskyAgent, ): Promise => { try { - const res = await agent.api.app.bsky.actor.profile.get({ - repo: agent.session?.did || '', + const res = await agent.app.bsky.actor.profile.get({ + repo: agent.accountDid, rkey: 'self', }) return res.value.displayName ?? '' @@ -81,7 +81,6 @@ describe('agent', () => { it('upsertProfile correctly handles CAS failures.', async () => { const agent = new BskyAgent({ service: network.pds.url }) - await agent.createAccount({ handle: 'user2.test', email: 'user2@test.com', @@ -96,7 +95,7 @@ describe('agent', () => { await agent.upsertProfile(async (_existing) => { if (!hasConflicted) { await agent.com.atproto.repo.putRecord({ - repo: agent.session?.did || '', + repo: agent.accountDid, collection: 'app.bsky.actor.profile', rkey: 'self', record: { @@ -120,7 +119,6 @@ describe('agent', () => { it('upsertProfile wont endlessly retry CAS failures.', async () => { const agent = new BskyAgent({ service: network.pds.url }) - await agent.createAccount({ handle: 'user3.test', email: 'user3@test.com', @@ -132,7 +130,7 @@ describe('agent', () => { const p = agent.upsertProfile(async (_existing) => { await agent.com.atproto.repo.putRecord({ - repo: agent.session?.did || '', + repo: agent.accountDid, collection: 'app.bsky.actor.profile', rkey: 'self', record: { @@ -149,7 +147,6 @@ describe('agent', () => { it('upsertProfile validates the record.', async () => { const agent = new BskyAgent({ service: network.pds.url }) - await agent.createAccount({ handle: 'user4.test', email: 'user4@test.com', @@ -167,7 +164,8 @@ describe('agent', () => { describe('app', () => { it('should retrieve the api app', () => { const agent = new BskyAgent({ service: network.pds.url }) - expect(agent.app).toBe(agent.api.app) + expect(agent.api).toBe(agent) + expect(agent.app).toBeDefined() }) }) @@ -230,7 +228,6 @@ describe('agent', () => { describe('preferences methods', () => { it('gets and sets preferences correctly', async () => { const agent = new BskyAgent({ service: network.pds.url }) - await agent.createAccount({ handle: 'user5.test', email: 'user5@test.com', @@ -3181,18 +3178,13 @@ describe('agent', () => { }) describe('queued nudges', () => { - let agent: BskyAgent - - beforeAll(async () => { - agent = new BskyAgent({ service: network.pds.url }) + it('queueNudges & dismissNudges', async () => { + const agent = new BskyAgent({ service: network.pds.url }) await agent.createAccount({ handle: 'user11.test', email: 'user11@test.com', password: 'password', }) - }) - - it('queueNudges & dismissNudges', async () => { await agent.bskyAppQueueNudges('first') await expect(agent.getPreferences()).resolves.toHaveProperty( 'bskyAppState.queuedNudges', @@ -3217,18 +3209,15 @@ describe('agent', () => { }) describe('guided tours', () => { - let agent: BskyAgent + it('setActiveProgressGuide', async () => { + const agent = new BskyAgent({ service: network.pds.url }) - beforeAll(async () => { - agent = new BskyAgent({ service: network.pds.url }) await agent.createAccount({ handle: 'user12.test', email: 'user12@test.com', password: 'password', }) - }) - it('setActiveProgressGuide', async () => { await agent.bskyAppSetActiveProgressGuide({ guide: 'test-guide', numThings: 0, diff --git a/packages/api/tests/agent.test.ts b/packages/api/tests/dispatcher.test.ts similarity index 78% rename from packages/api/tests/agent.test.ts rename to packages/api/tests/dispatcher.test.ts index d80036f474f..6ccd4783ea6 100644 --- a/packages/api/tests/agent.test.ts +++ b/packages/api/tests/dispatcher.test.ts @@ -1,18 +1,21 @@ import assert from 'assert' import getPort from 'get-port' -import { defaultFetchHandler } from '@atproto/xrpc' import { AtpAgent, - AtpAgentFetchHandlerResponse, AtpSessionEvent, AtpSessionData, BSKY_LABELER_DID, -} from '..' +} from '../src' import { TestNetworkNoAppView } from '@atproto/dev-env' import { getPdsEndpoint, isValidDidDoc } from '@atproto/common-web' import { createHeaderEchoServer } from './util/echo-server' -describe('agent', () => { +const getPdsEndpointUrl = (...args: Parameters) => { + const endpoint = getPdsEndpoint(...args) + return endpoint ? new URL(endpoint) : endpoint +} + +describe('AtpAgent', () => { let network: TestNetworkNoAppView beforeAll(async () => { @@ -33,7 +36,7 @@ describe('agent', () => { const agent = new AtpAgent({ service: network.pds.url, persistSession }) const agent2 = agent.clone() expect(agent2 instanceof AtpAgent).toBeTruthy() - expect(agent.service).toEqual(agent2.service) + expect(agent.serviceUrl).toEqual(agent2.serviceUrl) }) it('creates a new session on account creation.', async () => { @@ -60,11 +63,9 @@ describe('agent', () => { expect(agent.session?.email).toEqual('user1@test.com') expect(agent.session?.emailConfirmed).toEqual(false) assert(isValidDidDoc(res.data.didDoc)) - expect(agent.api.xrpc.uri.origin).toEqual(getPdsEndpoint(res.data.didDoc)) + expect(agent.pdsUrl).toEqual(getPdsEndpointUrl(res.data.didDoc)) - const { data: sessionInfo } = await agent.api.com.atproto.server.getSession( - {}, - ) + const { data: sessionInfo } = await agent.com.atproto.server.getSession({}) expect(sessionInfo).toMatchObject({ did: res.data.did, handle: res.data.handle, @@ -110,10 +111,9 @@ describe('agent', () => { expect(agent2.session?.email).toEqual('user2@test.com') expect(agent2.session?.emailConfirmed).toEqual(false) assert(isValidDidDoc(res1.data.didDoc)) - expect(agent2.api.xrpc.uri.origin).toEqual(getPdsEndpoint(res1.data.didDoc)) + expect(agent2.pdsUrl).toEqual(getPdsEndpointUrl(res1.data.didDoc)) - const { data: sessionInfo } = - await agent2.api.com.atproto.server.getSession({}) + const { data: sessionInfo } = await agent2.com.atproto.server.getSession({}) expect(sessionInfo).toMatchObject({ did: res1.data.did, handle: res1.data.handle, @@ -156,10 +156,9 @@ describe('agent', () => { expect(agent2.session?.handle).toEqual(res1.data.handle) expect(agent2.session?.did).toEqual(res1.data.did) assert(isValidDidDoc(res1.data.didDoc)) - expect(agent2.api.xrpc.uri.origin).toEqual(getPdsEndpoint(res1.data.didDoc)) + expect(agent2.pdsUrl).toEqual(getPdsEndpointUrl(res1.data.didDoc)) - const { data: sessionInfo } = - await agent2.api.com.atproto.server.getSession({}) + const { data: sessionInfo } = await agent2.com.atproto.server.getSession({}) expect(sessionInfo).toMatchObject({ did: res1.data.did, handle: res1.data.handle, @@ -192,7 +191,7 @@ describe('agent', () => { email: 'user4@test.com', password: 'password', }) - if (!agent.session) { + if (!agent.session?.refreshJwt) { throw new Error('No session created') } const session1 = agent.session @@ -203,26 +202,25 @@ describe('agent', () => { await new Promise((r) => setTimeout(r, 1000)) // patch the fetch handler to fake an expired token error on the next request - const tokenExpiredFetchHandler = async function ( - httpUri: string, - httpMethod: string, - httpHeaders: Record, - httpReqBody: unknown, - ): Promise { - if (httpHeaders.authorization === `Bearer ${origAccessJwt}`) { - return { - status: 400, - headers: {}, - body: { error: 'ExpiredToken' }, + agent.sessionManager.setFetch( + async (input: RequestInfo | URL, init?: RequestInit) => { + const req = new Request(input, init) + if ( + req.headers.get('authorization') === `Bearer ${origAccessJwt}` && + !req.url.includes('com.atproto.server.refreshSession') + ) { + return new Response(JSON.stringify({ error: 'ExpiredToken' }), { + status: 400, + headers: { 'Content-Type': 'application/json' }, + }) } - } - return defaultFetchHandler(httpUri, httpMethod, httpHeaders, httpReqBody) - } + + return globalThis.fetch(req) + }, + ) // put the agent through the auth flow - AtpAgent.configure({ fetch: tokenExpiredFetchHandler }) const res1 = await createPost(agent) - AtpAgent.configure({ fetch: defaultFetchHandler }) expect(res1.success).toEqual(true) expect(agent.hasSession).toEqual(true) @@ -270,34 +268,30 @@ describe('agent', () => { // patch the fetch handler to fake an expired token error on the next request let expiredCalls = 0 let refreshCalls = 0 - const tokenExpiredFetchHandler = async function ( - httpUri: string, - httpMethod: string, - httpHeaders: Record, - httpReqBody: unknown, - ): Promise { - if (httpHeaders.authorization === `Bearer ${origAccessJwt}`) { - expiredCalls++ - return { - status: 400, - headers: {}, - body: { error: 'ExpiredToken' }, + + agent.sessionManager.setFetch( + async (input: RequestInfo | URL, init?: RequestInit) => { + const req = new Request(input, init) + if (req.headers.get('authorization') === `Bearer ${origAccessJwt}`) { + expiredCalls++ + return new Response(JSON.stringify({ error: 'ExpiredToken' }), { + status: 400, + headers: { 'Content-Type': 'application/json' }, + }) } - } - if (httpUri.includes('com.atproto.server.refreshSession')) { - refreshCalls++ - } - return defaultFetchHandler(httpUri, httpMethod, httpHeaders, httpReqBody) - } + if (req.url.includes('com.atproto.server.refreshSession')) { + refreshCalls++ + } + return globalThis.fetch(req) + }, + ) // put the agent through the auth flow - AtpAgent.configure({ fetch: tokenExpiredFetchHandler }) const [res1, res2, res3] = await Promise.all([ createPost(agent), createPost(agent), createPost(agent), ]) - AtpAgent.configure({ fetch: defaultFetchHandler }) expect(expiredCalls).toEqual(3) expect(refreshCalls).toEqual(1) @@ -384,40 +378,31 @@ describe('agent', () => { const origAccessJwt = session1.accessJwt // patch the fetch handler to fake an expired token error on the next request - const tokenExpiredFetchHandler = async function ( - httpUri: string, - httpMethod: string, - httpHeaders: Record, - httpReqBody: unknown, - ): Promise { - if (httpHeaders.authorization === `Bearer ${origAccessJwt}`) { - return { - status: 400, - headers: {}, - body: { error: 'ExpiredToken' }, + agent.sessionManager.setFetch( + async (input: RequestInfo | URL, init?: RequestInit) => { + const req = new Request(input, init) + if (req.headers.get('authorization') === `Bearer ${origAccessJwt}`) { + return new Response(JSON.stringify({ error: 'ExpiredToken' }), { + status: 400, + headers: { 'Content-Type': 'application/json' }, + }) } - } - if (httpUri.includes('com.atproto.server.refreshSession')) { - return { - status: 500, - headers: {}, - body: undefined, + if (req.url.includes('com.atproto.server.refreshSession')) { + return new Response(undefined, { status: 500 }) } - } - return defaultFetchHandler(httpUri, httpMethod, httpHeaders, httpReqBody) - } + return globalThis.fetch(req) + }, + ) // put the agent through the auth flow - AtpAgent.configure({ fetch: tokenExpiredFetchHandler }) try { - await agent.api.app.bsky.feed.getTimeline() + await agent.app.bsky.feed.getTimeline() throw new Error('Should have failed') } catch (e: any) { // the original error passes through expect(e.status).toEqual(400) expect(e.error).toEqual('ExpiredToken') } - AtpAgent.configure({ fetch: defaultFetchHandler }) // still has session because it wasn't invalidated expect(agent.hasSession).toEqual(true) @@ -428,42 +413,6 @@ describe('agent', () => { expect(sessions[0]?.accessJwt).toEqual(origAccessJwt) }) - describe('setPersistSessionHandler', () => { - it('sets persist session handler', async () => { - let originalHandlerCallCount = 0 - let newHandlerCallCount = 0 - - const persistSession = () => { - originalHandlerCallCount++ - } - const newPersistSession = () => { - newHandlerCallCount++ - } - - const agent = new AtpAgent({ service: network.pds.url, persistSession }) - - await agent.createAccount({ - handle: 'user7.test', - email: 'user7@test.com', - password: 'password', - }) - - expect(originalHandlerCallCount).toEqual(1) - - agent.setPersistSessionHandler(newPersistSession) - agent.session = undefined - - await agent.createAccount({ - handle: 'user8.test', - email: 'user8@test.com', - password: 'password', - }) - - expect(originalHandlerCallCount).toEqual(1) - expect(newHandlerCallCount).toEqual(1) - }) - }) - describe('createAccount', () => { it('persists an empty session on failure', async () => { const events: string[] = [] @@ -519,19 +468,19 @@ describe('agent', () => { }) }) - describe('configureLabelersHeader', () => { + describe('configureLabelers', () => { it('adds the labelers header as expected', async () => { const port = await getPort() const server = await createHeaderEchoServer(port) const agent = new AtpAgent({ service: `http://localhost:${port}` }) - agent.configureLabelersHeader(['did:plc:test1']) + agent.configureLabelers(['did:plc:test1']) const res1 = await agent.com.atproto.server.describeServer() expect(res1.data['atproto-accept-labelers']).toEqual( `${BSKY_LABELER_DID};redact, did:plc:test1`, ) - agent.configureLabelersHeader(['did:plc:test1', 'did:plc:test2']) + agent.configureLabelers(['did:plc:test1', 'did:plc:test2']) const res2 = await agent.com.atproto.server.describeServer() expect(res2.data['atproto-accept-labelers']).toEqual( `${BSKY_LABELER_DID};redact, did:plc:test1, did:plc:test2`, @@ -541,7 +490,7 @@ describe('agent', () => { }) }) - describe('configureProxyHeader', () => { + describe('configureProxy', () => { it('adds the proxy header as expected', async () => { const port = await getPort() const server = await createHeaderEchoServer(port) @@ -550,7 +499,7 @@ describe('agent', () => { const res1 = await agent.com.atproto.server.describeServer() expect(res1.data['atproto-proxy']).toBeFalsy() - agent.configureProxyHeader('atproto_labeler', 'did:plc:test1') + agent.configureProxy('did:plc:test1#atproto_labeler') const res2 = await agent.com.atproto.server.describeServer() expect(res2.data['atproto-proxy']).toEqual( 'did:plc:test1#atproto_labeler', @@ -569,8 +518,8 @@ describe('agent', () => { }) const createPost = async (agent: AtpAgent) => { - return agent.api.com.atproto.repo.createRecord({ - repo: agent.session?.did ?? '', + return agent.com.atproto.repo.createRecord({ + repo: agent.accountDid, collection: 'app.bsky.feed.post', record: { text: 'hello there', diff --git a/packages/api/tests/moderation-prefs.test.ts b/packages/api/tests/moderation-prefs.test.ts index 0b5af69ba7b..a166df30db6 100644 --- a/packages/api/tests/moderation-prefs.test.ts +++ b/packages/api/tests/moderation-prefs.test.ts @@ -1,5 +1,5 @@ import { TestNetworkNoAppView } from '@atproto/dev-env' -import { BskyAgent, DEFAULT_LABEL_SETTINGS } from '..' +import { DEFAULT_LABEL_SETTINGS } from '../src' import './util/moderation-behavior' describe('agent', () => { @@ -16,7 +16,7 @@ describe('agent', () => { }) it('migrates legacy content-label prefs (no mutations)', async () => { - const agent = new BskyAgent({ service: network.pds.url }) + const agent = network.pds.getClient() await agent.createAccount({ handle: 'user1.test', @@ -63,9 +63,7 @@ describe('agent', () => { sexual: 'ignore', 'graphic-media': 'ignore', }, - labelers: [ - ...BskyAgent.appLabelers.map((did) => ({ did, labels: {} })), - ], + labelers: [...agent.appLabelers.map((did) => ({ did, labels: {} }))], hiddenPosts: [], mutedWords: [], }, @@ -91,7 +89,7 @@ describe('agent', () => { }) it('adds/removes moderation services', async () => { - const agent = new BskyAgent({ service: network.pds.url }) + const agent = network.pds.getClient() await agent.createAccount({ handle: 'user5.test', @@ -100,7 +98,7 @@ describe('agent', () => { }) await agent.addLabeler('did:plc:other') - expect(agent.labelersHeader).toStrictEqual(['did:plc:other']) + expect(agent.labelers).toStrictEqual(['did:plc:other']) await expect(agent.getPreferences()).resolves.toStrictEqual({ feeds: { pinned: undefined, saved: undefined }, savedFeeds: expect.any(Array), @@ -109,7 +107,7 @@ describe('agent', () => { adultContentEnabled: false, labels: DEFAULT_LABEL_SETTINGS, labelers: [ - ...BskyAgent.appLabelers.map((did) => ({ did, labels: {} })), + ...agent.appLabelers.map((did) => ({ did, labels: {} })), { did: 'did:plc:other', labels: {}, @@ -137,10 +135,10 @@ describe('agent', () => { queuedNudges: [], }, }) - expect(agent.labelersHeader).toStrictEqual(['did:plc:other']) + expect(agent.labelers).toStrictEqual(['did:plc:other']) await agent.removeLabeler('did:plc:other') - expect(agent.labelersHeader).toStrictEqual([]) + expect(agent.labelers).toStrictEqual([]) await expect(agent.getPreferences()).resolves.toStrictEqual({ feeds: { pinned: undefined, saved: undefined }, savedFeeds: expect.any(Array), @@ -148,9 +146,7 @@ describe('agent', () => { moderationPrefs: { adultContentEnabled: false, labels: DEFAULT_LABEL_SETTINGS, - labelers: [ - ...BskyAgent.appLabelers.map((did) => ({ did, labels: {} })), - ], + labelers: [...agent.appLabelers.map((did) => ({ did, labels: {} }))], hiddenPosts: [], mutedWords: [], }, @@ -173,11 +169,11 @@ describe('agent', () => { queuedNudges: [], }, }) - expect(agent.labelersHeader).toStrictEqual([]) + expect(agent.labelers).toStrictEqual([]) }) it('sets label preferences globally and per-moderator', async () => { - const agent = new BskyAgent({ service: network.pds.url }) + const agent = network.pds.getClient() await agent.createAccount({ handle: 'user7.test', @@ -198,7 +194,7 @@ describe('agent', () => { adultContentEnabled: false, labels: { ...DEFAULT_LABEL_SETTINGS, porn: 'ignore', nsfw: 'ignore' }, labelers: [ - ...BskyAgent.appLabelers.map((did) => ({ did, labels: {} })), + ...agent.appLabelers.map((did) => ({ did, labels: {} })), { did: 'did:plc:other', labels: { @@ -232,7 +228,7 @@ describe('agent', () => { }) it(`updates label pref`, async () => { - const agent = new BskyAgent({ service: network.pds.url }) + const agent = network.pds.getClient() await agent.createAccount({ handle: 'user8.test', @@ -256,7 +252,7 @@ describe('agent', () => { }) it(`double-write for legacy: 'graphic-media' in sync with 'gore'`, async () => { - const agent = new BskyAgent({ service: network.pds.url }) + const agent = network.pds.getClient() await agent.createAccount({ handle: 'user9.test', @@ -278,7 +274,7 @@ describe('agent', () => { }) it(`double-write for legacy: 'porn' in sync with 'nsfw'`, async () => { - const agent = new BskyAgent({ service: network.pds.url }) + const agent = network.pds.getClient() await agent.createAccount({ handle: 'user10.test', @@ -300,7 +296,7 @@ describe('agent', () => { }) it(`double-write for legacy: 'sexual' in sync with 'suggestive'`, async () => { - const agent = new BskyAgent({ service: network.pds.url }) + const agent = network.pds.getClient() await agent.createAccount({ handle: 'user11.test', @@ -322,7 +318,7 @@ describe('agent', () => { }) it(`double-write for legacy: filters out existing old label pref if double-written`, async () => { - const agent = new BskyAgent({ service: network.pds.url }) + const agent = network.pds.getClient() await agent.createAccount({ handle: 'user12.test', @@ -341,7 +337,7 @@ describe('agent', () => { }) it(`remaps old values to new on read`, async () => { - const agent = new BskyAgent({ service: network.pds.url }) + const agent = network.pds.getClient() await agent.createAccount({ handle: 'user13.test', diff --git a/packages/api/tests/rich-text-detection.test.ts b/packages/api/tests/rich-text-detection.test.ts index 084b5440a48..c87b5eab0a6 100644 --- a/packages/api/tests/rich-text-detection.test.ts +++ b/packages/api/tests/rich-text-detection.test.ts @@ -3,13 +3,13 @@ import { isTag } from '../src/client/types/app/bsky/richtext/facet' describe('detectFacets', () => { const agent = new AtpAgent({ service: 'http://localhost' }) - agent.resolveHandle = ({ handle }: { handle: string }) => { - return Promise.resolve({ - success: true, - headers: {}, - data: { did: 'did:fake:' + handle }, - }) - } + + // Mock handle resolution + agent.com.atproto.identity.resolveHandle = async (params) => ({ + success: true, + headers: {}, + data: { did: `did:fake:${params?.handle}` }, + }) const inputs = [ 'no mention', diff --git a/packages/api/tests/util/index.ts b/packages/api/tests/util/index.ts deleted file mode 100644 index 50334e8daf8..00000000000 --- a/packages/api/tests/util/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { AtpAgentFetchHandlerResponse } from '../../src' - -export async function fetchHandler( - httpUri: string, - httpMethod: string, - httpHeaders: Record, - httpReqBody: unknown, -): Promise { - // The duplex field is now required for streaming bodies, but not yet reflected - // anywhere in docs or types. See whatwg/fetch#1438, nodejs/node#46221. - const reqInit: RequestInit & { duplex: string } = { - method: httpMethod, - headers: httpHeaders, - body: httpReqBody - ? new TextEncoder().encode(JSON.stringify(httpReqBody)) - : undefined, - duplex: 'half', - } - const res = await fetch(httpUri, reqInit) - const resBody = await res.arrayBuffer() - return { - status: res.status, - headers: Object.fromEntries(res.headers.entries()), - body: resBody ? JSON.parse(new TextDecoder().decode(resBody)) : undefined, - } -} diff --git a/packages/aws/CHANGELOG.md b/packages/aws/CHANGELOG.md index 0fc7772784b..bb3ff22da5b 100644 --- a/packages/aws/CHANGELOG.md +++ b/packages/aws/CHANGELOG.md @@ -1,5 +1,12 @@ # @atproto/aws +## 0.2.2 + +### Patch Changes + +- Updated dependencies []: + - @atproto/repo@0.4.2 + ## 0.2.1 ### Patch Changes diff --git a/packages/aws/package.json b/packages/aws/package.json index 471cafd49a7..e46dd2d7cc6 100644 --- a/packages/aws/package.json +++ b/packages/aws/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/aws", - "version": "0.2.1", + "version": "0.2.2", "license": "MIT", "description": "Shared AWS cloud API helpers for atproto services", "keywords": [ diff --git a/packages/bsky/CHANGELOG.md b/packages/bsky/CHANGELOG.md index 98d4fb15bfb..3d275fa9dce 100644 --- a/packages/bsky/CHANGELOG.md +++ b/packages/bsky/CHANGELOG.md @@ -1,5 +1,39 @@ # @atproto/bsky +## 0.0.75 + +### Patch Changes + +- Updated dependencies [[`22af354a5`](https://github.com/bluesky-social/atproto/commit/22af354a5db595d7cbc0e65f02601de3565337e1)]: + - @atproto/api@0.13.1 + +## 0.0.74 + +### Patch Changes + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`2bdf75d7a`](https://github.com/bluesky-social/atproto/commit/2bdf75d7a63924c10e7a311f16cb447d595b933e), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd)]: + - @atproto/lexicon@0.4.1 + - @atproto/api@0.13.0 + - @atproto/repo@0.4.2 + - @atproto/xrpc-server@0.6.1 + +## 0.0.73 + +### Patch Changes + +- Updated dependencies [[`dc471da26`](https://github.com/bluesky-social/atproto/commit/dc471da267955d0962a8affaf983df60d962d97c), [`dc471da26`](https://github.com/bluesky-social/atproto/commit/dc471da267955d0962a8affaf983df60d962d97c)]: + - @atproto/api@0.12.29 + - @atproto/xrpc-server@0.6.0 + +## 0.0.72 + +### Patch Changes + +- [#2676](https://github.com/bluesky-social/atproto/pull/2676) [`951a3df15`](https://github.com/bluesky-social/atproto/commit/951a3df15aa9c1f5b0a2b66cfb0e2eaf6198fe41) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Remove `app.bsky.feed.detach` record, to be replaced by `app.bsky.feed.postgate` record in a future release. + +- Updated dependencies [[`951a3df15`](https://github.com/bluesky-social/atproto/commit/951a3df15aa9c1f5b0a2b66cfb0e2eaf6198fe41)]: + - @atproto/api@0.12.28 + ## 0.0.71 ### Patch Changes diff --git a/packages/bsky/package.json b/packages/bsky/package.json index 369e95dbe7b..10c1eca6b11 100644 --- a/packages/bsky/package.json +++ b/packages/bsky/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/bsky", - "version": "0.0.71", + "version": "0.0.75", "license": "MIT", "description": "Reference implementation of app.bsky App View (Bluesky API)", "keywords": [ @@ -17,7 +17,7 @@ "types": "dist/index.d.ts", "bin": "dist/bin.js", "scripts": { - "codegen": "lex gen-server ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/* ../../lexicons/chat/bsky/*/*", + "codegen": "lex gen-server --yes ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/* ../../lexicons/chat/bsky/*/*", "build": "tsc --build tsconfig.build.json", "start": "node --enable-source-maps dist/bin.js", "test": "../dev-infra/with-test-redis-and-db.sh jest", diff --git a/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts b/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts index d209a6f4cef..657a803bfe5 100644 --- a/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts +++ b/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts @@ -12,7 +12,7 @@ import { Views } from '../../../../views' import { DataPlaneClient } from '../../../../data-plane' import { parseString } from '../../../../hydration/util' import { resHeaders } from '../../../util' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' export default function (server: Server, ctx: AppContext) { const getSuggestions = createPipeline( diff --git a/packages/bsky/src/api/app/bsky/actor/searchActors.ts b/packages/bsky/src/api/app/bsky/actor/searchActors.ts index 1c9fd50ceef..9e0d944e706 100644 --- a/packages/bsky/src/api/app/bsky/actor/searchActors.ts +++ b/packages/bsky/src/api/app/bsky/actor/searchActors.ts @@ -1,7 +1,7 @@ import AppContext from '../../../../context' import { Server } from '../../../../lexicon' import { mapDefined } from '@atproto/common' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { QueryParams } from '../../../../lexicon/types/app/bsky/actor/searchActors' import { HydrationFnInput, @@ -53,7 +53,7 @@ const skeleton = async (inputs: SkeletonFnInput) => { if (ctx.searchAgent) { // @NOTE cursors won't change on appview swap const { data: res } = - await ctx.searchAgent.api.app.bsky.unspecced.searchActorsSkeleton({ + await ctx.searchAgent.app.bsky.unspecced.searchActorsSkeleton({ q: term, cursor: params.cursor, limit: params.limit, diff --git a/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts b/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts index 0667fbb645a..d745fd927f2 100644 --- a/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts +++ b/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts @@ -1,6 +1,6 @@ import AppContext from '../../../../context' import { Server } from '../../../../lexicon' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { mapDefined } from '@atproto/common' import { QueryParams } from '../../../../lexicon/types/app/bsky/actor/searchActorsTypeahead' import { @@ -52,7 +52,7 @@ const skeleton = async (inputs: SkeletonFnInput) => { if (ctx.searchAgent) { const { data: res } = - await ctx.searchAgent.api.app.bsky.unspecced.searchActorsSkeleton({ + await ctx.searchAgent.app.bsky.unspecced.searchActorsSkeleton({ typeahead: true, q: term, limit: params.limit, diff --git a/packages/bsky/src/api/app/bsky/feed/searchPosts.ts b/packages/bsky/src/api/app/bsky/feed/searchPosts.ts index 34a7c948be2..853c5453032 100644 --- a/packages/bsky/src/api/app/bsky/feed/searchPosts.ts +++ b/packages/bsky/src/api/app/bsky/feed/searchPosts.ts @@ -1,6 +1,6 @@ import AppContext from '../../../../context' import { Server } from '../../../../lexicon' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { mapDefined } from '@atproto/common' import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/searchPosts' import { diff --git a/packages/bsky/src/auth-verifier.ts b/packages/bsky/src/auth-verifier.ts index b39c58f97cd..a9ac7b408b1 100644 --- a/packages/bsky/src/auth-verifier.ts +++ b/packages/bsky/src/auth-verifier.ts @@ -248,7 +248,12 @@ export class AuthVerifier { if (!jwtStr) { throw new AuthRequiredError('missing jwt', 'MissingJwt') } - const payload = await verifyServiceJwt(jwtStr, opts.aud, getSigningKey) + const payload = await verifyServiceJwt( + jwtStr, + opts.aud, + null, + getSigningKey, + ) return { iss: payload.iss, aud: payload.aud } } diff --git a/packages/bsky/src/context.ts b/packages/bsky/src/context.ts index 1479df29f8c..160da3b59d9 100644 --- a/packages/bsky/src/context.ts +++ b/packages/bsky/src/context.ts @@ -1,9 +1,8 @@ import express from 'express' import * as plc from '@did-plc/lib' import { IdResolver } from '@atproto/identity' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { Keypair } from '@atproto/crypto' -import { createServiceJwt } from '@atproto/xrpc-server' import { ServerConfig } from './config' import { DataPlaneClient } from './data-plane/client' import { Hydrator } from './hydration/hydrator' @@ -89,15 +88,6 @@ export class AppContext { return this.opts.featureGates } - async serviceAuthJwt(aud: string) { - const iss = this.cfg.serverDid - return createServiceJwt({ - iss, - aud, - keypair: this.signingKey, - }) - } - reqLabelers(req: express.Request): ParsedLabelers { const val = req.header('atproto-accept-labelers') let parsed: ParsedLabelers | null diff --git a/packages/bsky/src/data-plane/server/indexing/index.ts b/packages/bsky/src/data-plane/server/indexing/index.ts index a8ba36b138e..e6b0e2d723f 100644 --- a/packages/bsky/src/data-plane/server/indexing/index.ts +++ b/packages/bsky/src/data-plane/server/indexing/index.ts @@ -1,6 +1,6 @@ import { sql } from 'kysely' import { CID } from 'multiformats/cid' -import AtpAgent, { ComAtprotoSyncGetLatestCommit } from '@atproto/api' +import { AtpAgent, ComAtprotoSyncGetLatestCommit } from '@atproto/api' import { readCarWithRoot, WriteOpAction, diff --git a/packages/bsky/src/hydration/hydrator.ts b/packages/bsky/src/hydration/hydrator.ts index 7e9e07f2be8..15b210e6b40 100644 --- a/packages/bsky/src/hydration/hydrator.ts +++ b/packages/bsky/src/hydration/hydrator.ts @@ -759,7 +759,7 @@ export class Hydrator { }) } - // provides partial hydration state withing getFollows / getFollowers, mainly for applying rules + // provides partial hydration state within getFollows / getFollowers, mainly for applying rules async hydrateFollows(uris: string[]): Promise { const follows = await this.graph.getFollows(uris) const pairs: RelationshipPair[] = [] diff --git a/packages/bsky/src/index.ts b/packages/bsky/src/index.ts index 58441189db2..8dbfc9545a7 100644 --- a/packages/bsky/src/index.ts +++ b/packages/bsky/src/index.ts @@ -5,7 +5,7 @@ import events from 'events' import { createHttpTerminator, HttpTerminator } from 'http-terminator' import cors from 'cors' import compression from 'compression' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { IdResolver } from '@atproto/identity' import { DAY, SECOND } from '@atproto/common' import API, { health, wellKnown, blobResolver } from './api' diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index 51c108efa93..07279e983d9 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -891,7 +891,7 @@ export const schemaDict = { labelValueDefinition: { type: 'object', description: - 'Declares a label value and its expected interpertations and behaviors.', + 'Declares a label value and its expected interpretations and behaviors.', required: ['identifier', 'severity', 'blurs', 'locales'], properties: { identifier: { @@ -2607,6 +2607,17 @@ export const schemaDict = { description: 'The DID of the service that the token will be used to authenticate with', }, + exp: { + type: 'integer', + description: + 'The time in Unix Epoch seconds that the JWT expires. Defaults to 60 seconds in the future. The service may enforce certain time bounds on tokens depending on the requested scope.', + }, + lxm: { + type: 'string', + format: 'nsid', + description: + 'Lexicon (XRPC) method to bind the requested token to', + }, }, }, output: { @@ -2621,6 +2632,13 @@ export const schemaDict = { }, }, }, + errors: [ + { + name: 'BadExpiration', + description: + 'Indicates that the requested expiration date is not a valid. May be in the past or may be reliant on the requested scopes.', + }, + ], }, }, }, @@ -5500,7 +5518,7 @@ export const schemaDict = { feedContext: { type: 'string', description: - 'Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton.', + 'Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton.', maxLength: 2000, }, }, diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/defs.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/defs.ts index e9d9332a406..fab573298a0 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/defs.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/defs.ts @@ -333,7 +333,7 @@ export interface Interaction { | 'app.bsky.feed.defs#interactionQuote' | 'app.bsky.feed.defs#interactionShare' | (string & {}) - /** Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton. */ + /** Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton. */ feedContext?: string [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/com/atproto/label/defs.ts b/packages/bsky/src/lexicon/types/com/atproto/label/defs.ts index d0225540a54..348e985622b 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/label/defs.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/label/defs.ts @@ -78,7 +78,7 @@ export function validateSelfLabel(v: unknown): ValidationResult { return lexicons.validate('com.atproto.label.defs#selfLabel', v) } -/** Declares a label value and its expected interpertations and behaviors. */ +/** Declares a label value and its expected interpretations and behaviors. */ export interface LabelValueDefinition { /** The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). */ identifier: string diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/importRepo.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/importRepo.ts index 921798c0ded..59288c7a027 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/importRepo.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/importRepo.ts @@ -11,7 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} -export type InputSchema = string | Uint8Array +export type InputSchema = string | Uint8Array | Blob export interface HandlerInput { encoding: 'application/vnd.ipld.car' diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/uploadBlob.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/uploadBlob.ts index febbbff9d16..4a712346a0b 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/uploadBlob.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/uploadBlob.ts @@ -11,7 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} -export type InputSchema = string | Uint8Array +export type InputSchema = string | Uint8Array | Blob export interface OutputSchema { blob: BlobRef diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/getServiceAuth.ts b/packages/bsky/src/lexicon/types/com/atproto/server/getServiceAuth.ts index 73efe2313a9..14f249fde44 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/getServiceAuth.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/getServiceAuth.ts @@ -11,6 +11,10 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the service that the token will be used to authenticate with */ aud: string + /** The time in Unix Epoch seconds that the JWT expires. Defaults to 60 seconds in the future. The service may enforce certain time bounds on tokens depending on the requested scope. */ + exp?: number + /** Lexicon (XRPC) method to bind the requested token to */ + lxm?: string } export type InputSchema = undefined @@ -31,6 +35,7 @@ export interface HandlerSuccess { export interface HandlerError { status: number message?: string + error?: 'BadExpiration' } export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough diff --git a/packages/bsky/tests/admin/admin-auth.test.ts b/packages/bsky/tests/admin/admin-auth.test.ts index cb13b58897a..d811bf55dc3 100644 --- a/packages/bsky/tests/admin/admin-auth.test.ts +++ b/packages/bsky/tests/admin/admin-auth.test.ts @@ -1,5 +1,5 @@ import { SeedClient, usersSeed, TestNetwork } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { Secp256k1Keypair } from '@atproto/crypto' import { createServiceAuthHeaders } from '@atproto/xrpc-server' import { RepoRef } from '../../src/lexicon/types/com/atproto/admin/defs' @@ -71,6 +71,7 @@ describe('admin auth', () => { const headers = await createServiceAuthHeaders({ iss: modServiceDid, aud: bskyDid, + lxm: null, keypair: modServiceKey, }) await agent.api.com.atproto.admin.updateSubjectStatus( @@ -96,6 +97,7 @@ describe('admin auth', () => { const headers = await createServiceAuthHeaders({ iss: altModDid, aud: bskyDid, + lxm: null, keypair: modServiceKey, }) const attempt = agent.api.com.atproto.admin.updateSubjectStatus( @@ -116,6 +118,7 @@ describe('admin auth', () => { const headers = await createServiceAuthHeaders({ iss: sc.dids.alice, aud: bskyDid, + lxm: null, keypair: aliceKey, }) const attempt = agent.api.com.atproto.admin.updateSubjectStatus( @@ -136,6 +139,7 @@ describe('admin auth', () => { const headers = await createServiceAuthHeaders({ iss: modServiceDid, aud: bskyDid, + lxm: null, keypair: badKey, }) const attempt = agent.api.com.atproto.admin.updateSubjectStatus( @@ -158,6 +162,7 @@ describe('admin auth', () => { const headers = await createServiceAuthHeaders({ iss: modServiceDid, aud: sc.dids.alice, + lxm: null, keypair: modServiceKey, }) const attempt = agent.api.com.atproto.admin.updateSubjectStatus( diff --git a/packages/bsky/tests/admin/moderation.test.ts b/packages/bsky/tests/admin/moderation.test.ts index 2f6caa8d17d..7bb1c551217 100644 --- a/packages/bsky/tests/admin/moderation.test.ts +++ b/packages/bsky/tests/admin/moderation.test.ts @@ -1,5 +1,5 @@ import { ImageRef, SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { RepoBlobRef, RepoRef, diff --git a/packages/bsky/tests/auth.test.ts b/packages/bsky/tests/auth.test.ts index d0903174a2b..da756723bb8 100644 --- a/packages/bsky/tests/auth.test.ts +++ b/packages/bsky/tests/auth.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { SeedClient, TestNetwork, usersSeed } from '@atproto/dev-env' import { createServiceJwt } from '@atproto/xrpc-server' import { Keypair, Secp256k1Keypair } from '@atproto/crypto' @@ -29,6 +29,7 @@ describe('auth', () => { const jwt = await createServiceJwt({ iss: issuer, aud: network.bsky.ctx.cfg.serverDid, + lxm: null, keypair, }) return agent.api.app.bsky.actor.getProfile( diff --git a/packages/bsky/tests/data-plane/indexing.test.ts b/packages/bsky/tests/data-plane/indexing.test.ts index c5a7d00df08..406d56305f3 100644 --- a/packages/bsky/tests/data-plane/indexing.test.ts +++ b/packages/bsky/tests/data-plane/indexing.test.ts @@ -4,12 +4,13 @@ import { cidForCbor, TID } from '@atproto/common' import { repoPrepare } from '@atproto/pds' import { WriteOpAction } from '@atproto/repo' import { AtUri } from '@atproto/syntax' -import AtpAgent, { +import { AppBskyActorProfile, AppBskyFeedPost, AppBskyFeedLike, AppBskyFeedRepost, AppBskyGraphFollow, + AtpAgent, } from '@atproto/api' import { TestNetwork, SeedClient, usersSeed, basicSeed } from '@atproto/dev-env' import { forSnapshot } from '../_util' @@ -561,7 +562,7 @@ describe('indexing', () => { it('reindexes handle for existing did when forced', async () => { const now = new Date().toISOString() - const sessionAgent = new AtpAgent({ service: network.pds.url }) + const sessionAgent = network.pds.getClient() const { data: { did }, } = await sessionAgent.createAccount({ @@ -582,14 +583,13 @@ describe('indexing', () => { it('handles profile aggregations out of order', async () => { const now = new Date().toISOString() - const sessionAgent = new AtpAgent({ service: network.pds.url }) - const { - data: { did }, - } = await sessionAgent.createAccount({ + const agent = network.pds.getClient() + await agent.createAccount({ email: 'did3@test.com', handle: 'did3.test', password: 'password', }) + const did = agent.accountDid const follow = await prepareCreate({ did: sc.dids.bob, collection: ids.AppBskyGraphFollow, diff --git a/packages/bsky/tests/data-plane/subscription/repo.test.ts b/packages/bsky/tests/data-plane/subscription/repo.test.ts index ff439c81366..8faa5538ab6 100644 --- a/packages/bsky/tests/data-plane/subscription/repo.test.ts +++ b/packages/bsky/tests/data-plane/subscription/repo.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { cborDecode, cborEncode } from '@atproto/common' import { DatabaseSchemaType } from '../../../src/data-plane/server/db/database-schema' import { SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env' diff --git a/packages/bsky/tests/data-plane/thread-mutes.test.ts b/packages/bsky/tests/data-plane/thread-mutes.test.ts index 480b2b5976d..8b03b0d973e 100644 --- a/packages/bsky/tests/data-plane/thread-mutes.test.ts +++ b/packages/bsky/tests/data-plane/thread-mutes.test.ts @@ -1,5 +1,5 @@ import { RecordRef, SeedClient, TestNetwork, usersSeed } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' describe('thread mutes', () => { let network: TestNetwork diff --git a/packages/bsky/tests/label-hydration.test.ts b/packages/bsky/tests/label-hydration.test.ts index 8ac7871695f..dedaf7577ae 100644 --- a/packages/bsky/tests/label-hydration.test.ts +++ b/packages/bsky/tests/label-hydration.test.ts @@ -40,7 +40,7 @@ describe('label hydration', () => { it('hydrates labels based on a supplied labeler header', async () => { AtpAgent.configure({ appLabelers: [alice] }) - pdsAgent.configureLabelersHeader([]) + pdsAgent.configureLabelers([]) const res = await pdsAgent.api.app.bsky.actor.getProfile( { actor: carol }, { @@ -55,12 +55,15 @@ describe('label hydration', () => { it('hydrates labels based on multiple a supplied labelers', async () => { AtpAgent.configure({ appLabelers: [bob] }) - pdsAgent.configureLabelersHeader([alice, labelerDid]) + pdsAgent.configureLabelers([alice]) const res = await pdsAgent.api.app.bsky.actor.getProfile( { actor: carol }, { - headers: sc.getHeaders(bob), + headers: { + 'atproto-accept-labelers': labelerDid, + ...sc.getHeaders(bob), + }, }, ) expect(res.data.labels?.length).toBe(3) @@ -95,7 +98,7 @@ describe('label hydration', () => { it('hydrates labels without duplication', async () => { AtpAgent.configure({ appLabelers: [alice] }) - pdsAgent.configureLabelersHeader([]) + pdsAgent.configureLabelers([]) const res = await pdsAgent.api.app.bsky.actor.getProfiles( { actors: [carol, carol] }, { headers: sc.getHeaders(bob) }, @@ -108,7 +111,7 @@ describe('label hydration', () => { it('does not hydrate labels from takendown labeler', async () => { AtpAgent.configure({ appLabelers: [alice, sc.dids.dan] }) - pdsAgent.configureLabelersHeader([]) + pdsAgent.configureLabelers([]) await network.bsky.ctx.dataplane.takedownActor({ did: alice }) const res = await pdsAgent.api.app.bsky.actor.getProfile( { actor: carol }, @@ -124,7 +127,7 @@ describe('label hydration', () => { it('hydrates labels onto list views.', async () => { AtpAgent.configure({ appLabelers: [labelerDid] }) - pdsAgent.configureLabelersHeader([]) + pdsAgent.configureLabelers([]) const list = await pdsAgent.api.app.bsky.graph.list.create( { repo: alice }, diff --git a/packages/bsky/tests/views/account-deactivation.test.ts b/packages/bsky/tests/views/account-deactivation.test.ts index 8c65cdfc9c8..31b07c01c5c 100644 --- a/packages/bsky/tests/views/account-deactivation.test.ts +++ b/packages/bsky/tests/views/account-deactivation.test.ts @@ -1,5 +1,5 @@ -import AtpAgent from '@atproto/api' -import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' +import { AtpAgent } from '@atproto/api' +import { basicSeed, SeedClient, TestNetwork } from '@atproto/dev-env' describe('bsky account deactivation', () => { let network: TestNetwork diff --git a/packages/bsky/tests/views/actor-likes.test.ts b/packages/bsky/tests/views/actor-likes.test.ts index 9d8bf1b87b5..b65aa8ccc1b 100644 --- a/packages/bsky/tests/views/actor-likes.test.ts +++ b/packages/bsky/tests/views/actor-likes.test.ts @@ -1,5 +1,5 @@ -import AtpAgent, { AtUri } from '@atproto/api' -import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' +import { AtpAgent, AtUri } from '@atproto/api' +import { basicSeed, SeedClient, TestNetwork } from '@atproto/dev-env' describe('bsky actor likes feed views', () => { let network: TestNetwork diff --git a/packages/bsky/tests/views/actor-search.test.ts b/packages/bsky/tests/views/actor-search.test.ts index 8a689af447c..7bbf7fa0af5 100644 --- a/packages/bsky/tests/views/actor-search.test.ts +++ b/packages/bsky/tests/views/actor-search.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { wait } from '@atproto/common' import { TestNetwork, SeedClient, usersBulkSeed } from '@atproto/dev-env' import { forSnapshot, paginateAll, stripViewer } from '../_util' diff --git a/packages/bsky/tests/views/author-feed.test.ts b/packages/bsky/tests/views/author-feed.test.ts index d20ddc267e6..16c4a6cd799 100644 --- a/packages/bsky/tests/views/author-feed.test.ts +++ b/packages/bsky/tests/views/author-feed.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, authorFeedSeed } from '@atproto/dev-env' import { forSnapshot, diff --git a/packages/bsky/tests/views/block-lists.test.ts b/packages/bsky/tests/views/block-lists.test.ts index 8d23b680fa9..4c1247da505 100644 --- a/packages/bsky/tests/views/block-lists.test.ts +++ b/packages/bsky/tests/views/block-lists.test.ts @@ -1,4 +1,4 @@ -import AtpAgent, { AtUri } from '@atproto/api' +import { AtpAgent, AtUri } from '@atproto/api' import { TestNetwork, SeedClient, RecordRef, basicSeed } from '@atproto/dev-env' import { forSnapshot } from '../_util' diff --git a/packages/bsky/tests/views/blocks.test.ts b/packages/bsky/tests/views/blocks.test.ts index 0e0b86dd05c..b21d194be59 100644 --- a/packages/bsky/tests/views/blocks.test.ts +++ b/packages/bsky/tests/views/blocks.test.ts @@ -1,6 +1,6 @@ import assert from 'assert' import { TestNetwork, RecordRef, SeedClient, basicSeed } from '@atproto/dev-env' -import AtpAgent, { AtUri } from '@atproto/api' +import { AtpAgent, AtUri } from '@atproto/api' import { assertIsThreadViewPost, forSnapshot } from '../_util' describe('pds views with blocking', () => { diff --git a/packages/bsky/tests/views/follows.test.ts b/packages/bsky/tests/views/follows.test.ts index 2331a0e990c..2300a1b1ed8 100644 --- a/packages/bsky/tests/views/follows.test.ts +++ b/packages/bsky/tests/views/follows.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, followsSeed } from '@atproto/dev-env' import { forSnapshot, paginateAll, stripViewer } from '../_util' diff --git a/packages/bsky/tests/views/known-followers.test.ts b/packages/bsky/tests/views/known-followers.test.ts index d948f4673e4..0959d36ecca 100644 --- a/packages/bsky/tests/views/known-followers.test.ts +++ b/packages/bsky/tests/views/known-followers.test.ts @@ -1,5 +1,5 @@ import { TestNetwork, SeedClient } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { knownFollowersSeed } from '../seed/known-followers' diff --git a/packages/bsky/tests/views/labeler-service.test.ts b/packages/bsky/tests/views/labeler-service.test.ts index 72f1e32acd9..96deab9d9e9 100644 --- a/packages/bsky/tests/views/labeler-service.test.ts +++ b/packages/bsky/tests/views/labeler-service.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, basicSeed, RecordRef } from '@atproto/dev-env' import { forSnapshot, stripViewerFromLabeler } from '../_util' import { ids } from '../../src/lexicon/lexicons' diff --git a/packages/bsky/tests/views/likes.test.ts b/packages/bsky/tests/views/likes.test.ts index dd5135357bb..3f32afc56e9 100644 --- a/packages/bsky/tests/views/likes.test.ts +++ b/packages/bsky/tests/views/likes.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, likesSeed } from '@atproto/dev-env' import { constantDate, forSnapshot, paginateAll, stripViewer } from '../_util' diff --git a/packages/bsky/tests/views/list-feed.test.ts b/packages/bsky/tests/views/list-feed.test.ts index 9cdc93cc919..593123f6acd 100644 --- a/packages/bsky/tests/views/list-feed.test.ts +++ b/packages/bsky/tests/views/list-feed.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, RecordRef, basicSeed } from '@atproto/dev-env' import { forSnapshot, diff --git a/packages/bsky/tests/views/lists.test.ts b/packages/bsky/tests/views/lists.test.ts index 2c209136fcf..f5a91d6d8e2 100644 --- a/packages/bsky/tests/views/lists.test.ts +++ b/packages/bsky/tests/views/lists.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' import { forSnapshot } from '../_util' diff --git a/packages/bsky/tests/views/mute-lists.test.ts b/packages/bsky/tests/views/mute-lists.test.ts index 3f90586f681..ee052e49f00 100644 --- a/packages/bsky/tests/views/mute-lists.test.ts +++ b/packages/bsky/tests/views/mute-lists.test.ts @@ -1,4 +1,4 @@ -import AtpAgent, { AtUri } from '@atproto/api' +import { AtpAgent, AtUri } from '@atproto/api' import { TestNetwork, SeedClient, RecordRef, basicSeed } from '@atproto/dev-env' import { forSnapshot } from '../_util' diff --git a/packages/bsky/tests/views/mutes.test.ts b/packages/bsky/tests/views/mutes.test.ts index b9d276b975e..49219dc1a46 100644 --- a/packages/bsky/tests/views/mutes.test.ts +++ b/packages/bsky/tests/views/mutes.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, diff --git a/packages/bsky/tests/views/notifications.test.ts b/packages/bsky/tests/views/notifications.test.ts index d2982f5516f..511941eb14a 100644 --- a/packages/bsky/tests/views/notifications.test.ts +++ b/packages/bsky/tests/views/notifications.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' import { forSnapshot, paginateAll } from '../_util' import { Notification } from '../../src/lexicon/types/app/bsky/notification/listNotifications' diff --git a/packages/bsky/tests/views/posts.test.ts b/packages/bsky/tests/views/posts.test.ts index 24b84469864..8f0fb7c4103 100644 --- a/packages/bsky/tests/views/posts.test.ts +++ b/packages/bsky/tests/views/posts.test.ts @@ -1,4 +1,4 @@ -import AtpAgent, { AppBskyFeedPost } from '@atproto/api' +import { AppBskyFeedPost, AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' import { forSnapshot, stripViewerFromPost } from '../_util' diff --git a/packages/bsky/tests/views/profile.test.ts b/packages/bsky/tests/views/profile.test.ts index 8c83c3e49aa..b65cda53d75 100644 --- a/packages/bsky/tests/views/profile.test.ts +++ b/packages/bsky/tests/views/profile.test.ts @@ -1,5 +1,5 @@ import fs from 'fs/promises' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' import { forSnapshot, stripViewer } from '../_util' import { ids } from '../../src/lexicon/lexicons' diff --git a/packages/bsky/tests/views/reposts.test.ts b/packages/bsky/tests/views/reposts.test.ts index e00650fc6c4..0ae2eea8773 100644 --- a/packages/bsky/tests/views/reposts.test.ts +++ b/packages/bsky/tests/views/reposts.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, repostsSeed } from '@atproto/dev-env' import { forSnapshot, paginateAll, stripViewer } from '../_util' diff --git a/packages/bsky/tests/views/starter-packs.test.ts b/packages/bsky/tests/views/starter-packs.test.ts index 1f410689980..dde39f70936 100644 --- a/packages/bsky/tests/views/starter-packs.test.ts +++ b/packages/bsky/tests/views/starter-packs.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, basicSeed, RecordRef } from '@atproto/dev-env' import { isRecord as isProfile } from '../../src/lexicon/types/app/bsky/actor/profile' import { forSnapshot } from '../_util' diff --git a/packages/bsky/tests/views/suggested-follows.test.ts b/packages/bsky/tests/views/suggested-follows.test.ts index 5d39f3f90ef..27e6c09864e 100644 --- a/packages/bsky/tests/views/suggested-follows.test.ts +++ b/packages/bsky/tests/views/suggested-follows.test.ts @@ -1,4 +1,4 @@ -import AtpAgent, { AtUri } from '@atproto/api' +import { AtpAgent, AtUri } from '@atproto/api' import { TestNetwork, SeedClient, likesSeed } from '@atproto/dev-env' describe('suggested follows', () => { diff --git a/packages/bsky/tests/views/suggestions.test.ts b/packages/bsky/tests/views/suggestions.test.ts index 49bc5858a34..050c2e01f67 100644 --- a/packages/bsky/tests/views/suggestions.test.ts +++ b/packages/bsky/tests/views/suggestions.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' import { stripViewer } from '../_util' diff --git a/packages/bsky/tests/views/takedown-labels.test.ts b/packages/bsky/tests/views/takedown-labels.test.ts index d58505fc453..270260dd62e 100644 --- a/packages/bsky/tests/views/takedown-labels.test.ts +++ b/packages/bsky/tests/views/takedown-labels.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, basicSeed, RecordRef } from '@atproto/dev-env' import { ids } from '../../src/lexicon/lexicons' diff --git a/packages/bsky/tests/views/thread.test.ts b/packages/bsky/tests/views/thread.test.ts index da29380de09..f68898ea5bc 100644 --- a/packages/bsky/tests/views/thread.test.ts +++ b/packages/bsky/tests/views/thread.test.ts @@ -1,4 +1,4 @@ -import AtpAgent, { AppBskyFeedGetPostThread } from '@atproto/api' +import { AppBskyFeedGetPostThread, AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' import { assertIsThreadViewPost, diff --git a/packages/bsky/tests/views/threadgating.test.ts b/packages/bsky/tests/views/threadgating.test.ts index 90607b28a25..4d0027c5e35 100644 --- a/packages/bsky/tests/views/threadgating.test.ts +++ b/packages/bsky/tests/views/threadgating.test.ts @@ -1,5 +1,5 @@ import assert from 'assert' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' import { isNotFoundPost, diff --git a/packages/bsky/tests/views/timeline.test.ts b/packages/bsky/tests/views/timeline.test.ts index 03e865f7ed2..0be6162f029 100644 --- a/packages/bsky/tests/views/timeline.test.ts +++ b/packages/bsky/tests/views/timeline.test.ts @@ -1,5 +1,5 @@ import assert from 'assert' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, diff --git a/packages/dev-env/CHANGELOG.md b/packages/dev-env/CHANGELOG.md index c764d05aaa9..d249381447f 100644 --- a/packages/dev-env/CHANGELOG.md +++ b/packages/dev-env/CHANGELOG.md @@ -1,5 +1,57 @@ # @atproto/dev-env +## 0.3.40 + +### Patch Changes + +- Updated dependencies [[`22af354a5`](https://github.com/bluesky-social/atproto/commit/22af354a5db595d7cbc0e65f02601de3565337e1)]: + - @atproto/api@0.13.1 + - @atproto/bsky@0.0.75 + - @atproto/ozone@0.1.37 + - @atproto/pds@0.4.49 + +## 0.3.39 + +### Patch Changes + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`2bdf75d7a`](https://github.com/bluesky-social/atproto/commit/2bdf75d7a63924c10e7a311f16cb447d595b933e), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd)]: + - @atproto/lexicon@0.4.1 + - @atproto/api@0.13.0 + - @atproto/bsky@0.0.74 + - @atproto/ozone@0.1.36 + - @atproto/pds@0.4.48 + - @atproto/xrpc-server@0.6.1 + +## 0.3.38 + +### Patch Changes + +- Updated dependencies [[`269cbc87c`](https://github.com/bluesky-social/atproto/commit/269cbc87c5ec9d65d1d479269ac5e91dffbb186c)]: + - @atproto/pds@0.4.47 + - @atproto/bsky@0.0.73 + - @atproto/ozone@0.1.35 + +## 0.3.37 + +### Patch Changes + +- Updated dependencies [[`dc471da26`](https://github.com/bluesky-social/atproto/commit/dc471da267955d0962a8affaf983df60d962d97c), [`dc471da26`](https://github.com/bluesky-social/atproto/commit/dc471da267955d0962a8affaf983df60d962d97c)]: + - @atproto/api@0.12.29 + - @atproto/xrpc-server@0.6.0 + - @atproto/pds@0.4.46 + - @atproto/bsky@0.0.73 + - @atproto/ozone@0.1.35 + +## 0.3.36 + +### Patch Changes + +- Updated dependencies [[`951a3df15`](https://github.com/bluesky-social/atproto/commit/951a3df15aa9c1f5b0a2b66cfb0e2eaf6198fe41)]: + - @atproto/ozone@0.1.34 + - @atproto/bsky@0.0.72 + - @atproto/api@0.12.28 + - @atproto/pds@0.4.45 + ## 0.3.35 ### Patch Changes diff --git a/packages/dev-env/package.json b/packages/dev-env/package.json index cf6e7a54246..7541a36511c 100644 --- a/packages/dev-env/package.json +++ b/packages/dev-env/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/dev-env", - "version": "0.3.35", + "version": "0.3.40", "license": "MIT", "description": "Local development environment helper for atproto development", "keywords": [ diff --git a/packages/dev-env/src/bsky.ts b/packages/dev-env/src/bsky.ts index c4a5c398443..68165fae663 100644 --- a/packages/dev-env/src/bsky.ts +++ b/packages/dev-env/src/bsky.ts @@ -105,9 +105,9 @@ export class TestBsky { return this.server.ctx } - getClient() { + getClient(): AtpAgent { const agent = new AtpAgent({ service: this.url }) - agent.configureLabelersHeader([EXAMPLE_LABELER]) + agent.configureLabelers([EXAMPLE_LABELER]) return agent } diff --git a/packages/dev-env/src/mock/index.ts b/packages/dev-env/src/mock/index.ts index 10a7e433622..ba128367dea 100644 --- a/packages/dev-env/src/mock/index.ts +++ b/packages/dev-env/src/mock/index.ts @@ -1,5 +1,5 @@ import { AtUri } from '@atproto/syntax' -import AtpAgent, { COM_ATPROTO_MODERATION } from '@atproto/api' +import { COM_ATPROTO_MODERATION, AtpAgent } from '@atproto/api' import { Database } from '@atproto/bsky' import { EXAMPLE_LABELER, RecordRef, TestNetwork } from '../index' import { postTexts, replyTexts } from './data' @@ -30,90 +30,67 @@ export async function generateMockSetup(env: TestNetwork) { throw new Error('Not found') } - const clients = { - loggedout: env.pds.getClient(), - alice: env.pds.getClient(), - bob: env.pds.getClient(), - carla: env.pds.getClient(), - } - interface User { - email: string - did: string - handle: string - password: string - agent: AtpAgent - } - const users: User[] = [ + const loggedOut = env.pds.getClient() + + const users = [ { email: 'alice@test.com', - did: '', handle: `alice.test`, password: 'hunter2', - agent: clients.alice, }, { email: 'bob@test.com', - did: '', handle: `bob.test`, password: 'hunter2', - agent: clients.bob, }, { email: 'carla@test.com', - did: '', handle: `carla.test`, password: 'hunter2', - agent: clients.carla, }, ] - const alice = users[0] - const bob = users[1] - const carla = users[2] - let _i = 1 - for (const user of users) { - const res = await clients.loggedout.api.com.atproto.server.createAccount({ - email: user.email, - handle: user.handle, - password: user.password, - }) - user.agent.api.setHeader('Authorization', `Bearer ${res.data.accessJwt}`) - user.did = res.data.did - await user.agent.api.app.bsky.actor.profile.create( - { repo: user.did }, - { - displayName: ucfirst(user.handle).slice(0, -5), - description: `Test user ${_i++}`, - }, - ) - } + const userAgents = await Promise.all( + users.map(async (user, i) => { + const client: AtpAgent = env.pds.getClient() + await client.createAccount(user) + client.assertAuthenticated() + await client.app.bsky.actor.profile.create( + { repo: client.did }, + { + displayName: ucfirst(user.handle).slice(0, -5), + description: `Test user ${i}`, + }, + ) + return client + }), + ) + + const [alice, bob, carla] = userAgents // Create moderator accounts - const triageRes = - await clients.loggedout.api.com.atproto.server.createAccount({ - email: 'triage@test.com', - handle: 'triage.test', - password: 'triage-pass', - }) + const triageRes = await loggedOut.com.atproto.server.createAccount({ + email: 'triage@test.com', + handle: 'triage.test', + password: 'triage-pass', + }) await env.ozone.addTriageDid(triageRes.data.did) - const modRes = await clients.loggedout.api.com.atproto.server.createAccount({ + const modRes = await loggedOut.com.atproto.server.createAccount({ email: 'mod@test.com', handle: 'mod.test', password: 'mod-pass', }) await env.ozone.addModeratorDid(modRes.data.did) - const adminRes = await clients.loggedout.api.com.atproto.server.createAccount( - { - email: 'admin-mod@test.com', - handle: 'admin-mod.test', - password: 'admin-mod-pass', - }, - ) + const adminRes = await loggedOut.com.atproto.server.createAccount({ + email: 'admin-mod@test.com', + handle: 'admin-mod.test', + password: 'admin-mod-pass', + }) await env.ozone.addAdminDid(adminRes.data.did) // Report one user - const reporter = picka(users) - await reporter.agent.api.com.atproto.moderation.createReport({ + const reporter = picka(userAgents) + await reporter.com.atproto.moderation.createReport({ reasonType: picka([ COM_ATPROTO_MODERATION.DefsReasonSpam, COM_ATPROTO_MODERATION.DefsReasonOther, @@ -121,16 +98,16 @@ export async function generateMockSetup(env: TestNetwork) { reason: picka(["Didn't look right to me", undefined, undefined]), subject: { $type: 'com.atproto.admin.defs#repoRef', - did: picka(users).did, + did: picka(userAgents).did, }, }) // everybody follows everybody - const follow = async (author: User, subject: User) => { - await author.agent.api.app.bsky.graph.follow.create( - { repo: author.did }, + const follow = async (author: AtpAgent, subject: AtpAgent) => { + await author.app.bsky.graph.follow.create( + { repo: author.accountDid }, { - subject: subject.did, + subject: subject.accountDid, createdAt: date.next().value, }, ) @@ -145,8 +122,8 @@ export async function generateMockSetup(env: TestNetwork) { // a set of posts and reposts const posts: { uri: string; cid: string }[] = [] for (let i = 0; i < postTexts.length; i++) { - const author = picka(users) - const post = await author.agent.api.app.bsky.feed.post.create( + const author = picka(userAgents) + const post = await author.app.bsky.feed.post.create( { repo: author.did }, { text: postTexts[i], @@ -155,8 +132,8 @@ export async function generateMockSetup(env: TestNetwork) { ) posts.push(post) if (rand(10) === 0) { - const reposter = picka(users) - await reposter.agent.api.app.bsky.feed.repost.create( + const reposter = picka(userAgents) + await reposter.app.bsky.feed.repost.create( { repo: reposter.did }, { subject: picka(posts), @@ -165,8 +142,8 @@ export async function generateMockSetup(env: TestNetwork) { ) } if (rand(6) === 0) { - const reporter = picka(users) - await reporter.agent.api.com.atproto.moderation.createReport({ + const reporter = picka(userAgents) + await reporter.com.atproto.moderation.createReport({ reasonType: picka([ COM_ATPROTO_MODERATION.DefsReasonSpam, COM_ATPROTO_MODERATION.DefsReasonOther, @@ -183,11 +160,11 @@ export async function generateMockSetup(env: TestNetwork) { // make some naughty posts & label them const file = Buffer.from(labeledImgB64, 'base64') - const uploadedImg = await bob.agent.api.com.atproto.repo.uploadBlob(file, { + const uploadedImg = await bob.com.atproto.repo.uploadBlob(file, { encoding: 'image/png', }) - const labeledPost = await bob.agent.api.app.bsky.feed.post.create( - { repo: bob.did }, + const labeledPost = await bob.app.bsky.feed.post.create( + { repo: bob.accountDid }, { text: 'naughty post', embed: { @@ -203,8 +180,8 @@ export async function generateMockSetup(env: TestNetwork) { }, ) - const filteredPost = await bob.agent.api.app.bsky.feed.post.create( - { repo: bob.did }, + const filteredPost = await bob.app.bsky.feed.post.create( + { repo: bob.accountDid }, { text: 'reallly bad post should be deleted', createdAt: date.next().value, @@ -226,13 +203,13 @@ export async function generateMockSetup(env: TestNetwork) { for (let i = 0; i < 100; i++) { const targetUri = picka(posts).uri const urip = new AtUri(targetUri) - const target = await alice.agent.api.app.bsky.feed.post.get({ + const target = await alice.app.bsky.feed.post.get({ repo: urip.host, rkey: urip.rkey, }) - const author = picka(users) + const author = picka(userAgents) posts.push( - await author.agent.api.app.bsky.feed.post.create( + await author.app.bsky.feed.post.create( { repo: author.did }, { text: picka(replyTexts), @@ -248,9 +225,9 @@ export async function generateMockSetup(env: TestNetwork) { // a set of likes for (const post of posts) { - for (const user of users) { + for (const user of userAgents) { if (rand(3) === 0) { - await user.agent.api.app.bsky.feed.like.create( + await user.app.bsky.feed.like.create( { repo: user.did }, { subject: post, @@ -262,7 +239,11 @@ export async function generateMockSetup(env: TestNetwork) { } // a couple feed generators that returns some posts - const fg1Uri = AtUri.make(alice.did, 'app.bsky.feed.generator', 'alice-favs') + const fg1Uri = AtUri.make( + alice.accountDid, + 'app.bsky.feed.generator', + 'alice-favs', + ) const fg1 = await env.createFeedGen({ [fg1Uri.toString()]: async () => { const feed = posts @@ -277,14 +258,11 @@ export async function generateMockSetup(env: TestNetwork) { }, }) const avatarImg = Buffer.from(blurHashB64, 'base64') - const avatarRes = await alice.agent.api.com.atproto.repo.uploadBlob( - avatarImg, - { - encoding: 'image/png', - }, - ) - const fgAliceRes = await alice.agent.api.app.bsky.feed.generator.create( - { repo: alice.did, rkey: fg1Uri.rkey }, + const avatarRes = await alice.com.atproto.repo.uploadBlob(avatarImg, { + encoding: 'image/png', + }) + const fgAliceRes = await alice.app.bsky.feed.generator.create( + { repo: alice.accountDid, rkey: fg1Uri.rkey }, { did: fg1.did, displayName: 'alices feed', @@ -294,8 +272,8 @@ export async function generateMockSetup(env: TestNetwork) { }, ) - await alice.agent.api.app.bsky.feed.post.create( - { repo: alice.did }, + await alice.app.bsky.feed.post.create( + { repo: alice.accountDid }, { text: 'check out my algorithm!', embed: { @@ -306,7 +284,7 @@ export async function generateMockSetup(env: TestNetwork) { }, ) for (const user of [alice, bob, carla]) { - await user.agent.api.app.bsky.feed.like.create( + await user.app.bsky.feed.like.create( { repo: user.did }, { subject: fgAliceRes, @@ -315,7 +293,11 @@ export async function generateMockSetup(env: TestNetwork) { ) } - const fg2Uri = AtUri.make(bob.did, 'app.bsky.feed.generator', 'bob-redux') + const fg2Uri = AtUri.make( + bob.accountDid, + 'app.bsky.feed.generator', + 'bob-redux', + ) const fg2 = await env.createFeedGen({ [fg2Uri.toString()]: async () => { const feed = posts @@ -329,8 +311,8 @@ export async function generateMockSetup(env: TestNetwork) { } }, }) - const fgBobRes = await bob.agent.api.app.bsky.feed.generator.create( - { repo: bob.did, rkey: fg2Uri.rkey }, + const fgBobRes = await bob.app.bsky.feed.generator.create( + { repo: bob.accountDid, rkey: fg2Uri.rkey }, { did: fg2.did, displayName: 'Bobby boy hot new algo', @@ -338,8 +320,8 @@ export async function generateMockSetup(env: TestNetwork) { }, ) - await alice.agent.api.app.bsky.feed.post.create( - { repo: alice.did }, + await alice.app.bsky.feed.post.create( + { repo: alice.accountDid }, { text: `bobs feed is neat too`, embed: { @@ -352,14 +334,13 @@ export async function generateMockSetup(env: TestNetwork) { // create a labeler account { - const res = await clients.loggedout.api.com.atproto.server.createAccount({ + const labeler = env.pds.getClient() + const res = await labeler.createAccount({ email: 'labeler@test.com', handle: 'labeler.test', password: 'hunter2', }) - const agent = env.pds.getClient() - agent.api.setHeader('Authorization', `Bearer ${res.data.accessJwt}`) - await agent.api.app.bsky.actor.profile.create( + await labeler.app.bsky.actor.profile.create( { repo: res.data.did }, { displayName: 'Test Labeler', @@ -367,7 +348,7 @@ export async function generateMockSetup(env: TestNetwork) { }, ) - await agent.api.app.bsky.labeler.service.create( + await labeler.app.bsky.labeler.service.create( { repo: res.data.did, rkey: 'self' }, { policies: { @@ -455,25 +436,25 @@ export async function generateMockSetup(env: TestNetwork) { }, ) await createLabel(env.bsky.db, { - uri: alice.did, + uri: alice.accountDid, cid: '', val: 'rude', src: res.data.did, }) await createLabel(env.bsky.db, { - uri: `at://${alice.did}/app.bsky.feed.generator/alice-favs`, + uri: `at://${alice.accountDid}/app.bsky.feed.generator/alice-favs`, cid: '', val: 'cool', src: res.data.did, }) await createLabel(env.bsky.db, { - uri: bob.did, + uri: bob.accountDid, cid: '', val: 'cool', src: res.data.did, }) await createLabel(env.bsky.db, { - uri: carla.did, + uri: carla.accountDid, cid: '', val: 'spam', src: res.data.did, @@ -482,8 +463,8 @@ export async function generateMockSetup(env: TestNetwork) { // Create lists and add people to the lists { - const flowerLovers = await alice.agent.api.app.bsky.graph.list.create( - { repo: alice.did }, + const flowerLovers = await alice.app.bsky.graph.list.create( + { repo: alice.accountDid }, { name: 'Flower Lovers', purpose: 'app.bsky.graph.defs#curatelist', @@ -491,8 +472,8 @@ export async function generateMockSetup(env: TestNetwork) { description: 'A list of posts about flowers', }, ) - const labelHaters = await bob.agent.api.app.bsky.graph.list.create( - { repo: bob.did }, + const labelHaters = await bob.app.bsky.graph.list.create( + { repo: bob.accountDid }, { name: 'Label Haters', purpose: 'app.bsky.graph.defs#modlist', @@ -500,18 +481,18 @@ export async function generateMockSetup(env: TestNetwork) { description: 'A list of people who hate labels', }, ) - await alice.agent.api.app.bsky.graph.listitem.create( - { repo: alice.did }, + await alice.app.bsky.graph.listitem.create( + { repo: alice.accountDid }, { - subject: bob.did, + subject: bob.accountDid, createdAt: new Date().toISOString(), list: new RecordRef(flowerLovers.uri, flowerLovers.cid).uriStr, }, ) - await bob.agent.api.app.bsky.graph.listitem.create( - { repo: bob.did }, + await bob.app.bsky.graph.listitem.create( + { repo: bob.accountDid }, { - subject: alice.did, + subject: alice.accountDid, createdAt: new Date().toISOString(), list: new RecordRef(labelHaters.uri, labelHaters.cid).uriStr, }, diff --git a/packages/dev-env/src/moderator-client.ts b/packages/dev-env/src/moderator-client.ts index 5687371c5de..81a024b4ae2 100644 --- a/packages/dev-env/src/moderator-client.ts +++ b/packages/dev-env/src/moderator-client.ts @@ -1,4 +1,5 @@ -import AtpAgent, { +import { + AtpAgent, ToolsOzoneModerationEmitEvent as EmitModerationEvent, ToolsOzoneModerationQueryStatuses as QueryModerationStatuses, ToolsOzoneModerationQueryEvents as QueryModerationEvents, @@ -17,7 +18,7 @@ export class ModeratorClient { } async getEvent(id: number, role?: ModLevel) { - const result = await this.agent.api.tools.ozone.moderation.getEvent( + const result = await this.agent.tools.ozone.moderation.getEvent( { id }, { headers: await this.ozone.modHeaders(role), @@ -27,7 +28,7 @@ export class ModeratorClient { } async queryStatuses(input: QueryStatusesParams, role?: ModLevel) { - const result = await this.agent.api.tools.ozone.moderation.queryStatuses( + const result = await this.agent.tools.ozone.moderation.queryStatuses( input, { headers: await this.ozone.modHeaders(role), @@ -37,12 +38,9 @@ export class ModeratorClient { } async queryEvents(input: QueryEventsParams, role?: ModLevel) { - const result = await this.agent.api.tools.ozone.moderation.queryEvents( - input, - { - headers: await this.ozone.modHeaders(role), - }, - ) + const result = await this.agent.tools.ozone.moderation.queryEvents(input, { + headers: await this.ozone.modHeaders(role), + }) return result.data } @@ -64,7 +62,7 @@ export class ModeratorClient { reason = 'X', createdBy = 'did:example:admin', } = opts - const result = await this.agent.api.tools.ozone.moderation.emitEvent( + const result = await this.agent.tools.ozone.moderation.emitEvent( { event, subject, subjectBlobCids, createdBy, reason }, { encoding: 'application/json', @@ -84,7 +82,7 @@ export class ModeratorClient { role?: ModLevel, ) { const { subject, reason = 'X', createdBy = 'did:example:admin' } = opts - const result = await this.agent.api.tools.ozone.moderation.emitEvent( + const result = await this.agent.tools.ozone.moderation.emitEvent( { subject, event: { diff --git a/packages/dev-env/src/network.ts b/packages/dev-env/src/network.ts index 82c5e173fa2..4477c5d270b 100644 --- a/packages/dev-env/src/network.ts +++ b/packages/dev-env/src/network.ts @@ -162,6 +162,7 @@ export class TestNetwork extends TestNetworkNoAppView { const jwt = await createServiceJwt({ iss: did, aud: aud ?? this.bsky.ctx.cfg.serverDid, + lxm: null, keypair, }) return { authorization: `Bearer ${jwt}` } diff --git a/packages/dev-env/src/ozone-service-profile.ts b/packages/dev-env/src/ozone-service-profile.ts index 325e95ee1ce..9b8e10d7ad1 100644 --- a/packages/dev-env/src/ozone-service-profile.ts +++ b/packages/dev-env/src/ozone-service-profile.ts @@ -18,16 +18,9 @@ export class OzoneServiceProfile { } async createDidAndKey() { - const modUser = - await this.thirdPartyPdsClient.api.com.atproto.server.createAccount( - this.modUserDetails, - ) - await this.thirdPartyPdsClient.login({ - identifier: this.modUserDetails.handle, - password: this.modUserDetails.password, - }) + await this.thirdPartyPdsClient.createAccount(this.modUserDetails) - this.did = modUser.data.did + this.did = this.thirdPartyPdsClient.accountDid this.key = await Secp256k1Keypair.create({ exportable: true }) return { did: this.did, key: this.key } } @@ -41,33 +34,33 @@ export class OzoneServiceProfile { throw new Error('No DID/key found!') } const pdsClient = pds.getClient() - const describeRes = await pdsClient.api.com.atproto.server.describeServer() + const describeRes = await pdsClient.com.atproto.server.describeServer() const newServerDid = describeRes.data.did const serviceJwtRes = await this.thirdPartyPdsClient.com.atproto.server.getServiceAuth({ aud: newServerDid, + lxm: 'com.atproto.server.createAccount', }) const serviceJwt = serviceJwtRes.data.token - const accountResponse = - await pdsClient.api.com.atproto.server.createAccount( - { - ...this.modUserDetails, - ...userDetails, - did: this.did, - }, - { - headers: { authorization: `Bearer ${serviceJwt}` }, - encoding: 'application/json', - }, - ) - - pdsClient.api.setHeader( - 'Authorization', - `Bearer ${accountResponse.data.accessJwt}`, + await pdsClient.createAccount( + { + ...this.modUserDetails, + ...userDetails, + did: this.did, + }, + { + headers: { authorization: `Bearer ${serviceJwt}` }, + encoding: 'application/json', + }, ) + // For some reason, the tests fail if the client uses the PDS URL to make + // its requests. This is a workaround to make the tests pass by simulating + // old behavior (that was not relying on the session management). + pdsClient.sessionManager.pdsUrl = undefined + const getDidCredentials = await pdsClient.com.atproto.identity.getRecommendedDidCredentials() @@ -103,9 +96,9 @@ export class OzoneServiceProfile { operation: plcOp.data.operation, }) - await pdsClient.api.com.atproto.server.activateAccount() + await pdsClient.com.atproto.server.activateAccount() - await pdsClient.api.app.bsky.actor.profile.create( + await pdsClient.app.bsky.actor.profile.create( { repo: this.did }, { displayName: 'Dev-env Moderation', @@ -113,7 +106,7 @@ export class OzoneServiceProfile { }, ) - await pdsClient.api.app.bsky.labeler.service.create( + await pdsClient.app.bsky.labeler.service.create( { repo: this.did, rkey: 'self' }, { policies: { diff --git a/packages/dev-env/src/ozone.ts b/packages/dev-env/src/ozone.ts index d8cd0c4bd59..56a56775be2 100644 --- a/packages/dev-env/src/ozone.ts +++ b/packages/dev-env/src/ozone.ts @@ -6,7 +6,7 @@ import { AtpAgent } from '@atproto/api' import { createServiceJwt } from '@atproto/xrpc-server' import { Keypair, Secp256k1Keypair } from '@atproto/crypto' import { DidAndKey, OzoneConfig } from './types' -import { ADMIN_PASSWORD } from './const' +import { ADMIN_PASSWORD, EXAMPLE_LABELER } from './const' import { createDidAndKey } from './util' import { ModeratorClient } from './moderator-client' @@ -103,8 +103,10 @@ export class TestOzone { return this.server.ctx } - getClient() { - return new AtpAgent({ service: this.url }) + getClient(): AtpAgent { + const agent = new AtpAgent({ service: this.url }) + agent.configureLabelers([EXAMPLE_LABELER]) + return agent } getModClient() { @@ -151,6 +153,7 @@ export class TestOzone { const jwt = await createServiceJwt({ iss: account.did, aud: this.ctx.cfg.service.did, + lxm: null, keypair: account.key, }) return { authorization: `Bearer ${jwt}` } diff --git a/packages/dev-env/src/pds.ts b/packages/dev-env/src/pds.ts index bbc43e50bee..87750227c2b 100644 --- a/packages/dev-env/src/pds.ts +++ b/packages/dev-env/src/pds.ts @@ -73,7 +73,7 @@ export class TestPds { getClient(): AtpAgent { const agent = new AtpAgent({ service: this.url }) - agent.configureLabelersHeader([EXAMPLE_LABELER]) + agent.configureLabelers([EXAMPLE_LABELER]) return agent } diff --git a/packages/dev-env/src/seed/client.ts b/packages/dev-env/src/seed/client.ts index 47dcae3d619..c493735c0e9 100644 --- a/packages/dev-env/src/seed/client.ts +++ b/packages/dev-env/src/seed/client.ts @@ -1,12 +1,13 @@ import fs from 'fs/promises' import { CID } from 'multiformats/cid' -import AtpAgent, { +import { ComAtprotoModerationCreateReport, AppBskyFeedPost, AppBskyRichtextFacet, AppBskyFeedLike, AppBskyGraphFollow, AppBskyGraphList, + AtpAgent, } from '@atproto/api' import { AtUri } from '@atproto/syntax' import { BlobRef } from '@atproto/lexicon' @@ -133,7 +134,7 @@ export class SeedClient< }, ) { const { data: account } = - await this.agent.api.com.atproto.server.createAccount(params) + await this.agent.com.atproto.server.createAccount(params) this.dids[shortName] = account.did this.accounts[account.did] = { ...account, @@ -144,7 +145,7 @@ export class SeedClient< } async updateHandle(by: string, handle: string) { - await this.agent.api.com.atproto.identity.updateHandle( + await this.agent.com.atproto.identity.updateHandle( { handle }, { encoding: 'application/json', headers: this.getHeaders(by) }, ) @@ -156,14 +157,20 @@ export class SeedClient< description: string, selfLabels?: string[], joinedViaStarterPack?: RecordRef, - ) { + ): Promise<{ + displayName: string + description: string + avatar: { cid: string; mimeType: string } + ref: RecordRef + joinedViaStarterPack?: RecordRef + }> { AVATAR_IMG ??= await fs.readFile( '../dev-env/src/seed/img/key-portrait-small.jpg', ) let avatarBlob { - const res = await this.agent.api.com.atproto.repo.uploadBlob(AVATAR_IMG, { + const res = await this.agent.com.atproto.repo.uploadBlob(AVATAR_IMG, { encoding: 'image/jpeg', headers: this.getHeaders(by), } as any) @@ -171,7 +178,7 @@ export class SeedClient< } { - const res = await this.agent.api.app.bsky.actor.profile.create( + const res = await this.agent.app.bsky.actor.profile.create( { repo: by }, { displayName, @@ -200,7 +207,7 @@ export class SeedClient< } async updateProfile(by: string, record: Record) { - const res = await this.agent.api.com.atproto.repo.putRecord( + const res = await this.agent.com.atproto.repo.putRecord( { repo: by, collection: 'app.bsky.actor.profile', @@ -222,7 +229,7 @@ export class SeedClient< to: string, overrides?: Partial, ) { - const res = await this.agent.api.app.bsky.graph.follow.create( + const res = await this.agent.app.bsky.graph.follow.create( { repo: from }, { subject: to, @@ -241,7 +248,7 @@ export class SeedClient< if (!follow) { throw new Error('follow does not exist') } - await this.agent.api.app.bsky.graph.follow.delete( + await this.agent.app.bsky.graph.follow.delete( { repo: from, rkey: follow.uri.rkey }, this.getHeaders(from), ) @@ -253,7 +260,7 @@ export class SeedClient< to: string, overrides?: Partial, ) { - const res = await this.agent.api.app.bsky.graph.block.create( + const res = await this.agent.app.bsky.graph.block.create( { repo: from }, { subject: to, @@ -272,7 +279,7 @@ export class SeedClient< if (!block) { throw new Error('block does not exist') } - await this.agent.api.app.bsky.graph.block.delete( + await this.agent.app.bsky.graph.block.delete( { repo: from, rkey: block.uri.rkey }, this.getHeaders(from), ) @@ -304,7 +311,7 @@ export class SeedClient< : recordEmbed ? { $type: 'app.bsky.embed.record', ...recordEmbed } : imageEmbed - const res = await this.agent.api.app.bsky.feed.post.create( + const res = await this.agent.app.bsky.feed.post.create( { repo: by }, { text: text, @@ -327,7 +334,7 @@ export class SeedClient< } async deletePost(by: string, uri: AtUri) { - await this.agent.api.app.bsky.feed.post.delete( + await this.agent.app.bsky.feed.post.delete( { repo: by, rkey: uri.rkey, @@ -342,7 +349,7 @@ export class SeedClient< encoding: string, ): Promise { const file = await fs.readFile(filePath) - const res = await this.agent.api.com.atproto.repo.uploadBlob(file, { + const res = await this.agent.com.atproto.repo.uploadBlob(file, { headers: this.getHeaders(by), encoding, } as any) @@ -354,7 +361,7 @@ export class SeedClient< subject: RecordRef, overrides?: Partial, ) { - const res = await this.agent.api.app.bsky.feed.like.create( + const res = await this.agent.app.bsky.feed.like.create( { repo: by }, { subject: subject.raw, @@ -382,7 +389,7 @@ export class SeedClient< images, } : undefined - const res = await this.agent.api.app.bsky.feed.post.create( + const res = await this.agent.app.bsky.feed.post.create( { repo: by }, { text: text, @@ -407,7 +414,7 @@ export class SeedClient< } async repost(by: string, subject: RecordRef) { - const res = await this.agent.api.app.bsky.feed.repost.create( + const res = await this.agent.app.bsky.feed.repost.create( { repo: by }, { subject: subject.raw, createdAt: new Date().toISOString() }, this.getHeaders(by), @@ -424,7 +431,7 @@ export class SeedClient< purpose: 'mod' | 'curate' | 'reference', overrides?: Partial, ) { - const res = await this.agent.api.app.bsky.graph.list.create( + const res = await this.agent.app.bsky.graph.list.create( { repo: by }, { name, @@ -449,7 +456,7 @@ export class SeedClient< } async createFeedGen(by: string, feedDid: string, name: string) { - const res = await this.agent.api.app.bsky.feed.generator.create( + const res = await this.agent.app.bsky.feed.generator.create( { repo: by }, { did: feedDid, @@ -477,7 +484,7 @@ export class SeedClient< for (const did of actors) { await this.addToList(by, did, list) } - const res = await this.agent.api.app.bsky.graph.starterpack.create( + const res = await this.agent.app.bsky.graph.starterpack.create( { repo: by }, { name, @@ -499,7 +506,7 @@ export class SeedClient< } async addToList(by: string, subject: string, list: RecordRef) { - const res = await this.agent.api.app.bsky.graph.listitem.create( + const res = await this.agent.app.bsky.graph.listitem.create( { repo: by }, { subject, list: list.uriStr, createdAt: new Date().toISOString() }, this.getHeaders(by), @@ -517,7 +524,7 @@ export class SeedClient< if (!foundList) return const foundItem = foundList.items[subject] if (!foundItem) return - await this.agent.api.app.bsky.graph.listitem.delete( + await this.agent.app.bsky.graph.listitem.delete( { repo: by, rkey: foundItem.uri.rkey }, this.getHeaders(by), ) @@ -531,7 +538,7 @@ export class SeedClient< reportedBy: string }) { const { reasonType, subject, reason, reportedBy } = opts - const result = await this.agent.api.com.atproto.moderation.createReport( + const result = await this.agent.com.atproto.moderation.createReport( { reasonType, subject, reason }, { encoding: 'application/json', diff --git a/packages/did/CHANGELOG.md b/packages/did/CHANGELOG.md index 9314176dabd..7acdf5bc6d3 100644 --- a/packages/did/CHANGELOG.md +++ b/packages/did/CHANGELOG.md @@ -1,5 +1,11 @@ # @atproto/did +## 0.1.1 + +### Patch Changes + +- [#2483](https://github.com/bluesky-social/atproto/pull/2483) [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Expose atproto specific types and utilities + ## 0.1.0 ### Minor Changes diff --git a/packages/did/package.json b/packages/did/package.json index c9f654944b0..94a8449ca33 100644 --- a/packages/did/package.json +++ b/packages/did/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/did", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "description": "DID resolution and verification library", "keywords": [ diff --git a/packages/did/src/atproto.ts b/packages/did/src/atproto.ts new file mode 100644 index 00000000000..cdd69291d4a --- /dev/null +++ b/packages/did/src/atproto.ts @@ -0,0 +1,75 @@ +import { InvalidDidError } from './did-error.js' +import { Did } from './did.js' +import { + checkDidPlc, + checkDidWeb, + DID_PLC_PREFIX, + DID_WEB_PREFIX, + isDidPlc, + isDidWeb, +} from './methods.js' + +// This file contains atproto-specific DID validation utilities. + +export type AtprotoIdentityDidMethods = 'plc' | 'web' +export type AtprotoDid = Did + +export function isAtprotoDid(input: unknown): input is AtprotoDid { + // Optimized equivalent of: + // return isDidPlc(input) || isAtprotoDidWeb(input) + + if (typeof input !== 'string') { + return false + } else if (input.startsWith(DID_PLC_PREFIX)) { + return isDidPlc(input) + } else if (input.startsWith(DID_WEB_PREFIX)) { + return isAtprotoDidWeb(input) + } else { + return false + } +} + +export function asAtprotoDid(input: unknown): AtprotoDid { + checkAtprotoDid(input) + return input +} + +export function checkAtprotoDid(input: unknown): asserts input is AtprotoDid { + if (typeof input !== 'string') { + throw new InvalidDidError(typeof input, `DID must be a string`) + } else if (input.startsWith(DID_PLC_PREFIX)) { + checkDidPlc(input) + } else if (input.startsWith(DID_WEB_PREFIX)) { + checkDidWeb(input) + } else { + throw new InvalidDidError( + input, + `Atproto only allows "plc" and "web" DID methods`, + ) + } +} + +/** + * @see {@link https://atproto.com/specs/did#blessed-did-methods} + */ +export function isAtprotoDidWeb(input: unknown): input is Did<'web'> { + // Optimization: make cheap checks first + if (typeof input !== 'string') { + return false + } + + // Path are not allowed + if (input.includes(':', DID_WEB_PREFIX.length)) { + return false + } + + // Port numbers are not allowed, except for localhost + if ( + input.includes('%3A', DID_WEB_PREFIX.length) && + !input.startsWith('did:web:localhost%3A') + ) { + return false + } + + return isDidWeb(input) +} diff --git a/packages/did/src/did.ts b/packages/did/src/did.ts index e12874e7995..55a4f1696d3 100644 --- a/packages/did/src/did.ts +++ b/packages/did/src/did.ts @@ -238,10 +238,17 @@ export function isDid(input: unknown): input is Did { if (err instanceof DidError) { return false } + + // Unexpected TypeError (should never happen) throw err } } +export function asDid(input: unknown): Did { + checkDid(input) + return input +} + export const didSchema = z .string() .superRefine((value: string, ctx: z.RefinementCtx): value is Did => { diff --git a/packages/did/src/index.ts b/packages/did/src/index.ts index 26c57de9942..b155a92c461 100644 --- a/packages/did/src/index.ts +++ b/packages/did/src/index.ts @@ -1,3 +1,4 @@ +export * from './atproto.js' export * from './did-document.js' export * from './did-error.js' export * from './did.js' diff --git a/packages/did/src/methods/plc.ts b/packages/did/src/methods/plc.ts index f1e7138b23d..bb851befecc 100644 --- a/packages/did/src/methods/plc.ts +++ b/packages/did/src/methods/plc.ts @@ -8,7 +8,9 @@ const DID_PLC_LENGTH = 32 export { DID_PLC_PREFIX } export function isDidPlc(input: unknown): input is Did<'plc'> { + // Optimization: make cheap checks first if (typeof input !== 'string') return false + try { checkDidPlc(input) return true @@ -17,7 +19,16 @@ export function isDidPlc(input: unknown): input is Did<'plc'> { } } -export function checkDidPlc(input: string): asserts input is Did<'plc'> { +export function asDidPlc(input: unknown): Did<'plc'> { + checkDidPlc(input) + return input +} + +export function checkDidPlc(input: unknown): asserts input is Did<'plc'> { + if (typeof input !== 'string') { + throw new InvalidDidError(typeof input, `DID must be a string`) + } + if (input.length !== DID_PLC_LENGTH) { throw new InvalidDidError( input, diff --git a/packages/did/src/methods/web.ts b/packages/did/src/methods/web.ts index 05a187c53c0..4dcaf50c307 100644 --- a/packages/did/src/methods/web.ts +++ b/packages/did/src/methods/web.ts @@ -1,50 +1,34 @@ import { InvalidDidError } from '../did-error.js' import { Did, checkDidMsid } from '../did.js' -export const DID_WEB_PREFIX = `did:web:` +export const DID_WEB_PREFIX = `did:web:` satisfies Did<'web'> /** * This function checks if the input is a valid Web DID, as per DID spec. - * ATPROTO adds additional constraints to allowed DID values for the `did:web` - * method. Use {@link isAtprotoDidWeb} if that's what you need. */ export function isDidWeb(input: unknown): input is Did<'web'> { + // Optimization: make cheap checks first if (typeof input !== 'string') return false + try { - didWebToUrl(input) + checkDidWeb(input) return true } catch { return false } } -/** - * @see {@link https://atproto.com/specs/did#blessed-did-methods} - */ -export function isAtprotoDidWeb(input: unknown): input is Did<'web'> { - // Optimization: make cheap checks first - if (typeof input !== 'string') { - return false - } - - // Path are not allowed - if (input.includes(':', DID_WEB_PREFIX.length)) { - return false - } +export function asDidWeb(input: unknown): Did<'web'> { + checkDidWeb(input) + return input +} - // Port numbers are not allowed, except for localhost - if ( - input.includes('%3A', DID_WEB_PREFIX.length) && - !input.startsWith('did:web:localhost%3A') - ) { - return false +export function checkDidWeb(input: unknown): asserts input is Did<'web'> { + if (typeof input !== 'string') { + throw new InvalidDidError(typeof input, `DID must be a string`) } - return isDidWeb(input) -} - -export function checkDidWeb(input: string): asserts input is Did<'web'> { - didWebToUrl(input) + void didWebToUrl(input) } export function didWebToUrl(did: string): URL { diff --git a/packages/internal/did-resolver/CHANGELOG.md b/packages/internal/did-resolver/CHANGELOG.md index 53d7c85049f..54a415d2341 100644 --- a/packages/internal/did-resolver/CHANGELOG.md +++ b/packages/internal/did-resolver/CHANGELOG.md @@ -1,5 +1,12 @@ # @atproto-labs/did-resolver +## 0.1.2 + +### Patch Changes + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd)]: + - @atproto/did@0.1.1 + ## 0.1.1 ### Patch Changes diff --git a/packages/internal/did-resolver/package.json b/packages/internal/did-resolver/package.json index 4a5fcedccba..bb74cee42ae 100644 --- a/packages/internal/did-resolver/package.json +++ b/packages/internal/did-resolver/package.json @@ -1,6 +1,6 @@ { "name": "@atproto-labs/did-resolver", - "version": "0.1.1", + "version": "0.1.2", "license": "MIT", "description": "DID resolution and verification library", "keywords": [ diff --git a/packages/internal/handle-resolver-node/CHANGELOG.md b/packages/internal/handle-resolver-node/CHANGELOG.md index 85d5be0027e..6e8933bc61c 100644 --- a/packages/internal/handle-resolver-node/CHANGELOG.md +++ b/packages/internal/handle-resolver-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @atproto-labs/handle-resolver-node +## 0.1.2 + +### Patch Changes + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd)]: + - @atproto-labs/handle-resolver@0.1.2 + - @atproto/did@0.1.1 + ## 0.1.1 ### Patch Changes diff --git a/packages/internal/handle-resolver-node/package.json b/packages/internal/handle-resolver-node/package.json index b23113bb96b..8488319d66a 100644 --- a/packages/internal/handle-resolver-node/package.json +++ b/packages/internal/handle-resolver-node/package.json @@ -1,6 +1,6 @@ { "name": "@atproto-labs/handle-resolver-node", - "version": "0.1.1", + "version": "0.1.2", "license": "MIT", "description": "Node specific ATProto handle to DID resolver", "keywords": [ diff --git a/packages/internal/handle-resolver/CHANGELOG.md b/packages/internal/handle-resolver/CHANGELOG.md index 0d807ab8a2e..723ffd691ce 100644 --- a/packages/internal/handle-resolver/CHANGELOG.md +++ b/packages/internal/handle-resolver/CHANGELOG.md @@ -1,5 +1,14 @@ # @atproto-labs/handle-resolver +## 0.1.2 + +### Patch Changes + +- [#2483](https://github.com/bluesky-social/atproto/pull/2483) [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Updated to use "AtprotoDid" utils from @atproto/did + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd)]: + - @atproto/did@0.1.1 + ## 0.1.1 ### Patch Changes diff --git a/packages/internal/handle-resolver/package.json b/packages/internal/handle-resolver/package.json index 1bc5e78f82e..d31e93b6fe7 100644 --- a/packages/internal/handle-resolver/package.json +++ b/packages/internal/handle-resolver/package.json @@ -1,6 +1,6 @@ { "name": "@atproto-labs/handle-resolver", - "version": "0.1.1", + "version": "0.1.2", "license": "MIT", "description": "Isomorphic ATProto handle to DID resolver", "keywords": [ diff --git a/packages/internal/handle-resolver/src/types.ts b/packages/internal/handle-resolver/src/types.ts index b5d7d357965..0800161c626 100644 --- a/packages/internal/handle-resolver/src/types.ts +++ b/packages/internal/handle-resolver/src/types.ts @@ -1,4 +1,5 @@ -import { Did, isAtprotoDidWeb, isDidPlc } from '@atproto/did' +import { AtprotoDid, isAtprotoDid } from '@atproto/did' +export type { AtprotoDid, AtprotoIdentityDidMethods } from '@atproto/did' export type ResolveHandleOptions = { signal?: AbortSignal @@ -8,17 +9,13 @@ export type ResolveHandleOptions = { /** * @see {@link https://atproto.com/specs/did#blessed-did-methods} */ -export type ResolvedHandle = null | Did<'plc' | 'web'> - -export { type Did } +export type ResolvedHandle = null | AtprotoDid /** * @see {@link https://atproto.com/specs/did#blessed-did-methods} */ -export function isResolvedHandle( - value: T, -): value is T & ResolvedHandle { - return value === null || isDidPlc(value) || isAtprotoDidWeb(value) +export function isResolvedHandle(value: T): value is T & ResolvedHandle { + return value === null || isAtprotoDid(value) } export interface HandleResolver { diff --git a/packages/internal/identity-resolver/CHANGELOG.md b/packages/internal/identity-resolver/CHANGELOG.md index 0b7fd7c433b..16526b20802 100644 --- a/packages/internal/identity-resolver/CHANGELOG.md +++ b/packages/internal/identity-resolver/CHANGELOG.md @@ -1,5 +1,15 @@ # @atproto-labs/identity-resolver +## 0.1.2 + +### Patch Changes + +- [#2483](https://github.com/bluesky-social/atproto/pull/2483) [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Expose getDocumentFromDid and getDocumentFromHandle as public methods on IdentityResolver + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd)]: + - @atproto-labs/handle-resolver@0.1.2 + - @atproto-labs/did-resolver@0.1.2 + ## 0.1.1 ### Patch Changes diff --git a/packages/internal/identity-resolver/package.json b/packages/internal/identity-resolver/package.json index aa51468c07d..4cfe7684b20 100644 --- a/packages/internal/identity-resolver/package.json +++ b/packages/internal/identity-resolver/package.json @@ -1,6 +1,6 @@ { "name": "@atproto-labs/identity-resolver", - "version": "0.1.1", + "version": "0.1.2", "license": "MIT", "description": "A library resolving ATPROTO identities", "keywords": [ diff --git a/packages/internal/identity-resolver/src/identity-resolver.ts b/packages/internal/identity-resolver/src/identity-resolver.ts index 229832a9d6a..a0c4d8fa8f2 100644 --- a/packages/internal/identity-resolver/src/identity-resolver.ts +++ b/packages/internal/identity-resolver/src/identity-resolver.ts @@ -1,15 +1,16 @@ import { Did, DidDocument, - ResolveDidOptions, DidResolver, DidService, + ResolveDidOptions, } from '@atproto-labs/did-resolver' import { - ResolveHandleOptions, + AtprotoIdentityDidMethods, HandleResolver, - ResolvedHandle, isResolvedHandle, + ResolvedHandle, + ResolveHandleOptions, } from '@atproto-labs/handle-resolver' import { normalizeAndEnsureValidHandle } from '@atproto/syntax' @@ -22,7 +23,7 @@ export type ResolveIdentityOptions = ResolveDidOptions & ResolveHandleOptions export class IdentityResolver { constructor( - readonly didResolver: DidResolver<'plc' | 'web'>, + readonly didResolver: DidResolver, readonly handleResolver: HandleResolver, ) {} @@ -30,35 +31,61 @@ export class IdentityResolver { input: string, options?: ResolveIdentityOptions, ): Promise { - const did = isResolvedHandle(input) - ? input // Already a did - : await this.handleResolver.resolve( - normalizeAndEnsureValidHandle(input), - options, - ) - - options?.signal?.throwIfAborted() - - if (!did) { - throw new TypeError(`Handle "${input}" does not resolve to a DID`) - } - - const document = await this.didResolver.resolve(did, options) + const document = isResolvedHandle(input) + ? await this.getDocumentFromDid(input, options) + : await this.getDocumentFromHandle(input, options) const service = document.service?.find( - isAtprotoPersonalDataServerService<'plc' | 'web'>, + isAtprotoPersonalDataServerService, document, ) if (!service) { throw new TypeError( - `No valid "AtprotoPersonalDataServer" service found in "${did}" DID document`, + `No valid "AtprotoPersonalDataServer" service found in "${document.id}" DID document`, ) } - const pds = new URL(service.serviceEndpoint) + return { + did: document.id, + pds: new URL(service.serviceEndpoint), + } + } + + public async getDocumentFromDid( + did: NonNullable, + options?: ResolveDidOptions, + ): Promise> { + return this.didResolver.resolve(did, options) + } + + public async getDocumentFromHandle( + input: string, + options?: ResolveHandleOptions, + ): Promise> { + const handle = normalizeAndEnsureValidHandle(input) + + const did = await this.handleResolver.resolve(handle, options) + + if (!did) { + throw new TypeError(`Handle "${handle}" does not resolve to a DID`) + } + + options?.signal?.throwIfAborted() + + // Note: Not using "return this.resolveDid(did, options)" to make the extra + // check for the handle in the DID document: + + const document = await this.didResolver.resolve(did, options) + + // Ensure that the handle is included in the document + if (!document.alsoKnownAs?.includes(`at://${handle}`)) { + throw new TypeError( + `Did document for "${did}" does not include the handle "${handle}"`, + ) + } - return { did, pds } + return document } } @@ -73,6 +100,8 @@ function isAtprotoPersonalDataServerService( return ( typeof s.serviceEndpoint === 'string' && s.type === 'AtprotoPersonalDataServer' && - (s.id === '#atproto_pds' || s.id === `${this.id}#atproto_pds`) + (s.id.startsWith('#') + ? s.id === '#atproto_pds' + : s.id === `${this.id}#atproto_pds`) ) } diff --git a/packages/lex-cli/CHANGELOG.md b/packages/lex-cli/CHANGELOG.md index 23a918ba4cb..735c481db47 100644 --- a/packages/lex-cli/CHANGELOG.md +++ b/packages/lex-cli/CHANGELOG.md @@ -1,5 +1,22 @@ # @atproto/lex-cli +## 0.5.0 + +### Minor Changes + +- [#2707](https://github.com/bluesky-social/atproto/pull/2707) [`2bdf75d7a`](https://github.com/bluesky-social/atproto/commit/2bdf75d7a63924c10e7a311f16cb447d595b933e) Thanks [@matthieusieben](https://github.com/matthieusieben)! - The generated client implementation uses the new `XrpcClient` class from `@atproto/xrpc`, instead of the deprecated `Client` and `ServiceClient` class. + +### Patch Changes + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`2bdf75d7a`](https://github.com/bluesky-social/atproto/commit/2bdf75d7a63924c10e7a311f16cb447d595b933e)]: + - @atproto/lexicon@0.4.1 + +## 0.4.1 + +### Patch Changes + +- [#2691](https://github.com/bluesky-social/atproto/pull/2691) [`08ef309c9`](https://github.com/bluesky-social/atproto/commit/08ef309c9c1f35f0e7093cb845321e876133d23e) Thanks [@dholms](https://github.com/dholms)! - Fix use of prettier.format for codegen + ## 0.4.0 ### Minor Changes diff --git a/packages/lex-cli/package.json b/packages/lex-cli/package.json index f14cb0f8324..efbbb346c32 100644 --- a/packages/lex-cli/package.json +++ b/packages/lex-cli/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/lex-cli", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "description": "TypeScript codegen tool for atproto Lexicon schemas", "keywords": [ diff --git a/packages/lex-cli/src/codegen/client.ts b/packages/lex-cli/src/codegen/client.ts index bf7c8892819..eee06071277 100644 --- a/packages/lex-cli/src/codegen/client.ts +++ b/packages/lex-cli/src/codegen/client.ts @@ -61,13 +61,14 @@ const indexTs = ( nsidTokens: Record, ) => gen(project, '/index.ts', async (file) => { - //= import {Client as XrpcClient, AtpServiceClient as XrpcServiceClient} from '@atproto/xrpc' + //= import { XrpcClient, FetchHandler, FetchHandlerOptions } from '@atproto/xrpc' const xrpcImport = file.addImportDeclaration({ moduleSpecifier: '@atproto/xrpc', }) xrpcImport.addNamedImports([ - { name: 'Client', alias: 'XrpcClient' }, - { name: 'ServiceClient', alias: 'XrpcServiceClient' }, + { name: 'XrpcClient' }, + { name: 'FetchHandler' }, + { name: 'FetchHandlerOptions' }, ]) //= import {schemas} from './lexicons' file @@ -119,87 +120,44 @@ const indexTs = ( const clientCls = file.addClass({ name: 'AtpBaseClient', isExported: true, + extends: 'XrpcClient', }) - //= xrpc: XrpcClient = new XrpcClient() - clientCls.addProperty({ - name: 'xrpc', - type: 'XrpcClient', - initializer: 'new XrpcClient()', - }) - //= constructor () { - //= this.xrpc.addLexicons(schemas) - //= } - clientCls.addConstructor().setBodyText(`this.xrpc.addLexicons(schemas)`) - //= service(serviceUri: string | URL): AtpServiceClient { - //= return new AtpServiceClient(this, this.xrpc.service(serviceUri)) - //= } - clientCls - .addMethod({ - name: 'service', - parameters: [{ name: 'serviceUri', type: 'string | URL' }], - returnType: 'AtpServiceClient', - }) - .setBodyText( - `return new AtpServiceClient(this, this.xrpc.service(serviceUri))`, - ) - //= export class AtpServiceClient {...} - const serviceClientCls = file.addClass({ - name: 'AtpServiceClient', - isExported: true, - }) - //= _baseClient: AtpBaseClient - serviceClientCls.addProperty({ name: '_baseClient', type: 'AtpBaseClient' }) - //= xrpc: XrpcServiceClient - serviceClientCls.addProperty({ - name: 'xrpc', - type: 'XrpcServiceClient', - }) for (const ns of nsidTree) { //= ns: NS - serviceClientCls.addProperty({ + clientCls.addProperty({ name: ns.propName, type: ns.className, }) } - //= constructor (baseClient: AtpBaseClient, xrpcService: XrpcServiceClient) { - //= this.baseClient = baseClient - //= this.xrpcService = xrpcService + + //= constructor (options: FetchHandler | FetchHandlerOptions) { + //= super(options, schemas) //= {namespace declarations} //= } - serviceClientCls - .addConstructor({ - parameters: [ - { name: 'baseClient', type: 'AtpBaseClient' }, - { name: 'xrpcService', type: 'XrpcServiceClient' }, - ], - }) - .setBodyText( - [ - `this._baseClient = baseClient`, - `this.xrpc = xrpcService`, - ...nsidTree.map( - (ns) => `this.${ns.propName} = new ${ns.className}(this)`, - ), - ].join('\n'), - ) + clientCls.addConstructor({ + parameters: [ + { name: 'options', type: 'FetchHandler | FetchHandlerOptions' }, + ], + statements: [ + 'super(options, schemas)', + ...nsidTree.map( + (ns) => `this.${ns.propName} = new ${ns.className}(this)`, + ), + ], + }) - //= setHeader(key: string, value: string): void { - //= this.xrpc.setHeader(key, value) + //= /** @deprecated use `this` instead */ + //= get xrpc(): XrpcClient { + //= return this //= } - const setHeaderMethod = serviceClientCls.addMethod({ - name: 'setHeader', - returnType: 'void', - }) - setHeaderMethod.addParameter({ - name: 'key', - type: 'string', - }) - setHeaderMethod.addParameter({ - name: 'value', - type: 'string', - }) - setHeaderMethod.setBodyText('this.xrpc.setHeader(key, value)') + clientCls + .addGetAccessor({ + name: 'xrpc', + returnType: 'XrpcClient', + statements: ['return this'], + }) + .addJsDoc('@deprecated use `this` instead') // generate classes for the schemas for (const ns of nsidTree) { @@ -213,10 +171,10 @@ function genNamespaceCls(file: SourceFile, ns: DefTreeNode) { name: ns.className, isExported: true, }) - //= _service: AtpServiceClient + //= _client: XrpcClient cls.addProperty({ - name: '_service', - type: 'AtpServiceClient', + name: '_client', + type: 'XrpcClient', }) for (const userType of ns.userTypes) { @@ -242,21 +200,22 @@ function genNamespaceCls(file: SourceFile, ns: DefTreeNode) { genNamespaceCls(file, child) } - //= constructor(service: AtpServiceClient) { - //= this._service = service + //= constructor(public client: XrpcClient) { + //= this._client = client //= {child namespace prop declarations} //= {record prop declarations} //= } - const cons = cls.addConstructor() - cons.addParameter({ - name: 'service', - type: 'AtpServiceClient', - }) - cons.setBodyText( - [ - `this._service = service`, + cls.addConstructor({ + parameters: [ + { + name: 'client', + type: 'XrpcClient', + }, + ], + statements: [ + `this._client = client`, ...ns.children.map( - (ns) => `this.${ns.propName} = new ${ns.className}(service)`, + (ns) => `this.${ns.propName} = new ${ns.className}(client)`, ), ...ns.userTypes .filter((ut) => ut.def.type === 'record') @@ -264,10 +223,10 @@ function genNamespaceCls(file: SourceFile, ns: DefTreeNode) { const name = NSID.parse(ut.nsid).name || '' return `this.${toCamelCase(name)} = new ${toTitleCase( name, - )}Record(service)` + )}Record(client)` }), - ].join('\n'), - ) + ], + }) // methods for (const userType of ns.userTypes) { @@ -298,13 +257,14 @@ function genNamespaceCls(file: SourceFile, ns: DefTreeNode) { }) method.setBodyText( [ - `return this._service.xrpc`, + `return this._client`, isGetReq ? `.call('${userType.nsid}', params, undefined, opts)` : `.call('${userType.nsid}', opts?.qp, data, opts)`, - ` .catch((e) => {`, - ` throw ${moduleName}.toKnownErr(e)`, - ` })`, + userType.def.errors?.length + ? // Only add a catch block if there are custom errors + ` .catch((e) => { throw ${moduleName}.toKnownErr(e) })` + : '', ].join('\n'), ) } @@ -325,21 +285,21 @@ function genRecordCls(file: SourceFile, nsid: string, lexRecord: LexRecord) { name: `${toTitleCase(name)}Record`, isExported: true, }) - //= _service: AtpServiceClient + //= _client: XrpcClient cls.addProperty({ - name: '_service', - type: 'AtpServiceClient', + name: '_client', + type: 'XrpcClient', }) - //= constructor(service: AtpServiceClient) { - //= this._service = service + //= constructor(client: XrpcClient) { + //= this._client = client //= } const cons = cls.addConstructor() cons.addParameter({ - name: 'service', - type: 'AtpServiceClient', + name: 'client', + type: 'XrpcClient', }) - cons.setBodyText(`this._service = service`) + cons.setBodyText(`this._client = client`) // methods const typeModule = toTitleCase(nsid) @@ -356,7 +316,7 @@ function genRecordCls(file: SourceFile, nsid: string, lexRecord: LexRecord) { }) method.setBodyText( [ - `const res = await this._service.xrpc.call('${ATP_METHODS.list}', { collection: '${nsid}', ...params })`, + `const res = await this._client.call('${ATP_METHODS.list}', { collection: '${nsid}', ...params })`, `return res.data`, ].join('\n'), ) @@ -374,7 +334,7 @@ function genRecordCls(file: SourceFile, nsid: string, lexRecord: LexRecord) { }) method.setBodyText( [ - `const res = await this._service.xrpc.call('${ATP_METHODS.get}', { collection: '${nsid}', ...params })`, + `const res = await this._client.call('${ATP_METHODS.get}', { collection: '${nsid}', ...params })`, `return res.data`, ].join('\n'), ) @@ -406,7 +366,7 @@ function genRecordCls(file: SourceFile, nsid: string, lexRecord: LexRecord) { method.setBodyText( [ `record.$type = '${nsid}'`, - `const res = await this._service.xrpc.call('${ATP_METHODS.create}', undefined, { collection: '${nsid}', ${maybeRkeyPart}...params, record }, {encoding: 'application/json', headers })`, + `const res = await this._client.call('${ATP_METHODS.create}', undefined, { collection: '${nsid}', ${maybeRkeyPart}...params, record }, {encoding: 'application/json', headers })`, `return res.data`, ].join('\n'), ) @@ -433,7 +393,7 @@ function genRecordCls(file: SourceFile, nsid: string, lexRecord: LexRecord) { // method.setBodyText( // [ // `record.$type = '${userType.nsid}'`, - // `const res = await this._service.xrpc.call('${ATP_METHODS.put}', undefined, { collection: '${userType.nsid}', record, ...params }, {encoding: 'application/json', headers})`, + // `const res = await this._client.call('${ATP_METHODS.put}', undefined, { collection: '${userType.nsid}', record, ...params }, {encoding: 'application/json', headers})`, // `return res.data`, // ].join('\n'), // ) @@ -458,7 +418,7 @@ function genRecordCls(file: SourceFile, nsid: string, lexRecord: LexRecord) { method.setBodyText( [ - `await this._service.xrpc.call('${ATP_METHODS.delete}', undefined, { collection: '${nsid}', ...params }, { headers })`, + `await this._client.call('${ATP_METHODS.delete}', undefined, { collection: '${nsid}', ...params }, { headers })`, ].join('\n'), ) } @@ -477,11 +437,14 @@ const lexiconTs = (project, lexicons: Lexicons, lexiconDoc: LexiconDoc) => main?.type === 'subscription' || main?.type === 'procedure' ) { - //= import {Headers, XRPCError} from '@atproto/xrpc' + //= import {HeadersMap, XRPCError} from '@atproto/xrpc' const xrpcImport = file.addImportDeclaration({ moduleSpecifier: '@atproto/xrpc', }) - xrpcImport.addNamedImports([{ name: 'Headers' }, { name: 'XRPCError' }]) + xrpcImport.addNamedImports([ + { name: 'HeadersMap' }, + { name: 'XRPCError' }, + ]) } //= import {ValidationResult, BlobRef} from '@atproto/lexicon' file @@ -550,7 +513,8 @@ function genClientXrpcCommon( name: 'CallOptions', isExported: true, }) - opts.addProperty({ name: 'headers?', type: 'Headers' }) + opts.addProperty({ name: 'signal?', type: 'AbortSignal' }) + opts.addProperty({ name: 'headers?', type: 'HeadersMap' }) if (def.type === 'procedure') { opts.addProperty({ name: 'qp?', type: 'QueryParams' }) } @@ -563,7 +527,7 @@ function genClientXrpcCommon( .join(' | ') } opts.addProperty({ - name: 'encoding', + name: 'encoding?', type: encodingType, }) } @@ -574,7 +538,7 @@ function genClientXrpcCommon( isExported: true, }) res.addProperty({ name: 'success', type: 'boolean' }) - res.addProperty({ name: 'headers', type: 'Headers' }) + res.addProperty({ name: 'headers', type: 'HeadersMap' }) if (def.output?.schema) { if (def.output.encoding?.includes(',')) { res.addProperty({ name: 'data', type: 'OutputSchema | Uint8Array' }) @@ -595,30 +559,32 @@ function genClientXrpcCommon( extends: 'XRPCError', isExported: true, }) - errCls - .addConstructor({ - parameters: [{ name: 'src', type: 'XRPCError' }], - }) - .setBodyText(`super(src.status, src.error, src.message, src.headers)`) + errCls.addConstructor({ + parameters: [{ name: 'src', type: 'XRPCError' }], + statements: [ + 'super(src.status, src.error, src.message, src.headers, { cause: src })', + ], + }) + customErrors.push({ name: error.name, cls: name }) } // export function toKnownErr(err: any) {...} - const toKnownErrFn = file.addFunction({ + file.addFunction({ name: 'toKnownErr', isExported: true, + parameters: [{ name: 'e', type: 'any' }], + statements: customErrors.length + ? [ + 'if (e instanceof XRPCError) {', + ...customErrors.map( + (err) => `if (e.error === '${err.name}') return new ${err.cls}(e)`, + ), + '}', + 'return e', + ] + : ['return e'], }) - toKnownErrFn.addParameter({ name: 'e', type: 'any' }) - toKnownErrFn.setBodyText( - [ - `if (e instanceof XRPCError) {`, - ...customErrors.map( - (err) => `if (e.error === '${err.name}') return new ${err.cls}(e)`, - ), - `}`, - `return e`, - ].join('\n'), - ) } function genClientRecord( diff --git a/packages/lex-cli/src/codegen/lex-gen.ts b/packages/lex-cli/src/codegen/lex-gen.ts index e2856437d70..a50eae9c2e5 100644 --- a/packages/lex-cli/src/codegen/lex-gen.ts +++ b/packages/lex-cli/src/codegen/lex-gen.ts @@ -335,11 +335,11 @@ export function genXrpcInput( ) } } else if (def.type === 'procedure' && def.input?.encoding) { - //= export type InputSchema = string | Uint8Array + //= export type InputSchema = string | Uint8Array | Blob file.addTypeAlias({ isExported: true, name: 'InputSchema', - type: 'string | Uint8Array', + type: 'string | Uint8Array | Blob', }) } else { //= export type InputSchema = undefined diff --git a/packages/lex-cli/src/index.ts b/packages/lex-cli/src/index.ts index 5b446bdd733..574a84e8b0a 100644 --- a/packages/lex-cli/src/index.ts +++ b/packages/lex-cli/src/index.ts @@ -20,25 +20,23 @@ program.name('lex').description('Lexicon CLI').version('0.0.0') program .command('gen-md') .description('Generate markdown documentation') + .option('--yes', 'skip confirmation') .argument('', 'path of the file to write to', toPath) .argument('', 'paths of the lexicon files to include', toPaths) - .action(async (outFilePath: string, lexiconPaths: string[]) => { - if (!outFilePath.endsWith('.md')) { - console.error('Must supply the path to a .md file as the first parameter') - process.exit(1) - } - console.log('Writing', outFilePath) - const ok = await yesno({ - question: 'Are you sure you want to continue? [y/N]', - defaultValue: false, - }) - if (!ok) { - console.log('Aborted.') - process.exit(0) - } - const lexicons = readAllLexicons(lexiconPaths) - await mdGen.process(outFilePath, lexicons) - }) + .action( + async (outFile: string, lexiconPaths: string[], o: { yes?: true }) => { + if (!outFile.endsWith('.md')) { + console.error( + 'Must supply the path to a .md file as the first parameter', + ) + process.exit(1) + } + if (!o?.yes) await confirmOrExit() + console.log('Writing', outFile) + const lexicons = readAllLexicons(lexiconPaths) + await mdGen.process(outFile, lexicons) + }, + ) program .command('gen-ts-obj') @@ -52,22 +50,16 @@ program program .command('gen-api') .description('Generate a TS client API') + .option('--yes', 'skip confirmation') .argument('', 'path of the directory to write to', toPath) .argument('', 'paths of the lexicon files to include', toPaths) - .action(async (outDir: string, lexiconPaths: string[]) => { + .action(async (outDir: string, lexiconPaths: string[], o: { yes?: true }) => { const lexicons = readAllLexicons(lexiconPaths) const api = await genClientApi(lexicons) const diff = genFileDiff(outDir, api) console.log('This will write the following files:') printFileDiff(diff) - const ok = await yesno({ - question: 'Are you sure you want to continue? [y/N]', - defaultValue: false, - }) - if (!ok) { - console.log('Aborted.') - process.exit(0) - } + if (!o?.yes) await confirmOrExit() applyFileDiff(diff) console.log('API generated.') }) @@ -75,22 +67,16 @@ program program .command('gen-server') .description('Generate a TS server API') + .option('--yes', 'skip confirmation') .argument('', 'path of the directory to write to', toPath) .argument('', 'paths of the lexicon files to include', toPaths) - .action(async (outDir: string, lexiconPaths: string[]) => { + .action(async (outDir: string, lexiconPaths: string[], o: { yes?: true }) => { const lexicons = readAllLexicons(lexiconPaths) const api = await genServerApi(lexicons) const diff = genFileDiff(outDir, api) console.log('This will write the following files:') printFileDiff(diff) - const ok = await yesno({ - question: 'Are you sure you want to continue? [y/N]', - defaultValue: false, - }) - if (!ok) { - console.log('Aborted.') - process.exit(0) - } + if (!o?.yes) await confirmOrExit() applyFileDiff(diff) console.log('API generated.') }) @@ -106,3 +92,14 @@ function toPaths(v: string, acc: string[]) { acc.push(path.resolve(v)) return acc } + +async function confirmOrExit() { + const ok = await yesno({ + question: 'Are you sure you want to continue? [y/N]', + defaultValue: false, + }) + if (!ok) { + console.log('Aborted.') + process.exit(0) + } +} diff --git a/packages/lexicon/CHANGELOG.md b/packages/lexicon/CHANGELOG.md index e040d71ab6f..b3960fcba62 100644 --- a/packages/lexicon/CHANGELOG.md +++ b/packages/lexicon/CHANGELOG.md @@ -1,5 +1,13 @@ # @atproto/lexicon +## 0.4.1 + +### Patch Changes + +- [#2483](https://github.com/bluesky-social/atproto/pull/2483) [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add the ability to instantiate a Lexicon from an iterable, and to use a Lexicon as iterable. + +- [#2707](https://github.com/bluesky-social/atproto/pull/2707) [`2bdf75d7a`](https://github.com/bluesky-social/atproto/commit/2bdf75d7a63924c10e7a311f16cb447d595b933e) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Remove internal circular dependency. + ## 0.4.0 ### Minor Changes diff --git a/packages/lexicon/package.json b/packages/lexicon/package.json index abea5d22607..c9c2de88fcb 100644 --- a/packages/lexicon/package.json +++ b/packages/lexicon/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/lexicon", - "version": "0.4.0", + "version": "0.4.1", "license": "MIT", "description": "atproto Lexicon schema language library", "keywords": [ diff --git a/packages/lexicon/src/util.ts b/packages/lexicon/src/util.ts index ea32574b0a9..beae2922d59 100644 --- a/packages/lexicon/src/util.ts +++ b/packages/lexicon/src/util.ts @@ -1,13 +1,6 @@ -import { Lexicons } from './lexicons' -import * as ComplexValidators from './validators/complex' -import { - LexUserType, - LexRefVariant, - ValidationError, - ValidationResult, - isDiscriminatedObject, -} from './types' import { z } from 'zod' +import { Lexicons } from './lexicons' +import { LexRefVariant, LexUserType } from './types' export function toLexUri(str: string, baseUri?: string): string { if (str.split('#').length > 2) { @@ -26,77 +19,6 @@ export function toLexUri(str: string, baseUri?: string): string { return `lex:${str}` } -export function validateOneOf( - lexicons: Lexicons, - path: string, - def: LexRefVariant | LexUserType, - value: unknown, - mustBeObj = false, // this is the only type constraint we need currently (used by xrpc body schema validators) -): ValidationResult { - let error - - let concreteDefs - if (def.type === 'union') { - if (!isDiscriminatedObject(value)) { - return { - success: false, - error: new ValidationError( - `${path} must be an object which includes the "$type" property`, - ), - } - } - if (!refsContainType(def.refs, value.$type)) { - if (def.closed) { - return { - success: false, - error: new ValidationError( - `${path} $type must be one of ${def.refs.join(', ')}`, - ), - } - } - return { success: true, value } - } else { - concreteDefs = toConcreteTypes(lexicons, { - type: 'ref', - ref: value.$type, - }) - } - } else { - concreteDefs = toConcreteTypes(lexicons, def) - } - - for (const concreteDef of concreteDefs) { - const result = mustBeObj - ? ComplexValidators.object(lexicons, path, concreteDef, value) - : ComplexValidators.validate(lexicons, path, concreteDef, value) - if (result.success) { - return result - } - error ??= result.error - } - if (concreteDefs.length > 1) { - return { - success: false, - error: new ValidationError( - `${path} did not match any of the expected definitions`, - ), - } - } - return { success: false, error } -} - -export function assertValidOneOf( - lexicons: Lexicons, - path: string, - def: LexRefVariant | LexUserType, - value: unknown, - mustBeObj = false, -) { - const res = validateOneOf(lexicons, path, def, value, mustBeObj) - if (!res.success) throw res.error - return res.value -} - export function toConcreteTypes( lexicons: Lexicons, def: LexRefVariant | LexUserType, @@ -149,18 +71,3 @@ export function requiredPropertiesRefinement< } } } - -// to avoid bugs like #0189 this needs to handle both -// explicit and implicit #main -const refsContainType = (refs: string[], type: string) => { - const lexUri = toLexUri(type) - if (refs.includes(lexUri)) { - return true - } - - if (lexUri.endsWith('#main')) { - return refs.includes(lexUri.replace('#main', '')) - } else { - return refs.includes(lexUri + '#main') - } -} diff --git a/packages/lexicon/src/validation.ts b/packages/lexicon/src/validation.ts index d2fa9d0a84b..715c37e530b 100644 --- a/packages/lexicon/src/validation.ts +++ b/packages/lexicon/src/validation.ts @@ -1,21 +1,22 @@ import { Lexicons } from './lexicons' import { LexRecord, + LexRefVariant, + LexUserType, LexXrpcProcedure, LexXrpcQuery, LexXrpcSubscription, } from './types' -import { assertValidOneOf } from './util' -import * as ComplexValidators from './validators/complex' -import * as XrpcValidators from './validators/xrpc' +import { object, validateOneOf } from './validators/complex' +import { params } from './validators/xrpc' export function assertValidRecord( lexicons: Lexicons, def: LexRecord, value: unknown, ) { - const res = ComplexValidators.object(lexicons, 'Record', def.record, value) + const res = object(lexicons, 'Record', def.record, value) if (!res.success) throw res.error return res.value } @@ -26,7 +27,7 @@ export function assertValidXrpcParams( value: unknown, ) { if (def.parameters) { - const res = XrpcValidators.params(lexicons, 'Params', def.parameters, value) + const res = params(lexicons, 'Params', def.parameters, value) if (!res.success) throw res.error return res.value } @@ -70,3 +71,15 @@ export function assertValidXrpcMessage( ) } } + +function assertValidOneOf( + lexicons: Lexicons, + path: string, + def: LexRefVariant | LexUserType, + value: unknown, + mustBeObj = false, +) { + const res = validateOneOf(lexicons, path, def, value, mustBeObj) + if (!res.success) throw res.error + return res.value +} diff --git a/packages/lexicon/src/validators/complex.ts b/packages/lexicon/src/validators/complex.ts index b3ea359c3ec..8e54eba66ac 100644 --- a/packages/lexicon/src/validators/complex.ts +++ b/packages/lexicon/src/validators/complex.ts @@ -2,14 +2,16 @@ import { Lexicons } from '../lexicons' import { LexArray, LexObject, + LexRefVariant, LexUserType, - ValidationResult, ValidationError, + ValidationResult, + isDiscriminatedObject, } from '../types' -import { validateOneOf } from '../util' +import { toConcreteTypes, toLexUri } from '../util' -import * as Primitives from './primitives' -import * as Blob from './blob' +import { blob } from './blob' +import { boolean, integer, string, bytes, cidLink, unknown } from './primitives' export function validate( lexicons: Lexicons, @@ -19,23 +21,23 @@ export function validate( ): ValidationResult { switch (def.type) { case 'boolean': - return Primitives.boolean(lexicons, path, def, value) + return boolean(lexicons, path, def, value) case 'integer': - return Primitives.integer(lexicons, path, def, value) + return integer(lexicons, path, def, value) case 'string': - return Primitives.string(lexicons, path, def, value) + return string(lexicons, path, def, value) case 'bytes': - return Primitives.bytes(lexicons, path, def, value) + return bytes(lexicons, path, def, value) case 'cid-link': - return Primitives.cidLink(lexicons, path, def, value) + return cidLink(lexicons, path, def, value) case 'unknown': - return Primitives.unknown(lexicons, path, def, value) + return unknown(lexicons, path, def, value) case 'object': return object(lexicons, path, def, value) case 'array': return array(lexicons, path, def, value) case 'blob': - return Blob.blob(lexicons, path, def, value) + return blob(lexicons, path, def, value) default: return { success: false, @@ -164,3 +166,77 @@ export function object( return { success: true, value: resultValue } } + +export function validateOneOf( + lexicons: Lexicons, + path: string, + def: LexRefVariant | LexUserType, + value: unknown, + mustBeObj = false, // this is the only type constraint we need currently (used by xrpc body schema validators) +): ValidationResult { + let error + + let concreteDefs + if (def.type === 'union') { + if (!isDiscriminatedObject(value)) { + return { + success: false, + error: new ValidationError( + `${path} must be an object which includes the "$type" property`, + ), + } + } + if (!refsContainType(def.refs, value.$type)) { + if (def.closed) { + return { + success: false, + error: new ValidationError( + `${path} $type must be one of ${def.refs.join(', ')}`, + ), + } + } + return { success: true, value } + } else { + concreteDefs = toConcreteTypes(lexicons, { + type: 'ref', + ref: value.$type, + }) + } + } else { + concreteDefs = toConcreteTypes(lexicons, def) + } + + for (const concreteDef of concreteDefs) { + const result = mustBeObj + ? object(lexicons, path, concreteDef, value) + : validate(lexicons, path, concreteDef, value) + if (result.success) { + return result + } + error ??= result.error + } + if (concreteDefs.length > 1) { + return { + success: false, + error: new ValidationError( + `${path} did not match any of the expected definitions`, + ), + } + } + return { success: false, error } +} + +// to avoid bugs like #0189 this needs to handle both +// explicit and implicit #main +const refsContainType = (refs: string[], type: string) => { + const lexUri = toLexUri(type) + if (refs.includes(lexUri)) { + return true + } + + if (lexUri.endsWith('#main')) { + return refs.includes(lexUri.replace('#main', '')) + } else { + return refs.includes(lexUri + '#main') + } +} diff --git a/packages/oauth/jwk-jose/CHANGELOG.md b/packages/oauth/jwk-jose/CHANGELOG.md index cb04b716794..9b2fbbf567f 100644 --- a/packages/oauth/jwk-jose/CHANGELOG.md +++ b/packages/oauth/jwk-jose/CHANGELOG.md @@ -1,5 +1,13 @@ # @atproto/jwk-jose +## 0.1.2 + +### Patch Changes + +- [#2483](https://github.com/bluesky-social/atproto/pull/2483) [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Misc fixes for confidential client usage + +- [#2483](https://github.com/bluesky-social/atproto/pull/2483) [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Allow importing JoseKey without specifying a kid + ## 0.1.1 ### Patch Changes diff --git a/packages/oauth/jwk-jose/package.json b/packages/oauth/jwk-jose/package.json index 079274ad30f..82f3f083f9c 100644 --- a/packages/oauth/jwk-jose/package.json +++ b/packages/oauth/jwk-jose/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/jwk-jose", - "version": "0.1.1", + "version": "0.1.2", "license": "MIT", "description": "`jose` based implementation of @atproto/jwk Key's", "keywords": [ diff --git a/packages/oauth/jwk-jose/src/jose-key.ts b/packages/oauth/jwk-jose/src/jose-key.ts index 80623c14be4..7bb0567ce21 100644 --- a/packages/oauth/jwk-jose/src/jose-key.ts +++ b/packages/oauth/jwk-jose/src/jose-key.ts @@ -146,7 +146,6 @@ export class JoseKey extends Key { } // KeyLike - if (!kid) throw new JwkError('Missing "kid" for KeyLike key') return this.fromKeyLike(input, kid) } diff --git a/packages/oauth/jwk-webcrypto/CHANGELOG.md b/packages/oauth/jwk-webcrypto/CHANGELOG.md index 85a2601dc80..e03e87da399 100644 --- a/packages/oauth/jwk-webcrypto/CHANGELOG.md +++ b/packages/oauth/jwk-webcrypto/CHANGELOG.md @@ -1,5 +1,12 @@ # @atproto/jwk-webcrypto +## 0.1.2 + +### Patch Changes + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd)]: + - @atproto/jwk-jose@0.1.2 + ## 0.1.1 ### Patch Changes diff --git a/packages/oauth/jwk-webcrypto/package.json b/packages/oauth/jwk-webcrypto/package.json index d815b11fd4d..9b0f9688d11 100644 --- a/packages/oauth/jwk-webcrypto/package.json +++ b/packages/oauth/jwk-webcrypto/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/jwk-webcrypto", - "version": "0.1.1", + "version": "0.1.2", "license": "MIT", "description": "Webcrypto based implementation of @atproto/jwk Key's", "keywords": [ diff --git a/packages/oauth/oauth-client-browser/CHANGELOG.md b/packages/oauth/oauth-client-browser/CHANGELOG.md index 3c9b4cdc319..3da2c4e04b8 100644 --- a/packages/oauth/oauth-client-browser/CHANGELOG.md +++ b/packages/oauth/oauth-client-browser/CHANGELOG.md @@ -1,5 +1,24 @@ # @atproto/oauth-client-browser +## 0.1.3 + +### Patch Changes + +- Updated dependencies []: + - @atproto/oauth-client@0.1.3 + +## 0.1.2 + +### Patch Changes + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd)]: + - @atproto/oauth-client@0.1.2 + - @atproto/oauth-types@0.1.2 + - @atproto-labs/handle-resolver@0.1.2 + - @atproto/did@0.1.1 + - @atproto/jwk-webcrypto@0.1.2 + - @atproto-labs/did-resolver@0.1.2 + ## 0.1.1 ### Patch Changes diff --git a/packages/oauth/oauth-client-browser/README.md b/packages/oauth/oauth-client-browser/README.md index e81c5f60a23..344987bc88e 100644 --- a/packages/oauth/oauth-client-browser/README.md +++ b/packages/oauth/oauth-client-browser/README.md @@ -1,17 +1,17 @@ -# ATPROTO OAuth Client for the Browser +# atproto OAuth Client for the Browser This package provides an OAuth bases `@atproto/api` agent interface for the browser. It implements all the OAuth features required by [ATPROTO] (PKCE, DPoP, etc.). -`@atproto/oauth-client-browser` is destined to front-end applications that do -not have a back-end server to manage OAuth sessions. +`@atproto/oauth-client-browser` is designed for front-end applications that do +not have a backend server to manage OAuth sessions. > [!IMPORTANT] > > When a backend server is available, it is recommended to use > [`@atproto/oauth-client-node`](https://www.npmjs.com/package/@atproto/oauth-client-node) -> to manage OAuth sessions from the server side, and use a session cookie to map +> to manage OAuth sessions from the server side and use a session cookie to map > the OAuth session to the front-end. Because this mechanism allows the backend > to invalidate OAuth credentials at scale, this method is more secure than > managing OAuth sessions from the front-end directly. Thanks to the added @@ -23,7 +23,7 @@ not have a back-end server to manage OAuth sessions. ### Client ID The `client_id` is what identifies your application to the OAuth server. It is -used to fetch the client metadata, and to initiate the OAuth flow. The +used to fetch the client metadata and to initiate the OAuth flow. The `client_id` must be a URL that points to the [client metadata](#client-metadata). @@ -32,7 +32,7 @@ metadata](#client-metadata). Your OAuth client metadata should be hosted at a URL that corresponds to the `client_id` of your application. This URL should return a JSON object with the client metadata. The client metadata should be configured according to the -needs of your application, and must respect the [ATPROTO] spec. +needs of your application and must respect the [ATPROTO] spec. ```json { @@ -86,18 +86,21 @@ metadata into the script at runtime. ### Handle Resolver -Whenever you application will initiate an OAuth flow, it will start to resolve +Whenever your application initiates an OAuth flow, it will start to resolve the (user provider) APTROTO handle of the user. This is typically done though a -DNS request. However, since DNS resolution is not available in the browser, a +DNS request. However, because DNS resolution is not available in the browser, a backend service must be provided. > [!CAUTION] > -> Not using a handle resolver service hosted by you will leak the user's IP -> address (and associated ATPROTO handle) to any service you rely on to perform -> the resolution. This is a privacy concern, that you should be aware of, and -> that you **must** warn your users about. Bluesky declines any responsibility -> in case of misusage of the handle resolver service. +> Using Bluesky-hosted services for handle resolution (eg, the `bsky.social` +> endpoint) will leak both user IP addresses and handle identifiers to Bluesky, +> a third party. While Bluesky has a declared privacy policy, both developers +> and users of applications need to be informed and aware of the privacy +> implications of this arrangement. Application developers are encouraged to +> improve user privacy by operating their own handle resolution service when +> possible. If you are a PDS self-hoster, you can use your PDS's URL for +> `handleResolver`. If a `string` or `URL` object is used as `handleResolver`, the library will expect this value to be the URL of a service running the @@ -105,7 +108,7 @@ expect this value to be the URL of a service running the > [!TIP] > -> If you host your own PDS, you can use it's URL as a handle resolver. +> If you host your own PDS, you can use its URL as a handle resolver. ```typescript import { BrowserOAuthClient } from '@atproto/oauth-client-browser' @@ -146,12 +149,12 @@ following optional configuration options: response is returned to the client. Defaults to `fragment`. - `plcDirectoryUrl`: The URL of the PLC directory. This will typically not be - needed unless you run an entire ATPROTO stack locally. Defaults to + needed unless you run an entire atproto stack locally. Defaults to `https://plc.directory`. ## Usage -Once the `client` is setup, it can be used to initiate & manage OAuth sessions. +Once the `client` is set up, it can be used to initiate & manage OAuth sessions. ### Initializing the client @@ -183,8 +186,8 @@ In order to initiate an OAuth flow, we must fist determine which PDS the authentication flow will be initiated from. This means that the user must provide one of the following information: -- The user's ATPROTO handle -- The user's ATPROTO DID +- The user's handle +- The user's DID - A PDS/Entryway URL Using that information, the OAuthClient will resolve all the needed information @@ -254,13 +257,15 @@ client.addEventListener( ## Usage with `@atproto/api` The `@atproto/api` package provides a way to interact with the `com.atproto` and -`app.bsky` XRPC lexicons through the `ApiAgent` interface. The `agent` returned -by the `BrowserOAuthClient` extend the `ApiAgent` class, allowing to use the -`BrowserOAuthClient` as a regular `ApiAgent` (akin to `AtpAgent` class +`app.bsky` XRPC lexicons through the `Agent` interface. The `agent` returned +by the `BrowserOAuthClient` extend the `Agent` class, allowing to use the +`BrowserOAuthClient` as a regular `Agent` (akin to `AtpAgent` class instances). ```typescript const aliceAgent = await client.restore('did:plc:alice') + +await aliceAgent.getProfile({ actor: aliceAgent.did }) ``` Any refresh of the credentials will happen under the hood, and the new tokens @@ -280,7 +285,7 @@ The `client_id` will then be something like There is however a special case for loopback clients. A loopback client is a client that runs on `localhost`. In this case, the OAuth server will not be able to fetch the `client_metadata` object because `localhost` is not accessible from -the outside. To work around this, ATPROTO OAuth server are required to support +the outside. To work around this, atproto OAuth servers are required to support this case by providing an hard coded `client_metadata` object for the client. This has several restrictions: diff --git a/packages/oauth/oauth-client-browser/example/src/app.tsx b/packages/oauth/oauth-client-browser/example/src/app.tsx index 5d4904e11b9..3625d2f67ca 100644 --- a/packages/oauth/oauth-client-browser/example/src/app.tsx +++ b/packages/oauth/oauth-client-browser/example/src/app.tsx @@ -1,73 +1,53 @@ -import { BrowserOAuthClient } from '@atproto/oauth-client-browser' import { useCallback, useState } from 'react' - -import LoginForm from './login-form' -import { useOAuth } from './oauth' - -const client = new BrowserOAuthClient({ - // dev-env - // plcDirectoryUrl: 'http://localhost:2582', - // handleResolver: 'http://localhost:2584', - - // staging - // plcDirectoryUrl: 'https://plc.staging.bsky.dev', - // handleResolver: 'https://staging.bsky.dev', - - // production - plcDirectoryUrl: undefined, - handleResolver: 'https://bsky.app', -}) +import { useAuthContext } from './auth/auth-provider' function App() { - const { agent, signedIn, signOut, loading, signIn } = useOAuth(client) - const [profile, setProfile] = useState<{ - value: { displayName?: string } - } | null>(null) - + const { pdsAgent, signOut } = useAuthContext() + + // A call that requires to be authenticated + const [serviceAuth, setServiceAuth] = useState(undefined) + const loadServiceAuth = useCallback(async () => { + const serviceAuth = await pdsAgent.com.atproto.server.getServiceAuth({ + aud: pdsAgent.accountDid, + }) + console.log('serviceAuth', serviceAuth) + setServiceAuth(serviceAuth.data) + }, [pdsAgent]) + + // This call does not require authentication + const [profile, setProfile] = useState(undefined) const loadProfile = useCallback(async () => { - if (!agent) return - - const info = await agent.getInfo() - console.log('info', info) - - // A call that requires to be authenticated - console.log( - await agent - .request( - '/xrpc/com.atproto.server.getServiceAuth?' + - new URLSearchParams({ aud: info.sub }).toString(), - ) - .then((r) => r.json()), - ) - - // This call does not require authentication - const profile = await agent - .request( - '/xrpc/com.atproto.repo.getRecord?' + - new URLSearchParams({ - repo: info.sub, - collection: 'app.bsky.actor.profile', - rkey: 'self', - }).toString(), - ) - .then((r) => r.json()) + const profile = await pdsAgent.com.atproto.repo.getRecord({ + repo: pdsAgent.accountDid, + collection: 'app.bsky.actor.profile', + rkey: 'self', + }) console.log(profile) - setProfile(profile.data) - }, [agent]) + }, [pdsAgent]) - return signedIn ? ( + return (

Logged in!

+ -
{profile ? JSON.stringify(profile, undefined, 2) : null}
+
+          {profile !== undefined ? JSON.stringify(profile, undefined, 2) : null}
+        
+
+ + + +
+          {serviceAuth !== undefined
+            ? JSON.stringify(serviceAuth, undefined, 2)
+            : null}
+        
- +
- ) : ( - ) } diff --git a/packages/oauth/oauth-client-browser/example/src/auth/atp/atp-sign-in-form.tsx b/packages/oauth/oauth-client-browser/example/src/auth/atp/atp-sign-in-form.tsx new file mode 100644 index 00000000000..00387181ce5 --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/src/auth/atp/atp-sign-in-form.tsx @@ -0,0 +1,108 @@ +import { FormEvent, useCallback, useState } from 'react' + +export type AtpSignIn = (input: { + identifier: string + password: string + authFactorToken?: string + service: string +}) => unknown + +/** + * @returns Nice tailwind css form asking to enter either a handle or the host + * to use to login. + */ +export function AtpSignInForm({ + signIn, + ...props +}: { + signIn: AtpSignIn +} & Omit, 'onSubmit'>) { + const [error, setError] = useState(null) + const [loading, setLoading] = useState(false) + + const [identifier, setIdentifier] = useState('') + const [password, setPassword] = useState('') + const [service, setService] = useState('http://localhost:2583') + + // TODO: add auth factor support ? + + const onSubmit = useCallback( + async (e: FormEvent) => { + e.preventDefault() + if (loading) return + + setLoading(true) + + try { + await signIn({ + identifier, + password, + service, + }) + } catch (err) { + setError((err as any)?.message || String(err)) + } finally { + setLoading(false) + } + }, + [loading, identifier, password, service, signIn], + ) + + return ( +
+
+
+ setIdentifier(e.target.value)} + /> + + setPassword(e.target.value)} + /> + + setService(e.target.value)} + /> + + + {loading && Loading...} +
+
+ + {error ?
{error}
: null} +
+ ) +} diff --git a/packages/oauth/oauth-client-browser/example/src/auth/atp/use-atp-auth.ts b/packages/oauth/oauth-client-browser/example/src/auth/atp/use-atp-auth.ts new file mode 100644 index 00000000000..f6679cde008 --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/src/auth/atp/use-atp-auth.ts @@ -0,0 +1,81 @@ +import { AtpSessionData, AtpAgent } from '@atproto/api' +import { useCallback, useMemo, useState } from 'react' + +type Session = AtpSessionData & { service: string } + +export function useAtpAuth() { + const createAgent = useCallback((service: string) => { + const agent = new AtpAgent({ + service, + persistSession: (type, session) => { + if (session) { + saveSession({ ...session, service }) + } else { + setAgent((a) => (a === agent ? null : a)) + deleteSession() + } + }, + }) + return agent + }, []) + + const [agent, setAgent] = useState(() => { + const prev = loadSession() + if (!prev) return null + + const agent = createAgent(prev.service) + agent.resumeSession(prev) + return agent + }) + + const signIn = useCallback( + async ({ + identifier, + password, + authFactorToken, + service, + }: { + identifier: string + password: string + authFactorToken?: string + service: string + }) => { + const agent = createAgent(service) + await agent.login({ identifier, password, authFactorToken }) + setAgent(agent) + }, + [createAgent], + ) + + return useMemo(() => ({ signIn, agent }), [signIn, agent]) +} + +const SESSION_KEY = '@@ATPROTO/SESSION' + +function loadSession(): Session | undefined { + try { + const str = localStorage.getItem(SESSION_KEY) + const obj: unknown = str ? JSON.parse(str) : undefined + if ( + obj && + obj['service'] && + obj['refreshJwt'] && + obj['accessJwt'] && + obj['handle'] && + obj['did'] + ) { + return obj as Session + } + return undefined + } catch (e) { + return undefined + } +} + +function saveSession(session: Session) { + localStorage.setItem(SESSION_KEY, JSON.stringify(session)) +} + +function deleteSession() { + localStorage.removeItem(SESSION_KEY) +} diff --git a/packages/oauth/oauth-client-browser/example/src/auth/auth-form.tsx b/packages/oauth/oauth-client-browser/example/src/auth/auth-form.tsx new file mode 100644 index 00000000000..336ef420f45 --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/src/auth/auth-form.tsx @@ -0,0 +1,62 @@ +import { useCallback, useEffect, useState } from 'react' + +import { AtpSignIn, AtpSignInForm } from './atp/atp-sign-in-form' +import { OAuthSignIn, OAuthSignInForm } from './oauth/oauth-sign-in-form' + +export function AuthForm({ + atpSignIn, + oauthSignIn, +}: { + atpSignIn?: AtpSignIn + oauthSignIn?: OAuthSignIn +}) { + const defaultMethod = useCallback( + () => (oauthSignIn ? 'oauth' : atpSignIn ? 'atp' : undefined), + [], + ) + + const [method, setMethod] = useState( + defaultMethod, + ) + + useEffect(() => { + if (method === 'oauth' && !oauthSignIn) { + setMethod(defaultMethod) + } else if (method === 'atp' && !atpSignIn) { + setMethod(defaultMethod) + } else if (!method) { + setMethod(defaultMethod) + } + }, [atpSignIn, oauthSignIn, defaultMethod, method]) + + // Tailwind css tabs + return ( +
+
+ + + +
+ + {method === 'oauth' && } + {method === 'atp' && } + {method == null &&
No auth method available
} +
+ ) +} diff --git a/packages/oauth/oauth-client-browser/example/src/auth/auth-provider.tsx b/packages/oauth/oauth-client-browser/example/src/auth/auth-provider.tsx new file mode 100644 index 00000000000..3841106fa48 --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/src/auth/auth-provider.tsx @@ -0,0 +1,68 @@ +'use client' + +import { Agent } from '@atproto/api' +import { createContext, ReactNode, useContext, useMemo } from 'react' + +import { useAtpAuth } from './atp/use-atp-auth' +import { AuthForm } from './auth-form' +import { useOAuth, UseOAuthOptions } from './oauth/use-oauth' + +export type AuthContext = { + pdsAgent: Agent + signOut: () => void +} + +const AuthContext = createContext(null) + +export const AuthProvider = ({ + children, + ...options +}: { + children: ReactNode +} & UseOAuthOptions) => { + const { + isLoginPopup, + isInitializing, + client: oauthClient, + agent: oauthAgent, + signIn: oauthSignIn, + } = useOAuth(options) + + const { agent: atpAgent, signIn: atpSignIn } = useAtpAuth() + + const value = useMemo( + () => + oauthAgent + ? { pdsAgent: oauthAgent, signOut: () => oauthAgent.signOut() } + : atpAgent + ? { pdsAgent: atpAgent, signOut: () => atpAgent.logout() } + : null, + [atpAgent, oauthAgent], + ) + + if (isLoginPopup) { + return
This window can be closed
+ } + + if (isInitializing) { + return
Initializing...
+ } + + if (!value) { + return ( + + ) + } + + return {children} +} + +export function useAuthContext(): AuthContext { + const context = useContext(AuthContext) + if (context) return context + + throw new Error(`useAuthContext() must be used within an `) +} diff --git a/packages/oauth/oauth-client-browser/example/src/auth/oauth/oauth-sign-in-form.tsx b/packages/oauth/oauth-client-browser/example/src/auth/oauth/oauth-sign-in-form.tsx new file mode 100644 index 00000000000..72b07c67c87 --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/src/auth/oauth/oauth-sign-in-form.tsx @@ -0,0 +1,85 @@ +import { AuthorizeOptions } from '@atproto/oauth-client-browser' +import { FormEvent, useCallback, useState } from 'react' + +export type OAuthSignIn = (input: string, options?: AuthorizeOptions) => unknown + +/** + * @returns Nice tailwind css form asking to enter either a handle or the host + * to use to login. + */ +export function OAuthSignInForm({ + signIn, + ...props +}: { + signIn: OAuthSignIn +} & Omit, 'onSubmit'>) { + const [value, setValue] = useState('') + const [error, setError] = useState(null) + const [loading, setLoading] = useState(false) + + const onSubmit = useCallback( + async (e: FormEvent) => { + e.preventDefault() + if (loading) return + + setError(null) + setLoading(true) + + try { + if (value.startsWith('did:')) { + if (value.length > 5) await signIn(value) + else setError('DID must be at least 6 characters') + } else if ( + value.startsWith('https://') || + value.startsWith('http://') + ) { + const url = new URL(value) + if (value !== url.origin) throw new Error('PDS URL must be a origin') + await signIn(value) + } else if (value.includes('.') && value.length > 3) { + const handle = value.startsWith('@') ? value.slice(1) : value + if (handle.length > 3) await signIn(handle) + else setError('Handle must be at least 4 characters') + } + + throw new Error('Please provide a valid handle, DID or PDS URL') + } catch (err) { + setError((err as any)?.message || String(err)) + } finally { + setLoading(false) + } + }, + [loading, value, signIn], + ) + + return ( +
+
+
+ setValue(e.target.value)} + /> + + {loading && Loading...} +
+
+ + {error ?
{error}
: null} +
+ ) +} diff --git a/packages/oauth/oauth-client-browser/example/src/auth/oauth/use-oauth.ts b/packages/oauth/oauth-client-browser/example/src/auth/oauth/use-oauth.ts new file mode 100644 index 00000000000..79cdd5367aa --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/src/auth/oauth/use-oauth.ts @@ -0,0 +1,278 @@ +'use client' + +import { + AuthorizeOptions, + BrowserOAuthClient, + BrowserOAuthClientLoadOptions, + BrowserOAuthClientOptions, + LoginContinuedInParentWindowError, + OAuthAtpAgent, +} from '@atproto/oauth-client-browser' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' + +export type OnRestored = (agent: OAuthAtpAgent | null) => void +export type OnSignedIn = (agent: OAuthAtpAgent, state: null | string) => void +export type OnSignedOut = () => void +export type GetState = () => + | undefined + | string + | PromiseLike + +function useCallbackRef any>( + fn: T, +): (this: ThisParameterType, ...args: Parameters) => ReturnType + +function useCallbackRef any>( + fn?: T, +): (this: ThisParameterType, ...args: Parameters) => void | ReturnType + +function useCallbackRef any>(fn?: T) { + const fnRef = useRef(fn) + useEffect(() => { + fnRef.current = fn + }, [fn]) + return useCallback(function ( + this: ThisParameterType, + ...args: Parameters + ): void | ReturnType { + const { current } = fnRef + if (current) return current.call(this, ...args) + }, []) +} + +type ClientOptions = + | { client: BrowserOAuthClient } + | Pick< + BrowserOAuthClientLoadOptions, + | 'clientId' + | 'handleResolver' + | 'responseMode' + | 'plcDirectoryUrl' + | 'fetch' + > + | Pick< + BrowserOAuthClientOptions, + | 'clientMetadata' + | 'handleResolver' + | 'responseMode' + | 'plcDirectoryUrl' + | 'fetch' + > + +function useOAuthClient(options: ClientOptions): null | BrowserOAuthClient +function useOAuthClient( + options: Partial< + { client: BrowserOAuthClient } & BrowserOAuthClientLoadOptions & + BrowserOAuthClientOptions + >, +) { + const { + client: clientInput, + clientId, + clientMetadata, + handleResolver, + responseMode, + plcDirectoryUrl, + } = options + + const [client, setClient] = useState( + clientInput || null, + ) + const fetch = useCallbackRef(options.fetch || globalThis.fetch) + + useEffect(() => { + if (clientInput) { + setClient(clientInput) + } else if (!handleResolver) { + throw new TypeError('handleResolver is required') + } else if (clientMetadata || !clientId) { + const client = new BrowserOAuthClient({ + clientMetadata, + handleResolver, + responseMode, + plcDirectoryUrl, + fetch, + }) + setClient(client) + return () => client.dispose() + } else { + const ac = new AbortController() + const { signal } = ac + + setClient(null) + + void BrowserOAuthClient.load({ + clientId, + handleResolver, + responseMode, + plcDirectoryUrl, + fetch, + signal, + }).then( + (client) => { + if (!signal.aborted) { + signal.addEventListener('abort', () => client.dispose(), { + once: true, + }) + setClient(client) + } else { + client.dispose() + } + }, + (err) => { + if (!signal.aborted) throw err + }, + ) + + return () => ac.abort() + } + }, [ + clientInput, + clientId, + clientMetadata, + handleResolver, + responseMode, + plcDirectoryUrl, + fetch, + ]) + + return client +} + +export type UseOAuthOptions = ClientOptions & { + onRestored?: OnRestored + onSignedIn?: OnSignedIn + onSignedOut?: OnSignedOut + getState?: GetState +} + +export function useOAuth(options: UseOAuthOptions) { + const onRestored = useCallbackRef(options.onRestored) + const onSignedIn = useCallbackRef(options.onSignedIn) + const onSignedOut = useCallbackRef(options.onSignedOut) + const getState = useCallbackRef(options.getState) + + const clientForInit = useOAuthClient(options) + + const [agent, setAgent] = useState(null) + const [client, setClient] = useState(null) + const [isInitializing, setIsInitializing] = useState(true) + const [isLoginPopup, setIsLoginPopup] = useState(false) + + const clientForInitRef = useRef() + useEffect(() => { + // In strict mode, we don't want to re-init() the client if it's the same + if (clientForInitRef.current === clientForInit) return + clientForInitRef.current = clientForInit + + setAgent(null) + setClient(null) + setIsLoginPopup(false) + setIsInitializing(clientForInit != null) + + clientForInit + ?.init() + .then( + async (r) => { + if (clientForInitRef.current !== clientForInit) return + + setClient(clientForInit) + if (r) { + setAgent(r.agent) + + if ('state' in r) { + await onSignedIn(r.agent, r.state) + } else { + await onRestored(r.agent) + } + } else { + await onRestored(null) + } + }, + async (err) => { + if (clientForInitRef.current !== clientForInit) return + if (err instanceof LoginContinuedInParentWindowError) { + setIsLoginPopup(true) + return + } + + setClient(clientForInit) + await onRestored(null) + + console.error('Failed to init:', err) + }, + ) + .finally(() => { + if (clientForInitRef.current !== clientForInit) return + + setIsInitializing(false) + }) + }, [clientForInit, onSignedIn, onRestored]) + + useEffect(() => { + if (!client) return + + const controller = new AbortController() + const { signal } = controller + + client.addEventListener( + 'updated', + ({ detail: { sub } }) => { + if (!agent || agent.did !== sub) { + setAgent(null) + client.restore(sub, false).then((agent) => { + if (!signal.aborted) setAgent(agent) + }) + } + }, + { signal }, + ) + + if (agent) { + client.addEventListener( + 'deleted', + ({ detail: { sub } }) => { + if (agent.did === sub) { + setAgent(null) + void onSignedOut() + } + }, + { signal }, + ) + } + + void agent?.refreshIfNeeded() + + return () => { + controller.abort() + } + }, [client, agent, onSignedOut]) + + const signIn = useCallback( + async (input: string, options?: AuthorizeOptions) => { + if (!client) throw new Error('Client not initialized') + + const state = options?.state ?? (await getState()) ?? undefined + const agent = await client.signIn(input, { ...options, state }) + setAgent(agent) + await onSignedIn(agent, state ?? null) + return agent + }, + [client, getState, onSignedIn], + ) + + // Memoize the return value to avoid re-renders in consumers + return useMemo( + () => ({ + isInitializing, + isInitialized: client != null, + isLoginPopup, + + signIn, + + client, + agent, + }), + [isInitializing, isLoginPopup, agent, client, signIn], + ) +} diff --git a/packages/oauth/oauth-client-browser/example/src/login-form.tsx b/packages/oauth/oauth-client-browser/example/src/login-form.tsx deleted file mode 100644 index ac0d11b21fe..00000000000 --- a/packages/oauth/oauth-client-browser/example/src/login-form.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { FormEvent, useEffect, useState } from 'react' - -/** - * @returns Nice tailwind css form asking to enter either a handle or the host - * to use to login. - */ -export default function LoginForm({ - onLogin, - loading, - error = null, - ...props -}: { - loading?: boolean - error?: null | string - onLogin: (input: string, options?: { display?: 'popup' | 'page' }) => void -} & React.HTMLAttributes) { - const [value, setValue] = useState('') - const [display, setDisplay] = useState<'popup' | 'page'>('popup') - const [localError, setLocalError] = useState(error) - - useEffect(() => { - setLocalError(null) - }, [value]) - - useEffect(() => { - setLocalError(error) - }, [error]) - - const onSubmit = (e: FormEvent) => { - e.preventDefault() - if (loading) return - - if (value.startsWith('did:')) { - if (value.length > 5) onLogin(value, { display }) - else setLocalError('DID must be at least 6 characters') - return - } - - if (value.startsWith('https://') || value.startsWith('http://')) { - try { - const url = new URL(value) - if (value !== url.origin) throw new Error('PDS URL must be a origin') - onLogin(value, { display }) - } catch (err) { - setLocalError((err as any)?.message || String(err)) - } - return - } - - if (value.includes('.') && value.length > 3) { - const handle = value.startsWith('@') ? value.slice(1) : value - if (handle.length > 3) onLogin(handle, { display }) - else setLocalError('Handle must be at least 4 characters') - return - } - - setLocalError('Please provide a valid handle, DID or PDS URL') - } - - return ( -
-
-
- -
- - {/*
*/} - -
- setValue(e.target.value)} - /> - -
-
- - {localError ? ( -
{localError}
- ) : null} -
- ) -} diff --git a/packages/oauth/oauth-client-browser/example/src/main.tsx b/packages/oauth/oauth-client-browser/example/src/main.tsx index d8db51390fa..aa85dcc1379 100644 --- a/packages/oauth/oauth-client-browser/example/src/main.tsx +++ b/packages/oauth/oauth-client-browser/example/src/main.tsx @@ -4,9 +4,19 @@ import React from 'react' import ReactDOM from 'react-dom/client' import App from './app' +import { AuthProvider } from './auth/auth-provider' ReactDOM.createRoot(document.getElementById('root')!).render( - + + + , ) diff --git a/packages/oauth/oauth-client-browser/example/src/oauth.ts b/packages/oauth/oauth-client-browser/example/src/oauth.ts deleted file mode 100644 index 6a24434f8c1..00000000000 --- a/packages/oauth/oauth-client-browser/example/src/oauth.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { OAuthAgent, AuthorizeOptions } from '@atproto/oauth-client' -import { - BrowserOAuthClient, - LoginContinuedInParentWindowError, -} from '@atproto/oauth-client-browser' -import { useCallback, useEffect, useRef, useState } from 'react' - -export function useOAuth(client: BrowserOAuthClient) { - const [agent, setAgent] = useState(null) - const [loading, setLoading] = useState(true) - - const clientRef = useRef() - useEffect(() => { - // In strict mode, we don't want to reinitialize the client if it's the same - if (clientRef.current === client) return - clientRef.current = client - - setLoading(true) - setAgent(null) - - client - .init() - .then(async (r) => { - if (clientRef.current !== client) return - - setAgent(r?.agent || null) - }) - .catch((err) => { - console.error('Failed to init:', err) - - if (clientRef.current !== client) return - if (err instanceof LoginContinuedInParentWindowError) return - - setAgent(null) - }) - .finally(() => { - if (clientRef.current !== client) return - - setLoading(false) - }) - }, [client]) - - useEffect(() => { - if (!agent) return - - const clear = ({ detail }: { detail: { sub: string } }) => { - if (detail.sub === agent.sub) { - setAgent(null) - } - } - - client.addEventListener('deleted', clear) - - return () => { - client.removeEventListener('deleted', clear) - } - }, [client, agent]) - - const signOut = useCallback(async () => { - if (!agent) return - - setAgent(null) - setLoading(true) - - try { - await agent.signOut() - } catch (err) { - console.error('Failed to clear credentials', err) - throw err - } finally { - setLoading(false) - } - }, [agent]) - - const signIn = useCallback( - async (input: string, options?: AuthorizeOptions) => { - if (agent) return - - setLoading(true) - - try { - const agent = await client.signIn(input, options) - setAgent(agent) - } catch (err) { - console.error('Failed to login', err) - throw err - } finally { - setLoading(false) - } - }, - [agent, client], - ) - - return { - agent, - loading, - signedIn: agent != null, - signIn, - signOut, - } -} diff --git a/packages/oauth/oauth-client-browser/package.json b/packages/oauth/oauth-client-browser/package.json index 79ea65bafa3..e68c887d127 100644 --- a/packages/oauth/oauth-client-browser/package.json +++ b/packages/oauth/oauth-client-browser/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/oauth-client-browser", - "version": "0.1.1", + "version": "0.1.3", "license": "MIT", "description": "ATPROTO OAuth client for the browser (relies on WebCrypto & Indexed DB)", "keywords": [ diff --git a/packages/oauth/oauth-client-browser/src/browser-oauth-client.ts b/packages/oauth/oauth-client-browser/src/browser-oauth-client.ts index 6a69c815eec..ebfdf6a5c19 100644 --- a/packages/oauth/oauth-client-browser/src/browser-oauth-client.ts +++ b/packages/oauth/oauth-client-browser/src/browser-oauth-client.ts @@ -3,7 +3,7 @@ import { AuthorizeOptions, ClientMetadata, Fetch, - OAuthAgent, + OAuthAtpAgent, OAuthCallbackError, OAuthClient, SessionEventMap, @@ -172,7 +172,7 @@ export class BrowserOAuthClient extends OAuthClient implements Disposable { const signInResult = await this.signInCallback() if (signInResult) { - localStorage.setItem(`${NAMESPACE}(sub)`, signInResult.agent.sub) + localStorage.setItem(`${NAMESPACE}(sub)`, signInResult.agent.did) return signInResult } @@ -190,7 +190,7 @@ export class BrowserOAuthClient extends OAuthClient implements Disposable { async restore(sub: string, refresh?: boolean) { const agent = await super.restore(sub, refresh) - localStorage.setItem(`${NAMESPACE}(sub)`, agent.sub) + localStorage.setItem(`${NAMESPACE}(sub)`, agent.did) return agent } @@ -202,7 +202,7 @@ export class BrowserOAuthClient extends OAuthClient implements Disposable { signIn( input: string, options: AuthorizeOptions & { display: 'popup' }, - ): Promise + ): Promise signIn(input: string, options?: AuthorizeOptions): Promise async signIn(input: string, options?: AuthorizeOptions) { if (options?.display === 'popup') { @@ -239,7 +239,7 @@ export class BrowserOAuthClient extends OAuthClient implements Disposable { async signInPopup( input: string, options?: Omit, - ): Promise { + ): Promise { // Open new window asap to prevent popup busting by browsers const popupFeatures = 'width=600,height=600,menubar=no,toolbar=no' let popup: Window | null = window.open( @@ -266,7 +266,7 @@ export class BrowserOAuthClient extends OAuthClient implements Disposable { popup?.focus() - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const popupChannel = new BroadcastChannel(POPUP_CHANNEL_NAME) const cleanup = () => { @@ -381,7 +381,7 @@ export class BrowserOAuthClient extends OAuthClient implements Disposable { key: result.state.slice(POPUP_STATE_PREFIX.length), result: { status: 'fulfilled', - value: result.agent.sub, + value: result.agent.did, }, }) diff --git a/packages/oauth/oauth-client-node/CHANGELOG.md b/packages/oauth/oauth-client-node/CHANGELOG.md index 16b34fc5f9f..f5679791c4d 100644 --- a/packages/oauth/oauth-client-node/CHANGELOG.md +++ b/packages/oauth/oauth-client-node/CHANGELOG.md @@ -1,5 +1,27 @@ # @atproto/oauth-client-node +## 0.0.3 + +### Patch Changes + +- Updated dependencies []: + - @atproto/oauth-client@0.1.3 + +## 0.0.2 + +### Patch Changes + +- [#2483](https://github.com/bluesky-social/atproto/pull/2483) [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Better implement aptroto OAuth spec + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd)]: + - @atproto/oauth-client@0.1.2 + - @atproto/jwk-jose@0.1.2 + - @atproto/oauth-types@0.1.2 + - @atproto/did@0.1.1 + - @atproto/jwk-webcrypto@0.1.2 + - @atproto-labs/handle-resolver-node@0.1.2 + - @atproto-labs/did-resolver@0.1.2 + ## 0.0.1 ### Patch Changes diff --git a/packages/oauth/oauth-client-node/README.md b/packages/oauth/oauth-client-node/README.md index ea7798d6c49..67c3cb9c1e8 100644 --- a/packages/oauth/oauth-client-node/README.md +++ b/packages/oauth/oauth-client-node/README.md @@ -1,7 +1,8 @@ -# ATPROTO OAuth Client for NodeJS +# atproto OAuth Client for NodeJS This package implements all the OAuth features required by [ATPROTO] (PKCE, -etc.) to run in a NodeJS based environment (Election APP or Backend). +etc.) to run in a NodeJS based environment such as desktop apps built with +Electron or traditional web app backends built with frameworks like Express. ## Setup @@ -39,7 +40,7 @@ const client = new NodeOAuthClientOptions({ grant_types: ['authorization_code', 'refresh_token'], response_types: ['code'], application_type: 'web', - token_endpoint_auth_method: 'client_secret_jwt', + token_endpoint_auth_method: 'private_key_jwt', dpop_bound_access_tokens: true, jwks_uri: 'https://my-app.com/jwks.json', }, @@ -280,6 +281,69 @@ const requestLock: RuntimeLock = async (key, fn) => { } ``` +## Usage with `@atproto/api` + +`@atproto/oauth-client-*` packages all return an `ApiClient` instance upon +successful authentication. This instance can be used to make authenticated +requests using all the `ApiClient` methods defined in [[API]] (non exhaustive +list of examples below). Any refresh of the credentials will happen under the +hood, and the new tokens will be saved in the session store. + +```ts +const agent = await client.restore('did:plc:123') + +// Feeds and content +await agent.getTimeline(params, opts) +await agent.getAuthorFeed(params, opts) +await agent.getPostThread(params, opts) +await agent.getPost(params) +await agent.getPosts(params, opts) +await agent.getLikes(params, opts) +await agent.getRepostedBy(params, opts) +await agent.post(record) +await agent.deletePost(postUri) +await agent.like(uri, cid) +await agent.deleteLike(likeUri) +await agent.repost(uri, cid) +await agent.deleteRepost(repostUri) +await agent.uploadBlob(data, opts) + +// Social graph +await agent.getFollows(params, opts) +await agent.getFollowers(params, opts) +await agent.follow(did) +await agent.deleteFollow(followUri) + +// Actors +await agent.getProfile(params, opts) +await agent.upsertProfile(updateFn) +await agent.getProfiles(params, opts) +await agent.getSuggestions(params, opts) +await agent.searchActors(params, opts) +await agent.searchActorsTypeahead(params, opts) +await agent.mute(did) +await agent.unmute(did) +await agent.muteModList(listUri) +await agent.unmuteModList(listUri) +await agent.blockModList(listUri) +await agent.unblockModList(listUri) + +// Notifications +await agent.listNotifications(params, opts) +await agent.countUnreadNotifications(params, opts) +await agent.updateSeenNotifications() + +// Identity +await agent.resolveHandle(params, opts) +await agent.updateHandle(params, opts) + +// etc. + +if (agent instanceof OAuthAtpAgent) { + agent.signOut() +} +``` + ## Advances use-cases ### Listening for session updates and deletion diff --git a/packages/oauth/oauth-client-node/package.json b/packages/oauth/oauth-client-node/package.json index b8e0bd8780f..77c29af10f8 100644 --- a/packages/oauth/oauth-client-node/package.json +++ b/packages/oauth/oauth-client-node/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/oauth-client-node", - "version": "0.0.1", + "version": "0.0.3", "license": "MIT", "description": "ATPROTO OAuth client for the NodeJS", "keywords": [ diff --git a/packages/oauth/oauth-client/CHANGELOG.md b/packages/oauth/oauth-client/CHANGELOG.md index c285e3bacd7..7ca69406da2 100644 --- a/packages/oauth/oauth-client/CHANGELOG.md +++ b/packages/oauth/oauth-client/CHANGELOG.md @@ -1,5 +1,29 @@ # @atproto/oauth-client +## 0.1.3 + +### Patch Changes + +- Updated dependencies [[`22af354a5`](https://github.com/bluesky-social/atproto/commit/22af354a5db595d7cbc0e65f02601de3565337e1)]: + - @atproto/api@0.13.1 + +## 0.1.2 + +### Patch Changes + +- [#2483](https://github.com/bluesky-social/atproto/pull/2483) [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Misc fixes for confidential client usage + +- [#2483](https://github.com/bluesky-social/atproto/pull/2483) [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Better implement aptroto OAuth spec + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd)]: + - @atproto/oauth-types@0.1.2 + - @atproto-labs/handle-resolver@0.1.2 + - @atproto/did@0.1.1 + - @atproto/xrpc@0.6.0 + - @atproto/api@0.13.0 + - @atproto-labs/identity-resolver@0.1.2 + - @atproto-labs/did-resolver@0.1.2 + ## 0.1.1 ### Patch Changes diff --git a/packages/oauth/oauth-client/README.md b/packages/oauth/oauth-client/README.md index ba09126f568..b7c5f4139c8 100644 --- a/packages/oauth/oauth-client/README.md +++ b/packages/oauth/oauth-client/README.md @@ -142,17 +142,23 @@ Make user visit `url`. Then, once it was redirected to the callback URI, perform const params = new URLSearchParams('code=...&state=...') // Process the callback using the OAuth client -const { agent, state } = await client.callback(params) +const result = await client.callback(params) // Verify the state (e.g. to link to an internal user) -state === '434321' // true +result.state === '434321' // true + +const agent = result.agent // Make an authenticated request to the server. New credentials will be // automatically fetched if needed (causing sessionStore.set() to be called). -await result.agent.request('/xrpc/foo.bar') +await agent.post({ + text: 'Hello, world!', +}) -// revoke credentials on the server (causing sessionStore.del() to be called) -await agent.signOut() +if (agent instanceof AtpAgent) { + // revoke credentials on the server (causing sessionStore.del() to be called) + await agent.logout() +} ``` ## Advances use-cases diff --git a/packages/oauth/oauth-client/package.json b/packages/oauth/oauth-client/package.json index 272e668f9af..6f9cbffe6df 100644 --- a/packages/oauth/oauth-client/package.json +++ b/packages/oauth/oauth-client/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/oauth-client", - "version": "0.1.1", + "version": "0.1.3", "license": "MIT", "description": "OAuth client for ATPROTO PDS. This package serves as common base for environment-specific implementations (NodeJS, Browser, React-Native).", "keywords": [ @@ -31,9 +31,11 @@ "@atproto-labs/identity-resolver": "workspace:*", "@atproto-labs/simple-store": "workspace:*", "@atproto-labs/simple-store-memory": "workspace:*", + "@atproto/api": "workspace:*", "@atproto/did": "workspace:*", "@atproto/jwk": "workspace:*", "@atproto/oauth-types": "workspace:*", + "@atproto/xrpc": "workspace:*", "multiformats": "^9.9.0", "zod": "^3.23.8" }, diff --git a/packages/oauth/oauth-client/src/index.ts b/packages/oauth/oauth-client/src/index.ts index b7e46c7f918..2fe62de455f 100644 --- a/packages/oauth/oauth-client/src/index.ts +++ b/packages/oauth/oauth-client/src/index.ts @@ -10,6 +10,7 @@ export * from '@atproto/did' export * from '@atproto/oauth-types' export * from './oauth-agent.js' +export * from './oauth-atp-agent.js' export * from './oauth-authorization-server-metadata-resolver.js' export * from './oauth-callback-error.js' export * from './oauth-client.js' diff --git a/packages/oauth/oauth-client/src/oauth-atp-agent.ts b/packages/oauth/oauth-client/src/oauth-atp-agent.ts new file mode 100644 index 00000000000..8b35492d5de --- /dev/null +++ b/packages/oauth/oauth-client/src/oauth-atp-agent.ts @@ -0,0 +1,48 @@ +import { Agent } from '@atproto/api' +import { XRPCError } from '@atproto/xrpc' +import { FetchError, FetchResponseError } from '@atproto-labs/fetch' + +import { OAuthAgent } from './oauth-agent.js' + +export class OAuthAtpAgent extends Agent { + constructor(readonly oauthAgent: OAuthAgent) { + super(async (url, init) => { + try { + return await this.oauthAgent.request(url, init) + } catch (cause) { + if (cause instanceof FetchError) { + const { statusCode, message } = cause + throw new XRPCError(statusCode, undefined, message, undefined, { + cause, + }) + } + + if (cause instanceof FetchResponseError) { + const { statusCode, message, response } = cause + const headers = Object.fromEntries(response.headers.entries()) + throw new XRPCError(statusCode, undefined, message, headers, { + cause, + }) + } + + throw cause + } + }) + } + + clone(): OAuthAtpAgent { + return this.copyInto(new OAuthAtpAgent(this.oauthAgent)) + } + + get did(): string { + return this.oauthAgent.sub + } + + async signOut() { + await this.oauthAgent.signOut() + } + + public async refreshIfNeeded(): Promise { + await this.oauthAgent.refreshIfNeeded() + } +} diff --git a/packages/oauth/oauth-client/src/oauth-authorization-server-metadata-resolver.ts b/packages/oauth/oauth-client/src/oauth-authorization-server-metadata-resolver.ts index 44e7ce60be4..083bf9e1f1c 100644 --- a/packages/oauth/oauth-client/src/oauth-authorization-server-metadata-resolver.ts +++ b/packages/oauth/oauth-client/src/oauth-authorization-server-metadata-resolver.ts @@ -93,6 +93,14 @@ export class OAuthAuthorizationServerMetadataResolver extends CachedGetter< throw new TypeError(`Invalid issuer ${metadata.issuer}`) } + // ATPROTO requires client_id_metadata_document + // http://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html + if (metadata.client_id_metadata_document_supported !== true) { + throw new TypeError( + `Authorization server "${issuer}" does not support client_id_metadata_document`, + ) + } + return metadata } } diff --git a/packages/oauth/oauth-client/src/oauth-client.ts b/packages/oauth/oauth-client/src/oauth-client.ts index dc024eae439..326e98373df 100644 --- a/packages/oauth/oauth-client/src/oauth-client.ts +++ b/packages/oauth/oauth-client/src/oauth-client.ts @@ -24,6 +24,7 @@ import { import { FALLBACK_ALG } from './constants.js' import { TokenRevokedError } from './errors/token-revoked-error.js' import { OAuthAgent } from './oauth-agent.js' +import { OAuthAtpAgent } from './oauth-atp-agent.js' import { AuthorizationServerMetadataCache, OAuthAuthorizationServerMetadataResolver, @@ -256,16 +257,10 @@ export class OAuthClient extends CustomEventTarget { throw new TypeError('Invalid redirect_uri') } - const signal = options?.signal - const { identity, metadata } = /^https?:\/\//.test(input) - ? // Allow using an entryway url directly as login input (e.g. when the - // user forgot their handle, or when the handle does not resolve to a - // DID) - { - identity: undefined, - metadata: await this.oauthResolver.resolveMetadata(input, { signal }), - } - : await this.oauthResolver.resolve(input, { signal }) + const { identity, metadata } = await this.oauthResolver.resolve( + input, + options, + ) const nonce = await this.runtime.generateNonce() const pkce = await this.runtime.generatePKCE() @@ -367,7 +362,7 @@ export class OAuthClient extends CustomEventTarget { } async callback(params: URLSearchParams): Promise<{ - agent: OAuthAgent + agent: OAuthAtpAgent state: string | null }> { const responseJwt = params.get('response') @@ -478,7 +473,7 @@ export class OAuthClient extends CustomEventTarget { * * @param refresh See {@link SessionGetter.getSession} */ - async restore(sub: string, refresh?: boolean): Promise { + async restore(sub: string, refresh?: boolean): Promise { const { dpopKey, tokenSet } = await this.sessionGetter.getSession( sub, refresh, @@ -509,7 +504,7 @@ export class OAuthClient extends CustomEventTarget { } } - createAgent(server: OAuthServerAgent, sub: string): OAuthAgent { + createAgent(server: OAuthServerAgent, sub: string): OAuthAtpAgent { const oauthAgent = new OAuthAgent( server, sub, @@ -517,6 +512,6 @@ export class OAuthClient extends CustomEventTarget { this.fetch, ) - return oauthAgent + return new OAuthAtpAgent(oauthAgent) } } diff --git a/packages/oauth/oauth-client/src/oauth-protected-resource-metadata-resolver.ts b/packages/oauth/oauth-client/src/oauth-protected-resource-metadata-resolver.ts index 52c9e8828a2..c54e2b957eb 100644 --- a/packages/oauth/oauth-client/src/oauth-protected-resource-metadata-resolver.ts +++ b/packages/oauth/oauth-client/src/oauth-protected-resource-metadata-resolver.ts @@ -10,6 +10,7 @@ import { SimpleStore, } from '@atproto-labs/simple-store' import { + ALLOW_UNSECURE_ORIGINS, OAuthProtectedResourceMetadata, oauthProtectedResourceMetadataSchema, } from '@atproto/oauth-types' @@ -45,10 +46,14 @@ export class OAuthProtectedResourceMetadataResolver extends CachedGetter< options?: GetCachedOptions, ): Promise { const { protocol, origin } = new URL(resource) - if (protocol !== 'https:' && protocol !== 'http:') { - throw new TypeError(`Invalid resource server ${protocol}`) + if ( + protocol === 'https:' || + (protocol === 'http:' && ALLOW_UNSECURE_ORIGINS) + ) { + return super.get(origin, options) } - return super.get(origin, options) + + throw new TypeError(`Forbidden resource sercure protocol "${protocol}"`) } private async fetchMetadata( @@ -62,7 +67,7 @@ export class OAuthProtectedResourceMetadataResolver extends CachedGetter< const request = new Request(url, { signal: options?.signal, headers, - redirect: 'error', // response must be 200 OK + redirect: 'manual', // response must be 200 OK }) const response = await this.fetch(request) diff --git a/packages/oauth/oauth-client/src/oauth-resolver.ts b/packages/oauth/oauth-client/src/oauth-resolver.ts index ea2f7048622..fdd73d83853 100644 --- a/packages/oauth/oauth-client/src/oauth-resolver.ts +++ b/packages/oauth/oauth-client/src/oauth-resolver.ts @@ -1,16 +1,19 @@ import { - ResolveIdentityOptions, IdentityResolver, ResolvedIdentity, + ResolveIdentityOptions, } from '@atproto-labs/identity-resolver' -import { OAuthAuthorizationServerMetadata } from '@atproto/oauth-types' +import { + OAuthAuthorizationServerMetadata, + oauthIssuerIdentifierSchema, +} from '@atproto/oauth-types' -import { OAuthResolverError } from './oauth-resolver-error.js' import { GetCachedOptions, OAuthAuthorizationServerMetadataResolver, } from './oauth-authorization-server-metadata-resolver.js' import { OAuthProtectedResourceMetadataResolver } from './oauth-protected-resource-metadata-resolver.js' +import { OAuthResolverError } from './oauth-resolver-error.js' export type { GetCachedOptions } export type ResolveOAuthOptions = GetCachedOptions & ResolveIdentityOptions @@ -22,6 +25,75 @@ export class OAuthResolver { readonly authorizationServerMetadataResolver: OAuthAuthorizationServerMetadataResolver, ) {} + /** + * @param input - A handle, DID, PDS URL or Entryway URL + */ + public async resolve( + input: string, + options?: ResolveOAuthOptions, + ): Promise<{ + identity?: ResolvedIdentity + metadata: OAuthAuthorizationServerMetadata + }> { + // Allow using an entryway, or PDS url, directly as login input (e.g. + // when the user forgot their handle, or when the handle does not + // resolve to a DID) + return /^https?:\/\//.test(input) + ? this.resolveFromService(input, options) + : this.resolveFromIdentity(input, options) + } + + /** + * @note this method can be used to verify if a particular uri supports OAuth + * based sign-in (for compatibility with legacy implementation). + */ + public async resolveFromService( + input: string, + options?: ResolveOAuthOptions, + ): Promise<{ + metadata: OAuthAuthorizationServerMetadata + }> { + try { + // Assume first that input is a PDS URL (as required by ATPROTO) + const metadata = await this.getResourceServerMetadata(input, options) + return { metadata } + } catch (err) { + if (!options?.signal?.aborted && err instanceof OAuthResolverError) { + try { + // Fallback to trying to fetch as an issuer (Entryway) + const result = oauthIssuerIdentifierSchema.safeParse(input) + if (result.success) { + const metadata = await this.getAuthorizationServerMetadata( + result.data, + options, + ) + return { metadata } + } + } catch { + // Fallback failed, throw original error + } + } + + throw err + } + } + + public async resolveFromIdentity( + input: string, + options?: ResolveOAuthOptions, + ): Promise<{ + identity: ResolvedIdentity + metadata: OAuthAuthorizationServerMetadata + }> { + const identity = await this.resolveIdentity(input, options) + + options?.signal?.throwIfAborted() + + const metadata = await this.getResourceServerMetadata(identity.pds, options) + + return { identity, metadata } + } + public async resolveIdentity( input: string, options?: ResolveIdentityOptions, @@ -36,7 +108,7 @@ export class OAuthResolver { } } - public async resolveMetadata( + public async getAuthorizationServerMetadata( issuer: string, options?: GetCachedOptions, ): Promise { @@ -50,62 +122,49 @@ export class OAuthResolver { } } - public async resolvePdsMetadata( - pds: string | URL, + public async getResourceServerMetadata( + pdsUrl: string | URL, options?: GetCachedOptions, ) { try { const rsMetadata = await this.protectedResourceMetadataResolver.get( - pds, + pdsUrl, options, ) - const issuer = rsMetadata.authorization_servers?.[0] - if (!issuer) { + // ATPROTO requires one, and only one, authorization server entry + if (rsMetadata.authorization_servers?.length !== 1) { throw new OAuthResolverError( - `No authorization servers found for PDS: ${pds}`, + rsMetadata.authorization_servers?.length + ? `Unable to determine authorization server for PDS: ${pdsUrl}` + : `No authorization servers found for PDS: ${pdsUrl}`, ) } + const issuer = rsMetadata.authorization_servers![0]! + options?.signal?.throwIfAborted() - const asMetadata = await this.resolveMetadata(issuer, options) + const asMetadata = await this.getAuthorizationServerMetadata( + issuer, + options, + ) // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-05#section-4 if (asMetadata.protected_resources) { if (!asMetadata.protected_resources.includes(rsMetadata.resource)) { throw new OAuthResolverError( - `PDS "${pds}" not protected by issuer "${issuer}"`, + `PDS "${pdsUrl}" not protected by issuer "${issuer}"`, ) } } return asMetadata } catch (cause) { - options?.signal?.throwIfAborted() - throw OAuthResolverError.from( cause, - `Failed to resolve OAuth server metadata for resource: ${pds}`, + `Failed to resolve OAuth server metadata for resource: ${pdsUrl}`, ) } } - - public async resolve( - input: string, - options?: ResolveOAuthOptions, - ): Promise<{ - identity: ResolvedIdentity - metadata: OAuthAuthorizationServerMetadata - }> { - options?.signal?.throwIfAborted() - - const identity = await this.resolveIdentity(input, options) - - options?.signal?.throwIfAborted() - - const metadata = await this.resolvePdsMetadata(identity.pds, options) - - return { identity, metadata } - } } diff --git a/packages/oauth/oauth-client/src/oauth-server-agent.ts b/packages/oauth/oauth-client/src/oauth-server-agent.ts index ce89a45ed3e..b8fab44fd75 100644 --- a/packages/oauth/oauth-client/src/oauth-server-agent.ts +++ b/packages/oauth/oauth-client/src/oauth-server-agent.ts @@ -134,7 +134,9 @@ export class OAuthServerAgent { // @TODO (?) make timeout configurable using signal = timeoutSignal(10e3) - const resolved = await this.oauthResolver.resolve(sub, { signal }) + const resolved = await this.oauthResolver.resolveFromIdentity(sub, { + signal, + }) if (resolved.metadata.issuer !== this.serverMetadata.issuer) { // Best case scenario; the user switched PDS. Worst case scenario; a bad diff --git a/packages/oauth/oauth-client/src/oauth-server-factory.ts b/packages/oauth/oauth-client/src/oauth-server-factory.ts index def97d3b22e..a4e1164eaa4 100644 --- a/packages/oauth/oauth-client/src/oauth-server-factory.ts +++ b/packages/oauth/oauth-client/src/oauth-server-factory.ts @@ -19,7 +19,10 @@ export class OAuthServerFactory { ) {} async fromIssuer(issuer: string, dpopKey: Key, options?: GetCachedOptions) { - const serverMetadata = await this.resolver.resolveMetadata(issuer, options) + const serverMetadata = await this.resolver.getAuthorizationServerMetadata( + issuer, + options, + ) return this.fromMetadata(serverMetadata, dpopKey) } diff --git a/packages/oauth/oauth-client/src/refresh-error.ts b/packages/oauth/oauth-client/src/refresh-error.ts deleted file mode 100644 index 4bfc6283aea..00000000000 --- a/packages/oauth/oauth-client/src/refresh-error.ts +++ /dev/null @@ -1,9 +0,0 @@ -export class RefreshError extends Error { - constructor( - public readonly sub: string, - message: string, - options?: { cause?: unknown }, - ) { - super(message, options) - } -} diff --git a/packages/oauth/oauth-client/src/session-getter.ts b/packages/oauth/oauth-client/src/session-getter.ts index 88eebf6b52c..4ba3a5896e5 100644 --- a/packages/oauth/oauth-client/src/session-getter.ts +++ b/packages/oauth/oauth-client/src/session-getter.ts @@ -82,7 +82,7 @@ export class SessionGetter extends CachedGetter { // concurrency issues if multiple tabs/instances are trying to refresh // the same token. The chances of this happening when multiple instances // are started simultaneously is reduced by randomizing the expiry time - // (see isStale() bellow). Even so, There still exist chances that + // (see isStale() below). Even so, There still exist chances that // multiple tabs will try to refresh the token at the same time. The // best solution would be to use a mutex/lock to ensure that only one // instance is refreshing the token at a time. A simpler workaround is diff --git a/packages/oauth/oauth-client/src/util.ts b/packages/oauth/oauth-client/src/util.ts index b451317a3b3..7ae51ef0f13 100644 --- a/packages/oauth/oauth-client/src/util.ts +++ b/packages/oauth/oauth-client/src/util.ts @@ -50,6 +50,39 @@ export function contentMime(headers: Headers): string | undefined { return headers.get('content-type')?.split(';')[0]!.trim() } +/** + * Ponyfill for `CustomEvent` constructor. + */ +export const CustomEvent: typeof globalThis.CustomEvent = + globalThis.CustomEvent ?? + (() => { + class CustomEvent extends Event { + #detail: T | null + constructor(type: string, options?: CustomEventInit) { + if (!arguments.length) throw new TypeError('type argument is required') + super(type, options) + this.#detail = options?.detail ?? null + } + get detail() { + return this.#detail + } + } + + Object.defineProperties(CustomEvent.prototype, { + [Symbol.toStringTag]: { + writable: false, + enumerable: false, + configurable: true, + value: 'CustomEvent', + }, + detail: { + enumerable: true, + }, + }) + + return CustomEvent + })() + export class CustomEventTarget> { readonly eventTarget = new EventTarget() @@ -76,7 +109,10 @@ export class CustomEventTarget> { dispatchCustomEvent>( type: T, detail: EventDetailMap[T], + init?: EventInit, ): boolean { - return this.eventTarget.dispatchEvent(new CustomEvent(type, { detail })) + return this.eventTarget.dispatchEvent( + new CustomEvent(type, { ...init, detail }), + ) } } diff --git a/packages/oauth/oauth-client/src/validate-client-metadata.ts b/packages/oauth/oauth-client/src/validate-client-metadata.ts index 90477e8f500..7fb4e11048f 100644 --- a/packages/oauth/oauth-client/src/validate-client-metadata.ts +++ b/packages/oauth/oauth-client/src/validate-client-metadata.ts @@ -58,7 +58,7 @@ export function validateClientMetadata( ) } break - case 'client_secret_jwt': + case 'private_key_jwt': if (!keyset) { throw new TypeError(`Keyset is required for ${method} method`) } diff --git a/packages/oauth/oauth-provider/CHANGELOG.md b/packages/oauth/oauth-provider/CHANGELOG.md index c914bdab627..c845238ef23 100644 --- a/packages/oauth/oauth-provider/CHANGELOG.md +++ b/packages/oauth/oauth-provider/CHANGELOG.md @@ -1,5 +1,15 @@ # @atproto/oauth-provider +## 0.1.2 + +### Patch Changes + +- [#2483](https://github.com/bluesky-social/atproto/pull/2483) [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Remove unused file + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd)]: + - @atproto/jwk-jose@0.1.2 + - @atproto/oauth-types@0.1.2 + ## 0.1.1 ### Patch Changes diff --git a/packages/oauth/oauth-provider/package.json b/packages/oauth/oauth-provider/package.json index 9106007bffc..d228c2a3220 100644 --- a/packages/oauth/oauth-provider/package.json +++ b/packages/oauth/oauth-provider/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/oauth-provider", - "version": "0.1.1", + "version": "0.1.2", "license": "MIT", "description": "Generic OAuth2 and OpenID Connect provider for Node.js. Currently only supports features needed for Atproto.", "keywords": [ diff --git a/packages/oauth/oauth-provider/src/assets/app/components/error-card.tsx b/packages/oauth/oauth-provider/src/assets/app/components/error-card.tsx deleted file mode 100644 index 3a41112717b..00000000000 --- a/packages/oauth/oauth-provider/src/assets/app/components/error-card.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { HtmlHTMLAttributes } from 'react' -import { clsx } from '../lib/clsx' - -export type ErrorCardProps = { - message?: null | string - role?: 'alert' | 'status' -} - -export function ErrorCard({ - message, - - role = 'alert', - className, - ...attrs -}: Partial & - Omit, keyof ErrorCardProps | 'children'>) { - return ( -
- - - - -
-

- {typeof message === 'string' ? message : 'An unknown error occurred'} -

-
-
- ) -} diff --git a/packages/oauth/oauth-provider/src/token/token-store.ts b/packages/oauth/oauth-provider/src/token/token-store.ts index 2ecbbe4a7cd..53d8e9a9b90 100644 --- a/packages/oauth/oauth-provider/src/token/token-store.ts +++ b/packages/oauth/oauth-provider/src/token/token-store.ts @@ -45,7 +45,7 @@ export interface TokenStore { /** * Find a token by its refresh token. Note that previous refresh tokens - * should also return the token. The data model is reponsible for storing + * should also return the token. The data model is responsible for storing * old refresh tokens when a new one is issued. */ findTokenByRefreshToken( diff --git a/packages/oauth/oauth-types/CHANGELOG.md b/packages/oauth/oauth-types/CHANGELOG.md index fba3aec8d06..0aeb3d8b23d 100644 --- a/packages/oauth/oauth-types/CHANGELOG.md +++ b/packages/oauth/oauth-types/CHANGELOG.md @@ -1,5 +1,11 @@ # @atproto/oauth-types +## 0.1.2 + +### Patch Changes + +- [#2483](https://github.com/bluesky-social/atproto/pull/2483) [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Better implement aptroto OAuth spec + ## 0.1.1 ### Patch Changes diff --git a/packages/oauth/oauth-types/package.json b/packages/oauth/oauth-types/package.json index 31c372d4630..4b9fe46c41d 100644 --- a/packages/oauth/oauth-types/package.json +++ b/packages/oauth/oauth-types/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/oauth-types", - "version": "0.1.1", + "version": "0.1.2", "license": "MIT", "description": "OAuth typing & validation library", "keywords": [ diff --git a/packages/oauth/oauth-types/src/atproto-loopback-client-metadata.ts b/packages/oauth/oauth-types/src/atproto-loopback-client-metadata.ts index 715454ba8d1..f6a0346b91f 100644 --- a/packages/oauth/oauth-types/src/atproto-loopback-client-metadata.ts +++ b/packages/oauth/oauth-types/src/atproto-loopback-client-metadata.ts @@ -11,18 +11,25 @@ export function atprotoLoopbackClientMetadata( const { origin, pathname, searchParams } = parseOAuthClientIdUrl(clientId) + for (const name of searchParams.keys()) { + if (name !== 'redirect_uri') { + throw new TypeError(`Invalid query parameter ${name} in client ID`) + } + } + const redirectUris = searchParams.getAll('redirect_uri') + return { client_id: clientId, client_name: 'Loopback client', response_types: ['code id_token', 'code'], grant_types: ['authorization_code', 'implicit', 'refresh_token'], scope: 'openid profile offline_access', - redirect_uris: searchParams.has('redirect_uri') - ? (searchParams.getAll('redirect_uri') as [string, ...string[]]) - : (['127.0.0.1', '[::1]'].map( + redirect_uris: (redirectUris.length + ? redirectUris + : (['127.0.0.1', '[::1]'] as const).map( (ip) => Object.assign(new URL(pathname, origin), { hostname: ip }).href, - ) as [string, ...string[]]), + )) as [string, ...string[]], token_endpoint_auth_method: 'none', application_type: 'native', dpop_bound_access_tokens: true, diff --git a/packages/oauth/oauth-types/src/constants.ts b/packages/oauth/oauth-types/src/constants.ts index 21be83e0bb2..568d9da3187 100644 --- a/packages/oauth/oauth-types/src/constants.ts +++ b/packages/oauth/oauth-types/src/constants.ts @@ -1,3 +1,19 @@ +/** + * A variable that allows to determine if unsecure origins should be allowed + * in OAuth related URI's. This variable is only set to `true` when NODE_ENV + * is either `development` or `test`. + */ +export const ALLOW_UNSECURE_ORIGINS = (() => { + // try/catch to support running in a browser, including when process.env is + // shimmed (e.g. by webpack) + try { + const env = process.env.NODE_ENV + return env === 'development' || env === 'test' + } catch { + return false + } +})() + export const CLIENT_ASSERTION_TYPE_JWT_BEARER = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' diff --git a/packages/oauth/oauth-types/src/oauth-issuer-identifier.ts b/packages/oauth/oauth-types/src/oauth-issuer-identifier.ts index 7d4699cb13e..78ff77f4af1 100644 --- a/packages/oauth/oauth-types/src/oauth-issuer-identifier.ts +++ b/packages/oauth/oauth-types/src/oauth-issuer-identifier.ts @@ -1,19 +1,9 @@ import { z } from 'zod' - -// try/catch to support running in a browser, including when process.env is -// shimmed (e.g. by webpack) -const ALLOW_INSECURE = (() => { - try { - const env = process.env.NODE_ENV - return env === 'development' || env === 'test' - } catch { - return false - } -})() +import { ALLOW_UNSECURE_ORIGINS } from './constants.js' +import { safeUrl } from './util.js' export const oauthIssuerIdentifierSchema = z .string() - .url() .superRefine((value, ctx) => { // Validate the issuer (MIX-UP attacks) @@ -24,10 +14,16 @@ export const oauthIssuerIdentifierSchema = z }) } - const url = new URL(value) + const url = safeUrl(value) + if (!url) { + return ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Invalid url', + }) + } if (url.protocol !== 'https:') { - if (ALLOW_INSECURE && url.protocol === 'http:') { + if (ALLOW_UNSECURE_ORIGINS && url.protocol === 'http:') { // We'll allow HTTP in development mode } else { ctx.addIssue({ diff --git a/packages/oauth/oauth-types/src/util.ts b/packages/oauth/oauth-types/src/util.ts index aa31fa47813..98bc6dbcf6b 100644 --- a/packages/oauth/oauth-types/src/util.ts +++ b/packages/oauth/oauth-types/src/util.ts @@ -18,3 +18,11 @@ export function isLoopbackUrl(input: URL | string): boolean { const url = typeof input === 'string' ? new URL(input) : input return isLoopbackHost(url.hostname) } + +export function safeUrl(input: URL | string): URL | null { + try { + return new URL(input) + } catch { + return null + } +} diff --git a/packages/ozone/CHANGELOG.md b/packages/ozone/CHANGELOG.md index 17d927bc007..883ab189652 100644 --- a/packages/ozone/CHANGELOG.md +++ b/packages/ozone/CHANGELOG.md @@ -1,5 +1,39 @@ # @atproto/ozone +## 0.1.37 + +### Patch Changes + +- Updated dependencies [[`22af354a5`](https://github.com/bluesky-social/atproto/commit/22af354a5db595d7cbc0e65f02601de3565337e1)]: + - @atproto/api@0.13.1 + +## 0.1.36 + +### Patch Changes + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`2bdf75d7a`](https://github.com/bluesky-social/atproto/commit/2bdf75d7a63924c10e7a311f16cb447d595b933e), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd)]: + - @atproto/lexicon@0.4.1 + - @atproto/xrpc@0.6.0 + - @atproto/api@0.13.0 + - @atproto/xrpc-server@0.6.1 + +## 0.1.35 + +### Patch Changes + +- Updated dependencies [[`dc471da26`](https://github.com/bluesky-social/atproto/commit/dc471da267955d0962a8affaf983df60d962d97c), [`dc471da26`](https://github.com/bluesky-social/atproto/commit/dc471da267955d0962a8affaf983df60d962d97c)]: + - @atproto/api@0.12.29 + - @atproto/xrpc-server@0.6.0 + +## 0.1.34 + +### Patch Changes + +- [#2676](https://github.com/bluesky-social/atproto/pull/2676) [`951a3df15`](https://github.com/bluesky-social/atproto/commit/951a3df15aa9c1f5b0a2b66cfb0e2eaf6198fe41) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Remove `app.bsky.feed.detach` record, to be replaced by `app.bsky.feed.postgate` record in a future release. + +- Updated dependencies [[`951a3df15`](https://github.com/bluesky-social/atproto/commit/951a3df15aa9c1f5b0a2b66cfb0e2eaf6198fe41)]: + - @atproto/api@0.12.28 + ## 0.1.33 ### Patch Changes diff --git a/packages/ozone/package.json b/packages/ozone/package.json index 08e743942e8..334f0a4405a 100644 --- a/packages/ozone/package.json +++ b/packages/ozone/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/ozone", - "version": "0.1.33", + "version": "0.1.37", "license": "MIT", "description": "Backend service for moderating the Bluesky network.", "keywords": [ @@ -17,7 +17,7 @@ "types": "dist/index.d.ts", "bin": "dist/bin.js", "scripts": { - "codegen": "lex gen-server ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/* ../../lexicons/chat/bsky/*/* ../../lexicons/tools/ozone/*/*", + "codegen": "lex gen-server --yes ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/* ../../lexicons/chat/bsky/*/* ../../lexicons/tools/ozone/*/*", "build": "tsc --build tsconfig.build.json", "start": "node --enable-source-maps dist/bin.js", "test": "../dev-infra/with-test-redis-and-db.sh jest", diff --git a/packages/ozone/src/auth-verifier.ts b/packages/ozone/src/auth-verifier.ts index b09389bd0f3..7cb8f532ec1 100644 --- a/packages/ozone/src/auth-verifier.ts +++ b/packages/ozone/src/auth-verifier.ts @@ -106,7 +106,12 @@ export class AuthVerifier { if (!jwtStr) { throw new AuthRequiredError('missing jwt', 'MissingJwt') } - const payload = await verifyJwt(jwtStr, this.serviceDid, getSigningKey) + const payload = await verifyJwt( + jwtStr, + this.serviceDid, + null, + getSigningKey, + ) const iss = payload.iss const member = await this.teamService.getMember(iss) diff --git a/packages/ozone/src/context.ts b/packages/ozone/src/context.ts index 9ad50000f24..749e162da3e 100644 --- a/packages/ozone/src/context.ts +++ b/packages/ozone/src/context.ts @@ -88,6 +88,7 @@ export class AppContext { createServiceAuthHeaders({ iss: `${cfg.service.did}#atproto_labeler`, aud, + lxm: null, keypair: signingKey, }) @@ -230,6 +231,7 @@ export class AppContext { return createServiceAuthHeaders({ iss, aud, + lxm: null, keypair: this.signingKey, }) } diff --git a/packages/ozone/src/daemon/context.ts b/packages/ozone/src/daemon/context.ts index 3ffa021d37e..14ea9abf9ed 100644 --- a/packages/ozone/src/daemon/context.ts +++ b/packages/ozone/src/daemon/context.ts @@ -1,7 +1,7 @@ import { Keypair, Secp256k1Keypair } from '@atproto/crypto' import { createServiceAuthHeaders } from '@atproto/xrpc-server' import { IdResolver } from '@atproto/identity' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { OzoneConfig, OzoneSecrets } from '../config' import { Database } from '../db' import { EventPusher } from './event-pusher' @@ -43,6 +43,7 @@ export class DaemonContext { createServiceAuthHeaders({ iss: `${cfg.service.did}#atproto_labeler`, aud, + lxm: null, keypair: signingKey, }) diff --git a/packages/ozone/src/daemon/event-pusher.ts b/packages/ozone/src/daemon/event-pusher.ts index 908b1e875d4..61882a66ec3 100644 --- a/packages/ozone/src/daemon/event-pusher.ts +++ b/packages/ozone/src/daemon/event-pusher.ts @@ -1,5 +1,5 @@ import assert from 'node:assert' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { SECOND } from '@atproto/common' import Database from '../db' import { RepoPushEventType } from '../db/schema/repo_push_event' diff --git a/packages/ozone/src/lexicon/lexicons.ts b/packages/ozone/src/lexicon/lexicons.ts index 5c1068f0022..edb97f078f6 100644 --- a/packages/ozone/src/lexicon/lexicons.ts +++ b/packages/ozone/src/lexicon/lexicons.ts @@ -891,7 +891,7 @@ export const schemaDict = { labelValueDefinition: { type: 'object', description: - 'Declares a label value and its expected interpertations and behaviors.', + 'Declares a label value and its expected interpretations and behaviors.', required: ['identifier', 'severity', 'blurs', 'locales'], properties: { identifier: { @@ -2607,6 +2607,17 @@ export const schemaDict = { description: 'The DID of the service that the token will be used to authenticate with', }, + exp: { + type: 'integer', + description: + 'The time in Unix Epoch seconds that the JWT expires. Defaults to 60 seconds in the future. The service may enforce certain time bounds on tokens depending on the requested scope.', + }, + lxm: { + type: 'string', + format: 'nsid', + description: + 'Lexicon (XRPC) method to bind the requested token to', + }, }, }, output: { @@ -2621,6 +2632,13 @@ export const schemaDict = { }, }, }, + errors: [ + { + name: 'BadExpiration', + description: + 'Indicates that the requested expiration date is not a valid. May be in the past or may be reliant on the requested scopes.', + }, + ], }, }, }, @@ -5500,7 +5518,7 @@ export const schemaDict = { feedContext: { type: 'string', description: - 'Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton.', + 'Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton.', maxLength: 2000, }, }, diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/defs.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/defs.ts index e9d9332a406..fab573298a0 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/defs.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/defs.ts @@ -333,7 +333,7 @@ export interface Interaction { | 'app.bsky.feed.defs#interactionQuote' | 'app.bsky.feed.defs#interactionShare' | (string & {}) - /** Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton. */ + /** Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton. */ feedContext?: string [k: string]: unknown } diff --git a/packages/ozone/src/lexicon/types/com/atproto/label/defs.ts b/packages/ozone/src/lexicon/types/com/atproto/label/defs.ts index d0225540a54..348e985622b 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/label/defs.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/label/defs.ts @@ -78,7 +78,7 @@ export function validateSelfLabel(v: unknown): ValidationResult { return lexicons.validate('com.atproto.label.defs#selfLabel', v) } -/** Declares a label value and its expected interpertations and behaviors. */ +/** Declares a label value and its expected interpretations and behaviors. */ export interface LabelValueDefinition { /** The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). */ identifier: string diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/importRepo.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/importRepo.ts index 921798c0ded..59288c7a027 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/repo/importRepo.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/importRepo.ts @@ -11,7 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} -export type InputSchema = string | Uint8Array +export type InputSchema = string | Uint8Array | Blob export interface HandlerInput { encoding: 'application/vnd.ipld.car' diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/uploadBlob.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/uploadBlob.ts index febbbff9d16..4a712346a0b 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/repo/uploadBlob.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/uploadBlob.ts @@ -11,7 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} -export type InputSchema = string | Uint8Array +export type InputSchema = string | Uint8Array | Blob export interface OutputSchema { blob: BlobRef diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/getServiceAuth.ts b/packages/ozone/src/lexicon/types/com/atproto/server/getServiceAuth.ts index 73efe2313a9..14f249fde44 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/getServiceAuth.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/getServiceAuth.ts @@ -11,6 +11,10 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the service that the token will be used to authenticate with */ aud: string + /** The time in Unix Epoch seconds that the JWT expires. Defaults to 60 seconds in the future. The service may enforce certain time bounds on tokens depending on the requested scope. */ + exp?: number + /** Lexicon (XRPC) method to bind the requested token to */ + lxm?: string } export type InputSchema = undefined @@ -31,6 +35,7 @@ export interface HandlerSuccess { export interface HandlerError { status: number message?: string + error?: 'BadExpiration' } export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough diff --git a/packages/ozone/src/mod-service/index.ts b/packages/ozone/src/mod-service/index.ts index 5ae330e2583..1721068e55d 100644 --- a/packages/ozone/src/mod-service/index.ts +++ b/packages/ozone/src/mod-service/index.ts @@ -6,7 +6,7 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { addHoursToDate } from '@atproto/common' import { Keypair } from '@atproto/crypto' import { IdResolver } from '@atproto/identity' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { Database } from '../db' import { AuthHeaders, ModerationViews } from './views' import { Main as StrongRef } from '../lexicon/types/com/atproto/repo/strongRef' @@ -921,14 +921,13 @@ export class ModerationService { throw new InvalidRequestError('Invalid pds service in DID doc') } const agent = new AtpAgent({ service: url }) - const { data: serverInfo } = - await agent.api.com.atproto.server.describeServer() + const { data: serverInfo } = await agent.com.atproto.server.describeServer() if (serverInfo.did !== `did:web:${url.hostname}`) { // @TODO do bidirectional check once implemented. in the meantime, // matching did to hostname we're talking to is pretty good. throw new InvalidRequestError('Invalid pds service in DID doc') } - const { data: delivery } = await agent.api.com.atproto.admin.sendEmail( + const { data: delivery } = await agent.com.atproto.admin.sendEmail( { subject, content, diff --git a/packages/ozone/src/mod-service/views.ts b/packages/ozone/src/mod-service/views.ts index 8cf73060087..0b51e04d189 100644 --- a/packages/ozone/src/mod-service/views.ts +++ b/packages/ozone/src/mod-service/views.ts @@ -1,6 +1,6 @@ import { sql } from 'kysely' import { AtUri, INVALID_HANDLE, normalizeDatetimeAlways } from '@atproto/syntax' -import AtpAgent, { AppBskyFeedDefs } from '@atproto/api' +import { AtpAgent, AppBskyFeedDefs } from '@atproto/api' import { dedupeStrs } from '@atproto/common' import { BlobRef } from '@atproto/lexicon' import { Keypair } from '@atproto/crypto' diff --git a/packages/ozone/tests/3p-labeler.test.ts b/packages/ozone/tests/3p-labeler.test.ts index 3f7e20ae890..99739162e67 100644 --- a/packages/ozone/tests/3p-labeler.test.ts +++ b/packages/ozone/tests/3p-labeler.test.ts @@ -6,7 +6,7 @@ import { ModeratorClient, createOzoneDid, } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { Secp256k1Keypair } from '@atproto/crypto' import { LABELER_HEADER_NAME } from '../src/util' diff --git a/packages/ozone/tests/communication-templates.test.ts b/packages/ozone/tests/communication-templates.test.ts index fc9e8e3d006..0c396156faa 100644 --- a/packages/ozone/tests/communication-templates.test.ts +++ b/packages/ozone/tests/communication-templates.test.ts @@ -1,5 +1,5 @@ import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' describe('communication-templates', () => { let network: TestNetwork diff --git a/packages/ozone/tests/get-config.test.ts b/packages/ozone/tests/get-config.test.ts index 5b160a93748..9dec6ba5d1b 100644 --- a/packages/ozone/tests/get-config.test.ts +++ b/packages/ozone/tests/get-config.test.ts @@ -1,5 +1,5 @@ import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TOOLS_OZONE_TEAM } from '../src/lexicon' describe('get-config', () => { diff --git a/packages/ozone/tests/get-lists.test.ts b/packages/ozone/tests/get-lists.test.ts index bbbd0584f47..38e08b52b7c 100644 --- a/packages/ozone/tests/get-lists.test.ts +++ b/packages/ozone/tests/get-lists.test.ts @@ -6,7 +6,7 @@ import { ModeratorClient, RecordRef, } from '@atproto/dev-env' -import AtpAgent, { BSKY_LABELER_DID } from '@atproto/api' +import { AtpAgent, BSKY_LABELER_DID } from '@atproto/api' import { TAKEDOWN_LABEL } from '../src/mod-service' describe('admin get lists', () => { diff --git a/packages/ozone/tests/get-record.test.ts b/packages/ozone/tests/get-record.test.ts index 673b97d169b..6201521129e 100644 --- a/packages/ozone/tests/get-record.test.ts +++ b/packages/ozone/tests/get-record.test.ts @@ -5,7 +5,7 @@ import { TestOzone, ModeratorClient, } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { AtUri } from '@atproto/syntax' import { REASONOTHER, diff --git a/packages/ozone/tests/get-repo.test.ts b/packages/ozone/tests/get-repo.test.ts index ffe50b6a1a0..260d29c690b 100644 --- a/packages/ozone/tests/get-repo.test.ts +++ b/packages/ozone/tests/get-repo.test.ts @@ -5,7 +5,7 @@ import { basicSeed, ModeratorClient, } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { REASONOTHER, REASONSPAM, diff --git a/packages/ozone/tests/get-starter-pack.test.ts b/packages/ozone/tests/get-starter-pack.test.ts index 3ea09925bb0..73f4417e10b 100644 --- a/packages/ozone/tests/get-starter-pack.test.ts +++ b/packages/ozone/tests/get-starter-pack.test.ts @@ -5,7 +5,7 @@ import { TestOzone, RecordRef, } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { forSnapshot } from './_util' import { TAKEDOWN_LABEL } from '../src/mod-service' diff --git a/packages/ozone/tests/moderation.test.ts b/packages/ozone/tests/moderation.test.ts index ef11413385b..0206c93a54b 100644 --- a/packages/ozone/tests/moderation.test.ts +++ b/packages/ozone/tests/moderation.test.ts @@ -7,7 +7,7 @@ import { basicSeed, ModeratorClient, } from '@atproto/dev-env' -import AtpAgent, { ToolsOzoneModerationEmitEvent } from '@atproto/api' +import { AtpAgent, ToolsOzoneModerationEmitEvent } from '@atproto/api' import { AtUri } from '@atproto/syntax' import { forSnapshot } from './_util' import { diff --git a/packages/ozone/tests/query-labels.test.ts b/packages/ozone/tests/query-labels.test.ts index 33ae952cf51..f1e47c48fd7 100644 --- a/packages/ozone/tests/query-labels.test.ts +++ b/packages/ozone/tests/query-labels.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { EXAMPLE_LABELER, TestNetwork } from '@atproto/dev-env' import { DisconnectError, Subscription } from '@atproto/xrpc-server' import { ids, lexicons } from '../src/lexicon/lexicons' diff --git a/packages/ozone/tests/repo-search.test.ts b/packages/ozone/tests/repo-search.test.ts index 93fb0577bb3..c5d98467265 100644 --- a/packages/ozone/tests/repo-search.test.ts +++ b/packages/ozone/tests/repo-search.test.ts @@ -4,7 +4,7 @@ import { TestNetwork, usersBulkSeed, } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { paginateAll } from './_util' describe('admin repo search view', () => { diff --git a/packages/ozone/tests/team.test.ts b/packages/ozone/tests/team.test.ts index e01c02f5fdd..4a3487127c6 100644 --- a/packages/ozone/tests/team.test.ts +++ b/packages/ozone/tests/team.test.ts @@ -1,5 +1,5 @@ import { SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { forSnapshot } from './_util' describe('team management', () => { diff --git a/packages/pds/CHANGELOG.md b/packages/pds/CHANGELOG.md index 21feed62635..06c2b82bf2b 100644 --- a/packages/pds/CHANGELOG.md +++ b/packages/pds/CHANGELOG.md @@ -1,5 +1,50 @@ # @atproto/pds +## 0.4.49 + +### Patch Changes + +- Updated dependencies [[`22af354a5`](https://github.com/bluesky-social/atproto/commit/22af354a5db595d7cbc0e65f02601de3565337e1)]: + - @atproto/api@0.13.1 + +## 0.4.48 + +### Patch Changes + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`2bdf75d7a`](https://github.com/bluesky-social/atproto/commit/2bdf75d7a63924c10e7a311f16cb447d595b933e), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd)]: + - @atproto/lexicon@0.4.1 + - @atproto/xrpc@0.6.0 + - @atproto/api@0.13.0 + - @atproto/oauth-provider@0.1.2 + - @atproto/repo@0.4.2 + - @atproto/xrpc-server@0.6.1 + - @atproto/aws@0.2.2 + +## 0.4.47 + +### Patch Changes + +- [#2688](https://github.com/bluesky-social/atproto/pull/2688) [`269cbc87c`](https://github.com/bluesky-social/atproto/commit/269cbc87c5ec9d65d1d479269ac5e91dffbb186c) Thanks [@dholms](https://github.com/dholms)! - Inspect bearer auth token on uploadBlob + +## 0.4.46 + +### Patch Changes + +- [#2668](https://github.com/bluesky-social/atproto/pull/2668) [`dc471da26`](https://github.com/bluesky-social/atproto/commit/dc471da267955d0962a8affaf983df60d962d97c) Thanks [@dholms](https://github.com/dholms)! - Add lxm and nonce to signed service auth tokens. + +- Updated dependencies [[`dc471da26`](https://github.com/bluesky-social/atproto/commit/dc471da267955d0962a8affaf983df60d962d97c), [`dc471da26`](https://github.com/bluesky-social/atproto/commit/dc471da267955d0962a8affaf983df60d962d97c)]: + - @atproto/api@0.12.29 + - @atproto/xrpc-server@0.6.0 + +## 0.4.45 + +### Patch Changes + +- [#2676](https://github.com/bluesky-social/atproto/pull/2676) [`951a3df15`](https://github.com/bluesky-social/atproto/commit/951a3df15aa9c1f5b0a2b66cfb0e2eaf6198fe41) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Remove `app.bsky.feed.detach` record, to be replaced by `app.bsky.feed.postgate` record in a future release. + +- Updated dependencies [[`951a3df15`](https://github.com/bluesky-social/atproto/commit/951a3df15aa9c1f5b0a2b66cfb0e2eaf6198fe41)]: + - @atproto/api@0.12.28 + ## 0.4.44 ### Patch Changes diff --git a/packages/pds/package.json b/packages/pds/package.json index d696f40c83f..b94db80c234 100644 --- a/packages/pds/package.json +++ b/packages/pds/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/pds", - "version": "0.4.44", + "version": "0.4.49", "license": "MIT", "description": "Reference implementation of atproto Personal Data Server (PDS)", "keywords": [ @@ -17,7 +17,7 @@ "types": "dist/index.d.ts", "bin": "dist/bin.js", "scripts": { - "codegen": "lex gen-server ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/* ../../lexicons/chat/bsky/*/* ../../lexicons/tools/ozone/*/*", + "codegen": "lex gen-server --yes ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/* ../../lexicons/chat/bsky/*/* ../../lexicons/tools/ozone/*/*", "build": "tsc --build tsconfig.build.json", "postbuild": "node ./build.templates.js", "dev": "node ./build.templates.js --watch", diff --git a/packages/pds/src/api/app/bsky/feed/getFeed.ts b/packages/pds/src/api/app/bsky/feed/getFeed.ts index 6ae97d5e1bb..f4f2ade83d9 100644 --- a/packages/pds/src/api/app/bsky/feed/getFeed.ts +++ b/packages/pds/src/api/app/bsky/feed/getFeed.ts @@ -1,6 +1,7 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' +import { ids } from '../../../../lexicon/lexicons' export default function (server: Server, ctx: AppContext) { const { appViewAgent } = ctx @@ -14,9 +15,15 @@ export default function (server: Server, ctx: AppContext) { const { data: feed } = await appViewAgent.api.app.bsky.feed.getFeedGenerator( { feed: params.feed }, - await ctx.appviewAuthHeaders(requester), + await ctx.appviewAuthHeaders( + requester, + ids.AppBskyFeedGetFeedGenerator, + ), ) - return pipethrough(ctx, req, requester, feed.view.did) + return pipethrough(ctx, req, requester, { + aud: feed.view.did, + lxm: ids.AppBskyFeedGetFeedSkeleton, + }) }, }) } diff --git a/packages/pds/src/api/app/bsky/feed/getPostThread.ts b/packages/pds/src/api/app/bsky/feed/getPostThread.ts index 04fb78c4c07..a497aca0613 100644 --- a/packages/pds/src/api/app/bsky/feed/getPostThread.ts +++ b/packages/pds/src/api/app/bsky/feed/getPostThread.ts @@ -22,6 +22,7 @@ import { formatMungedResponse, } from '../../../../read-after-write' import { pipethrough } from '../../../../pipethrough' +import { ids } from '../../../../lexicon/lexicons' const METHOD_NSID = 'app.bsky.feed.getPostThread' @@ -189,7 +190,7 @@ const readAfterWriteNotFound = async ( assert(ctx.appViewAgent) const parentsRes = await ctx.appViewAgent.api.app.bsky.feed.getPostThread( { uri: highestParent, parentHeight: params.parentHeight, depth: 0 }, - await ctx.appviewAuthHeaders(requester), + await ctx.appviewAuthHeaders(requester, ids.AppBskyFeedGetPostThread), ) thread.parent = parentsRes.data.thread } catch (err) { diff --git a/packages/pds/src/api/app/bsky/notification/registerPush.ts b/packages/pds/src/api/app/bsky/notification/registerPush.ts index f9b7fb3c41c..0e481e639a9 100644 --- a/packages/pds/src/api/app/bsky/notification/registerPush.ts +++ b/packages/pds/src/api/app/bsky/notification/registerPush.ts @@ -5,6 +5,7 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { AtpAgent } from '@atproto/api' import { getDidDoc } from '../util/resolver' import { AuthScope } from '../../../../auth-verifier' +import { ids } from '../../../../lexicon/lexicons' export default function (server: Server, ctx: AppContext) { const { appViewAgent } = ctx @@ -19,7 +20,11 @@ export default function (server: Server, ctx: AppContext) { credentials: { did }, } = auth - const authHeaders = await ctx.serviceAuthHeaders(did, serviceDid) + const authHeaders = await ctx.serviceAuthHeaders( + did, + serviceDid, + ids.AppBskyNotificationRegisterPush, + ) if (ctx.cfg.bskyAppView?.did === serviceDid) { await appViewAgent.api.app.bsky.notification.registerPush(input.body, { diff --git a/packages/pds/src/api/chat/index.ts b/packages/pds/src/api/chat/index.ts deleted file mode 100644 index e502c727ea2..00000000000 --- a/packages/pds/src/api/chat/index.ts +++ /dev/null @@ -1,90 +0,0 @@ -import AppContext from '../../context' -import { Server } from '../../lexicon' -import { pipethrough, pipethroughProcedure } from '../../pipethrough' - -export default function (server: Server, ctx: AppContext) { - server.chat.bsky.actor.deleteAccount({ - auth: ctx.authVerifier.accessPrivileged(), - handler: async ({ req, auth }) => { - return pipethroughProcedure(ctx, req, auth.credentials.did) - }, - }) - server.chat.bsky.actor.exportAccountData({ - auth: ctx.authVerifier.accessPrivileged(), - handler: ({ req, auth }) => { - return pipethrough(ctx, req, auth.credentials.did) - }, - }) - server.chat.bsky.convo.deleteMessageForSelf({ - auth: ctx.authVerifier.accessPrivileged(), - handler: ({ req, auth, input }) => { - return pipethroughProcedure(ctx, req, auth.credentials.did, input.body) - }, - }) - server.chat.bsky.convo.getConvo({ - auth: ctx.authVerifier.accessPrivileged(), - handler: ({ req, auth }) => { - return pipethrough(ctx, req, auth.credentials.did) - }, - }) - server.chat.bsky.convo.getConvoForMembers({ - auth: ctx.authVerifier.accessPrivileged(), - handler: ({ req, auth }) => { - return pipethrough(ctx, req, auth.credentials.did) - }, - }) - server.chat.bsky.convo.getLog({ - auth: ctx.authVerifier.accessPrivileged(), - handler: ({ req, auth }) => { - return pipethrough(ctx, req, auth.credentials.did) - }, - }) - server.chat.bsky.convo.getMessages({ - auth: ctx.authVerifier.accessPrivileged(), - handler: ({ req, auth }) => { - return pipethrough(ctx, req, auth.credentials.did) - }, - }) - server.chat.bsky.convo.leaveConvo({ - auth: ctx.authVerifier.accessPrivileged(), - handler: ({ req, auth, input }) => { - return pipethroughProcedure(ctx, req, auth.credentials.did, input.body) - }, - }) - server.chat.bsky.convo.listConvos({ - auth: ctx.authVerifier.accessPrivileged(), - handler: ({ req, auth }) => { - return pipethrough(ctx, req, auth.credentials.did) - }, - }) - server.chat.bsky.convo.muteConvo({ - auth: ctx.authVerifier.accessPrivileged(), - handler: ({ req, auth, input }) => { - return pipethroughProcedure(ctx, req, auth.credentials.did, input.body) - }, - }) - server.chat.bsky.convo.sendMessage({ - auth: ctx.authVerifier.accessPrivileged(), - handler: ({ req, auth, input }) => { - return pipethroughProcedure(ctx, req, auth.credentials.did, input.body) - }, - }) - server.chat.bsky.convo.sendMessageBatch({ - auth: ctx.authVerifier.accessPrivileged(), - handler: ({ req, auth, input }) => { - return pipethroughProcedure(ctx, req, auth.credentials.did, input.body) - }, - }) - server.chat.bsky.convo.unmuteConvo({ - auth: ctx.authVerifier.accessPrivileged(), - handler: ({ req, auth, input }) => { - return pipethroughProcedure(ctx, req, auth.credentials.did, input.body) - }, - }) - server.chat.bsky.convo.updateRead({ - auth: ctx.authVerifier.accessPrivileged(), - handler: ({ req, auth, input }) => { - return pipethroughProcedure(ctx, req, auth.credentials.did, input.body) - }, - }) -} diff --git a/packages/pds/src/api/com/atproto/admin/sendEmail.ts b/packages/pds/src/api/com/atproto/admin/sendEmail.ts index 67b1755fedc..2cd48307686 100644 --- a/packages/pds/src/api/com/atproto/admin/sendEmail.ts +++ b/packages/pds/src/api/com/atproto/admin/sendEmail.ts @@ -3,6 +3,7 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { resultPassthru } from '../../../proxy' +import { ids } from '../../../../lexicon/lexicons' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.sendEmail({ @@ -29,7 +30,8 @@ export default function (server: Server, ctx: AppContext) { encoding: 'application/json', ...(await ctx.serviceAuthHeaders( recipientDid, - ctx.cfg.entryway?.did, + ctx.cfg.entryway.did, + ids.ComAtprotoAdminSendEmail, )), }), ) diff --git a/packages/pds/src/api/com/atproto/repo/uploadBlob.ts b/packages/pds/src/api/com/atproto/repo/uploadBlob.ts index de7f481c35f..e512d9a6e8a 100644 --- a/packages/pds/src/api/com/atproto/repo/uploadBlob.ts +++ b/packages/pds/src/api/com/atproto/repo/uploadBlob.ts @@ -6,7 +6,7 @@ import { BlobMetadata } from '../../../../actor-store/blob/transactor' export default function (server: Server, ctx: AppContext) { server.com.atproto.repo.uploadBlob({ - auth: ctx.authVerifier.accessStandard({ + auth: ctx.authVerifier.accessOrUserServiceAuth({ checkTakedown: true, }), rateLimit: { diff --git a/packages/pds/src/api/com/atproto/server/createAccount.ts b/packages/pds/src/api/com/atproto/server/createAccount.ts index abbad8284be..4d6fd499852 100644 --- a/packages/pds/src/api/com/atproto/server/createAccount.ts +++ b/packages/pds/src/api/com/atproto/server/createAccount.ts @@ -20,9 +20,9 @@ export default function (server: Server, ctx: AppContext) { durationMs: 5 * MINUTE, points: 100, }, - auth: ctx.authVerifier.userDidAuthOptional, + auth: ctx.authVerifier.userServiceAuthOptional, handler: async ({ input, auth, req }) => { - const requester = auth.credentials?.iss ?? null + const requester = auth.credentials?.did ?? null const { did, handle, diff --git a/packages/pds/src/api/com/atproto/server/getServiceAuth.ts b/packages/pds/src/api/com/atproto/server/getServiceAuth.ts index bae1d97f8ab..e13a82b57f5 100644 --- a/packages/pds/src/api/com/atproto/server/getServiceAuth.ts +++ b/packages/pds/src/api/com/atproto/server/getServiceAuth.ts @@ -1,4 +1,5 @@ -import { createServiceJwt } from '@atproto/xrpc-server' +import { InvalidRequestError, createServiceJwt } from '@atproto/xrpc-server' +import { MINUTE } from '@atproto/common' import AppContext from '../../../../context' import { Server } from '../../../../lexicon' @@ -8,9 +9,27 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth }) => { const did = auth.credentials.did const keypair = await ctx.actorStore.keypair(did) + const exp = params.exp ? params.exp * 1000 : undefined + if (exp) { + const diff = exp - Date.now() + if (diff < 0) { + throw new InvalidRequestError( + 'expiration is in past', + 'BadExpiration', + ) + } else if (diff > MINUTE) { + throw new InvalidRequestError( + 'cannot request a token with an expiration more than a minute in the future', + 'BadExpiration', + ) + } + } + const token = await createServiceJwt({ iss: did, aud: params.aud, + lxm: params.lxm ?? null, + exp, keypair, }) return { diff --git a/packages/pds/src/api/index.ts b/packages/pds/src/api/index.ts index d4625ee76b7..f0d08cd559e 100644 --- a/packages/pds/src/api/index.ts +++ b/packages/pds/src/api/index.ts @@ -1,12 +1,10 @@ import { Server } from '../lexicon' import comAtproto from './com/atproto' import appBsky from './app/bsky' -import chat from './chat' import AppContext from '../context' export default function (server: Server, ctx: AppContext) { comAtproto(server, ctx) appBsky(server, ctx) - chat(server, ctx) return server } diff --git a/packages/pds/src/auth-verifier.ts b/packages/pds/src/auth-verifier.ts index ae1365c57be..90b82a9a5ed 100644 --- a/packages/pds/src/auth-verifier.ts +++ b/packages/pds/src/auth-verifier.ts @@ -1,5 +1,8 @@ import { KeyObject, createPublicKey, createSecretKey } from 'node:crypto' +import { IncomingMessage, ServerResponse } from 'node:http' +import { getVerificationMaterial } from '@atproto/common' +import { IdResolver, getDidKeyFromMultibase } from '@atproto/identity' import { OAuthError, OAuthVerifier, @@ -7,24 +10,19 @@ import { } from '@atproto/oauth-provider' import { AuthRequiredError, + AuthVerifierContext, ForbiddenError, InvalidRequestError, + StreamAuthVerifierContext, XRPCError, verifyJwt as verifyServiceJwt, } from '@atproto/xrpc-server' -import { IdResolver, getDidKeyFromMultibase } from '@atproto/identity' -import express from 'express' import * as jose from 'jose' import KeyEncoder from 'key-encoder' import { AccountManager } from './account-manager' import { softDeleted } from './db' -import { getVerificationMaterial } from '@atproto/common' -type ReqCtx = { - req: express.Request - // StreamAuthVerifier does not have "res" - res?: express.Response -} +type ReqCtx = AuthVerifierContext | StreamAuthVerifierContext // @TODO sync-up with current method names, consider backwards compat. export enum AuthScope { @@ -71,6 +69,7 @@ type AccessOutput = { did: string scope: AuthScope audience: string | undefined + isPrivileged: boolean } artifacts: string } @@ -86,11 +85,11 @@ type RefreshOutput = { artifacts: string } -type UserDidOutput = { +type UserServiceAuthOutput = { credentials: { - type: 'user_did' + type: 'user_service_auth' aud: string - iss: string + did: string } } @@ -221,30 +220,52 @@ export class AuthVerifier { } } - userDidAuth = async (ctx: ReqCtx): Promise => { + userServiceAuth = async (ctx: ReqCtx): Promise => { const payload = await this.verifyServiceJwt(ctx, { - aud: this.dids.entryway ?? this.dids.pds, + aud: null, iss: null, }) + if ( + payload.aud !== this.dids.pds && + (!this.dids.entryway || payload.aud !== this.dids.entryway) + ) { + throw new AuthRequiredError( + 'jwt audience does not match service did', + 'BadJwtAudience', + ) + } return { credentials: { - type: 'user_did', + type: 'user_service_auth', aud: payload.aud, - iss: payload.iss, + did: payload.iss, }, } } - userDidAuthOptional = async ( + userServiceAuthOptional = async ( ctx: ReqCtx, - ): Promise => { + ): Promise => { if (isBearerToken(ctx.req)) { - return await this.userDidAuth(ctx) + return await this.userServiceAuth(ctx) } else { return this.null(ctx) } } + accessOrUserServiceAuth = + (opts: Partial = {}) => + async (ctx: ReqCtx): Promise => { + const token = bearerTokenFromReq(ctx.req) + if (token) { + const payload = jose.decodeJwt(token) + if (payload['lxm']) { + return this.userServiceAuth(ctx) + } + } + return this.accessStandard(opts)(ctx) + } + modService = async (ctx: ReqCtx): Promise => { if (!this.dids.modService) { throw new AuthRequiredError('Untrusted issuer', 'UntrustedIss') @@ -439,7 +460,8 @@ export class AuthVerifier { this.setAuthHeaders(ctx) - const { req, res } = ctx + const { req } = ctx + const res = 'res' in ctx ? ctx.res : null // https://datatracker.ietf.org/doc/html/rfc9449#section-8.2 if (res) { @@ -451,9 +473,11 @@ export class AuthVerifier { } try { - const url = new URL(req.originalUrl || req.url, this._publicUrl) + const originalUrl = + ('originalUrl' in req && req.originalUrl) || req.url || '/' + const url = new URL(originalUrl, this._publicUrl) const result = await this.oauthVerifier.authenticateRequest( - req.method, + req.method || 'GET', url, req.headers, { audience: [this.dids.pds] }, @@ -470,6 +494,7 @@ export class AuthVerifier { did: result.claims.sub, scope: AuthScope.Access, audience: this.dids.pds, + isPrivileged: true, }, artifacts: result.token, } @@ -498,12 +523,17 @@ export class AuthVerifier { scopes, { audience: this.dids.pds }, ) + const isPrivileged = [ + AuthScope.Access, + AuthScope.AppPassPrivileged, + ].includes(scope) return { credentials: { type: 'access', did, scope, audience, + isPrivileged, }, artifacts: token, } @@ -544,7 +574,12 @@ export class AuthVerifier { if (!jwtStr) { throw new AuthRequiredError('missing jwt', 'MissingJwt') } - const payload = await verifyServiceJwt(jwtStr, opts.aud, getSigningKey) + const payload = await verifyServiceJwt( + jwtStr, + opts.aud, + null, + getSigningKey, + ) return { iss: payload.iss, aud: payload.aud } } @@ -585,7 +620,8 @@ export class AuthVerifier { } } - protected setAuthHeaders({ res }: ReqCtx) { + protected setAuthHeaders(ctx: ReqCtx) { + const res = 'res' in ctx ? ctx.res : null if (res) { res.setHeader('Cache-Control', 'private') vary(res, 'Authorization') @@ -627,22 +663,22 @@ export const parseAuthorizationHeader = ( ) } -const isAccessToken = (req: express.Request): boolean => { +const isAccessToken = (req: IncomingMessage): boolean => { const [type] = parseAuthorizationHeader(req.headers.authorization) return type === AuthType.BEARER || type === AuthType.DPOP } -const isBearerToken = (req: express.Request): boolean => { +const isBearerToken = (req: IncomingMessage): boolean => { const [type] = parseAuthorizationHeader(req.headers.authorization) return type === AuthType.BEARER } -const isBasicToken = (req: express.Request): boolean => { +const isBasicToken = (req: IncomingMessage): boolean => { const [type] = parseAuthorizationHeader(req.headers.authorization) return type === AuthType.BASIC } -const bearerTokenFromReq = (req: express.Request) => { +const bearerTokenFromReq = (req: IncomingMessage) => { const [type, token] = parseAuthorizationHeader(req.headers.authorization) return type === AuthType.BEARER ? token : null } @@ -681,7 +717,7 @@ export const createPublicKeyObject = (publicKeyHex: string): KeyObject => { const keyEncoder = new KeyEncoder('secp256k1') -function vary(res: express.Response, value: string) { +function vary(res: ServerResponse, value: string) { const current = res.getHeader('Vary') if (current == null || typeof current === 'number') { res.setHeader('Vary', value) diff --git a/packages/pds/src/context.ts b/packages/pds/src/context.ts index c76c3966c2d..f7b01330e14 100644 --- a/packages/pds/src/context.ts +++ b/packages/pds/src/context.ts @@ -342,16 +342,17 @@ export class AppContext { }) } - async appviewAuthHeaders(did: string) { + async appviewAuthHeaders(did: string, lxm: string) { assert(this.cfg.bskyAppView) - return this.serviceAuthHeaders(did, this.cfg.bskyAppView.did) + return this.serviceAuthHeaders(did, this.cfg.bskyAppView.did, lxm) } - async serviceAuthHeaders(did: string, aud: string) { + async serviceAuthHeaders(did: string, aud: string, lxm: string) { const keypair = await this.actorStore.keypair(did) return createServiceAuthHeaders({ iss: did, aud, + lxm, keypair, }) } diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index 5c1068f0022..edb97f078f6 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -891,7 +891,7 @@ export const schemaDict = { labelValueDefinition: { type: 'object', description: - 'Declares a label value and its expected interpertations and behaviors.', + 'Declares a label value and its expected interpretations and behaviors.', required: ['identifier', 'severity', 'blurs', 'locales'], properties: { identifier: { @@ -2607,6 +2607,17 @@ export const schemaDict = { description: 'The DID of the service that the token will be used to authenticate with', }, + exp: { + type: 'integer', + description: + 'The time in Unix Epoch seconds that the JWT expires. Defaults to 60 seconds in the future. The service may enforce certain time bounds on tokens depending on the requested scope.', + }, + lxm: { + type: 'string', + format: 'nsid', + description: + 'Lexicon (XRPC) method to bind the requested token to', + }, }, }, output: { @@ -2621,6 +2632,13 @@ export const schemaDict = { }, }, }, + errors: [ + { + name: 'BadExpiration', + description: + 'Indicates that the requested expiration date is not a valid. May be in the past or may be reliant on the requested scopes.', + }, + ], }, }, }, @@ -5500,7 +5518,7 @@ export const schemaDict = { feedContext: { type: 'string', description: - 'Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton.', + 'Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton.', maxLength: 2000, }, }, diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/defs.ts b/packages/pds/src/lexicon/types/app/bsky/feed/defs.ts index e9d9332a406..fab573298a0 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/defs.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/defs.ts @@ -333,7 +333,7 @@ export interface Interaction { | 'app.bsky.feed.defs#interactionQuote' | 'app.bsky.feed.defs#interactionShare' | (string & {}) - /** Context on a feed item that was orginally supplied by the feed generator on getFeedSkeleton. */ + /** Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton. */ feedContext?: string [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/com/atproto/label/defs.ts b/packages/pds/src/lexicon/types/com/atproto/label/defs.ts index d0225540a54..348e985622b 100644 --- a/packages/pds/src/lexicon/types/com/atproto/label/defs.ts +++ b/packages/pds/src/lexicon/types/com/atproto/label/defs.ts @@ -78,7 +78,7 @@ export function validateSelfLabel(v: unknown): ValidationResult { return lexicons.validate('com.atproto.label.defs#selfLabel', v) } -/** Declares a label value and its expected interpertations and behaviors. */ +/** Declares a label value and its expected interpretations and behaviors. */ export interface LabelValueDefinition { /** The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). */ identifier: string diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/importRepo.ts b/packages/pds/src/lexicon/types/com/atproto/repo/importRepo.ts index 921798c0ded..59288c7a027 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/importRepo.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/importRepo.ts @@ -11,7 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} -export type InputSchema = string | Uint8Array +export type InputSchema = string | Uint8Array | Blob export interface HandlerInput { encoding: 'application/vnd.ipld.car' diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/uploadBlob.ts b/packages/pds/src/lexicon/types/com/atproto/repo/uploadBlob.ts index febbbff9d16..4a712346a0b 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/uploadBlob.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/uploadBlob.ts @@ -11,7 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} -export type InputSchema = string | Uint8Array +export type InputSchema = string | Uint8Array | Blob export interface OutputSchema { blob: BlobRef diff --git a/packages/pds/src/lexicon/types/com/atproto/server/getServiceAuth.ts b/packages/pds/src/lexicon/types/com/atproto/server/getServiceAuth.ts index 73efe2313a9..14f249fde44 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/getServiceAuth.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/getServiceAuth.ts @@ -11,6 +11,10 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the service that the token will be used to authenticate with */ aud: string + /** The time in Unix Epoch seconds that the JWT expires. Defaults to 60 seconds in the future. The service may enforce certain time bounds on tokens depending on the requested scope. */ + exp?: number + /** Lexicon (XRPC) method to bind the requested token to */ + lxm?: string } export type InputSchema = undefined @@ -31,6 +35,7 @@ export interface HandlerSuccess { export interface HandlerError { status: number message?: string + error?: 'BadExpiration' } export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough diff --git a/packages/pds/src/pipethrough.ts b/packages/pds/src/pipethrough.ts index af8284af2a0..5a85d62b3e0 100644 --- a/packages/pds/src/pipethrough.ts +++ b/packages/pds/src/pipethrough.ts @@ -14,14 +14,22 @@ import { ids, lexicons } from './lexicon/lexicons' import { httpLogger } from './logger' import { getServiceEndpoint, noUndefinedVals } from '@atproto/common' import AppContext from './context' +import { parseReqNsid } from '@atproto/xrpc-server' export const proxyHandler = (ctx: AppContext): CatchallHandler => { const accessStandard = ctx.authVerifier.accessStandard() return async (req, res, next) => { try { - const { url, aud } = await formatUrlAndAud(ctx, req) - const auth = await accessStandard({ req }) - const headers = await formatHeaders(ctx, req, aud, auth.credentials.did) + const { url, aud, nsid } = await formatUrlAndAud(ctx, req) + const auth = await accessStandard({ req, res }) + if (!auth.credentials.isPrivileged && PRIVILEGED_METHODS.has(nsid)) { + throw new InvalidRequestError('Bad token method', 'InvalidToken') + } + const headers = await formatHeaders(ctx, req, { + aud, + lxm: nsid, + requester: auth.credentials.did, + }) const body: webStream.ReadableStream = stream.Readable.toWeb(req) const reqInit = formatReqInit(req, headers, body) @@ -38,10 +46,14 @@ export const pipethrough = async ( ctx: AppContext, req: express.Request, requester: string | null, - audOverride?: string, + override: { + aud?: string + lxm?: string + } = {}, ): Promise => { - const { url, aud } = await formatUrlAndAud(ctx, req, audOverride) - const headers = await formatHeaders(ctx, req, aud, requester) + const { url, aud, nsid } = await formatUrlAndAud(ctx, req, override.aud) + const lxm = override.lxm ?? nsid + const headers = await formatHeaders(ctx, req, { aud, lxm, requester }) const reqInit = formatReqInit(req, headers) const res = await makeRequest(url, reqInit) return parseProxyRes(res) @@ -53,8 +65,8 @@ export const pipethroughProcedure = async ( requester: string | null, body?: LexValue, ): Promise => { - const { url, aud } = await formatUrlAndAud(ctx, req) - const headers = await formatHeaders(ctx, req, aud, requester) + const { url, aud, nsid: lxm } = await formatUrlAndAud(ctx, req) + const headers = await formatHeaders(ctx, req, { aud, lxm, requester }) const encodedBody = body ? new TextEncoder().encode(stringifyLex(body)) : undefined @@ -77,9 +89,10 @@ export const formatUrlAndAud = async ( ctx: AppContext, req: express.Request, audOverride?: string, -): Promise<{ url: URL; aud: string }> => { +): Promise<{ url: URL; aud: string; nsid: string }> => { const proxyTo = await parseProxyHeader(ctx, req) - const defaultProxy = defaultService(ctx, req) + const nsid = parseReqNsid(req) + const defaultProxy = defaultService(ctx, nsid) const serviceUrl = proxyTo?.serviceUrl ?? defaultProxy?.url const aud = audOverride ?? proxyTo?.did ?? defaultProxy?.did if (!serviceUrl || !aud) { @@ -89,17 +102,21 @@ export const formatUrlAndAud = async ( if (!ctx.cfg.service.devMode && !isSafeUrl(url)) { throw new InvalidRequestError(`Invalid service url: ${url.toString()}`) } - return { url, aud } + return { url, aud, nsid } } export const formatHeaders = async ( ctx: AppContext, req: express.Request, - aud: string, - requester: string | null, + opts: { + aud: string + lxm: string + requester: string | null + }, ): Promise<{ authorization?: string }> => { + const { aud, lxm, requester } = opts const headers = requester - ? (await ctx.serviceAuthHeaders(requester, aud)).headers + ? (await ctx.serviceAuthHeaders(requester, aud, lxm)).headers : {} // forward select headers to upstream services for (const header of REQ_HEADERS_TO_FORWARD) { @@ -241,11 +258,28 @@ export const parseProxyRes = async (res: Response) => { // Utils // ------------------- +export const PRIVILEGED_METHODS = new Set([ + ids.ChatBskyActorDeleteAccount, + ids.ChatBskyActorExportAccountData, + ids.ChatBskyConvoDeleteMessageForSelf, + ids.ChatBskyConvoGetConvo, + ids.ChatBskyConvoGetConvoForMembers, + ids.ChatBskyConvoGetLog, + ids.ChatBskyConvoGetMessages, + ids.ChatBskyConvoLeaveConvo, + ids.ChatBskyConvoListConvos, + ids.ChatBskyConvoMuteConvo, + ids.ChatBskyConvoSendMessage, + ids.ChatBskyConvoSendMessageBatch, + ids.ChatBskyConvoUnmuteConvo, + ids.ChatBskyConvoUpdateRead, + ids.ComAtprotoServerCreateAccount, +]) + const defaultService = ( ctx: AppContext, - req: express.Request, + nsid: string, ): { url: string; did: string } | null => { - const nsid = req.originalUrl.split('?')[0].replace('/xrpc/', '') switch (nsid) { case ids.ToolsOzoneTeamAddMember: case ids.ToolsOzoneTeamDeleteMember: diff --git a/packages/pds/src/read-after-write/viewer.ts b/packages/pds/src/read-after-write/viewer.ts index 1373f9ec622..165adf81404 100644 --- a/packages/pds/src/read-after-write/viewer.ts +++ b/packages/pds/src/read-after-write/viewer.ts @@ -89,7 +89,7 @@ export class LocalViewer { return util.format(this.appviewCdnUrlPattern, pattern, this.did, cid) } - async serviceAuthHeaders(did: string) { + async serviceAuthHeaders(did: string, lxm: string) { if (!this.appviewDid) { throw new Error('Could not find bsky appview did') } @@ -98,6 +98,7 @@ export class LocalViewer { return createServiceAuthHeaders({ iss: did, aud: this.appviewDid, + lxm, keypair, }) } @@ -244,7 +245,7 @@ export class LocalViewer { if (collection === ids.AppBskyFeedPost) { const res = await this.appViewAgent.api.app.bsky.feed.getPosts( { uris: [embed.record.uri] }, - await this.serviceAuthHeaders(this.did), + await this.serviceAuthHeaders(this.did, ids.AppBskyFeedGetPosts), ) const post = res.data.posts[0] if (!post) return null @@ -261,7 +262,10 @@ export class LocalViewer { } else if (collection === ids.AppBskyFeedGenerator) { const res = await this.appViewAgent.api.app.bsky.feed.getFeedGenerator( { feed: embed.record.uri }, - await this.serviceAuthHeaders(this.did), + await this.serviceAuthHeaders( + this.did, + ids.AppBskyFeedGetFeedGenerator, + ), ) return { $type: 'app.bsky.feed.defs#generatorView', @@ -270,7 +274,7 @@ export class LocalViewer { } else if (collection === ids.AppBskyGraphList) { const res = await this.appViewAgent.api.app.bsky.graph.getList( { list: embed.record.uri }, - await this.serviceAuthHeaders(this.did), + await this.serviceAuthHeaders(this.did, ids.AppBskyGraphGetList), ) return { $type: 'app.bsky.graph.defs#listView', diff --git a/packages/pds/tests/account-deactivation.test.ts b/packages/pds/tests/account-deactivation.test.ts index 4d73b6a0b69..21a726008a6 100644 --- a/packages/pds/tests/account-deactivation.test.ts +++ b/packages/pds/tests/account-deactivation.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { ImageRef, SeedClient, diff --git a/packages/pds/tests/account-deletion.test.ts b/packages/pds/tests/account-deletion.test.ts index 2bc5afa72c5..482bad589cc 100644 --- a/packages/pds/tests/account-deletion.test.ts +++ b/packages/pds/tests/account-deletion.test.ts @@ -2,7 +2,7 @@ import { TestNetworkNoAppView, SeedClient } from '@atproto/dev-env' import { once, EventEmitter } from 'events' import { Selectable } from 'kysely' import Mail from 'nodemailer/lib/mailer' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import basicSeed from './seeds/basic' import { ServerMailer } from '../src/mailer' import { BlobNotFoundError } from '@atproto/repo' @@ -39,7 +39,7 @@ describe('account deletion', () => { // @ts-expect-error Error due to circular dependency with the dev-env package ctx = network.pds.ctx mailer = ctx.mailer - agent = new AtpAgent({ service: network.pds.url }) + agent = network.pds.getClient() sc = network.getSeedClient() await basicSeed(sc) carol = sc.accounts[sc.dids.carol] diff --git a/packages/pds/tests/account-migration.test.ts b/packages/pds/tests/account-migration.test.ts index beb14599e38..5de1ef29b05 100644 --- a/packages/pds/tests/account-migration.test.ts +++ b/packages/pds/tests/account-migration.test.ts @@ -1,9 +1,9 @@ -import AtpAgent, { AtUri } from '@atproto/api' +import { AtpAgent, AtUri } from '@atproto/api' import { + mockNetworkUtilities, SeedClient, TestNetworkNoAppView, TestPds, - mockNetworkUtilities, } from '@atproto/dev-env' import { readCar } from '@atproto/repo' import assert from 'assert' diff --git a/packages/pds/tests/account.test.ts b/packages/pds/tests/account.test.ts index a0cddde4d4b..8922262e76b 100644 --- a/packages/pds/tests/account.test.ts +++ b/packages/pds/tests/account.test.ts @@ -1,8 +1,8 @@ -import { once, EventEmitter } from 'events' -import AtpAgent, { ComAtprotoServerResetPassword } from '@atproto/api' -import { IdResolver } from '@atproto/identity' +import { AtpAgent, ComAtprotoServerResetPassword } from '@atproto/api' import * as crypto from '@atproto/crypto' import { TestNetworkNoAppView } from '@atproto/dev-env' +import { IdResolver } from '@atproto/identity' +import { EventEmitter, once } from 'events' import Mail from 'nodemailer/lib/mailer' import { AppContext } from '../src' import { ServerMailer } from '../src/mailer' @@ -381,6 +381,7 @@ describe('account', () => { }) it('can perform authenticated requests', async () => { + // @TODO each test should be able to run independently & concurrently agent.api.setHeader('authorization', `Bearer ${jwt}`) const res = await agent.api.com.atproto.server.getSession({}) expect(res.data.did).toBe(did) diff --git a/packages/pds/tests/app-passwords.test.ts b/packages/pds/tests/app-passwords.test.ts index d7654622718..5104e5542bf 100644 --- a/packages/pds/tests/app-passwords.test.ts +++ b/packages/pds/tests/app-passwords.test.ts @@ -1,5 +1,5 @@ +import { AtpAgent } from '@atproto/api' import { TestNetworkNoAppView } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' import * as jose from 'jose' describe('app_passwords', () => { diff --git a/packages/pds/tests/auth.test.ts b/packages/pds/tests/auth.test.ts index 4d02a7c00df..a078bdbc841 100644 --- a/packages/pds/tests/auth.test.ts +++ b/packages/pds/tests/auth.test.ts @@ -1,5 +1,5 @@ import * as jose from 'jose' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetworkNoAppView, SeedClient } from '@atproto/dev-env' import { createRefreshToken } from '../src/account-manager/helpers/auth' diff --git a/packages/pds/tests/blob-deletes.test.ts b/packages/pds/tests/blob-deletes.test.ts index 73b466c8727..81afa686fad 100644 --- a/packages/pds/tests/blob-deletes.test.ts +++ b/packages/pds/tests/blob-deletes.test.ts @@ -1,5 +1,5 @@ import { TestNetworkNoAppView, SeedClient } from '@atproto/dev-env' -import AtpAgent, { BlobRef } from '@atproto/api' +import { AtpAgent, BlobRef } from '@atproto/api' import { ids } from '../src/lexicon/lexicons' import { AppContext } from '../src' diff --git a/packages/pds/tests/create-post.test.ts b/packages/pds/tests/create-post.test.ts index 81d813b9aeb..742fe2bb841 100644 --- a/packages/pds/tests/create-post.test.ts +++ b/packages/pds/tests/create-post.test.ts @@ -1,9 +1,10 @@ import { TestNetworkNoAppView, SeedClient } from '@atproto/dev-env' -import AtpAgent, { +import { AppBskyFeedPost, AtUri, RichText, AppBskyRichtextFacet, + AtpAgent, } from '@atproto/api' import basicSeed from './seeds/basic' diff --git a/packages/pds/tests/crud.test.ts b/packages/pds/tests/crud.test.ts index d8319135078..caca1763e6c 100644 --- a/packages/pds/tests/crud.test.ts +++ b/packages/pds/tests/crud.test.ts @@ -1,29 +1,15 @@ import fs from 'fs/promises' import { AtUri } from '@atproto/syntax' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { BlobRef } from '@atproto/lexicon' import { TestNetworkNoAppView } from '@atproto/dev-env' import { cidForCbor, TID, ui8ToArrayBuffer } from '@atproto/common' import { BlobNotFoundError } from '@atproto/repo' -import { defaultFetchHandler } from '@atproto/xrpc' import * as Post from '../src/lexicon/types/app/bsky/feed/post' import { paginateAll } from './_util' import AppContext from '../src/context' import { ids, lexicons } from '../src/lexicon/lexicons' -const alice = { - email: 'alice@test.com', - handle: 'alice.test', - did: '', - password: 'alice-pass', -} -const bob = { - email: 'bob@test.com', - handle: 'bob.test', - did: '', - password: 'bob-pass', -} - describe('crud operations', () => { let network: TestNetworkNoAppView let ctx: AppContext @@ -40,6 +26,20 @@ describe('crud operations', () => { agent = network.pds.getClient() aliceAgent = network.pds.getClient() bobAgent = network.pds.getClient() + + await aliceAgent.createAccount({ + email: 'alice@test.com', + handle: 'alice.test', + password: 'alice-pass', + }) + + await bobAgent.createAccount({ + email: 'bob@test.com', + handle: 'bob.test', + password: 'bob-pass', + }) + + expect(bobAgent.accountDid).not.toBe(aliceAgent.accountDid) }) afterAll(async () => { @@ -48,38 +48,30 @@ describe('crud operations', () => { it('registers users', async () => { const res = await agent.api.com.atproto.server.createAccount({ - email: alice.email, - handle: alice.handle, - password: alice.password, - }) - aliceAgent.api.setHeader('authorization', `Bearer ${res.data.accessJwt}`) - alice.did = res.data.did - const res2 = await agent.api.com.atproto.server.createAccount({ - email: bob.email, - handle: bob.handle, - password: bob.password, - }) - bobAgent.api.setHeader('authorization', `Bearer ${res2.data.accessJwt}`) - bob.did = res2.data.did + handle: 'user1.test', + email: 'user1@test.com', + password: 'password', + }) + expect(res.data.handle).toBe('user1.test') + expect(res.data.accessJwt).toBeDefined() }) it('describes repo', async () => { const description = await agent.api.com.atproto.repo.describeRepo({ - repo: alice.did, + repo: aliceAgent.accountDid, }) - expect(description.data.handle).toBe(alice.handle) - expect(description.data.did).toBe(alice.did) + expect(description.data.handle).toBe('alice.test') + expect(description.data.did).toBe(aliceAgent.accountDid) const description2 = await agent.api.com.atproto.repo.describeRepo({ - repo: bob.did, + repo: bobAgent.accountDid, }) - expect(description2.data.handle).toBe(bob.handle) - expect(description2.data.did).toBe(bob.did) + expect(description2.data.handle).toBe('bob.test') + expect(description2.data.did).toBe(bobAgent.accountDid) }) - let uri: AtUri it('creates records', async () => { const res = await aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.post', record: { $type: 'app.bsky.feed.post', @@ -87,15 +79,13 @@ describe('crud operations', () => { createdAt: new Date().toISOString(), }, }) - uri = new AtUri(res.data.uri) + const uri = new AtUri(res.data.uri) expect(res.data.uri).toBe( - `at://${alice.did}/app.bsky.feed.post/${uri.rkey}`, + `at://${aliceAgent.accountDid}/app.bsky.feed.post/${uri.rkey}`, ) - }) - it('lists records', async () => { const res1 = await agent.api.com.atproto.repo.listRecords({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.post', }) expect(res1.data.records.length).toBe(1) @@ -104,47 +94,43 @@ describe('crud operations', () => { 'Hello, world!', ) - const res2 = await agent.api.app.bsky.feed.post.list({ - repo: alice.did, + const res2 = await agent.app.bsky.feed.post.list({ + repo: aliceAgent.accountDid, }) expect(res2.records.length).toBe(1) expect(res2.records[0].uri).toBe(uri.toString()) expect(res2.records[0].value.text).toBe('Hello, world!') - }) - it('gets records', async () => { - const res1 = await agent.api.com.atproto.repo.getRecord({ - repo: alice.did, + const res3 = await agent.api.com.atproto.repo.getRecord({ + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.post', rkey: uri.rkey, }) - expect(res1.data.uri).toBe(uri.toString()) - expect((res1.data.value as Post.Record).text).toBe('Hello, world!') + expect(res3.data.uri).toBe(uri.toString()) + expect((res3.data.value as Post.Record).text).toBe('Hello, world!') - const res2 = await agent.api.app.bsky.feed.post.get({ - repo: alice.did, + const res4 = await agent.app.bsky.feed.post.get({ + repo: aliceAgent.accountDid, rkey: uri.rkey, }) - expect(res2.uri).toBe(uri.toString()) - expect(res2.value.text).toBe('Hello, world!') - }) + expect(res4.uri).toBe(uri.toString()) + expect(res4.value.text).toBe('Hello, world!') - it('deletes records', async () => { await aliceAgent.api.com.atproto.repo.deleteRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.post', rkey: uri.rkey, }) - const res1 = await agent.api.com.atproto.repo.listRecords({ - repo: alice.did, + const res5 = await agent.api.com.atproto.repo.listRecords({ + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.post', }) - expect(res1.data.records.length).toBe(0) + expect(res5.data.records.length).toBe(0) }) it('CRUDs records with the semantic sugars', async () => { - const res1 = await aliceAgent.api.app.bsky.feed.post.create( - { repo: alice.did }, + const res1 = await aliceAgent.app.bsky.feed.post.create( + { repo: aliceAgent.accountDid }, { $type: 'app.bsky.feed.post', text: 'Hello, world!', @@ -153,18 +139,18 @@ describe('crud operations', () => { ) const uri = new AtUri(res1.uri) - const res2 = await agent.api.app.bsky.feed.post.list({ - repo: alice.did, + const res2 = await agent.app.bsky.feed.post.list({ + repo: aliceAgent.accountDid, }) expect(res2.records.length).toBe(1) - await aliceAgent.api.app.bsky.feed.post.delete({ - repo: alice.did, + await aliceAgent.app.bsky.feed.post.delete({ + repo: aliceAgent.accountDid, rkey: uri.rkey, }) - const res3 = await agent.api.app.bsky.feed.post.list({ - repo: alice.did, + const res3 = await agent.app.bsky.feed.post.list({ + repo: aliceAgent.accountDid, }) expect(res3.records.length).toBe(0) }) @@ -179,11 +165,11 @@ describe('crud operations', () => { const uploaded = uploadedRes.data.blob // Expect blobstore not to have image yet await expect( - ctx.blobstore(alice.did).getBytes(uploaded.ref), + ctx.blobstore(aliceAgent.accountDid).getBytes(uploaded.ref), ).rejects.toThrow(BlobNotFoundError) // Associate image with post, image should be placed in blobstore - const res = await aliceAgent.api.app.bsky.feed.post.create( - { repo: alice.did }, + const res = await aliceAgent.app.bsky.feed.post.create( + { repo: aliceAgent.accountDid }, { $type: 'app.bsky.feed.post', text: "Here's a key!", @@ -196,25 +182,25 @@ describe('crud operations', () => { ) // Ensure image is on post record const postUri = new AtUri(res.uri) - const post = await aliceAgent.api.app.bsky.feed.post.get({ + const post = await aliceAgent.app.bsky.feed.post.get({ rkey: postUri.rkey, - repo: alice.did, + repo: aliceAgent.accountDid, }) const images = post.value.embed?.images as { image: BlobRef }[] expect(images.length).toEqual(1) expect(uploaded.ref.equals(images[0].image.ref)).toBeTruthy() // Ensure that the uploaded image is now in the blobstore, i.e. doesn't throw BlobNotFoundError - await ctx.blobstore(alice.did).getBytes(uploaded.ref) + await ctx.blobstore(aliceAgent.accountDid).getBytes(uploaded.ref) // Cleanup - await aliceAgent.api.app.bsky.feed.post.delete({ + await aliceAgent.app.bsky.feed.post.delete({ rkey: postUri.rkey, - repo: alice.did, + repo: aliceAgent.accountDid, }) }) it('creates records with the correct key described by the schema', async () => { - const res1 = await aliceAgent.api.app.bsky.actor.profile.create( - { repo: alice.did }, + const res1 = await aliceAgent.app.bsky.actor.profile.create( + { repo: aliceAgent.accountDid }, { displayName: 'alice', createdAt: new Date().toISOString(), @@ -233,8 +219,8 @@ describe('crud operations', () => { beforeAll(async () => { const createPost = async (text: string) => { - const res = await aliceAgent.api.app.bsky.feed.post.create( - { repo: alice.did }, + const res = await aliceAgent.app.bsky.feed.post.create( + { repo: aliceAgent.accountDid }, { $type: 'app.bsky.feed.post', text, @@ -252,8 +238,8 @@ describe('crud operations', () => { afterAll(async () => { for (const uri of [uri1, uri2, uri3, uri4, uri5]) { - await aliceAgent.api.app.bsky.feed.post.delete({ - repo: alice.did, + await aliceAgent.app.bsky.feed.post.delete({ + repo: aliceAgent.accountDid, rkey: uri.rkey, }) } @@ -262,8 +248,8 @@ describe('crud operations', () => { it('in forwards order', async () => { const results = (results) => results.flatMap((res) => res.records) const paginator = async (cursor?: string) => { - const res = await agent.api.app.bsky.feed.post.list({ - repo: alice.did, + const res = await agent.app.bsky.feed.post.list({ + repo: aliceAgent.accountDid, cursor, limit: 2, }) @@ -275,8 +261,8 @@ describe('crud operations', () => { expect(res.records.length).toBeLessThanOrEqual(2), ) - const full = await agent.api.app.bsky.feed.post.list({ - repo: alice.did, + const full = await agent.app.bsky.feed.post.list({ + repo: aliceAgent.accountDid, }) expect(full.records.length).toEqual(5) @@ -286,8 +272,8 @@ describe('crud operations', () => { it('in reverse order', async () => { const results = (results) => results.flatMap((res) => res.records) const paginator = async (cursor?: string) => { - const res = await agent.api.app.bsky.feed.post.list({ - repo: alice.did, + const res = await agent.app.bsky.feed.post.list({ + repo: aliceAgent.accountDid, reverse: true, cursor, limit: 2, @@ -300,8 +286,8 @@ describe('crud operations', () => { expect(res.records.length).toBeLessThanOrEqual(2), ) - const full = await agent.api.app.bsky.feed.post.list({ - repo: alice.did, + const full = await agent.app.bsky.feed.post.list({ + repo: aliceAgent.accountDid, reverse: true, }) @@ -310,11 +296,11 @@ describe('crud operations', () => { }) it('reverses', async () => { - const forwards = await agent.api.app.bsky.feed.post.list({ - repo: alice.did, + const forwards = await agent.app.bsky.feed.post.list({ + repo: aliceAgent.accountDid, }) - const reverse = await agent.api.app.bsky.feed.post.list({ - repo: alice.did, + const reverse = await agent.app.bsky.feed.post.list({ + repo: aliceAgent.accountDid, reverse: true, }) expect(forwards.cursor).toEqual(uri1.rkey) @@ -329,7 +315,7 @@ describe('crud operations', () => { it('deletes a record if it exists', async () => { const { repo } = aliceAgent.api.com.atproto const { data: post } = await repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyFeedPost, record: { text: 'post', createdAt: new Date().toISOString() }, }) @@ -350,7 +336,7 @@ describe('crud operations', () => { it("no-ops if record doesn't exist", async () => { const { repo } = aliceAgent.api.com.atproto const { data: post } = await repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyFeedPost, record: { text: 'post', createdAt: new Date().toISOString() }, }) @@ -378,12 +364,12 @@ describe('crud operations', () => { const { repo } = aliceAgent.api.com.atproto const record = { text: 'post', createdAt: new Date().toISOString() } const { data: post1 } = await repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyFeedPost, record, }) const { data: post2 } = await repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyFeedPost, record, }) @@ -412,21 +398,26 @@ describe('crud operations', () => { it("creates a new record if it doesn't already exist", async () => { const { repo } = bobAgent.api.com.atproto - const exists = repo.getRecord({ ...profilePath, repo: bob.did }) + const exists = repo.getRecord({ + ...profilePath, + repo: bobAgent.accountDid, + }) await expect(exists).rejects.toThrow('Could not locate record') const { data: put } = await repo.putRecord({ ...profilePath, - repo: bob.did, + repo: bobAgent.accountDid, record: { displayName: 'Robert', }, }) - expect(put.uri).toEqual(`at://${bob.did}/${ids.AppBskyActorProfile}/self`) + expect(put.uri).toEqual( + `at://${bobAgent.accountDid}/${ids.AppBskyActorProfile}/self`, + ) const { data: profile } = await repo.getRecord({ ...profilePath, - repo: bob.did, + repo: bobAgent.accountDid, }) expect(profile.value).toEqual({ $type: ids.AppBskyActorProfile, @@ -438,17 +429,19 @@ describe('crud operations', () => { const { repo } = bobAgent.api.com.atproto const { data: put } = await repo.putRecord({ ...profilePath, - repo: bob.did, + repo: bobAgent.accountDid, record: { displayName: 'Robert', description: 'Dog lover', }, }) - expect(put.uri).toEqual(`at://${bob.did}/${ids.AppBskyActorProfile}/self`) + expect(put.uri).toEqual( + `at://${bobAgent.accountDid}/${ids.AppBskyActorProfile}/self`, + ) const { data: profile } = await repo.getRecord({ ...profilePath, - repo: bob.did, + repo: bobAgent.accountDid, }) expect(profile.value).toEqual({ $type: ids.AppBskyActorProfile, @@ -460,20 +453,22 @@ describe('crud operations', () => { it('does not produce commit on no-op update', async () => { const { repo } = bobAgent.api.com.atproto const rootRes1 = await bobAgent.api.com.atproto.sync.getLatestCommit({ - did: bob.did, + did: bobAgent.accountDid, }) const { data: put } = await repo.putRecord({ ...profilePath, - repo: bob.did, + repo: bobAgent.accountDid, record: { displayName: 'Robert', description: 'Dog lover', }, }) - expect(put.uri).toEqual(`at://${bob.did}/${ids.AppBskyActorProfile}/self`) + expect(put.uri).toEqual( + `at://${bobAgent.accountDid}/${ids.AppBskyActorProfile}/self`, + ) const rootRes2 = await bobAgent.api.com.atproto.sync.getLatestCommit({ - did: bob.did, + did: bobAgent.accountDid, }) expect(rootRes2.data.cid).toEqual(rootRes1.data.cid) @@ -483,11 +478,11 @@ describe('crud operations', () => { it('fails on user mismatch', async () => { const { repo } = aliceAgent.api.com.atproto const put = repo.putRecord({ - repo: bob.did, + repo: bobAgent.accountDid, collection: ids.AppBskyGraphFollow, rkey: TID.nextStr(), record: { - subject: alice.did, + subject: aliceAgent.accountDid, createdAt: new Date().toISOString(), }, }) @@ -498,7 +493,7 @@ describe('crud operations', () => { const { repo } = bobAgent.api.com.atproto const put = repo.putRecord({ ...profilePath, - repo: bob.did, + repo: bobAgent.accountDid, record: { displayName: 'Robert', description: 3.141, @@ -509,7 +504,7 @@ describe('crud operations', () => { ) const { data: profile } = await repo.getRecord({ ...profilePath, - repo: bob.did, + repo: bobAgent.accountDid, }) expect(profile.value).toEqual({ $type: ids.AppBskyActorProfile, @@ -530,7 +525,7 @@ describe('crud operations', () => { await repo.putRecord({ ...profilePath, - repo: bob.did, + repo: bobAgent.accountDid, record: { displayName: 'Robert', avatar: BlobRef.fromJsonRef({ @@ -542,7 +537,7 @@ describe('crud operations', () => { const got = await repo.getRecord({ ...profilePath, - repo: bob.did, + repo: bobAgent.accountDid, }) const gotAvatar = got.data.value['avatar'] as BlobRef expect(gotAvatar.original).toEqual(uploadedRes.data.blob.original) @@ -554,7 +549,7 @@ describe('crud operations', () => { it('defaults an undefined $type on records', async () => { const res = await aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.post', record: { text: 'blah', @@ -563,7 +558,7 @@ describe('crud operations', () => { }) const uri = new AtUri(res.data.uri) const got = await agent.api.com.atproto.repo.getRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: uri.collection, rkey: uri.rkey, }) @@ -572,7 +567,7 @@ describe('crud operations', () => { it('requires the schema to be known if validating', async () => { const prom = aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'com.example.foobar', record: { $type: 'com.example.foobar' }, }) @@ -584,7 +579,7 @@ describe('crud operations', () => { it('requires the $type to match the schema', async () => { await expect( aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.post', record: { $type: 'app.bsky.feed.like' }, }), @@ -596,7 +591,7 @@ describe('crud operations', () => { it('requires valid rkey', async () => { await expect( aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.generator', record: { $type: 'app.bsky.feed.generator', @@ -612,7 +607,7 @@ describe('crud operations', () => { it('validates the record on write', async () => { await expect( aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.post', record: { $type: 'app.bsky.feed.post' }, }), @@ -630,7 +625,7 @@ describe('crud operations', () => { lexicons.assertValidRecord('app.bsky.feed.post', postRecord) await expect( aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.post', record: postRecord, }), @@ -642,7 +637,7 @@ describe('crud operations', () => { describe('unvalidated writes', () => { it('disallows creation of unknown lexicons when validate is set to true', async () => { const attempt = aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'com.example.record', record: { blah: 'thing', @@ -655,14 +650,14 @@ describe('crud operations', () => { it('allows creation of unknown lexicons when validate is set to false', async () => { const res = await aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'com.example.record', record: { blah: 'thing', }, validate: false, }) - const record = await ctx.actorStore.read(alice.did, (store) => + const record = await ctx.actorStore.read(aliceAgent.accountDid, (store) => store.record.getRecord(new AtUri(res.data.uri), res.data.cid), ) expect(record?.value).toEqual({ @@ -673,7 +668,7 @@ describe('crud operations', () => { it('allows update of unknown lexicons when validate is set to false', async () => { const createRes = await aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'com.example.record', record: { blah: 'thing', @@ -682,7 +677,7 @@ describe('crud operations', () => { }) const uri = new AtUri(createRes.data.uri) const updateRes = await aliceAgent.api.com.atproto.repo.putRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'com.example.record', rkey: uri.rkey, record: { @@ -690,7 +685,7 @@ describe('crud operations', () => { }, validate: false, }) - const record = await ctx.actorStore.read(alice.did, (store) => + const record = await ctx.actorStore.read(aliceAgent.accountDid, (store) => store.record.getRecord(uri, updateRes.data.cid), ) expect(record?.value).toEqual({ @@ -711,7 +706,7 @@ describe('crud operations', () => { ) const res = await aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'com.example.record', record: { blah: 'thing', @@ -719,20 +714,22 @@ describe('crud operations', () => { }, validate: false, }) - const record = await ctx.actorStore.read(alice.did, (store) => + const record = await ctx.actorStore.read(aliceAgent.accountDid, (store) => store.record.getRecord(new AtUri(res.data.uri), res.data.cid), ) expect(record?.value).toMatchObject({ $type: 'com.example.record', blah: 'thing', }) - const recordBlobs = await ctx.actorStore.read(alice.did, (store) => - store.db.db - .selectFrom('blob') - .innerJoin('record_blob', 'record_blob.blobCid', 'blob.cid') - .where('recordUri', '=', res.data.uri) - .selectAll() - .execute(), + const recordBlobs = await ctx.actorStore.read( + aliceAgent.accountDid, + (store) => + store.db.db + .selectFrom('blob') + .innerJoin('record_blob', 'record_blob.blobCid', 'blob.cid') + .where('recordUri', '=', res.data.uri) + .selectAll() + .execute(), ) expect(recordBlobs.length).toBe(1) expect(recordBlobs.at(0)?.cid).toBe(uploadedRes.data.blob.ref.toString()) @@ -740,7 +737,7 @@ describe('crud operations', () => { it('enforces record type constraint even when unvalidated', async () => { const attempt = aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'com.example.record', record: { $type: 'com.example.other', @@ -764,7 +761,7 @@ describe('crud operations', () => { ) const attempt = aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'com.example.record', record: { blah: 'thing', @@ -791,9 +788,11 @@ describe('crud operations', () => { it('createRecord succeeds on proper commit cas', async () => { const { repo, sync } = aliceAgent.api.com.atproto - const { data: commit } = await sync.getLatestCommit({ did: alice.did }) + const { data: commit } = await sync.getLatestCommit({ + did: aliceAgent.accountDid, + }) const { data: post } = await repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyFeedPost, swapCommit: commit.cid, record: postRecord(), @@ -810,16 +809,16 @@ describe('crud operations', () => { it('createRecord fails on bad commit cas', async () => { const { repo, sync } = aliceAgent.api.com.atproto const { data: staleCommit } = await sync.getLatestCommit({ - did: alice.did, + did: aliceAgent.accountDid, }) // Update repo, change head await repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyFeedPost, record: postRecord(), }) const attemptCreate = repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyFeedPost, swapCommit: staleCommit.cid, record: postRecord(), @@ -832,11 +831,13 @@ describe('crud operations', () => { it('deleteRecord succeeds on proper commit cas', async () => { const { repo, sync } = aliceAgent.api.com.atproto const { data: post } = await repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyFeedPost, record: postRecord(), }) - const { data: commit } = await sync.getLatestCommit({ did: alice.did }) + const { data: commit } = await sync.getLatestCommit({ + did: aliceAgent.accountDid, + }) const uri = new AtUri(post.uri) await repo.deleteRecord({ repo: uri.host, @@ -855,10 +856,10 @@ describe('crud operations', () => { it('deleteRecord fails on bad commit cas', async () => { const { repo, sync } = aliceAgent.api.com.atproto const { data: staleCommit } = await sync.getLatestCommit({ - did: alice.did, + did: aliceAgent.accountDid, }) const { data: post } = await repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyFeedPost, record: postRecord(), }) @@ -883,7 +884,7 @@ describe('crud operations', () => { it('deleteRecord succeeds on proper record cas', async () => { const { repo } = aliceAgent.api.com.atproto const { data: post } = await repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyFeedPost, record: postRecord(), }) @@ -905,7 +906,7 @@ describe('crud operations', () => { it('deleteRecord fails on bad record cas', async () => { const { repo } = aliceAgent.api.com.atproto const { data: post } = await repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyFeedPost, record: postRecord(), }) @@ -929,16 +930,18 @@ describe('crud operations', () => { it('putRecord succeeds on proper commit cas', async () => { const { repo, sync } = aliceAgent.api.com.atproto - const { data: commit } = await sync.getLatestCommit({ did: alice.did }) + const { data: commit } = await sync.getLatestCommit({ + did: aliceAgent.accountDid, + }) const { data: profile } = await repo.putRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyActorProfile, rkey: 'self', swapCommit: commit.cid, record: profileRecord(), }) const { data: checkProfile } = await repo.getRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyActorProfile, rkey: 'self', }) @@ -948,16 +951,16 @@ describe('crud operations', () => { it('putRecord fails on bad commit cas', async () => { const { repo, sync } = aliceAgent.api.com.atproto const { data: staleCommit } = await sync.getLatestCommit({ - did: alice.did, + did: aliceAgent.accountDid, }) // Update repo, change head await repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyFeedPost, record: postRecord(), }) const attemptPut = repo.putRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyActorProfile, rkey: 'self', swapCommit: staleCommit.cid, @@ -970,34 +973,34 @@ describe('crud operations', () => { const { repo } = aliceAgent.api.com.atproto // Start with missing profile record, to test swapRecord=null await repo.deleteRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyActorProfile, rkey: 'self', }) // Test swapRecord w/ null (ensures create) const { data: profile1 } = await repo.putRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyActorProfile, rkey: 'self', swapRecord: null, record: profileRecord(), }) const { data: checkProfile1 } = await repo.getRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyActorProfile, rkey: 'self', }) expect(checkProfile1.cid).toEqual(profile1.cid) // Test swapRecord w/ cid (ensures update) const { data: profile2 } = await repo.putRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyActorProfile, rkey: 'self', swapRecord: profile1.cid, record: profileRecord(), }) const { data: checkProfile2 } = await repo.getRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyActorProfile, rkey: 'self', }) @@ -1008,7 +1011,7 @@ describe('crud operations', () => { const { repo } = aliceAgent.api.com.atproto // Test swapRecord w/ null (ensures create) const attemptPut1 = repo.putRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyActorProfile, rkey: 'self', swapRecord: null, @@ -1017,7 +1020,7 @@ describe('crud operations', () => { await expect(attemptPut1).rejects.toMatchObject({ error: 'InvalidSwap' }) // Test swapRecord w/ cid (ensures update) const attemptPut2 = repo.putRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyActorProfile, rkey: 'self', swapRecord: (await cidForCbor({})).toString(), @@ -1028,9 +1031,11 @@ describe('crud operations', () => { it('applyWrites succeeds on proper commit cas', async () => { const { repo, sync } = aliceAgent.api.com.atproto - const { data: commit } = await sync.getLatestCommit({ did: alice.did }) + const { data: commit } = await sync.getLatestCommit({ + did: aliceAgent.accountDid, + }) await repo.applyWrites({ - repo: alice.did, + repo: aliceAgent.accountDid, swapCommit: commit.cid, writes: [ { @@ -1046,16 +1051,16 @@ describe('crud operations', () => { it('applyWrites fails on bad commit cas', async () => { const { repo, sync } = aliceAgent.api.com.atproto const { data: staleCommit } = await sync.getLatestCommit({ - did: alice.did, + did: aliceAgent.accountDid, }) // Update repo, change head await repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: ids.AppBskyFeedPost, record: postRecord(), }) const attemptApplyWrite = repo.applyWrites({ - repo: alice.did, + repo: aliceAgent.accountDid, swapCommit: staleCommit.cid, writes: [ { @@ -1074,12 +1079,12 @@ describe('crud operations', () => { it("writes fail on values that can't reliably transform between cbor to lex", async () => { const passthroughBody = (data: unknown) => ui8ToArrayBuffer(new TextEncoder().encode(JSON.stringify(data))) - const result = await defaultFetchHandler( - aliceAgent.service.origin + `/xrpc/com.atproto.repo.createRecord`, - 'post', - { ...aliceAgent.api.xrpc.headers, 'Content-Type': 'application/json' }, + + const result = aliceAgent.call( + 'com.atproto.repo.createRecord', + {}, passthroughBody({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.post', record: { text: 'x', @@ -1087,9 +1092,13 @@ describe('crud operations', () => { deepObject: createDeepObject(3000), }, }), + { + encoding: 'application/json', + }, ) - expect(result.status).toEqual(400) - expect(result.body).toEqual({ + + await expect(result).rejects.toMatchObject({ + status: 400, error: 'InvalidRequest', message: 'Bad record', }) @@ -1098,13 +1107,21 @@ describe('crud operations', () => { it('prevents duplicate likes', async () => { const now = new Date().toISOString() - const uriA = AtUri.make(bob.did, 'app.bsky.feed.post', TID.nextStr()) + const uriA = AtUri.make( + bobAgent.accountDid, + 'app.bsky.feed.post', + TID.nextStr(), + ) const cidA = await cidForCbor({ post: 'a' }) - const uriB = AtUri.make(bob.did, 'app.bsky.feed.post', TID.nextStr()) + const uriB = AtUri.make( + bobAgent.accountDid, + 'app.bsky.feed.post', + TID.nextStr(), + ) const cidB = await cidForCbor({ post: 'b' }) const { data: like1 } = await aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.like', record: { $type: 'app.bsky.feed.like', @@ -1113,7 +1130,7 @@ describe('crud operations', () => { }, }) const { data: like2 } = await aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.like', record: { $type: 'app.bsky.feed.like', @@ -1122,7 +1139,7 @@ describe('crud operations', () => { }, }) const { data: like3 } = await aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.like', record: { $type: 'app.bsky.feed.like', @@ -1132,7 +1149,7 @@ describe('crud operations', () => { }) const getLike1 = aliceAgent.api.com.atproto.repo.getRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.like', rkey: new AtUri(like1.uri).rkey, }) @@ -1140,7 +1157,7 @@ describe('crud operations', () => { await expect(getLike1).rejects.toThrow('Could not locate record:') const getLike2 = aliceAgent.api.com.atproto.repo.getRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.like', rkey: new AtUri(like2.uri).rkey, }) @@ -1148,7 +1165,7 @@ describe('crud operations', () => { await expect(getLike2).resolves.toBeDefined() const getLike3 = aliceAgent.api.com.atproto.repo.getRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.like', rkey: new AtUri(like3.uri).rkey, }) @@ -1158,14 +1175,22 @@ describe('crud operations', () => { it('prevents duplicate reposts', async () => { const now = new Date().toISOString() - const uriA = AtUri.make(bob.did, 'app.bsky.feed.post', TID.nextStr()) + const uriA = AtUri.make( + bobAgent.accountDid, + 'app.bsky.feed.post', + TID.nextStr(), + ) const cidA = await cidForCbor({ post: 'a' }) - const uriB = AtUri.make(bob.did, 'app.bsky.feed.post', TID.nextStr()) + const uriB = AtUri.make( + bobAgent.accountDid, + 'app.bsky.feed.post', + TID.nextStr(), + ) const cidB = await cidForCbor({ post: 'b' }) const { data: repost1 } = await aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.repost', record: { $type: 'app.bsky.feed.repost', @@ -1175,7 +1200,7 @@ describe('crud operations', () => { }) const { data: repost2 } = await aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.repost', record: { $type: 'app.bsky.feed.repost', @@ -1185,7 +1210,7 @@ describe('crud operations', () => { }) const { data: repost3 } = await aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.repost', record: { $type: 'app.bsky.feed.repost', @@ -1195,7 +1220,7 @@ describe('crud operations', () => { }) const getRepost1 = aliceAgent.api.com.atproto.repo.getRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.repost', rkey: new AtUri(repost1.uri).rkey, }) @@ -1203,7 +1228,7 @@ describe('crud operations', () => { await expect(getRepost1).rejects.toThrow('Could not locate record:') const getRepost2 = aliceAgent.api.com.atproto.repo.getRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.repost', rkey: new AtUri(repost2.uri).rkey, }) @@ -1211,7 +1236,7 @@ describe('crud operations', () => { await expect(getRepost2).resolves.toBeDefined() const getRepost3 = aliceAgent.api.com.atproto.repo.getRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.feed.repost', rkey: new AtUri(repost3.uri).rkey, }) @@ -1224,40 +1249,40 @@ describe('crud operations', () => { const { data: block1 } = await aliceAgent.api.com.atproto.repo.createRecord( { - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.graph.block', record: { $type: 'app.bsky.graph.block', - subject: bob.did, + subject: bobAgent.accountDid, createdAt: now, }, }, ) const { data: block2 } = await bobAgent.api.com.atproto.repo.createRecord({ - repo: bob.did, + repo: bobAgent.accountDid, collection: 'app.bsky.graph.block', record: { $type: 'app.bsky.graph.block', - subject: alice.did, + subject: aliceAgent.accountDid, createdAt: now, }, }) const { data: block3 } = await aliceAgent.api.com.atproto.repo.createRecord( { - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.graph.block', record: { $type: 'app.bsky.graph.block', - subject: bob.did, + subject: bobAgent.accountDid, createdAt: now, }, }, ) const getBlock1 = aliceAgent.api.com.atproto.repo.getRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.graph.block', rkey: new AtUri(block1.uri).rkey, }) @@ -1265,7 +1290,7 @@ describe('crud operations', () => { await expect(getBlock1).rejects.toThrow('Could not locate record:') const getBlock2 = aliceAgent.api.com.atproto.repo.getRecord({ - repo: bob.did, + repo: bobAgent.accountDid, collection: 'app.bsky.graph.block', rkey: new AtUri(block2.uri).rkey, }) @@ -1273,7 +1298,7 @@ describe('crud operations', () => { await expect(getBlock2).resolves.toBeDefined() const getBlock3 = aliceAgent.api.com.atproto.repo.getRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.graph.block', rkey: new AtUri(block3.uri).rkey, }) @@ -1286,36 +1311,36 @@ describe('crud operations', () => { const { data: follow1 } = await aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.graph.follow', record: { $type: 'app.bsky.graph.follow', - subject: bob.did, + subject: bobAgent.accountDid, createdAt: now, }, }) const { data: follow2 } = await bobAgent.api.com.atproto.repo.createRecord({ - repo: bob.did, + repo: bobAgent.accountDid, collection: 'app.bsky.graph.follow', record: { $type: 'app.bsky.graph.follow', - subject: alice.did, + subject: aliceAgent.accountDid, createdAt: now, }, }) const { data: follow3 } = await aliceAgent.api.com.atproto.repo.createRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.graph.follow', record: { $type: 'app.bsky.graph.follow', - subject: bob.did, + subject: bobAgent.accountDid, createdAt: now, }, }) const getFollow1 = aliceAgent.api.com.atproto.repo.getRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.graph.follow', rkey: new AtUri(follow1.uri).rkey, }) @@ -1323,7 +1348,7 @@ describe('crud operations', () => { await expect(getFollow1).rejects.toThrow('Could not locate record:') const getFollow2 = aliceAgent.api.com.atproto.repo.getRecord({ - repo: bob.did, + repo: bobAgent.accountDid, collection: 'app.bsky.graph.follow', rkey: new AtUri(follow2.uri).rkey, }) @@ -1331,7 +1356,7 @@ describe('crud operations', () => { await expect(getFollow2).resolves.toBeDefined() const getFollow3 = aliceAgent.api.com.atproto.repo.getRecord({ - repo: alice.did, + repo: aliceAgent.accountDid, collection: 'app.bsky.graph.follow', rkey: new AtUri(follow3.uri).rkey, }) @@ -1343,8 +1368,8 @@ describe('crud operations', () => { // -------------- it("doesn't serve taken-down record", async () => { - const created = await aliceAgent.api.app.bsky.feed.post.create( - { repo: alice.did }, + const created = await aliceAgent.app.bsky.feed.post.create( + { repo: aliceAgent.accountDid }, { $type: 'app.bsky.feed.post', text: 'Hello, world!', @@ -1352,11 +1377,13 @@ describe('crud operations', () => { }, ) const postUri = new AtUri(created.uri) - const post = await agent.api.app.bsky.feed.post.get({ - repo: alice.did, + const post = await agent.app.bsky.feed.post.get({ + repo: aliceAgent.accountDid, rkey: postUri.rkey, }) - const posts = await agent.api.app.bsky.feed.post.list({ repo: alice.did }) + const posts = await agent.app.bsky.feed.post.list({ + repo: aliceAgent.accountDid, + }) expect(posts.records.map((r) => r.uri)).toContain(post.uri) const subject = { @@ -1375,13 +1402,13 @@ describe('crud operations', () => { }, ) - const postTakedownPromise = agent.api.app.bsky.feed.post.get({ - repo: alice.did, + const postTakedownPromise = agent.app.bsky.feed.post.get({ + repo: aliceAgent.accountDid, rkey: postUri.rkey, }) await expect(postTakedownPromise).rejects.toThrow('Could not locate record') - const postsTakedown = await agent.api.app.bsky.feed.post.list({ - repo: alice.did, + const postsTakedown = await agent.app.bsky.feed.post.list({ + repo: aliceAgent.accountDid, }) expect(postsTakedown.records.map((r) => r.uri)).not.toContain(post.uri) @@ -1399,12 +1426,14 @@ describe('crud operations', () => { }) it("doesn't serve taken-down actor", async () => { - const posts = await agent.api.app.bsky.feed.post.list({ repo: alice.did }) + const posts = await agent.app.bsky.feed.post.list({ + repo: aliceAgent.accountDid, + }) expect(posts.records.length).toBeGreaterThan(0) const subject = { $type: 'com.atproto.admin.defs#repoRef', - did: alice.did, + did: aliceAgent.accountDid, } await agent.api.com.atproto.admin.updateSubjectStatus( @@ -1418,8 +1447,8 @@ describe('crud operations', () => { }, ) - const tryListPosts = agent.api.app.bsky.feed.post.list({ - repo: alice.did, + const tryListPosts = agent.app.bsky.feed.post.list({ + repo: aliceAgent.accountDid, }) await expect(tryListPosts).rejects.toThrow(/Could not find repo/) diff --git a/packages/pds/tests/email-confirmation.test.ts b/packages/pds/tests/email-confirmation.test.ts index d41ed587476..440524842fa 100644 --- a/packages/pds/tests/email-confirmation.test.ts +++ b/packages/pds/tests/email-confirmation.test.ts @@ -1,6 +1,6 @@ import { once, EventEmitter } from 'events' import Mail from 'nodemailer/lib/mailer' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetworkNoAppView, SeedClient } from '@atproto/dev-env' import userSeed from './seeds/users' import { ServerMailer } from '../src/mailer' diff --git a/packages/pds/tests/entryway.test.ts b/packages/pds/tests/entryway.test.ts index 0e91496886e..ceb72784f7b 100644 --- a/packages/pds/tests/entryway.test.ts +++ b/packages/pds/tests/entryway.test.ts @@ -1,7 +1,7 @@ import * as os from 'node:os' import * as path from 'node:path' import * as plcLib from '@did-plc/lib' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { Secp256k1Keypair, randomStr } from '@atproto/crypto' import { SeedClient, TestPds, TestPlc, mockResolvers } from '@atproto/dev-env' import * as pdsEntryway from '@atproto/pds-entryway' diff --git a/packages/pds/tests/file-uploads.test.ts b/packages/pds/tests/file-uploads.test.ts index 0ce419a7209..b38b4103682 100644 --- a/packages/pds/tests/file-uploads.test.ts +++ b/packages/pds/tests/file-uploads.test.ts @@ -1,6 +1,6 @@ import fs from 'fs/promises' import { gzipSync } from 'zlib' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { AppContext } from '../src' import DiskBlobStore from '../src/disk-blobstore' import * as uint8arrays from 'uint8arrays' diff --git a/packages/pds/tests/handles.test.ts b/packages/pds/tests/handles.test.ts index ee7ec7716f4..50c4ba942fa 100644 --- a/packages/pds/tests/handles.test.ts +++ b/packages/pds/tests/handles.test.ts @@ -1,5 +1,5 @@ import { TestNetworkNoAppView, SeedClient } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { IdResolver } from '@atproto/identity' import basicSeed from './seeds/basic' import { AppContext } from '../src' diff --git a/packages/pds/tests/invite-codes.test.ts b/packages/pds/tests/invite-codes.test.ts index f1066feb7d2..cf0f2025ad7 100644 --- a/packages/pds/tests/invite-codes.test.ts +++ b/packages/pds/tests/invite-codes.test.ts @@ -1,8 +1,8 @@ -import AtpAgent, { ComAtprotoServerCreateAccount } from '@atproto/api' +import { AtpAgent, ComAtprotoServerCreateAccount } from '@atproto/api' +import { DAY } from '@atproto/common' import * as crypto from '@atproto/crypto' import { TestNetworkNoAppView } from '@atproto/dev-env' import { AppContext } from '../src' -import { DAY } from '@atproto/common' import { genInvCodes } from '../src/api/com/atproto/server/util' describe('account', () => { @@ -121,8 +121,7 @@ describe('account', () => { const account = await makeLoggedInAccount(network, agent) // no codes available yet - const res1 = - await account.agent.api.com.atproto.server.getAccountInviteCodes() + const res1 = await account.com.atproto.server.getAccountInviteCodes() expect(res1.data.codes.length).toBe(0) // next, pretend account was made 2 days in the past @@ -130,10 +129,9 @@ describe('account', () => { await ctx.accountManager.db.db .updateTable('actor') .set({ createdAt: twoDaysAgo }) - .where('did', '=', account.did) + .where('did', '=', account.accountDid) .execute() - const res2 = - await account.agent.api.com.atproto.server.getAccountInviteCodes() + const res2 = await account.com.atproto.server.getAccountInviteCodes() expect(res2.data.codes.length).toBe(2) // use both invites and confirm we can't get any more @@ -141,8 +139,7 @@ describe('account', () => { await createAccountWithInvite(agent, code.code) } - const res3 = - await account.agent.api.com.atproto.server.getAccountInviteCodes() + const res3 = await account.com.atproto.server.getAccountInviteCodes() expect(res3.data.codes.length).toBe(2) }) @@ -154,15 +151,14 @@ describe('account', () => { await ctx.accountManager.db.db .updateTable('actor') .set({ createdAt: twoDaysAgo }) - .where('did', '=', account.did) + .where('did', '=', account.accountDid) .execute() - await createInviteCode(network, agent, 1, account.did) - await createInviteCode(network, agent, 1, account.did) - await createInviteCode(network, agent, 1, account.did) + await createInviteCode(network, agent, 1, account.accountDid) + await createInviteCode(network, agent, 1, account.accountDid) + await createInviteCode(network, agent, 1, account.accountDid) - const res = - await account.agent.api.com.atproto.server.getAccountInviteCodes() + const res = await account.com.atproto.server.getAccountInviteCodes() expect(res.data.codes.length).toBe(5) const fromAdmin = res.data.codes.filter( @@ -171,7 +167,7 @@ describe('account', () => { expect(fromAdmin.length).toBe(3) const fromSelf = res.data.codes.filter( - (code) => code.createdBy === account.did, + (code) => code.createdBy === account.accountDid, ) expect(fromSelf.length).toBe(2) }) @@ -184,11 +180,10 @@ describe('account', () => { await ctx.accountManager.db.db .updateTable('actor') .set({ createdAt: twoDaysAgo }) - .where('did', '=', account.did) + .where('did', '=', account.accountDid) .execute() - const res1 = - await account.agent.api.com.atproto.server.getAccountInviteCodes() + const res1 = await account.com.atproto.server.getAccountInviteCodes() expect(res1.data.codes.length).toBe(2) // then pretend account was made ever so slightly over 10 days ago @@ -196,12 +191,11 @@ describe('account', () => { await ctx.accountManager.db.db .updateTable('actor') .set({ createdAt: tenDaysAgo }) - .where('did', '=', account.did) + .where('did', '=', account.accountDid) .execute() // we have a 3 day epoch so should still get 3 code - const res2 = - await account.agent.api.com.atproto.server.getAccountInviteCodes() + const res2 = await account.com.atproto.server.getAccountInviteCodes() expect(res2.data.codes.length).toBe(3) // use up these codes @@ -214,18 +208,17 @@ describe('account', () => { code: code, availableUses: 1, disabled: 0 as const, - forAccount: account.did, - createdBy: account.did, + forAccount: account.accountDid, + createdBy: account.accountDid, createdAt: new Date(Date.now() - 5 * DAY).toISOString(), })) await ctx.accountManager.db.db .insertInto('invite_code') .values(inviteRows) .execute() - const res3 = - await account.agent.api.com.atproto.server.getAccountInviteCodes({ - includeUsed: false, - }) + const res3 = await account.com.atproto.server.getAccountInviteCodes({ + includeUsed: false, + }) expect(res3.data.codes.length).toBe(10) // no we use the codes which should still not allow them to generate anymore @@ -240,23 +233,22 @@ describe('account', () => { ) .execute() - const res4 = - await account.agent.api.com.atproto.server.getAccountInviteCodes({ - includeUsed: false, - }) + const res4 = await account.com.atproto.server.getAccountInviteCodes({ + includeUsed: false, + }) expect(res4.data.codes.length).toBe(0) }) it('prevents use of disabled codes', async () => { const first = await createInviteCode(network, agent, 1) const account = await makeLoggedInAccount(network, agent) - const second = await createInviteCode(network, agent, 1, account.did) + const second = await createInviteCode(network, agent, 1, account.accountDid) // disabled first by code & second by did await agent.api.com.atproto.admin.disableInviteCodes( { codes: [first], - accounts: [account.did], + accounts: [account.accountDid], }, { headers: network.pds.adminAuthHeaders(), @@ -361,18 +353,14 @@ const createAccountsWithInvite = async ( const makeLoggedInAccount = async ( network: TestNetworkNoAppView, - agent: AtpAgent, -): Promise<{ did: string; agent: AtpAgent }> => { - const code = await createInviteCode(network, agent, 1) - const account = await createAccountWithInvite(agent, code) - const did = account.did - const loggedInAgent = new AtpAgent({ service: agent.service.toString() }) - await loggedInAgent.login({ + inviterAgent: AtpAgent, +) => { + const code = await createInviteCode(network, inviterAgent, 1) + const account = await createAccountWithInvite(inviterAgent, code) + const agent = network.pds.getClient() + await agent.login({ identifier: account.handle, password: account.password, }) - return { - did, - agent: loggedInAgent, - } + return agent } diff --git a/packages/pds/tests/invites-admin.test.ts b/packages/pds/tests/invites-admin.test.ts index c4462847eb3..7ffe1d3dd7e 100644 --- a/packages/pds/tests/invites-admin.test.ts +++ b/packages/pds/tests/invites-admin.test.ts @@ -1,5 +1,5 @@ import { TestNetworkNoAppView, SeedClient } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { randomStr } from '@atproto/crypto' describe('pds admin invite views', () => { diff --git a/packages/pds/tests/moderation.test.ts b/packages/pds/tests/moderation.test.ts index 2c9af9b48a9..50fc65d9326 100644 --- a/packages/pds/tests/moderation.test.ts +++ b/packages/pds/tests/moderation.test.ts @@ -1,5 +1,5 @@ +import { AtpAgent } from '@atproto/api' import { TestNetworkNoAppView, ImageRef, SeedClient } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' import { BlobNotFoundError } from '@atproto/repo' import basicSeed from './seeds/basic' import { diff --git a/packages/pds/tests/moderator-auth.test.ts b/packages/pds/tests/moderator-auth.test.ts index 0b89b4f0fd7..aba04a93273 100644 --- a/packages/pds/tests/moderator-auth.test.ts +++ b/packages/pds/tests/moderator-auth.test.ts @@ -1,5 +1,5 @@ +import { AtpAgent } from '@atproto/api' import { TestNetworkNoAppView, SeedClient } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' import { Keypair, Secp256k1Keypair } from '@atproto/crypto' import { createServiceAuthHeaders } from '@atproto/xrpc-server' import * as plc from '@did-plc/lib' @@ -76,6 +76,7 @@ describe('moderator auth', () => { const headers = await createServiceAuthHeaders({ iss: modServiceDid, aud: pdsDid, + lxm: null, keypair: modServiceKey, }) await agent.api.com.atproto.admin.updateSubjectStatus( @@ -103,6 +104,7 @@ describe('moderator auth', () => { const headers = await createServiceAuthHeaders({ iss: altModDid, aud: pdsDid, + lxm: null, keypair: modServiceKey, }) const attempt = agent.api.com.atproto.admin.updateSubjectStatus( @@ -123,6 +125,7 @@ describe('moderator auth', () => { const headers = await createServiceAuthHeaders({ iss: modServiceDid, aud: pdsDid, + lxm: null, keypair: badKey, }) const attempt = agent.api.com.atproto.admin.updateSubjectStatus( @@ -145,6 +148,7 @@ describe('moderator auth', () => { const headers = await createServiceAuthHeaders({ iss: modServiceDid, aud: sc.dids.alice, + lxm: null, keypair: modServiceKey, }) const attempt = agent.api.com.atproto.admin.updateSubjectStatus( diff --git a/packages/pds/tests/plc-operations.test.ts b/packages/pds/tests/plc-operations.test.ts index 20a149d4d94..8c3b3366c50 100644 --- a/packages/pds/tests/plc-operations.test.ts +++ b/packages/pds/tests/plc-operations.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { Secp256k1Keypair } from '@atproto/crypto' import { SeedClient, TestNetworkNoAppView, basicSeed } from '@atproto/dev-env' import * as plc from '@did-plc/lib' diff --git a/packages/pds/tests/preferences.test.ts b/packages/pds/tests/preferences.test.ts index d5b51c0ba69..93abdb61bba 100644 --- a/packages/pds/tests/preferences.test.ts +++ b/packages/pds/tests/preferences.test.ts @@ -1,5 +1,5 @@ +import { AtpAgent } from '@atproto/api' import { TestNetworkNoAppView, SeedClient } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' import usersSeed from './seeds/users' import { AuthScope } from '../dist/auth-verifier' diff --git a/packages/pds/tests/proxied/admin.test.ts b/packages/pds/tests/proxied/admin.test.ts index 1ff745597ea..1f51d2df9f5 100644 --- a/packages/pds/tests/proxied/admin.test.ts +++ b/packages/pds/tests/proxied/admin.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient } from '@atproto/dev-env' import basicSeed from '../seeds/basic' import { forSnapshot } from '../_util' diff --git a/packages/pds/tests/proxied/feedgen.test.ts b/packages/pds/tests/proxied/feedgen.test.ts index c3cd5d208d6..52831edab40 100644 --- a/packages/pds/tests/proxied/feedgen.test.ts +++ b/packages/pds/tests/proxied/feedgen.test.ts @@ -1,4 +1,4 @@ -import AtpAgent, { AtUri } from '@atproto/api' +import { AtpAgent, AtUri } from '@atproto/api' import { TestNetwork, SeedClient } from '@atproto/dev-env' import basicSeed from '../seeds/basic' import { forSnapshot } from '../_util' diff --git a/packages/pds/tests/proxied/notif.test.ts b/packages/pds/tests/proxied/notif.test.ts index a02b291fa75..5b86b87ac09 100644 --- a/packages/pds/tests/proxied/notif.test.ts +++ b/packages/pds/tests/proxied/notif.test.ts @@ -2,7 +2,7 @@ import { once } from 'events' import http from 'http' import { AddressInfo } from 'net' import express from 'express' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetworkNoAppView, SeedClient } from '@atproto/dev-env' import { verifyJwt } from '@atproto/xrpc-server' import usersSeed from '../seeds/users' @@ -69,6 +69,7 @@ describe('notif service proxy', () => { const auth = await verifyJwt( spy.current?.['jwt'] as string, notifDid, + null, async (did) => { const keypair = await network.pds.ctx.actorStore.keypair(did) return keypair.did() diff --git a/packages/pds/tests/proxied/procedures.test.ts b/packages/pds/tests/proxied/procedures.test.ts index 8b488621c41..3cc84e3f3d2 100644 --- a/packages/pds/tests/proxied/procedures.test.ts +++ b/packages/pds/tests/proxied/procedures.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient } from '@atproto/dev-env' import basicSeed from '../seeds/basic' diff --git a/packages/pds/tests/proxied/proxy-header.test.ts b/packages/pds/tests/proxied/proxy-header.test.ts index d00dc3bb342..9eb9842c1ee 100644 --- a/packages/pds/tests/proxied/proxy-header.test.ts +++ b/packages/pds/tests/proxied/proxy-header.test.ts @@ -66,6 +66,7 @@ describe('proxy header', () => { const verified = await verifyJwt( req.auth.replace('Bearer ', ''), proxyServer.did, + null, (iss) => network.pds.ctx.idResolver.did.resolveAtprotoKey(iss, true), ) expect(verified.aud).toBe(proxyServer.did) diff --git a/packages/pds/tests/proxied/read-after-write.test.ts b/packages/pds/tests/proxied/read-after-write.test.ts index 5f70416751a..2f3f94da3ed 100644 --- a/packages/pds/tests/proxied/read-after-write.test.ts +++ b/packages/pds/tests/proxied/read-after-write.test.ts @@ -1,6 +1,6 @@ import util from 'node:util' import assert from 'node:assert' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, RecordRef } from '@atproto/dev-env' import basicSeed from '../seeds/basic' import { ThreadViewPost } from '../../src/lexicon/types/app/bsky/feed/defs' diff --git a/packages/pds/tests/proxied/views.test.ts b/packages/pds/tests/proxied/views.test.ts index 9cfb7063bdf..01b097e8b3f 100644 --- a/packages/pds/tests/proxied/views.test.ts +++ b/packages/pds/tests/proxied/views.test.ts @@ -1,4 +1,4 @@ -import AtpAgent, { AtUri } from '@atproto/api' +import { AtpAgent, AtUri } from '@atproto/api' import { TestNetwork, SeedClient } from '@atproto/dev-env' import basicSeed from '../seeds/basic' import { forSnapshot } from '../_util' diff --git a/packages/pds/tests/races.test.ts b/packages/pds/tests/races.test.ts index d595b2ef397..f8bc141218f 100644 --- a/packages/pds/tests/races.test.ts +++ b/packages/pds/tests/races.test.ts @@ -1,4 +1,4 @@ -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { wait } from '@atproto/common' import { TestNetworkNoAppView } from '@atproto/dev-env' import { readCarWithRoot, verifyRepo } from '@atproto/repo' @@ -25,7 +25,7 @@ describe('races', () => { handle: 'alice.test', password: 'alice-pass', }) - did = agent.session?.did || '' + did = agent.accountDid signingKey = await network.pds.ctx.actorStore.keypair(did) }) diff --git a/packages/pds/tests/server.test.ts b/packages/pds/tests/server.test.ts index 81deaf83df7..90defe391b1 100644 --- a/packages/pds/tests/server.test.ts +++ b/packages/pds/tests/server.test.ts @@ -2,7 +2,7 @@ import { AddressInfo } from 'net' import express from 'express' import axios, { AxiosError } from 'axios' import { SeedClient, TestNetworkNoAppView } from '@atproto/dev-env' -import AtpAgent, { AtUri } from '@atproto/api' +import { AtpAgent, AtUri } from '@atproto/api' import { handler as errorHandler } from '../src/error' import basicSeed from './seeds/basic' import { randomStr } from '@atproto/crypto' diff --git a/packages/pds/tests/sync/list.test.ts b/packages/pds/tests/sync/list.test.ts index 6d09793b8f0..a71f9327794 100644 --- a/packages/pds/tests/sync/list.test.ts +++ b/packages/pds/tests/sync/list.test.ts @@ -1,5 +1,5 @@ import { TestNetworkNoAppView, SeedClient } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import basicSeed from '../seeds/basic' describe('sync listing', () => { diff --git a/packages/pds/tests/sync/subscribe-repos.test.ts b/packages/pds/tests/sync/subscribe-repos.test.ts index e9b6a736b3c..573596df536 100644 --- a/packages/pds/tests/sync/subscribe-repos.test.ts +++ b/packages/pds/tests/sync/subscribe-repos.test.ts @@ -1,5 +1,5 @@ import { TestNetworkNoAppView, SeedClient } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { cborDecode, HOUR, diff --git a/packages/pds/tests/sync/sync.test.ts b/packages/pds/tests/sync/sync.test.ts index e58ef14344c..b23366ec672 100644 --- a/packages/pds/tests/sync/sync.test.ts +++ b/packages/pds/tests/sync/sync.test.ts @@ -1,5 +1,5 @@ import { TestNetworkNoAppView, SeedClient } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { TID } from '@atproto/common' import { Keypair, randomStr } from '@atproto/crypto' import * as repo from '@atproto/repo' diff --git a/packages/repo/CHANGELOG.md b/packages/repo/CHANGELOG.md index d5f1bf3f75f..3cdbf4c1e54 100644 --- a/packages/repo/CHANGELOG.md +++ b/packages/repo/CHANGELOG.md @@ -1,5 +1,12 @@ # @atproto/repo +## 0.4.2 + +### Patch Changes + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`2bdf75d7a`](https://github.com/bluesky-social/atproto/commit/2bdf75d7a63924c10e7a311f16cb447d595b933e)]: + - @atproto/lexicon@0.4.1 + ## 0.4.1 ### Patch Changes diff --git a/packages/repo/package.json b/packages/repo/package.json index e31d882a555..8b89dac1a16 100644 --- a/packages/repo/package.json +++ b/packages/repo/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/repo", - "version": "0.4.1", + "version": "0.4.2", "license": "MIT", "description": "atproto repo and MST implementation", "keywords": [ diff --git a/packages/xrpc-server/CHANGELOG.md b/packages/xrpc-server/CHANGELOG.md index 0a55044bf9a..a02c2734018 100644 --- a/packages/xrpc-server/CHANGELOG.md +++ b/packages/xrpc-server/CHANGELOG.md @@ -1,5 +1,19 @@ # @atproto/xrpc-server +## 0.6.1 + +### Patch Changes + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`2bdf75d7a`](https://github.com/bluesky-social/atproto/commit/2bdf75d7a63924c10e7a311f16cb447d595b933e), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd)]: + - @atproto/lexicon@0.4.1 + - @atproto/xrpc@0.6.0 + +## 0.6.0 + +### Minor Changes + +- [#2668](https://github.com/bluesky-social/atproto/pull/2668) [`dc471da26`](https://github.com/bluesky-social/atproto/commit/dc471da267955d0962a8affaf983df60d962d97c) Thanks [@dholms](https://github.com/dholms)! - Add lxm and nonce to signed service auth tokens. + ## 0.5.3 ### Patch Changes diff --git a/packages/xrpc-server/package.json b/packages/xrpc-server/package.json index a62c4e130bf..9ca7f231ffe 100644 --- a/packages/xrpc-server/package.json +++ b/packages/xrpc-server/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/xrpc-server", - "version": "0.5.3", + "version": "0.6.1", "license": "MIT", "description": "atproto HTTP API (XRPC) server library", "keywords": [ diff --git a/packages/xrpc-server/src/auth.ts b/packages/xrpc-server/src/auth.ts index 32373248f51..548579d1eb3 100644 --- a/packages/xrpc-server/src/auth.ts +++ b/packages/xrpc-server/src/auth.ts @@ -4,14 +4,20 @@ import * as crypto from '@atproto/crypto' import * as ui8 from 'uint8arrays' import { AuthRequiredError } from './types' -type ServiceJwtPayload = { +type ServiceJwtParams = { iss: string aud: string exp?: number + lxm: string | null + keypair: crypto.Keypair } -type ServiceJwtParams = ServiceJwtPayload & { - keypair: crypto.Keypair +type ServiceJwtPayload = { + iss: string + aud: string + exp: number + lxm?: string + jti?: string } export const createServiceJwt = async ( @@ -19,15 +25,19 @@ export const createServiceJwt = async ( ): Promise => { const { iss, aud, keypair } = params const exp = params.exp ?? Math.floor((Date.now() + MINUTE) / 1000) + const lxm = params.lxm ?? undefined + const jti = await crypto.randomStr(16, 'hex') const header = { typ: 'JWT', alg: keypair.jwtAlg, } - const payload = { + const payload = common.noUndefinedVals({ iss, aud, exp, - } + lxm, + jti, + }) const toSignStr = `${jsonToB64Url(header)}.${jsonToB64Url(payload)}` const toSign = ui8.fromString(toSignStr, 'utf8') const sig = await keypair.sign(toSign) @@ -48,6 +58,7 @@ const jsonToB64Url = (json: Record): string => { export const verifyJwt = async ( jwtStr: string, ownDid: string | null, // null indicates to skip the audience check + lxm: string | null, // null indicates to skip the lxm check getSigningKey: (iss: string, forceRefresh: boolean) => Promise, ): Promise => { const parts = jwtStr.split('.') @@ -66,6 +77,12 @@ export const verifyJwt = async ( 'BadJwtAudience', ) } + if (lxm !== null && lxm !== payload.lxm) { + throw new AuthRequiredError( + `missing jwt lexicon method ("lxm"): ${lxm}`, + 'MissingJwtMethod', + ) + } const msgBytes = ui8.fromString(parts.slice(0, 2).join('.'), 'utf8') const sigBytes = ui8.fromString(sig, 'base64url') @@ -117,20 +134,18 @@ const parseB64UrlToJson = (b64: string) => { return JSON.parse(common.b64UrlToUtf8(b64)) } -const parsePayload = (b64: string): JwtPayload => { +const parsePayload = (b64: string): ServiceJwtPayload => { const payload = parseB64UrlToJson(b64) - if (!payload || typeof payload !== 'object') { - throw new AuthRequiredError('poorly formatted jwt', 'BadJwt') - } else if (typeof payload.exp !== 'number') { - throw new AuthRequiredError('poorly formatted jwt', 'BadJwt') - } else if (typeof payload.iss !== 'string') { + if ( + !payload || + typeof payload !== 'object' || + typeof payload.iss !== 'string' || + typeof payload.aud !== 'string' || + typeof payload.exp !== 'number' || + (payload.lxm && typeof payload.lxm !== 'string') || + (payload.nonce && typeof payload.nonce !== 'string') + ) { throw new AuthRequiredError('poorly formatted jwt', 'BadJwt') } return payload } - -type JwtPayload = { - iss: string - aud: string - exp: number -} diff --git a/packages/xrpc-server/src/index.ts b/packages/xrpc-server/src/index.ts index 1458d2ba070..5efd15d0e62 100644 --- a/packages/xrpc-server/src/index.ts +++ b/packages/xrpc-server/src/index.ts @@ -5,4 +5,4 @@ export * from './stream' export * from './rate-limiter' export type { ServerTiming } from './util' -export { serverTimingHeader, ServerTimer } from './util' +export { parseReqNsid, serverTimingHeader, ServerTimer } from './util' diff --git a/packages/xrpc-server/src/server.ts b/packages/xrpc-server/src/server.ts index 97c0ddfa3e5..c1123685f11 100644 --- a/packages/xrpc-server/src/server.ts +++ b/packages/xrpc-server/src/server.ts @@ -342,7 +342,7 @@ export class Server { } } catch (err: unknown) { // Express will not call the next middleware (errorMiddleware in this case) - // if the value passed to next is falsy (e.g. null, undefined, 0). + // if the value passed to next is false-y (e.g. null, undefined, 0). // Hence we replace it with an InternalServerError. if (!err) { next(new InternalServerError()) diff --git a/packages/xrpc-server/src/types.ts b/packages/xrpc-server/src/types.ts index c12c3c35891..db171e9ddea 100644 --- a/packages/xrpc-server/src/types.ts +++ b/packages/xrpc-server/src/types.ts @@ -90,14 +90,22 @@ export type XRPCStreamHandler = (ctx: { export type AuthOutput = HandlerAuth | HandlerError -export type AuthVerifier = (ctx: { +export interface AuthVerifierContext { req: express.Request res: express.Response -}) => Promise | AuthOutput +} + +export type AuthVerifier = ( + ctx: AuthVerifierContext, +) => Promise | AuthOutput -export type StreamAuthVerifier = (ctx: { +export interface StreamAuthVerifierContext { req: IncomingMessage -}) => Promise | AuthOutput +} + +export type StreamAuthVerifier = ( + ctx: StreamAuthVerifierContext, +) => Promise | AuthOutput export type CalcKeyFn = (ctx: XRPCReqContext) => string | null export type CalcPointsFn = (ctx: XRPCReqContext) => number diff --git a/packages/xrpc-server/src/util.ts b/packages/xrpc-server/src/util.ts index ed78dbc070c..47f9a5e764f 100644 --- a/packages/xrpc-server/src/util.ts +++ b/packages/xrpc-server/src/util.ts @@ -306,3 +306,8 @@ export interface ServerTiming { duration?: number description?: string } + +export const parseReqNsid = (req: express.Request): string => { + const nsid = req.originalUrl.split('?')[0].replace('/xrpc/', '') + return nsid.endsWith('/') ? nsid.slice(0, -1) : nsid // trim trailing slash +} diff --git a/packages/xrpc-server/tests/auth.test.ts b/packages/xrpc-server/tests/auth.test.ts index bbd202d1024..6e16e9a0227 100644 --- a/packages/xrpc-server/tests/auth.test.ts +++ b/packages/xrpc-server/tests/auth.test.ts @@ -7,7 +7,7 @@ import * as ui8 from 'uint8arrays' import { MINUTE } from '@atproto/common' import { Secp256k1Keypair } from '@atproto/crypto' import { LexiconDoc } from '@atproto/lexicon' -import xrpc, { ServiceClient, XRPCError } from '@atproto/xrpc' +import { XrpcClient, XRPCError } from '@atproto/xrpc' import * as xrpcServer from '../src' import { createServer, @@ -62,18 +62,59 @@ describe('Auth', () => { } }, }) - xrpc.addLexicons(LEXICONS) - let client: ServiceClient + let client: XrpcClient beforeAll(async () => { const port = await getPort() s = await createServer(port, server) - client = xrpc.service(`http://localhost:${port}`) + client = new XrpcClient(`http://localhost:${port}`, LEXICONS) }) + afterAll(async () => { await closeServer(s) }) + it('creates and validates service auth headers', async () => { + const keypair = await Secp256k1Keypair.create() + const iss = 'did:example:alice' + const aud = 'did:example:bob' + const token = await xrpcServer.createServiceJwt({ + iss, + aud, + keypair, + lxm: null, + }) + const validated = await xrpcServer.verifyJwt(token, null, null, async () => + keypair.did(), + ) + expect(validated.iss).toEqual(iss) + expect(validated.aud).toEqual(aud) + // should expire within the minute when no exp is provided + expect(validated.exp).toBeGreaterThan(Date.now() / 1000) + expect(validated.exp).toBeLessThan(Date.now() / 1000 + 60) + expect(typeof validated.jti).toBe('string') + expect(validated.lxm).toBeUndefined() + }) + + it('creates and validates service auth headers bound to a particular method', async () => { + const keypair = await Secp256k1Keypair.create() + const iss = 'did:example:alice' + const aud = 'did:example:bob' + const lxm = 'com.atproto.repo.createRecord' + const token = await xrpcServer.createServiceJwt({ + iss, + aud, + keypair, + lxm, + }) + const validated = await xrpcServer.verifyJwt(token, null, lxm, async () => + keypair.did(), + ) + expect(validated.iss).toEqual(iss) + expect(validated.aud).toEqual(aud) + expect(validated.lxm).toEqual(lxm) + }) + it('fails on bad auth before invalid request payload.', async () => { try { await client.call( @@ -147,10 +188,12 @@ describe('Auth', () => { iss: 'did:example:iss', keypair, exp: Math.floor((Date.now() - MINUTE) / 1000), + lxm: null, }) const tryVerify = xrpcServer.verifyJwt( jwt, 'did:example:aud', + null, async () => { return keypair.did() }, @@ -164,10 +207,12 @@ describe('Auth', () => { aud: 'did:example:aud1', iss: 'did:example:iss', keypair, + lxm: null, }) const tryVerify = xrpcServer.verifyJwt( jwt, 'did:example:aud2', + null, async () => { return keypair.did() }, @@ -177,6 +222,44 @@ describe('Auth', () => { ) }) + it('fails on bad lxm', async () => { + const keypair = await Secp256k1Keypair.create() + const jwt = await xrpcServer.createServiceJwt({ + aud: 'did:example:aud1', + iss: 'did:example:iss', + keypair, + lxm: 'com.atproto.repo.createRecord', + }) + const tryVerify = xrpcServer.verifyJwt( + jwt, + 'did:example:aud1', + 'com.atproto.repo.putRecord', + async () => { + return keypair.did() + }, + ) + await expect(tryVerify).rejects.toThrow(/missing jwt lexicon method/) + }) + + it('fails on null lxm when lxm is required', async () => { + const keypair = await Secp256k1Keypair.create() + const jwt = await xrpcServer.createServiceJwt({ + aud: 'did:example:aud1', + iss: 'did:example:iss', + keypair, + lxm: null, + }) + const tryVerify = xrpcServer.verifyJwt( + jwt, + 'did:example:aud1', + 'com.atproto.repo.putRecord', + async () => { + return keypair.did() + }, + ) + await expect(tryVerify).rejects.toThrow(/missing jwt lexicon method/) + }) + it('refreshes key on verification failure.', async () => { const keypair1 = await Secp256k1Keypair.create() const keypair2 = await Secp256k1Keypair.create() @@ -184,12 +267,14 @@ describe('Auth', () => { aud: 'did:example:aud', iss: 'did:example:iss', keypair: keypair2, + lxm: null, }) let usedKeypair1 = false let usedKeypair2 = false const tryVerify = xrpcServer.verifyJwt( jwt, 'did:example:aud', + null, async (_did, forceRefresh) => { if (forceRefresh) { usedKeypair2 = true @@ -222,6 +307,7 @@ describe('Auth', () => { const tryVerify = xrpcServer.verifyJwt( jwt, 'did:example:aud', + null, async () => { return keypair.did() }, diff --git a/packages/xrpc-server/tests/bodies.test.ts b/packages/xrpc-server/tests/bodies.test.ts index 18c7b4dd3e1..a0ba0ebbef6 100644 --- a/packages/xrpc-server/tests/bodies.test.ts +++ b/packages/xrpc-server/tests/bodies.test.ts @@ -3,8 +3,8 @@ import { Readable } from 'stream' import { gzipSync } from 'zlib' import getPort from 'get-port' import { LexiconDoc } from '@atproto/lexicon' -import xrpc, { ServiceClient } from '@atproto/xrpc' -import { bytesToStream, cidForCbor } from '@atproto/common' +import { XrpcClient } from '@atproto/xrpc' +import { cidForCbor } from '@atproto/common' import { randomBytes } from '@atproto/crypto' import { createServer, closeServer } from './_util' import * as xrpcServer from '../src' @@ -131,15 +131,14 @@ describe('Bodies', () => { } }, ) - xrpc.addLexicons(LEXICONS) - let client: ServiceClient + let client: XrpcClient let url: string beforeAll(async () => { const port = await getPort() s = await createServer(port, server) url = `http://localhost:${port}` - client = xrpc.service(url) + client = new XrpcClient(url, LEXICONS) }) afterAll(async () => { await closeServer(s) @@ -174,7 +173,65 @@ describe('Bodies', () => { { foo: 'hello', bar: 123 }, { encoding: 'image/jpeg' }, ), + ).rejects.toThrow(`Unable to encode object as image/jpeg data`) + await expect( + client.call( + 'io.example.validationTest', + {}, + // Does not need to be a valid jpeg + new Blob([randomBytes(123)], { type: 'image/jpeg' }), + ), ).rejects.toThrow(`Wrong request encoding (Content-Type): image/jpeg`) + await expect( + client.call( + 'io.example.validationTest', + {}, + (() => { + const formData = new FormData() + formData.append('foo', 'bar') + return formData + })(), + ), + ).rejects.toThrow( + `Wrong request encoding (Content-Type): multipart/form-data`, + ) + await expect( + client.call( + 'io.example.validationTest', + {}, + new URLSearchParams([['foo', 'bar']]), + ), + ).rejects.toThrow( + `Wrong request encoding (Content-Type): application/x-www-form-urlencoded`, + ) + await expect( + client.call( + 'io.example.validationTest', + {}, + new Blob([new Uint8Array([1])]), + ), + ).rejects.toThrow( + `Wrong request encoding (Content-Type): application/octet-stream`, + ) + await expect( + client.call( + 'io.example.validationTest', + {}, + new ReadableStream({ + pull(ctrl) { + ctrl.enqueue(new Uint8Array([1])) + ctrl.close() + }, + }), + ), + ).rejects.toThrow( + `Wrong request encoding (Content-Type): application/octet-stream`, + ) + await expect( + client.call('io.example.validationTest', {}, new Uint8Array([1])), + ).rejects.toThrow( + `Wrong request encoding (Content-Type): application/octet-stream`, + ) // 500 responses don't include details, so we nab details from the logger. let error: string | undefined @@ -201,6 +258,86 @@ describe('Bodies', () => { expect(bytesResponse.data.cid).toEqual(expectedCid.toString()) }) + it('supports empty payload on procedues with encoding', async () => { + const bytes = new Uint8Array(0) + const expectedCid = await cidForCbor(bytes) + const bytesResponse = await client.call('io.example.blobTest', {}, bytes) + expect(bytesResponse.data.cid).toEqual(expectedCid.toString()) + }) + + it('supports upload of empty txt file', async () => { + const txtFile = new Blob([], { type: 'text/plain' }) + const expectedCid = await cidForCbor(await txtFile.arrayBuffer()) + const fileResponse = await client.call('io.example.blobTest', {}, txtFile) + expect(fileResponse.data.cid).toEqual(expectedCid.toString()) + }) + + // This does not work because the xrpc-server will add a json middleware + // regardless of the "input" definition. This is probably a behavior that + // should be fixed in the xrpc-server. + it.skip('supports upload of json data', async () => { + const jsonFile = new Blob([Buffer.from(`{"foo":"bar","baz":[3, null]}`)], { + type: 'application/json', + }) + const expectedCid = await cidForCbor(await jsonFile.arrayBuffer()) + const fileResponse = await client.call('io.example.blobTest', {}, jsonFile) + expect(fileResponse.data.cid).toEqual(expectedCid.toString()) + }) + + it('supports ArrayBufferView', async () => { + const bytes = randomBytes(1024) + const expectedCid = await cidForCbor(bytes) + + const bufferResponse = await client.call( + 'io.example.blobTest', + {}, + Buffer.from(bytes), + ) + expect(bufferResponse.data.cid).toEqual(expectedCid.toString()) + }) + + it('supports Blob', async () => { + const bytes = randomBytes(1024) + const expectedCid = await cidForCbor(bytes) + + const blobResponse = await client.call( + 'io.example.blobTest', + {}, + new Blob([bytes], { type: 'application/octet-stream' }), + ) + expect(blobResponse.data.cid).toEqual(expectedCid.toString()) + }) + + it('supports Blob without explicit type', async () => { + const bytes = randomBytes(1024) + const expectedCid = await cidForCbor(bytes) + + const blobResponse = await client.call( + 'io.example.blobTest', + {}, + new Blob([bytes]), + ) + expect(blobResponse.data.cid).toEqual(expectedCid.toString()) + }) + + it('supports ReadableStream', async () => { + const bytes = randomBytes(1024) + const expectedCid = await cidForCbor(bytes) + + const streamResponse = await client.call( + 'io.example.blobTest', + {}, + // ReadableStream.from not available in node < 20 + new ReadableStream({ + pull(ctrl) { + ctrl.enqueue(bytes) + ctrl.close() + }, + }), + ) + expect(streamResponse.data.cid).toEqual(expectedCid.toString()) + }) + it('supports blobs and compression', async () => { const bytes = randomBytes(1024) const expectedCid = await cidForCbor(bytes) @@ -230,10 +367,11 @@ describe('Bodies', () => { }) it('supports empty payload', async () => { - const expectedCid = await cidForCbor(new Uint8Array(0)) + const bytes = new Uint8Array(0) + const expectedCid = await cidForCbor(bytes) // Using "undefined" as body to avoid encoding as lexicon { $bytes: "" } - const result = await client.call('io.example.blobTest', {}, undefined, { + const result = await client.call('io.example.blobTest', {}, bytes, { encoding: 'text/plain', }) @@ -264,7 +402,7 @@ describe('Bodies', () => { await client.call( 'io.example.blobTest', {}, - bytesToStream(bytes.slice(0, BLOB_LIMIT)), + bytesToReadableStream(bytes.slice(0, BLOB_LIMIT)), { encoding: 'application/octet-stream', }, @@ -274,7 +412,7 @@ describe('Bodies', () => { const promise = client.call( 'io.example.blobTest', {}, - bytesToStream(bytes), + bytesToReadableStream(bytes), { encoding: 'application/octet-stream', }, @@ -310,3 +448,13 @@ describe('Bodies', () => { }) }) }) + +const bytesToReadableStream = (bytes: Uint8Array): ReadableStream => { + // not using ReadableStream.from(), which lacks support in some contexts including nodejs v18. + return new ReadableStream({ + pull(ctrl) { + ctrl.enqueue(bytes) + ctrl.close() + }, + }) +} diff --git a/packages/xrpc-server/tests/errors.test.ts b/packages/xrpc-server/tests/errors.test.ts index 05da883a771..5628147b428 100644 --- a/packages/xrpc-server/tests/errors.test.ts +++ b/packages/xrpc-server/tests/errors.test.ts @@ -1,14 +1,9 @@ import * as http from 'http' import getPort from 'get-port' import { LexiconDoc } from '@atproto/lexicon' +import { XRPCError, XRPCInvalidResponseError, XrpcClient } from '@atproto/xrpc' import { createServer, closeServer } from './_util' import * as xrpcServer from '../src' -import xrpc, { - Client, - ServiceClient, - XRPCError, - XRPCInvalidResponseError, -} from '@atproto/xrpc' const LEXICONS: LexiconDoc[] = [ { @@ -130,17 +125,14 @@ describe('Errors', () => { server.method('io.example.procedure', () => { return undefined }) - xrpc.addLexicons(LEXICONS) - const badXrpc = new Client() - badXrpc.addLexicons(MISMATCHED_LEXICONS) - let client: ServiceClient - let badClient: ServiceClient + let client: XrpcClient + let badClient: XrpcClient beforeAll(async () => { const port = await getPort() s = await createServer(port, server) - client = xrpc.service(`http://localhost:${port}`) - badClient = badXrpc.service(`http://localhost:${port}`) + client = new XrpcClient(`http://localhost:${port}`, LEXICONS) + badClient = new XrpcClient(`http://localhost:${port}`, MISMATCHED_LEXICONS) }) afterAll(async () => { await closeServer(s) diff --git a/packages/xrpc-server/tests/ipld.test.ts b/packages/xrpc-server/tests/ipld.test.ts index 4ba1fec867e..7d2c4b4cc53 100644 --- a/packages/xrpc-server/tests/ipld.test.ts +++ b/packages/xrpc-server/tests/ipld.test.ts @@ -1,6 +1,6 @@ import * as http from 'http' import { LexiconDoc } from '@atproto/lexicon' -import xrpc, { ServiceClient } from '@atproto/xrpc' +import { XrpcClient } from '@atproto/xrpc' import { CID } from 'multiformats/cid' import getPort from 'get-port' import { createServer, closeServer } from './_util' @@ -63,13 +63,12 @@ describe('Ipld vals', () => { return { encoding: 'application/json', body: ctx.input?.body } }, ) - xrpc.addLexicons(LEXICONS) - let client: ServiceClient + let client: XrpcClient beforeAll(async () => { const port = await getPort() s = await createServer(port, server) - client = xrpc.service(`http://localhost:${port}`) + client = new XrpcClient(`http://localhost:${port}`, LEXICONS) }) afterAll(async () => { await closeServer(s) diff --git a/packages/xrpc-server/tests/parameters.test.ts b/packages/xrpc-server/tests/parameters.test.ts index 6ab2066ae8f..2767d11da92 100644 --- a/packages/xrpc-server/tests/parameters.test.ts +++ b/packages/xrpc-server/tests/parameters.test.ts @@ -1,7 +1,7 @@ import * as http from 'http' import getPort from 'get-port' import { LexiconDoc } from '@atproto/lexicon' -import xrpc, { ServiceClient } from '@atproto/xrpc' +import { XrpcClient } from '@atproto/xrpc' import { createServer, closeServer } from './_util' import * as xrpcServer from '../src' @@ -41,13 +41,12 @@ describe('Parameters', () => { body: ctx.params, }), ) - xrpc.addLexicons(LEXICONS) - let client: ServiceClient + let client: XrpcClient beforeAll(async () => { const port = await getPort() s = await createServer(port, server) - client = xrpc.service(`http://localhost:${port}`) + client = new XrpcClient(`http://localhost:${port}`, LEXICONS) }) afterAll(async () => { await closeServer(s) diff --git a/packages/xrpc-server/tests/procedures.test.ts b/packages/xrpc-server/tests/procedures.test.ts index df38fc855f9..e0130a313d4 100644 --- a/packages/xrpc-server/tests/procedures.test.ts +++ b/packages/xrpc-server/tests/procedures.test.ts @@ -1,7 +1,7 @@ import * as http from 'http' import { Readable } from 'stream' import { LexiconDoc } from '@atproto/lexicon' -import xrpc, { ServiceClient } from '@atproto/xrpc' +import { XrpcClient } from '@atproto/xrpc' import getPort from 'get-port' import { createServer, closeServer } from './_util' import * as xrpcServer from '../src' @@ -121,13 +121,12 @@ describe('Procedures', () => { } }, ) - xrpc.addLexicons(LEXICONS) - let client: ServiceClient + let client: XrpcClient beforeAll(async () => { const port = await getPort() s = await createServer(port, server) - client = xrpc.service(`http://localhost:${port}`) + client = new XrpcClient(`http://localhost:${port}`, LEXICONS) }) afterAll(async () => { await closeServer(s) diff --git a/packages/xrpc-server/tests/queries.test.ts b/packages/xrpc-server/tests/queries.test.ts index fd45d812e00..560b4d98a5f 100644 --- a/packages/xrpc-server/tests/queries.test.ts +++ b/packages/xrpc-server/tests/queries.test.ts @@ -1,7 +1,7 @@ import * as http from 'http' import getPort from 'get-port' import { LexiconDoc } from '@atproto/lexicon' -import xrpc, { ServiceClient } from '@atproto/xrpc' +import { XrpcClient } from '@atproto/xrpc' import { createServer, closeServer } from './_util' import * as xrpcServer from '../src' @@ -89,13 +89,12 @@ describe('Queries', () => { } }, ) - xrpc.addLexicons(LEXICONS) - let client: ServiceClient + let client: XrpcClient beforeAll(async () => { const port = await getPort() s = await createServer(port, server) - client = xrpc.service(`http://localhost:${port}`) + client = new XrpcClient(`http://localhost:${port}`, LEXICONS) }) afterAll(async () => { await closeServer(s) diff --git a/packages/xrpc-server/tests/rate-limiter.test.ts b/packages/xrpc-server/tests/rate-limiter.test.ts index 9a00e37bb81..b9987492540 100644 --- a/packages/xrpc-server/tests/rate-limiter.test.ts +++ b/packages/xrpc-server/tests/rate-limiter.test.ts @@ -1,7 +1,7 @@ import * as http from 'http' import getPort from 'get-port' import { LexiconDoc } from '@atproto/lexicon' -import xrpc, { ServiceClient } from '@atproto/xrpc' +import { XrpcClient } from '@atproto/xrpc' import { createServer, closeServer } from './_util' import * as xrpcServer from '../src' import { RateLimiter } from '../src' @@ -190,13 +190,11 @@ describe('Parameters', () => { }), }) - xrpc.addLexicons(LEXICONS) - - let client: ServiceClient + let client: XrpcClient beforeAll(async () => { const port = await getPort() s = await createServer(port, server) - client = xrpc.service(`http://localhost:${port}`) + client = new XrpcClient(`http://localhost:${port}`, LEXICONS) }) afterAll(async () => { await closeServer(s) diff --git a/packages/xrpc-server/tests/responses.test.ts b/packages/xrpc-server/tests/responses.test.ts index b61467dca22..fbc394e191f 100644 --- a/packages/xrpc-server/tests/responses.test.ts +++ b/packages/xrpc-server/tests/responses.test.ts @@ -1,7 +1,7 @@ import * as http from 'http' import getPort from 'get-port' import { LexiconDoc } from '@atproto/lexicon' -import xrpc, { ServiceClient } from '@atproto/xrpc' +import { XrpcClient } from '@atproto/xrpc' import { byteIterableToStream } from '@atproto/common' import { createServer, closeServer } from './_util' import * as xrpcServer from '../src' @@ -47,15 +47,12 @@ describe('Responses', () => { } }, ) - xrpc.addLexicons(LEXICONS) - let client: ServiceClient - let url: string + let client: XrpcClient beforeAll(async () => { const port = await getPort() s = await createServer(port, server) - url = `http://localhost:${port}` - client = xrpc.service(url) + client = new XrpcClient(`http://localhost:${port}`, LEXICONS) }) afterAll(async () => { await closeServer(s) diff --git a/packages/xrpc/CHANGELOG.md b/packages/xrpc/CHANGELOG.md index c44b94adc74..d7188122dd8 100644 --- a/packages/xrpc/CHANGELOG.md +++ b/packages/xrpc/CHANGELOG.md @@ -1,5 +1,576 @@ # @atproto/xrpc +## 0.6.0 + +### Minor Changes + +- [#2483](https://github.com/bluesky-social/atproto/pull/2483) [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! + + #### Motivation + + The motivation for these changes is the need to make the `@atproto/api` package + compatible with OAuth session management. We don't have OAuth client support + "launched" and documented quite yet, so you can keep using the current app + password authentication system. When we do "launch" OAuth support and begin + encouraging its usage in the near future (see the [OAuth + Roadmap](https://github.com/bluesky-social/atproto/discussions/2656)), these + changes will make it easier to migrate. + + In addition, the redesigned session management system fixes a bug that could + cause the session data to become invalid when Agent clones are created (e.g. + using `agent.withProxy()`). + + #### New Features + + We've restructured the `XrpcClient` HTTP fetch handler to be specified during + the instantiation of the XRPC client, through the constructor, instead of using + a default implementation (which was statically defined). + + With this refactor, the XRPC client is now more modular and reusable. Session + management, retries, cryptographic signing, and other request-specific logic can + be implemented in the fetch handler itself rather than by the calling code. + + A new abstract class named `Agent`, has been added to `@atproto/api`. This class + will be the base class for all Bluesky agents classes in the `@atproto` + ecosystem. It is meant to be extended by implementations that provide session + management and fetch handling. + + As you adapt your code to these changes, make sure to use the `Agent` type + wherever you expect to receive an agent, and use the `AtpAgent` type (class) + only to instantiate your client. The reason for this is to be forward compatible + with the OAuth agent implementation that will also extend `Agent`, and not + `AtpAgent`. + + ```ts + import { Agent, AtpAgent } from '@atproto/api' + + async function setupAgent( + service: string, + username: string, + password: string, + ): Promise { + const agent = new AtpAgent({ + service, + persistSession: (evt, session) => { + // handle session update + }, + }) + + await agent.login(username, password) + + return agent + } + ``` + + ```ts + import { Agent } from '@atproto/api' + + async function doStuffWithAgent(agent: Agent, arg: string) { + return agent.resolveHandle(arg) + } + ``` + + ```ts + import { Agent, AtpAgent } from '@atproto/api' + + class MyClass { + agent: Agent + + constructor() { + this.agent = new AtpAgent() + } + } + ``` + + #### Breaking changes + + Most of the changes introduced in this version are backward-compatible. However, + there are a couple of breaking changes you should be aware of: + + - Customizing `fetch`: The ability to customize the `fetch: FetchHandler` + property of `@atproto/xrpc`'s `Client` and `@atproto/api`'s `AtpAgent` classes + has been removed. Previously, the `fetch` property could be set to a function + that would be used as the fetch handler for that instance, and was initialized + to a default fetch handler. That property is still accessible in a read-only + fashion through the `fetchHandler` property and can only be set during the + instance creation. Attempting to set/get the `fetch` property will now result + in an error. + - The `fetch()` method, as well as WhatWG compliant `Request` and `Headers` + constructors, must be globally available in your environment. Use a polyfill + if necessary. + - The `AtpBaseClient` has been removed. The `AtpServiceClient` has been renamed + `AtpBaseClient`. Any code using either of these classes will need to be + updated. + - Instead of _wrapping_ an `XrpcClient` in its `xrpc` property, the + `AtpBaseClient` (formerly `AtpServiceClient`) class - created through + `lex-cli` - now _extends_ the `XrpcClient` class. This means that a client + instance now passes the `instanceof XrpcClient` check. The `xrpc` property now + returns the instance itself and has been deprecated. + - `setSessionPersistHandler` is no longer available on the `AtpAgent` or + `BskyAgent` classes. The session handler can only be set though the + `persistSession` options of the `AtpAgent` constructor. + - The new class hierarchy is as follows: + - `BskyAgent` extends `AtpAgent`: but add no functionality (hence its + deprecation). + - `AtpAgent` extends `Agent`: adds password based session management. + - `Agent` extends `AtpBaseClient`: this abstract class that adds syntactic sugar + methods `app.bsky` lexicons. It also adds abstract session management + methods and adds atproto specific utilities + (`labelers` & `proxy` headers, cloning capability) + - `AtpBaseClient` extends `XrpcClient`: automatically code that adds fully + typed lexicon defined namespaces (`instance.app.bsky.feed.getPosts()`) to + the `XrpcClient`. + - `XrpcClient` is the base class. + + #### Non-breaking changes + + - The `com.*` and `app.*` namespaces have been made directly available to every + `Agent` instances. + + #### Deprecations + + - The default export of the `@atproto/xrpc` package has been deprecated. Use + named exports instead. + - The `Client` and `ServiceClient` classes are now deprecated. They are replaced by a single `XrpcClient` class. + - The default export of the `@atproto/api` package has been deprecated. Use + named exports instead. + - The `BskyAgent` has been deprecated. Use the `AtpAgent` class instead. + - The `xrpc` property of the `AtpClient` instances has been deprecated. The + instance itself should be used as the XRPC client. + - The `api` property of the `AtpAgent` and `BskyAgent` instances has been + deprecated. Use the instance itself instead. + + #### Migration + + ##### The `@atproto/api` package + + If you were relying on the `AtpBaseClient` solely to perform validation, use + this: + + + + + + + + + +
Before
After
+ + ```ts + import { AtpBaseClient, ComAtprotoSyncSubscribeRepos } from '@atproto/api' + + const baseClient = new AtpBaseClient() + + baseClient.xrpc.lex.assertValidXrpcMessage('io.example.doStuff', { + // ... + }) + ``` + + + + ```ts + import { lexicons } from '@atproto/api' + + lexicons.assertValidXrpcMessage('io.example.doStuff', { + // ... + }) + ``` + +
+ + If you are extending the `BskyAgent` to perform custom `session` manipulation, define your own `Agent` subclass instead: + + + + + + + + + +
Before
After
+ + ```ts + import { BskyAgent } from '@atproto/api' + + class MyAgent extends BskyAgent { + private accessToken?: string + + async createOrRefreshSession(identifier: string, password: string) { + // custom logic here + + this.accessToken = 'my-access-jwt' + } + + async doStuff() { + return this.call('io.example.doStuff', { + headers: { + Authorization: this.accessToken && `Bearer ${this.accessToken}`, + }, + }) + } + } + ``` + + + + ```ts + import { Agent } from '@atproto/api' + + class MyAgent extends Agent { + private accessToken?: string + public did?: string + + constructor(private readonly service: string | URL) { + super({ + service, + headers: { + Authorization: () => + this.accessToken ? `Bearer ${this.accessToken}` : null, + }, + }) + } + + clone(): MyAgent { + const agent = new MyAgent(this.service) + agent.accessToken = this.accessToken + agent.did = this.did + return this.copyInto(agent) + } + + async createOrRefreshSession(identifier: string, password: string) { + // custom logic here + + this.did = 'did:example:123' + this.accessToken = 'my-access-jwt' + } + } + ``` + +
+ + If you are monkey patching the `xrpc` service client to perform client-side rate limiting, you can now do this in the `FetchHandler` function: + + + + + + + + + +
Before
After
+ + ```ts + import { BskyAgent } from '@atproto/api' + import { RateLimitThreshold } from 'rate-limit-threshold' + + const agent = new BskyAgent() + const limiter = new RateLimitThreshold(3000, 300_000) + + const origCall = agent.api.xrpc.call + agent.api.xrpc.call = async function (...args) { + await limiter.wait() + return origCall.call(this, ...args) + } + ``` + + + + ```ts + import { AtpAgent } from '@atproto/api' + import { RateLimitThreshold } from 'rate-limit-threshold' + + class LimitedAtpAgent extends AtpAgent { + constructor(options: AtpAgentOptions) { + const fetch: typeof globalThis.fetch = options.fetch ?? globalThis.fetch + const limiter = new RateLimitThreshold(3000, 300_000) + + super({ + ...options, + fetch: async (...args) => { + await limiter.wait() + return fetch(...args) + }, + }) + } + } + ``` + +
+ + If you configure a static `fetch` handler on the `BskyAgent` class - for example + to modify the headers of every request - you can now do this by providing your + own `fetch` function: + + + + + + + + + +
Before
After
+ + ```ts + import { BskyAgent, defaultFetchHandler } from '@atproto/api' + + BskyAgent.configure({ + fetch: async (httpUri, httpMethod, httpHeaders, httpReqBody) => { + const ua = httpHeaders['User-Agent'] + + httpHeaders['User-Agent'] = ua ? `${ua} ${userAgent}` : userAgent + + return defaultFetchHandler(httpUri, httpMethod, httpHeaders, httpReqBody) + }, + }) + ``` + + + + ```ts + import { AtpAgent } from '@atproto/api' + + class MyAtpAgent extends AtpAgent { + constructor(options: AtpAgentOptions) { + const fetch = options.fetch ?? globalThis.fetch + + super({ + ...options, + fetch: async (url, init) => { + const headers = new Headers(init.headers) + + const ua = headersList.get('User-Agent') + headersList.set('User-Agent', ua ? `${ua} ${userAgent}` : userAgent) + + return fetch(url, { ...init, headers }) + }, + }) + } + } + ``` + +
+ + + + ##### The `@atproto/xrpc` package + + The `Client` and `ServiceClient` classes are now **deprecated**. If you need a + lexicon based client, you should update the code to use the `XrpcClient` class + instead. + + The deprecated `ServiceClient` class now extends the new `XrpcClient` class. + Because of this, the `fetch` `FetchHandler` can no longer be configured on the + `Client` instances (including the default export of the package). If you are not + relying on the `fetch` `FetchHandler`, the new changes should have no impact on + your code. Beware that the deprecated classes will eventually be removed in a + future version. + + Since its use has completely changed, the `FetchHandler` type has also + completely changed. The new `FetchHandler` type is now a function that receives + a `url` pathname and a `RequestInit` object and returns a `Promise`. + This function is responsible for making the actual request to the server. + + ```ts + export type FetchHandler = ( + this: void, + /** + * The URL (pathname + query parameters) to make the request to, without the + * origin. The origin (protocol, hostname, and port) must be added by this + * {@link FetchHandler}, typically based on authentication or other factors. + */ + url: string, + init: RequestInit, + ) => Promise + ``` + + A noticeable change that has been introduced is that the `uri` field of the + `ServiceClient` class has _not_ been ported to the new `XrpcClient` class. It is + now the responsibility of the `FetchHandler` to determine the full URL to make + the request to. The same goes for the `headers`, which should now be set through + the `FetchHandler` function. + + If you _do_ rely on the legacy `Client.fetch` property to perform custom logic + upon request, you will need to migrate your code to use the new `XrpcClient` + class. The `XrpcClient` class has a similar API to the old `ServiceClient` + class, but with a few differences: + + - The `Client` + `ServiceClient` duality was removed in favor of a single + `XrpcClient` class. This means that: + + - There no longer exists a centralized lexicon registry. If you need a global + lexicon registry, you can maintain one yourself using a `new Lexicons` (from + `@atproto/lexicon`). + - The `FetchHandler` is no longer a statically defined property of the + `Client` class. Instead, it is passed as an argument to the `XrpcClient` + constructor. + + - The `XrpcClient` constructor now requires a `FetchHandler` function as the + first argument, and an optional `Lexicon` instance as the second argument. + - The `setHeader` and `unsetHeader` methods were not ported to the new + `XrpcClient` class. If you need to set or unset headers, you should do so in + the `FetchHandler` function provided in the constructor arg. + + + + + + + + + +
Before
After
+ + ```ts + import client, { defaultFetchHandler } from '@atproto/xrpc' + + client.fetch = function ( + httpUri: string, + httpMethod: string, + httpHeaders: Headers, + httpReqBody: unknown, + ) { + // Custom logic here + return defaultFetchHandler(httpUri, httpMethod, httpHeaders, httpReqBody) + } + + client.addLexicon({ + lexicon: 1, + id: 'io.example.doStuff', + defs: {}, + }) + + const instance = client.service('http://my-service.com') + + instance.setHeader('my-header', 'my-value') + + await instance.call('io.example.doStuff') + ``` + + + + ```ts + import { XrpcClient } from '@atproto/xrpc' + + const instance = new XrpcClient( + async (url, init) => { + const headers = new Headers(init.headers) + + headers.set('my-header', 'my-value') + + // Custom logic here + + const fullUrl = new URL(url, 'http://my-service.com') + + return fetch(fullUrl, { ...init, headers }) + }, + [ + { + lexicon: 1, + id: 'io.example.doStuff', + defs: {}, + }, + ], + ) + + await instance.call('io.example.doStuff') + ``` + +
+ + If your fetch handler does not require any "custom logic", and all you need is + an `XrpcClient` that makes its HTTP requests towards a static service URL, the + previous example can be simplified to: + + ```ts + import { XrpcClient } from '@atproto/xrpc' + + const instance = new XrpcClient('http://my-service.com', [ + { + lexicon: 1, + id: 'io.example.doStuff', + defs: {}, + }, + ]) + ``` + + If you need to add static headers to all requests, you can instead instantiate + the `XrpcClient` as follows: + + ```ts + import { XrpcClient } from '@atproto/xrpc' + + const instance = new XrpcClient( + { + service: 'http://my-service.com', + headers: { + 'my-header': 'my-value', + }, + }, + [ + { + lexicon: 1, + id: 'io.example.doStuff', + defs: {}, + }, + ], + ) + ``` + + If you need the headers or service url to be dynamic, you can define them using + functions: + + ```ts + import { XrpcClient } from '@atproto/xrpc' + + const instance = new XrpcClient( + { + service: () => 'http://my-service.com', + headers: { + 'my-header': () => 'my-value', + 'my-ignored-header': () => null, // ignored + }, + }, + [ + { + lexicon: 1, + id: 'io.example.doStuff', + defs: {}, + }, + ], + ) + ``` + +- [#2483](https://github.com/bluesky-social/atproto/pull/2483) [`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add the ability to use `fetch()` compatible `BodyInit` body when making XRPC calls. + +### Patch Changes + +- Updated dependencies [[`b934b396b`](https://github.com/bluesky-social/atproto/commit/b934b396b13ba32bf2bf7e75ecdf6871e5f310dd), [`2bdf75d7a`](https://github.com/bluesky-social/atproto/commit/2bdf75d7a63924c10e7a311f16cb447d595b933e)]: + - @atproto/lexicon@0.4.1 + ## 0.5.0 ### Minor Changes diff --git a/packages/xrpc/README.md b/packages/xrpc/README.md index 17c48c93735..6a3faa12e89 100644 --- a/packages/xrpc/README.md +++ b/packages/xrpc/README.md @@ -9,9 +9,9 @@ TypeScript client library for talking to [atproto](https://atproto.com) services ```typescript import { LexiconDoc } from '@atproto/lexicon' -import xrpc from '@atproto/xrpc' +import { XrpcClient } from '@atproto/xrpc' -const pingLexicon: LexiconDoc = { +const pingLexicon = { lexicon: 1, id: 'io.example.ping', defs: { @@ -32,44 +32,69 @@ const pingLexicon: LexiconDoc = { }, }, }, -} -xrpc.addLexicon(pingLexicon) +} satisfies LexiconDoc + +const xrpc = new XrpcClient('https://ping.example.com', [ + // Any number of lexicon here + pingLexicon, +]) -const res1 = await xrpc.call('https://example.com', 'io.example.ping', { +const res1 = await xrpc.call('io.example.ping', { message: 'hello world', }) res1.encoding // => 'application/json' res1.body // => {message: 'hello world'} -const res2 = await xrpc - .service('https://example.com') - .call('io.example.ping', { message: 'hello world' }) -res2.encoding // => 'application/json' -res2.body // => {message: 'hello world'} +``` -const writeJsonLexicon: LexiconDoc = { - lexicon: 1, - id: 'io.example.writeJsonFile', - defs: { - main: { - type: 'procedure', - description: 'Write a JSON file', - parameters: { - type: 'params', - properties: { fileName: { type: 'string' } }, - }, - input: { - encoding: 'application/json', - }, - }, +### With a custom fetch handler + +```typescript +import { XrpcClient } from '@atproto/xrpc' + +const session = { + serviceUrl: 'https://ping.example.com', + token: '', + async refreshToken() { + const { token } = await fetch('https://auth.example.com/refresh', { + method: 'POST', + headers: { Authorization: `Bearer ${this.token}` }, + }).then((res) => res.json()) + + this.token = token + + return token }, } -xrpc.addLexicon(writeJsonLexicon) -const res3 = await xrpc.service('https://example.com').call( - 'io.example.writeJsonFile', - { fileName: 'foo.json' }, // query parameters - { hello: 'world', thisIs: 'the file to write' }, // input body -) +const sessionBasedFetch: FetchHandler = async ( + url: string, + init: RequestInit, +) => { + const headers = new Headers(init.headers) + + headers.set('Authorization', `Bearer ${session.token}`) + + const response = await fetch(new URL(url, session.serviceUrl), { + ...init, + headers, + }) + + if (response.status === 401) { + // Refresh token, then try again. + const newToken = await session.refreshToken() + headers.set('Authorization', `Bearer ${newToken}`) + return fetch(new URL(url, session.serviceUrl), { ...init, headers }) + } + + return response +} + +const xrpc = new XrpcClient(sessionBasedFetch, [ + // Any number of lexicon here + pingLexicon, +]) + +// ``` ## License diff --git a/packages/xrpc/package.json b/packages/xrpc/package.json index 38be745fc03..6a1851c2806 100644 --- a/packages/xrpc/package.json +++ b/packages/xrpc/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/xrpc", - "version": "0.5.0", + "version": "0.6.0", "license": "MIT", "description": "atproto HTTP API (XRPC) client library", "keywords": [ diff --git a/packages/xrpc/src/client.ts b/packages/xrpc/src/client.ts index 6603345608a..0aa4460a1bc 100644 --- a/packages/xrpc/src/client.ts +++ b/packages/xrpc/src/client.ts @@ -1,29 +1,24 @@ -import { LexiconDoc, Lexicons, ValidationError } from '@atproto/lexicon' -import { - getMethodSchemaHTTPMethod, - constructMethodCallUri, - constructMethodCallHeaders, - encodeMethodCallBody, - httpResponseCodeToEnum, - httpResponseBodyParse, - normalizeHeaders, -} from './util' -import { - FetchHandler, - FetchHandlerResponse, - Headers, - CallOptions, - QueryParams, - ResponseType, - errorResponseBody, - ErrorResponseBody, - XRPCResponse, - XRPCError, - XRPCInvalidResponseError, -} from './types' +import { LexiconDoc, Lexicons } from '@atproto/lexicon' +import { CallOptions, QueryParams } from './types' +import { XrpcClient } from './xrpc-client' +import { combineHeaders } from './util' +/** @deprecated Use {@link XrpcClient} instead */ export class Client { - fetch: FetchHandler = defaultFetchHandler + /** @deprecated */ + get fetch(): never { + throw new Error( + 'Client.fetch is no longer supported. Use an XrpcClient instead.', + ) + } + + /** @deprecated */ + set fetch(_: never) { + throw new Error( + 'Client.fetch is no longer supported. Use an XrpcClient instead.', + ) + } + lex = new Lexicons() // method calls @@ -33,7 +28,7 @@ export class Client { serviceUri: string | URL, methodNsid: string, params?: QueryParams, - data?: unknown, + data?: BodyInit | null, opts?: CallOptions, ) { return this.service(serviceUri).call(methodNsid, params, data, opts) @@ -61,13 +56,19 @@ export class Client { } } -export class ServiceClient { - baseClient: Client +/** @deprecated Use {@link XrpcClient} instead */ +export class ServiceClient extends XrpcClient { uri: URL headers: Record = {} - constructor(baseClient: Client, serviceUri: string | URL) { - this.baseClient = baseClient + constructor( + public baseClient: Client, + serviceUri: string | URL, + ) { + super(async (input, init) => { + const headers = combineHeaders(init.headers, Object.entries(this.headers)) + return fetch(new URL(input, this.uri), { ...init, headers }) + }, baseClient.lex) this.uri = typeof serviceUri === 'string' ? new URL(serviceUri) : serviceUri } @@ -78,92 +79,4 @@ export class ServiceClient { unsetHeader(key: string): void { delete this.headers[key] } - - async call( - methodNsid: string, - params?: QueryParams, - data?: unknown, - opts?: CallOptions, - ) { - const def = this.baseClient.lex.getDefOrThrow(methodNsid) - if (!def || (def.type !== 'query' && def.type !== 'procedure')) { - throw new Error( - `Invalid lexicon: ${methodNsid}. Must be a query or procedure.`, - ) - } - - const httpMethod = getMethodSchemaHTTPMethod(def) - const httpUri = constructMethodCallUri(methodNsid, def, this.uri, params) - const httpHeaders = constructMethodCallHeaders(def, data, { - headers: { - ...this.headers, - ...opts?.headers, - }, - encoding: opts?.encoding, - }) - - const res = await this.baseClient.fetch( - httpUri, - httpMethod, - httpHeaders, - data, - ) - - const resCode = httpResponseCodeToEnum(res.status) - if (resCode === ResponseType.Success) { - try { - this.baseClient.lex.assertValidXrpcOutput(methodNsid, res.body) - } catch (e: any) { - if (e instanceof ValidationError) { - throw new XRPCInvalidResponseError(methodNsid, e, res.body) - } else { - throw e - } - } - return new XRPCResponse(res.body, res.headers) - } else { - if (res.body && isErrorResponseBody(res.body)) { - throw new XRPCError( - resCode, - res.body.error, - res.body.message, - res.headers, - ) - } else { - throw new XRPCError(resCode) - } - } - } -} - -export async function defaultFetchHandler( - httpUri: string, - httpMethod: string, - httpHeaders: Headers, - httpReqBody: unknown, -): Promise { - try { - // The duplex field is now required for streaming bodies, but not yet reflected - // anywhere in docs or types. See whatwg/fetch#1438, nodejs/node#46221. - const headers = normalizeHeaders(httpHeaders) - const reqInit: RequestInit & { duplex: string } = { - method: httpMethod, - headers, - body: encodeMethodCallBody(headers, httpReqBody), - duplex: 'half', - } - const res = await fetch(httpUri, reqInit) - const resBody = await res.arrayBuffer() - return { - status: res.status, - headers: Object.fromEntries(res.headers.entries()), - body: httpResponseBodyParse(res.headers.get('content-type'), resBody), - } - } catch (e) { - throw new XRPCError(ResponseType.Unknown, String(e)) - } -} - -function isErrorResponseBody(v: unknown): v is ErrorResponseBody { - return errorResponseBody.safeParse(v).success } diff --git a/packages/xrpc/src/fetch-handler.ts b/packages/xrpc/src/fetch-handler.ts new file mode 100644 index 00000000000..6f236c8217c --- /dev/null +++ b/packages/xrpc/src/fetch-handler.ts @@ -0,0 +1,73 @@ +import { Gettable } from './types' +import { combineHeaders } from './util' + +export type FetchHandler = ( + this: void, + /** + * The URL (pathname + query parameters) to make the request to, without the + * origin. The origin (protocol, hostname, and port) must be added by this + * {@link FetchHandler}, typically based on authentication or other factors. + */ + url: string, + init: RequestInit, +) => Promise + +export type FetchHandlerOptions = BuildFetchHandlerOptions | string | URL + +export type BuildFetchHandlerOptions = { + /** + * The service URL to make requests to. This can be a string, URL, or a + * function that returns a string or URL. This is useful for dynamic URLs, + * such as a service URL that changes based on authentication. + */ + service: Gettable + + /** + * Headers to be added to every request. If a function is provided, it will be + * called on each request to get the headers. This is useful for dynamic + * headers, such as authentication tokens that may expire. + */ + headers?: { + [_ in string]?: Gettable + } + + /** + * Bring your own fetch implementation. Typically useful for testing, logging, + * mocking, or adding retries, session management, signatures, proof of + * possession (DPoP), etc. Defaults to the global `fetch` function. + */ + fetch?: typeof globalThis.fetch +} + +export function buildFetchHandler( + options: FetchHandler | FetchHandlerOptions, +): FetchHandler { + // Already a fetch handler (allowed for convenience) + if (typeof options === 'function') return options + + const { + service, + headers: defaultHeaders = undefined, + fetch = globalThis.fetch, + } = typeof options === 'string' || options instanceof URL + ? { service: options } + : options + + if (typeof fetch !== 'function') { + throw new TypeError( + 'XrpcDispatcher requires fetch() to be available in your environment.', + ) + } + + const defaultHeadersEntries = + defaultHeaders != null ? Object.entries(defaultHeaders) : undefined + + return async function (url, init) { + const base = typeof service === 'function' ? service() : service + const fullUrl = new URL(url, base) + + const headers = combineHeaders(init.headers, defaultHeadersEntries) + + return fetch(fullUrl, { ...init, headers }) + } +} diff --git a/packages/xrpc/src/index.ts b/packages/xrpc/src/index.ts index b084901fa9c..f14bb111c25 100644 --- a/packages/xrpc/src/index.ts +++ b/packages/xrpc/src/index.ts @@ -1,6 +1,10 @@ -export * from './types' export * from './client' +export * from './fetch-handler' +export * from './types' +export * from './util' +export * from './xrpc-client' import { Client } from './client' +/** @deprecated create a local {@link XrpcClient} instance instead */ const defaultInst = new Client() export default defaultInst diff --git a/packages/xrpc/src/types.ts b/packages/xrpc/src/types.ts index b82fba737dc..41e01a39d77 100644 --- a/packages/xrpc/src/types.ts +++ b/packages/xrpc/src/types.ts @@ -2,26 +2,19 @@ import { z } from 'zod' import { ValidationError } from '@atproto/lexicon' export type QueryParams = Record -export type Headers = Record +export type HeadersMap = Record + +/** @deprecated not to be confused with the WHATWG Headers constructor */ +export type Headers = HeadersMap + +export type Gettable = T | (() => T) export interface CallOptions { encoding?: string - headers?: Headers -} - -export interface FetchHandlerResponse { - status: number - headers: Headers - body: ArrayBuffer | undefined + signal?: AbortSignal + headers?: HeadersMap } -export type FetchHandler = ( - httpUri: string, - httpMethod: string, - httpHeaders: Headers, - httpReqBody: any, -) => Promise - export const errorResponseBody = z.object({ error: z.string().optional(), message: z.string().optional(), @@ -45,7 +38,24 @@ export enum ResponseType { UpstreamTimeout = 504, } +export function httpResponseCodeToEnum(status: number): ResponseType { + if (status in ResponseType) { + return status + } else if (status >= 100 && status < 200) { + return ResponseType.XRPCNotSupported + } else if (status >= 200 && status < 300) { + return ResponseType.Success + } else if (status >= 300 && status < 400) { + return ResponseType.XRPCNotSupported + } else if (status >= 400 && status < 500) { + return ResponseType.InvalidRequest + } else { + return ResponseType.InternalServerError + } +} + export const ResponseTypeNames = { + [ResponseType.Unknown]: 'Unknown', [ResponseType.InvalidResponse]: 'InvalidResponse', [ResponseType.Success]: 'Success', [ResponseType.InvalidRequest]: 'InvalidRequest', @@ -61,7 +71,12 @@ export const ResponseTypeNames = { [ResponseType.UpstreamTimeout]: 'UpstreamTimeout', } +export function httpResponseCodeToName(status: number): string { + return ResponseTypeNames[httpResponseCodeToEnum(status)] +} + export const ResponseTypeStrings = { + [ResponseType.Unknown]: 'Unknown', [ResponseType.InvalidResponse]: 'Invalid Response', [ResponseType.Success]: 'Success', [ResponseType.InvalidRequest]: 'Invalid Request', @@ -77,6 +92,10 @@ export const ResponseTypeStrings = { [ResponseType.UpstreamTimeout]: 'Upstream Timeout', } +export function httpResponseCodeToString(status: number): string { + return ResponseTypeStrings[httpResponseCodeToEnum(status)] +} + export class XRPCResponse { success = true @@ -88,19 +107,48 @@ export class XRPCResponse { export class XRPCError extends Error { success = false - headers?: Headers + + public status: ResponseType constructor( - public status: ResponseType, - public error?: string, + statusCode: number, + public error: string = httpResponseCodeToName(statusCode), message?: string, - headers?: Headers, + public headers?: Headers, + options?: ErrorOptions, ) { - super(message || error || ResponseTypeStrings[status]) - if (!this.error) { - this.error = ResponseTypeNames[status] + super(message || error || httpResponseCodeToString(statusCode), options) + + this.status = httpResponseCodeToEnum(statusCode) + + // Pre 2022 runtimes won't handle the "options" constructor argument + const cause = options?.cause + if (this.cause === undefined && cause !== undefined) { + this.cause = cause } - this.headers = headers + } + + static from(cause: unknown, fallbackStatus?: ResponseType): XRPCError { + if (cause instanceof XRPCError) { + return cause + } + + // Extract status code from "http-errors" like errors + const statusCode: unknown = + cause instanceof Error + ? ('statusCode' in cause ? cause.statusCode : undefined) ?? + ('status' in cause ? cause.status : undefined) + : undefined + + const status: ResponseType = + typeof statusCode === 'number' + ? httpResponseCodeToEnum(statusCode) + : fallbackStatus ?? ResponseType.Unknown + + const error = ResponseTypeNames[status] + const message = cause instanceof Error ? cause.message : String(cause) + + return new XRPCError(status, error, message, undefined, { cause }) } } @@ -114,6 +162,8 @@ export class XRPCInvalidResponseError extends XRPCError { ResponseType.InvalidResponse, ResponseTypeStrings[ResponseType.InvalidResponse], `The server gave an invalid response and may be out of date.`, + undefined, + { cause: validationError }, ) } } diff --git a/packages/xrpc/src/util.ts b/packages/xrpc/src/util.ts index 5ceea2bd30e..558449c908f 100644 --- a/packages/xrpc/src/util.ts +++ b/packages/xrpc/src/util.ts @@ -6,12 +6,28 @@ import { } from '@atproto/lexicon' import { CallOptions, - Headers, + errorResponseBody, + ErrorResponseBody, + Gettable, QueryParams, ResponseType, XRPCError, } from './types' +const ReadableStream = + globalThis.ReadableStream || + (class { + constructor() { + // This anonymous class will never pass any "instanceof" check and cannot + // be instantiated. + throw new Error('ReadableStream is not supported in this environment') + } + } as typeof globalThis.ReadableStream) + +export function isErrorResponseBody(v: unknown): v is ErrorResponseBody { + return errorResponseBody.safeParse(v).success +} + export function getMethodSchemaHTTPMethod( schema: LexXrpcProcedure | LexXrpcQuery, ) { @@ -27,33 +43,43 @@ export function constructMethodCallUri( serviceUri: URL, params?: QueryParams, ): string { - const uri = new URL(serviceUri) - uri.pathname = `/xrpc/${nsid}` - - // given parameters - if (params) { - for (const [key, value] of Object.entries(params)) { - const paramSchema = schema.parameters?.properties?.[key] - if (!paramSchema) { - throw new Error(`Invalid query parameter: ${key}`) - } - if (value !== undefined) { - if (paramSchema.type === 'array') { - const vals: (typeof value)[] = [] - vals.concat(value).forEach((val) => { - uri.searchParams.append( - key, - encodeQueryParam(paramSchema.items.type, val), - ) - }) - } else { - uri.searchParams.set(key, encodeQueryParam(paramSchema.type, value)) + const uri = new URL(constructMethodCallUrl(nsid, schema, params), serviceUri) + return uri.toString() +} + +export function constructMethodCallUrl( + nsid: string, + schema: LexXrpcProcedure | LexXrpcQuery, + params?: QueryParams, +): string { + const pathname = `/xrpc/${encodeURIComponent(nsid)}` + if (!params) return pathname + + const searchParams: [string, string][] = [] + + for (const [key, value] of Object.entries(params)) { + const paramSchema = schema.parameters?.properties?.[key] + if (!paramSchema) { + throw new Error(`Invalid query parameter: ${key}`) + } + if (value !== undefined) { + if (paramSchema.type === 'array') { + const values = Array.isArray(value) ? value : [value] + for (const val of values) { + searchParams.push([ + key, + encodeQueryParam(paramSchema.items.type, val), + ]) } + } else { + searchParams.push([key, encodeQueryParam(paramSchema.type, value)]) } } } - return uri.toString() + if (!searchParams.length) return pathname + + return `${pathname}?${new URLSearchParams(searchParams).toString()}` } export function encodeQueryParam( @@ -85,100 +111,274 @@ export function encodeQueryParam( throw new Error(`Unsupported query param type: ${type}`) } -export function normalizeHeaders(headers: Headers): Headers { - const normalized: Headers = {} - for (const [header, value] of Object.entries(headers)) { - normalized[header.toLowerCase()] = value - } - - return normalized -} - export function constructMethodCallHeaders( schema: LexXrpcProcedure | LexXrpcQuery, - data?: any, + data?: unknown, opts?: CallOptions, ): Headers { - const headers: Headers = opts?.headers || {} + // Not using `new Headers(opts?.headers)` to avoid duplicating headers values + // due to inconsistent casing in headers name. In case of multiple headers + // with the same name (but using a different case), the last one will be used. + + // new Headers({ 'content-type': 'foo', 'Content-Type': 'bar' }).get('content-type') + // => 'foo, bar' + const headers = new Headers() + + if (opts?.headers) { + for (const name in opts.headers) { + if (headers.has(name)) { + throw new TypeError(`Duplicate header: ${name}`) + } + + const value = opts.headers[name] + if (value != null) { + headers.set(name, value) + } + } + } + if (schema.type === 'procedure') { if (opts?.encoding) { - headers['Content-Type'] = opts.encoding - } - if (data && typeof data === 'object') { - if (!headers['Content-Type']) { - headers['Content-Type'] = 'application/json' + headers.set('content-type', opts.encoding) + } else if (!headers.has('content-type') && typeof data !== 'undefined') { + // Special handling of BodyInit types before falling back to JSON encoding + if ( + data instanceof ArrayBuffer || + data instanceof ReadableStream || + ArrayBuffer.isView(data) + ) { + headers.set('content-type', 'application/octet-stream') + } else if (data instanceof FormData) { + // Note: The multipart form data boundary is missing from the header + // we set here, making that header invalid. This special case will be + // handled in encodeMethodCallBody() + headers.set('content-type', 'multipart/form-data') + } else if (data instanceof URLSearchParams) { + headers.set( + 'content-type', + 'application/x-www-form-urlencoded;charset=UTF-8', + ) + } else if (isBlobLike(data)) { + headers.set('content-type', data.type || 'application/octet-stream') + } else if (typeof data === 'string') { + headers.set('content-type', 'text/plain;charset=UTF-8') + } + // At this point, data is not a valid BodyInit type. + else if (isIterable(data)) { + headers.set('content-type', 'application/octet-stream') + } else if ( + typeof data === 'boolean' || + typeof data === 'number' || + typeof data === 'string' || + typeof data === 'object' // covers "null" + ) { + headers.set('content-type', 'application/json') + } else { + // symbol, function, bigint + throw new XRPCError( + ResponseType.InvalidRequest, + `Unsupported data type: ${typeof data}`, + ) } } } return headers } +export function combineHeaders( + headersInit: undefined | HeadersInit, + defaultHeaders?: Iterable<[string, undefined | Gettable]>, +): undefined | HeadersInit { + if (!defaultHeaders) return headersInit + + let headers: Headers | undefined = undefined + + for (const [name, definition] of defaultHeaders) { + // Ignore undefined values (allowed for convenience when using + // Object.entries). + if (definition === undefined) continue + + // Lazy initialization of the headers object + headers ??= new Headers(headersInit) + + if (headers.has(name)) continue + + const value = typeof definition === 'function' ? definition() : definition + + if (typeof value === 'string') headers.set(name, value) + else if (value === null) headers.delete(name) + else throw new TypeError(`Invalid "${name}" header value: ${typeof value}`) + } + + return headers ?? headersInit +} + +function isBlobLike(value: unknown): value is Blob { + if (value == null) return false + if (typeof value !== 'object') return false + if (typeof Blob === 'function' && value instanceof Blob) return true + + // Support for Blobs provided by libraries that don't use the native Blob + // (e.g. fetch-blob from node-fetch). + // https://github.com/node-fetch/fetch-blob/blob/a1a182e5978811407bef4ea1632b517567dda01f/index.js#L233-L244 + + const tag = value[Symbol.toStringTag] + if (tag === 'Blob' || tag === 'File') { + return 'stream' in value && typeof value.stream === 'function' + } + + return false +} + +export function isBodyInit(value: unknown): value is BodyInit { + switch (typeof value) { + case 'string': + return true + case 'object': + return ( + value instanceof ArrayBuffer || + value instanceof FormData || + value instanceof URLSearchParams || + value instanceof ReadableStream || + ArrayBuffer.isView(value) || + isBlobLike(value) + ) + default: + return false + } +} + +export function isIterable( + value: unknown, +): value is Iterable | AsyncIterable { + return ( + value != null && + typeof value === 'object' && + (Symbol.iterator in value || Symbol.asyncIterator in value) + ) +} + export function encodeMethodCallBody( headers: Headers, - data?: any, -): ArrayBuffer | undefined { - if (!headers['content-type'] || typeof data === 'undefined') { + data?: unknown, +): BodyInit | undefined { + // Silently ignore the body if there is no content-type header. + const contentType = headers.get('content-type') + if (!contentType) { return undefined } - if (data instanceof ArrayBuffer) { + + if (typeof data === 'undefined') { + // This error would be returned by the server, but we can catch it earlier + // to avoid un-necessary requests. Note that a content-length of 0 does not + // necessary mean that the body is "empty" (e.g. an empty txt file). + throw new XRPCError( + ResponseType.InvalidRequest, + `A request body is expected but none was provided`, + ) + } + + if (isBodyInit(data)) { + if (data instanceof FormData && contentType === 'multipart/form-data') { + // fetch() will encode FormData payload itself, but it won't override the + // content-type header if already present. This would cause the boundary + // to be missing from the content-type header, resulting in a 400 error. + // Deleting the content-type header here to let fetch() re-create it. + headers.delete('content-type') + } + + // Will be encoded by the fetch API. return data } - if (headers['content-type'].startsWith('text/')) { - return new TextEncoder().encode(data.toString()) + + if (isIterable(data)) { + // Note that some environments support using Iterable & AsyncIterable as the + // body (e.g. Node's fetch), but not all of them do (browsers). + return iterableToReadableStream(data) } - if (headers['content-type'].startsWith('application/json')) { - return new TextEncoder().encode(stringifyLex(data)) + + if (contentType.startsWith('text/')) { + return new TextEncoder().encode(String(data)) + } + if (contentType.startsWith('application/json')) { + const json = stringifyLex(data) + // Server would return a 400 error if the JSON is invalid (e.g. trying to + // JSONify a function, or an object that implements toJSON() poorly). + if (json === undefined) { + throw new XRPCError( + ResponseType.InvalidRequest, + `Failed to encode request body as JSON`, + ) + } + return new TextEncoder().encode(json) } - return data + + // At this point, "data" is not a valid BodyInit value, and we don't know how + // to encode it into one. Passing it to fetch would result in an error. Let's + // throw our own error instead. + + const type = + !data || typeof data !== 'object' + ? typeof data + : data.constructor !== Object && + typeof data.constructor === 'function' && + typeof data.constructor?.name === 'string' + ? data.constructor.name + : 'object' + + throw new XRPCError( + ResponseType.InvalidRequest, + `Unable to encode ${type} as ${contentType} data`, + ) } -export function httpResponseCodeToEnum(status: number): ResponseType { - let resCode: ResponseType - if (status in ResponseType) { - resCode = status - } else if (status >= 100 && status < 200) { - resCode = ResponseType.XRPCNotSupported - } else if (status >= 200 && status < 300) { - resCode = ResponseType.Success - } else if (status >= 300 && status < 400) { - resCode = ResponseType.XRPCNotSupported - } else if (status >= 400 && status < 500) { - resCode = ResponseType.InvalidRequest - } else { - resCode = ResponseType.InternalServerError - } - return resCode +/** + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/from_static} + */ +function iterableToReadableStream( + iterable: Iterable | AsyncIterable, +): ReadableStream { + // Use the native ReadableStream.from() if available. + if ('from' in ReadableStream && typeof ReadableStream.from === 'function') { + return ReadableStream.from(iterable) + } + + // If you see this error, consider using a polyfill for ReadableStream. For + // example, the "web-streams-polyfill" package: + // https://github.com/MattiasBuelens/web-streams-polyfill + + throw new TypeError( + 'ReadableStream.from() is not supported in this environment. ' + + 'It is required to support using iterables as the request body. ' + + 'Consider using a polyfill or re-write your code to use a different body type.', + ) } export function httpResponseBodyParse( mimeType: string | null, data: ArrayBuffer | undefined, ): any { - if (mimeType) { - if (mimeType.includes('application/json') && data?.byteLength) { - try { + try { + if (mimeType) { + if (mimeType.includes('application/json')) { const str = new TextDecoder().decode(data) return jsonStringToLex(str) - } catch (e) { - throw new XRPCError( - ResponseType.InvalidResponse, - `Failed to parse response body: ${String(e)}`, - ) } - } - if (mimeType.startsWith('text/') && data?.byteLength) { - try { + if (mimeType.startsWith('text/')) { return new TextDecoder().decode(data) - } catch (e) { - throw new XRPCError( - ResponseType.InvalidResponse, - `Failed to parse response body: ${String(e)}`, - ) } } + if (data instanceof ArrayBuffer) { + return new Uint8Array(data) + } + return data + } catch (cause) { + throw new XRPCError( + ResponseType.InvalidResponse, + undefined, + `Failed to parse response body: ${String(cause)}`, + undefined, + { cause }, + ) } - if (data instanceof ArrayBuffer) { - return new Uint8Array(data) - } - return data } diff --git a/packages/xrpc/src/xrpc-client.ts b/packages/xrpc/src/xrpc-client.ts new file mode 100644 index 00000000000..423bc98128f --- /dev/null +++ b/packages/xrpc/src/xrpc-client.ts @@ -0,0 +1,107 @@ +import { LexiconDoc, Lexicons, ValidationError } from '@atproto/lexicon' +import { + FetchHandler, + FetchHandlerOptions, + buildFetchHandler, +} from './fetch-handler' +import { + CallOptions, + QueryParams, + ResponseType, + XRPCError, + XRPCInvalidResponseError, + XRPCResponse, + httpResponseCodeToEnum, +} from './types' +import { + constructMethodCallHeaders, + constructMethodCallUrl, + encodeMethodCallBody, + getMethodSchemaHTTPMethod, + httpResponseBodyParse, + isErrorResponseBody, +} from './util' + +export class XrpcClient { + readonly fetchHandler: FetchHandler + readonly lex: Lexicons + + constructor( + fetchHandlerOpts: FetchHandler | FetchHandlerOptions, + // "Lexicons" is redundant here (because that class implements + // "Iterable") but we keep it for explicitness: + lex: Lexicons | Iterable, + ) { + this.fetchHandler = buildFetchHandler(fetchHandlerOpts) + + this.lex = lex instanceof Lexicons ? lex : new Lexicons(lex) + } + + async call( + methodNsid: string, + params?: QueryParams, + data?: unknown, + opts?: CallOptions, + ): Promise { + const def = this.lex.getDefOrThrow(methodNsid) + if (!def || (def.type !== 'query' && def.type !== 'procedure')) { + throw new TypeError( + `Invalid lexicon: ${methodNsid}. Must be a query or procedure.`, + ) + } + + // @TODO: should we validate the params and data here? + // this.lex.assertValidXrpcParams(methodNsid, params) + // if (data !== undefined) { + // this.lex.assertValidXrpcInput(methodNsid, data) + // } + + const reqUrl = constructMethodCallUrl(methodNsid, def, params) + const reqMethod = getMethodSchemaHTTPMethod(def) + const reqHeaders = constructMethodCallHeaders(def, data, opts) + const reqBody = encodeMethodCallBody(reqHeaders, data) + + // The duplex field is required for streaming bodies, but not yet reflected + // anywhere in docs or types. See whatwg/fetch#1438, nodejs/node#46221. + const init: RequestInit & { duplex: 'half' } = { + method: reqMethod, + headers: reqHeaders, + body: reqBody, + duplex: 'half', + signal: opts?.signal, + } + + try { + const response = await this.fetchHandler.call(undefined, reqUrl, init) + + const resStatus = response.status + const resHeaders = Object.fromEntries(response.headers.entries()) + const resBodyBytes = await response.arrayBuffer() + const resBody = httpResponseBodyParse( + response.headers.get('content-type'), + resBodyBytes, + ) + + const resCode = httpResponseCodeToEnum(resStatus) + if (resCode !== ResponseType.Success) { + const { error = undefined, message = undefined } = + resBody && isErrorResponseBody(resBody) ? resBody : {} + throw new XRPCError(resCode, error, message, resHeaders) + } + + try { + this.lex.assertValidXrpcOutput(methodNsid, resBody) + } catch (e: unknown) { + if (e instanceof ValidationError) { + throw new XRPCInvalidResponseError(methodNsid, e, resBody) + } + + throw e + } + + return new XRPCResponse(resBody, resHeaders) + } catch (err) { + throw XRPCError.from(err) + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c0871af626..0850cc5c798 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -771,6 +771,9 @@ importers: '@atproto-labs/simple-store-memory': specifier: workspace:* version: link:../../internal/simple-store-memory + '@atproto/api': + specifier: workspace:* + version: link:../../api '@atproto/did': specifier: workspace:* version: link:../../did @@ -780,6 +783,9 @@ importers: '@atproto/oauth-types': specifier: workspace:* version: link:../oauth-types + '@atproto/xrpc': + specifier: workspace:* + version: link:../../xrpc multiformats: specifier: ^9.9.0 version: 9.9.0 diff --git a/tsconfig/tests.json b/tsconfig/tests.json index 321864788ee..07573723164 100644 --- a/tsconfig/tests.json +++ b/tsconfig/tests.json @@ -3,6 +3,7 @@ "extends": "./node.json", "compilerOptions": { "types": ["node", "jest"], + "lib": ["ES2022", "DOM", "DOM.Iterable"], "noEmit": true } }