### Basic LexRouter Setup and Server Start Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex-server/README.md Set up a LexRouter with WebSocket support and add handlers for queries and procedures. This example demonstrates adding a query handler for 'getProfile' and a procedure handler for 'createPost', then starting the server. ```typescript import { LexRouter } from '@atproto/lex-server' import { serve, upgradeWebSocket } from '@atproto/lex-server/nodejs' import * as app from './lexicons/app.js' const router = new LexRouter({ upgradeWebSocket }) .add(app.bsky.actor.getProfile, async ({ params }) => { const profile = await db.getProfile(params.actor) return { body: profile } }) .add(app.bsky.feed.post.create, { auth: requireAuth, handler: async ({ credentials, input }) => { const result = await db.createPost(credentials.did, input.body) return { body: result } }, }) await serve(router, { port: 3000 }) ``` -------------------------------- ### Install @atproto/lex-server Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex-server/README.md Install the package using npm. ```bash npm install @atproto/lex-server ``` -------------------------------- ### Install Package and Client Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex-password-session/README.md Install the password session package along with the lex-client package. ```bash npm install @atproto/lex-password-session @atproto/lex-client ``` -------------------------------- ### Install Package Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex-password-session/README.md Install the package using npm. ```bash npm install @atproto/lex-password-session ``` -------------------------------- ### Install @atproto/tap Source: https://github.com/bluesky-social/atproto/blob/main/packages/tap/README.md Install the @atproto/tap package using npm. ```bash npm install @atproto/tap ``` -------------------------------- ### Install all Lexicons from manifest Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Installs all Lexicons defined in the lexicons.json manifest file. This is the default behavior when no specific Lexicons are provided. ```bash lex install ``` -------------------------------- ### Start UI Development Server Source: https://github.com/bluesky-social/atproto/blob/main/packages/oauth/oauth-provider-ui/error-page.html Use this command to start the development server for the OAuth provider UI. Access the UI at `http://localhost:5173/`. ```sh pnpm run start:ui ``` -------------------------------- ### Install AT Protocol API Package Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/README.md Install the @atproto/api package using yarn. ```sh yarn add @atproto/api ``` -------------------------------- ### Start Lex Server with serve() Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex-server/README.md Use the 'serve' function from '@atproto/lex-server/nodejs' to start a server and begin listening on a specified port. It supports graceful shutdown and AsyncDisposable. ```typescript import { serve } from '@atproto/lex-server/nodejs' const server = await serve(router, { port: 3000 }) console.log('Server listening on port 3000') // Graceful shutdown await server.terminate() ``` ```typescript await using server = await serve(router, { port: 3000 }) // Server is automatically terminated when scope exits ``` -------------------------------- ### Install Dependencies with npm Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/OAUTH.md Installs the @atproto/oauth-client-browser, @atproto/api, parcel, and parcel-reporter-static-files-copy packages as development dependencies. Also creates the src and static directories. ```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 ``` -------------------------------- ### Start Development Server with Parcel Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/OAUTH.md Command to start the Parcel development server, which will serve the SPA from the src/index.html file. ```bash npx parcel src/index.html ``` -------------------------------- ### Client Metadata JSON Example Source: https://github.com/bluesky-social/atproto/blob/main/packages/oauth/oauth-client-node/README.md Example of client metadata configuration for a native application. This JSON should be hosted on an internet-accessible URL. ```json { "client_id": "https://my-app.com/client-metadata.json", "client_name": "My App", "client_uri": "https://my-app.com", "logo_uri": "https://my-app.com/logo.png", "tos_uri": "https://my-app.com/tos", "policy_uri": "https://my-app.com/policy", "redirect_uris": ["https://my-app.com/atproto-oauth-callback"], "scope": "atproto", "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "application_type": "native", "token_endpoint_auth_method": "none", "dpop_bound_access_tokens": true } ``` -------------------------------- ### Install Dependencies and Build Packages Source: https://github.com/bluesky-social/atproto/blob/main/README.md After setting up Node.js and pnpm, use make commands to pull all project dependencies and build the local TypeScript packages. ```shell make deps make build ``` -------------------------------- ### Install Lexicon Schemas Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Install Lexicon schemas from the Atmosphere network using the `lex install` command. This command manages schemas locally and updates a manifest file. ```bash lex install app.bsky.feed.post app.bsky.feed.like ``` -------------------------------- ### Query Handler Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex-server/README.md Describes how to implement query handlers for GET requests, which receive parameters from the URL query string. Includes an example for fetching a user profile. ```APIDOC ## Queries and Procedures ### Query Handler Queries handle `GET` requests and receive parameters from the URL query string: ```typescript import * as app from './lexicons/app.js' router.add(app.bsky.actor.getProfile, async ({ params }) => { // params.actor is typed and validated const profile = await db.getProfile(params.actor) return { body: profile } }) ``` ``` -------------------------------- ### Create Lex Server without Starting Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex-server/README.md Use 'createServer' to instantiate a server without immediately starting it. This allows for manual control over the listening process and configuration of options like 'gracefulTerminationTimeout'. ```typescript import { createServer } from '@atproto/lex-server/nodejs' const server = createServer(router, { gracefulTerminationTimeout: 5000, }) server.listen(3000, () => { console.log('Server listening') }) ``` -------------------------------- ### Example Manifest Structure Source: https://github.com/bluesky-social/atproto/blob/main/packages/internal/rollup-plugin-bundle-manifest/README.md Illustrates the JSON structure of the generated bundle manifest, showing details for JavaScript chunks and assets. ```json { "main.js": { "type": "chunk", "mime": "application/javascript", "dynamicImports": [], "isDynamicEntry": false, "isEntry": true, "isImplicitEntry": false, "name": "main", "sha256": "", "data": "" }, "main.js.map": { "type": "asset", "mime": "application/json", "sha256": "", "data": "" }, "main.css": { "type": "asset", "mime": "text/css", "sha256": "", "data": "" } // ... more entries as needed } ``` -------------------------------- ### Run Local PDS and AppView Source: https://github.com/bluesky-social/atproto/blob/main/README.md Start a local instance of the Personal Data Server (PDS) and AppView with pre-configured fake test accounts and data for development. ```shell make run-local ``` -------------------------------- ### Install a specific Lexicon Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Installs a single Lexicon and updates the lexicons.json manifest by default. Use this for managing individual lexicon files. ```bash lex install app.bsky.feed.post ``` -------------------------------- ### Install Node.js and pnpm with nvm Source: https://github.com/bluesky-social/atproto/blob/main/README.md Use nvm to manage Node.js versions and install pnpm, the package manager used for this workspace. Requires Node.js v18. ```shell make nvm-setup ``` -------------------------------- ### Creating an Authenticated Client with OAuth Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Create an authenticated `Client` instance using an established OAuth session. Requires setup with `@atproto/oauth-client-node`. ```typescript import { Client } from '@atproto/lex' import { OAuthClient } from '@atproto/oauth-client-node' // Setup OAuth client (see @atproto/oauth-client documentation) const oauthClient = new OAuthClient({ /* ... */ }) const session = await oauthClient.restore(userDid) // Create authenticated client const client = new Client(session) ``` -------------------------------- ### Create and Mount XRPC Server with Express Source: https://github.com/bluesky-social/atproto/blob/main/packages/xrpc-server/README.md Demonstrates how to set up an XRPC server using @atproto/xrpc-server, define a Lexicon schema, implement a handler, and integrate it into an Express application. Ensure you have Express and @atproto/xrpc-server installed. ```typescript import { LexiconDoc } from '@atproto/lexicon' import * as xrpc from '@atproto/xrpc-server' import express from 'express' const lexicons: LexiconDoc[] = [ { lexicon: 1, id: 'io.example.ping', defs: { main: { type: 'query', parameters: { type: 'params', properties: { message: { type: 'string' } }, }, output: { encoding: 'application/json', }, }, }, }, ] // create xrpc server const server = xrpc.createServer(lexicons) function ping(ctx: { auth: xrpc.HandlerAuth | undefined params: xrpc.Params input: xrpc.HandlerInput | undefined req: express.Request res: express.Response }) { return { encoding: 'application/json', body: { message: ctx.params.message } } } server.method('io.example.ping', ping) // mount in express const app = express() app.use(server.router) app.listen(8080) ``` -------------------------------- ### Generate Lexicon Schemas Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex-server/README.md Use the 'lex' command-line tool to install and build Lexicon schemas. ```bash lex install app.bsky.actor.getProfile lex build ``` -------------------------------- ### Creating Posts Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Provides an example of an Action that can be used to create new posts on the AT Protocol. It abstracts the underlying `client.create` call and handles default values for fields like `createdAt`. ```APIDOC ## Building Library-Style APIs with Actions Actions enable you to create high-level, convenience APIs similar to [@atproto/api](https://www.npmjs.com/package/@atproto/api)'s `Agent` class. Here are patterns for common operations: #### Creating Posts ```typescript import { Action, l } from '@atproto/lex' import * as app from './lexicons/app.js' type PostInput = Partial & Omit export const post: Action = async ( client, record, options, ) => { return client.create( app.bsky.feed.post, { ...record, createdAt: record.createdAt || l.currentDatetimeString(), }, options, ) } // Usage await client.call(post, { text: 'Hello, AT Protocol!', langs: ['en'], }) ``` ``` -------------------------------- ### Connect to Tap and handle events Source: https://github.com/bluesky-social/atproto/blob/main/packages/tap/README.md Connect to a Tap instance, set up an indexer to handle identity and record events, and start receiving updates. Remember to add repositories to track and destroy the channel on shutdown. ```typescript import { Tap, SimpleIndexer } from '@atproto/tap' const tap = new Tap('http://localhost:2480', { adminPassword: 'secret' }) const indexer = new SimpleIndexer() indexer.identity(async (evt) => { console.log(`${evt.did} updated identity: ${evt.handle} (${evt.status})`) }) indexer.record(async (evt) => { const uri = `at://${evt.did}/${evt.collection}/${evt.rkey}` if (evt.action === 'create' || evt.action === 'update') { console.log(`${evt.action}: ${uri}`) } else { console.log(`deleted: ${uri}`) } }) indexer.error((err) => console.error(err)) const channel = tap.channel(indexer) channel.start() await tap.addRepos(['did:plc:ewvi7nxzyoun6zhxrhs64oiz']) // On shutdown await channel.destroy() ``` -------------------------------- ### Datetime Precisions Source: https://github.com/bluesky-social/atproto/blob/main/packages/syntax/tests/interop-files/datetime_valid.txt Examples illustrating various fractional second precisions, up to 12 digits, demonstrating the system's flexibility. ```text 1985-04-12T23:20:50.1Z ``` ```text 1985-04-12T23:20:50.12Z ``` ```text 1985-04-12T23:20:50.123Z ``` ```text 1985-04-12T23:20:50.1234Z ``` ```text 1985-04-12T23:20:50.12345Z ``` ```text 1985-04-12T23:20:50.123456Z ``` ```text 1985-04-12T23:20:50.1234567Z ``` ```text 1985-04-12T23:20:50.12345678Z ``` ```text 1985-04-12T23:20:50.123456789Z ``` ```text 1985-04-12T23:20:50.1234567890Z ``` ```text 1985-04-12T23:20:50.12345678901Z ``` ```text 1985-04-12T23:20:50.123456789012Z ``` -------------------------------- ### Subscription Handler Example Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex-server/README.md Implements real-time data streaming over WebSockets using async generators. Yields messages and handles errors gracefully. ```typescript import { LexRouter, LexError } from '@atproto/lex-server' import { serve, upgradeWebSocket } from '@atproto/lex-server/nodejs' import { scheduler } from 'node:timers/promises' const router = new LexRouter({ upgradeWebSocket, // Required for WebSocket support in nodejs }) router.add(com.example.stream, async function* ({ params, request }) { const { cursor = 0, limit = 10 } = params const { signal } = request for (let i = 0; i < limit; i++) { // Yield messages to the client yield com.example.stream.message.$build({ data: `Message ${cursor + i}`, cursor: cursor + i, }) // Wait between messages (respects abort signal) await scheduler.wait(1000, { signal }) } // Throwing a LexError closes the connection with an error frame throw new LexError('LimitReached', `Limit of ${limit} messages reached`) }) ``` -------------------------------- ### Query Handler Example Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex-server/README.md Handles GET requests for queries, extracting parameters from the URL query string. Ensures parameters are typed and validated. ```typescript import * as app from './lexicons/app.js' router.add(app.bsky.actor.getProfile, async ({ params }) => { // params.actor is typed and validated const profile = await db.getProfile(params.actor) return { body: profile } }) ``` -------------------------------- ### Commit Installed Lexicons Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md After installing Lexicons, verify and commit the `lexicons.json` manifest and the `lexicons/` directory to version control. ```bash git add lexicons.json lexicons/ git commit -m "Install Lexicons" ``` -------------------------------- ### Client Initialization with Service Proxy Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Shows how to initialize a client that routes requests through a specific service, useful for authenticated-only scenarios. ```APIDOC ## Client with Service Proxy (authenticated only) ```typescript import { Client } from '@atproto/lex' // Route requests through a specific service const client = new Client(session, { service: 'did:web:api.bsky.app#bsky_appview', }) ``` ``` -------------------------------- ### Client Initialization with Password Session Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Demonstrates how to initialize an authenticated client using password-based authentication via `@atproto/lex-password-session`. ```APIDOC ## Authenticated Client with Password For CLI tools, scripts, and bots, you can use password-based authentication with [`@atproto/lex-password-session`](../lex-password-session): ```typescript import { Client } from '@atproto/lex' import { PasswordSession } from '@atproto/lex-password-session' const session = await PasswordSession.login({ service: 'https://bsky.social', identifier: 'alice.bsky.social', password: 'xxxx-xxxx-xxxx-xxxx', // App password onUpdated: (data) => saveToStorage(data), onDeleted: (data) => clearStorage(data.did), }) const client = new Client(session) ``` For detailed password session setup, see the [@atproto/lex-password-session](../lex-password-session) documentation. ``` -------------------------------- ### Update all installed Lexicons Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Re-fetches and updates all currently installed Lexicons to their latest versions. This ensures you are using the most recent definitions. ```bash lex install --update ``` -------------------------------- ### Configure Login and Sign-up Options Source: https://github.com/bluesky-social/atproto/blob/main/packages/oauth/oauth-provider-ui/cookie-error-page.html Sets options for login hints, available user domains for sign-up, and the hCaptcha site key. Set `loginHint` to a username to test 'sign-in only' flow. Use an empty array for `availableUserDomains` to disable sign-up, or a single value to disable the domain selector. Set `hcaptchaSiteKey` to enable hCaptcha during sign-up. ```javascript // Provide a value here to test the "sign-in only" flow const loginHint = undefined // 'alice.test' // Use empty array to disable the "sign-up" flow, use a single value to // disable the domain selector. const availableUserDomains = [' .bsky.social', '.bsky.team'] // Use non empty string to enable hCaptcha during "sign-up" flow const hcaptchaSiteKey = undefined ``` -------------------------------- ### Configure Login Hint and Available Domains Source: https://github.com/bluesky-social/atproto/blob/main/packages/oauth/oauth-provider-ui/authorization-page.html Sets the `loginHint` for pre-filling the login field and `availableUserDomains` for sign-up domain selection. Set `loginHint` to `undefined` to disable pre-filling, and use an empty array for `availableUserDomains` to disable domain selection. ```javascript // Provide a value here to test the "sing-in only" flow const loginHint = undefined // 'alice.test' // Use empty array to disable the "sing-up" flow, use a single value to // disable the domain selector. const availableUserDomains = ['.bsky.social', '.bsky.team'] ``` -------------------------------- ### Client Assert DID Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Get the authenticated user's DID, asserting that the client is authenticated. This is a concise way to get the DID when authentication is required. ```APIDOC ## `client.assertDid` Get the authenticated user's DID, asserting that the client is authenticated. ```typescript const did = client.assertDid // Type: Did (throws if not authenticated) ``` This is equivalent to calling `client.assertAuthenticated()` followed by accessing `client.did`, but provides a more concise way to get the DID when you know authentication is required. ``` -------------------------------- ### Client Initialization with Validation and Strictness Options Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Explains how to configure the `Client` constructor with options for request/response validation and strictness of Lex data processing. ```APIDOC ## Validation and Strictness Options The `Client` constructor accepts options to control request/response validation and how invalid Lex data is handled. These defaults apply to all XRPC calls made through the client, and can be overridden per-call via `client.call()`, `client.xrpc()` or `client.xrpcSafe()`. ```typescript const client = new Client(session, { // Validate requests against the method's input schema (default: false) validateRequest: true, // Validate responses against the method's output schema (default: true) validateResponse: true, // Strictly process responses according to Lex encoding rules. When set to // false, accepts responses containing invalid Lex data such as floats or // malformed $bytes/$link objects (default: true) strictResponseProcessing: false, }) ``` - **`validateRequest`** — When `true`, outgoing request bodies are validated against the Lexicon input schema before sending. Useful in development to catch errors early. Default: `false`. - **`validateResponse`** — When `true`, incoming response bodies are validated against the Lexicon output schema. Disabling this can improve performance when you trust the upstream service. Default: `true`. - **`strictResponseProcessing`** — When `true` (default), the client will strictly process responses according to Lex encoding rules, rejecting responses containing invalid Lex data (e.g. floating-point numbers, malformed `$bytes` or `$link` objects). When `false`, the client accepts such responses in a lenient mode: invalid values are returned as-is rather than being rejected or converted, `datetime` string format checks become more lenient (e.g. datetimes without timezones are accepted) while other string formats remain strict, blob MIME type and size constraints are not enforced, and legacy blob reference format (objects with `cid` and `mimeType` properties) is accepted. Default: `true`. ``` -------------------------------- ### Configure Client Labelers After Sign-in in TypeScript Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Illustrates creating a base client, fetching user preferences to determine labelers, and then configuring the client. New clients created from this base client will inherit labeler updates. ```typescript import { Client } from '@atproto/lex' import * as app from './lexicons/app.js' async function createBaseClient(session: OAuthSession) { // Create base client const client = new Client(session, { service: 'did:web:api.bsky.app#bsky_appview', }) // Fetch user preferences const { preferences } = await client.call(app.bsky.actor.getPreferences) // Extract labeler preferences const labelerPref = preferences.findLast((p) => app.bsky.actor.defs.labelersPref.check(p), ) const labelers = labelerPref?.labelers.map((l) => l.did) ?? [] // Configure the client with the user's preferred labelers client.setLabelers(labelers) return client } // Usage const baseClient = await createBaseClient(session) // Create a new client with a different service, but reusing the labelers // from the base client. const otherClient = new Client(baseClient, { service: 'did:web:com.example.other#other_service', }) // Whenever you update labelers on the base client, the other client will automatically // receive the same updates, since they share the same labeler set. ``` -------------------------------- ### Install Lexicons without updating manifest Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Installs specified Lexicons without modifying the lexicons.json manifest. Use this when you want to temporarily use Lexicons or manage the manifest separately. ```bash lex install --no-save app.bsky.feed.post app.bsky.actor.profile ``` -------------------------------- ### Start and destroy Tap channel Source: https://github.com/bluesky-social/atproto/blob/main/packages/tap/README.md Start the Tap channel to begin receiving events. The promise resolves when the connection is destroyed or errors. Use destroy() to close the connection gracefully. ```typescript channel.start() channel.destroy() ``` -------------------------------- ### Login Hint and Domain Configuration Source: https://github.com/bluesky-social/atproto/blob/main/packages/oauth/oauth-provider-ui/error-page.html Configures the `loginHint` for pre-filling the login field and `availableUserDomains` for sign-up. Set `loginHint` to `undefined` to disable pre-filling, and use an empty array for `availableUserDomains` to disable sign-up. ```javascript // Provide a value here to test the "sing-in only" flow const loginHint = undefined // 'alice.test' // Use empty array to disable the "sing-up" flow, use a single value to // disable the domain selector. const availableUserDomains = ['.bsky.social', '.bsky.team'] ``` -------------------------------- ### Get Authenticated User DID Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Access the `client.did` property to retrieve the authenticated user's DID. It returns `Did | undefined`. ```typescript const did = client.did // Returns Did | undefined ``` -------------------------------- ### Packaging Actions as a Library Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Demonstrates how to group multiple actions into a single library for easier management and usage. ```APIDOC ## Packaging Actions ### Description Actions can be bundled together into a library for reusability. This allows for a centralized place to define and export various operations. ### Example Usage ```typescript // actions.ts import { Action, Client } from '@atproto/lex' import * as app from './lexicons/app.js' export const post: Action = async (client, input, options) => { /* ... */ } export const like: Action = async (client, input, options) => { /* ... */ } export const follow: Action = async (client, input, options) => { /* ... */ } export const updateProfile: Action = async ( client, input, options, ) => { /* ... */ } // In another file: import * as actions from './actions.js' await client.call(actions.post, { text: 'Hello!' }) ``` ``` -------------------------------- ### Invalid TID: Incorrect Length Source: https://github.com/bluesky-social/atproto/blob/main/packages/syntax/tests/interop-files/tid_syntax_invalid.txt TIDs have a specific length requirement. This example shows TIDs that are too long or too short. ```plaintext 3jzfcijpj2z2aa 3jzfcijpj2z2 ``` -------------------------------- ### Invalid TID: Not Base32 Source: https://github.com/bluesky-social/atproto/blob/main/packages/syntax/tests/interop-files/tid_syntax_invalid.txt TIDs must be encoded using Base32. This example shows characters that are not part of the Base32 alphabet. ```plaintext 3jzfcijpj2z21 0000000000000 ``` -------------------------------- ### Generate, Sign, and Verify with K-256 Keypair Source: https://github.com/bluesky-social/atproto/blob/main/packages/crypto/README.md Demonstrates generating a new K-256 private key, signing binary data, serializing the public key to a did:key string, and verifying the signature. Ensure the '@atproto/crypto' package is imported. ```typescript import { verifySignature, Secp256k1Keypair, P256Keypair } from '@atproto/crypto' // generate a new random K-256 private key const keypair = await Secp256k1Keypair.create({ exportable: true }) // sign binary data, resulting signature bytes. // SHA-256 hash of data is what actually gets signed. // signature output is often base64-encoded. const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]) const sig = await keypair.sign(data) // serialize the public key as a did:key string, which includes key type metadata const pubDidKey = keypair.did() console.log(pubDidKey) // output would look something like: 'did:key:zQ3shVRtgqTRHC7Lj4DYScoDgReNpsDp3HBnuKBKt1FSXKQ38' // verify signature using public key const ok = verifySignature(pubDidKey, data, sig) if (!ok) { throw new Error('Uh oh, something is fishy') } else { console.log('Success') } ``` -------------------------------- ### Create a Client from Another Client in TypeScript Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Shows how to instantiate a new `Client` from an existing one, inheriting its configuration while allowing for overrides. Child client settings take precedence. ```typescript import { Client } from '@atproto/lex' // Base client with authentication const baseClient = new Client(session) baseClient.setLabelers(['did:plc:labelerA', 'did:plc:labelerB']) baseClient.headers.set('x-app-version', '1.0.0') // Create a new client with additional configuration that will get merged with // baseClient's settings on every request. const configuredClient = new Client(baseClient, { labelers: ['did:plc:labelerC'], headers: { 'x-trace-id': 'abc123' }, }) ``` -------------------------------- ### Create HTML Entry Point Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/OAUTH.md Defines the basic HTML structure for the SPA, including meta tags and a script tag to load the main application logic. ```html My First OAuth App Loading... ``` -------------------------------- ### Procedure Handler Example Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex-server/README.md Handles POST requests for procedures, processing data from the request body. The input body is parsed and validated. ```typescript router.add(app.bsky.feed.post.create, async ({ input }) => { // input.body contains the parsed and validated request body const post = await db.createPost(input.body) return { body: { uri: post.uri, cid: post.cid } } }) ``` -------------------------------- ### Invalid TID: Old Dash Syntax Source: https://github.com/bluesky-social/atproto/blob/main/packages/syntax/tests/interop-files/tid_syntax_invalid.txt The syntax for TIDs with dashes is not supported. This example demonstrates an outdated or incorrect use of dashes. ```plaintext 3jzf-cij-pj2z-2a ``` -------------------------------- ### startPds(port?: number) Source: https://github.com/bluesky-social/atproto/blob/main/packages/dev-env/README.md Creates a new PDS (Personal Data Server) instance. Data for this instance is stored in memory. ```APIDOC ## startPds(port?: number) ### Description Creates a new PDS instance. Data is stored in memory. ### Method REPL Command ### Parameters #### Query Parameters - **port** (number) - Optional - The port number to start the PDS on. ``` -------------------------------- ### Initialize XRPC Client and Make Calls Source: https://context7.com/bluesky-social/atproto/llms.txt Instantiate XrpcClient with a base URL and an array of Lexicon documents. Use the call method to interact with XRPC endpoints. Ensure the correct Lexicon is provided during initialization. ```typescript import { XrpcClient } from '@atproto/xrpc' import type { LexiconDoc } from '@atproto/lexicon' const pingLexicon: LexiconDoc = { lexicon: 1, id: 'io.example.ping', defs: { main: { type: 'query', parameters: { type: 'params', properties: { message: { type: 'string' } }, }, output: { encoding: 'application/json', schema: { type: 'object', required: ['message'], properties: { message: { type: 'string' } }, }, }, }, }, } // Basic usage const xrpc = new XrpcClient('https://api.bsky.app', [pingLexicon]) const res = await xrpc.call('io.example.ping', { message: 'hello' }) console.log(res.encoding) // => 'application/json' console.log(res.body) // => { message: 'hello' } ``` -------------------------------- ### Implement NodeSavedSessionStore Source: https://github.com/bluesky-social/atproto/blob/main/packages/oauth/oauth-client-node/README.md Example implementation of a session store for saving and retrieving OAuth session data. This typically involves interacting with a database. ```typescript const sessionStore: NodeSavedSessionStore = { async set(sub: string, sessionData: NodeSavedSession) { // Insert or update the session data in your database await saveSessionDataToDb(sub, sessionData) }, async get(sub: string) { // Retrieve the session data from your database const sessionData = await getSessionDataFromDb(sub) if (!sessionData) return undefined return sessionData }, async del(sub: string) { // Delete the session data from your database await deleteSessionDataFromDb(sub) }, } ``` -------------------------------- ### Extreme Datetime Values Source: https://github.com/bluesky-social/atproto/blob/main/packages/syntax/tests/interop-files/datetime_valid.txt These examples show extreme year values that are still considered valid by the system, demonstrating its broad range. ```text 0010-12-31T23:00:00.000Z ``` ```text 1000-12-31T23:00:00.000Z ``` ```text 1900-12-31T23:00:00.000Z ``` ```text 3001-12-31T23:00:00.000Z ``` -------------------------------- ### Initialize XrpcClient with Lexicon Source: https://github.com/bluesky-social/atproto/blob/main/packages/xrpc/README.md Instantiate the XrpcClient with a service URL and an array of Lexicon definitions. This client can then be used to make calls to the atproto service. ```typescript import { LexiconDoc } from '@atproto/lexicon' import { XrpcClient } from '@atproto/xrpc' const pingLexicon = { lexicon: 1, id: 'io.example.ping', defs: { main: { type: 'query', description: 'Ping the server', parameters: { type: 'params', properties: { message: { type: 'string' } }, }, output: { encoding: 'application/json', schema: { type: 'object', required: ['message'], properties: { message: { type: 'string' } }, }, }, }, }, } satisfies LexiconDoc const xrpc = new XrpcClient('https://ping.example.com', [ // Any number of lexicon here pingLexicon, ]) const res1 = await xrpc.call('io.example.ping', { message: 'hello world', }) res1.encoding // => 'application/json' res1.body // => {message: 'hello world'} ``` -------------------------------- ### client.call() Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Allows calling procedures or queries defined in Lexicons. Supports GET for queries and POST for procedures, with optional call options. ```APIDOC ### Core Methods #### `client.call()` Call procedures or queries defined in Lexicons. ```typescript import * as app from './lexicons/app.js' // Query (GET request) const profile = await client.call(app.bsky.actor.getProfile, { actor: 'pfrazee.com', }) // Procedure (POST request) const result = await client.call(app.bsky.feed.sendInteractions, { interactions: [ /* ... */ ], }) // With options const timeline = await client.call( app.bsky.feed.getTimeline, { limit: 50, }, { signal: abortSignal, }, ) ``` ``` -------------------------------- ### Initialize and Use LexIndexer Source: https://github.com/bluesky-social/atproto/blob/main/packages/tap/README.md Demonstrates how to create a LexIndexer instance and register handlers for different event actions like create, update, delete, and general events. Records are validated against schemas before handlers are invoked. ```ts import { LexIndexer } from '@atproto/tap' import * as com from './lexicons/com' const indexer = new LexIndexer() // Handle creates for a specific record type indexer.create(com.example.post, async (evt) => { // evt.record is fully typed as com.example.post.Main console.log(`New post: ${evt.record.text}`) }) // Handle updates indexer.update(com.example.post, async (evt) => { console.log(`Updated post: ${evt.record.text}`) }) // Handle deletes (no record on delete events) indexer.delete(com.example.post, async (evt) => { console.log(`Deleted: at://${evt.did}/${evt.collection}/${evt.rkey}`) }) // Handle both creates and updates with put() indexer.put(com.example.like, async (evt) => { console.log(`Like ${evt.action}: ${evt.record.subject.uri}`) }) // Fallback for unhandled record types/actions indexer.other(async (evt) => { console.log(`Unhandled: ${evt.action}, ${evt.collection}`) }) // Identity and error handlers indexer.identity(async (evt) => { ... }) indexer.error((err) => { ... }) const channel = tap.channel(indexer) ``` -------------------------------- ### Authenticated API Calls and UI Setup Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/OAUTH.md Once authenticated, instantiate the Agent with the user's session to make API calls. This snippet includes fetching the user's profile, setting up UI buttons for fetching the profile and logging out. ```typescript if (session) { const agent = new Agent(session) 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 session.signOut() window.location.reload() } const outputPre = document.createElement('pre') document.body.appendChild(outputPre) } ``` -------------------------------- ### Lexicon Development Workflow Scripts Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Integrate these scripts into your `package.json` for automated lexicon verification, installation, and building. This ensures consistency and up-to-date schemas. ```json { "scripts": { "update-lexicons": "lex install --update --save", "postinstall": "lex install --ci", "prebuild": "lex build", "build": "# Your build command here" } } ``` -------------------------------- ### Use a Lexicon Action Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Invoke defined Actions using `client.call()`, similar to Lexicon methods. This example demonstrates calling the `likePost` action. ```typescript // Use the action const client = new Client(session) const like = await client.call(likePost, { uri: 'at://did:plc:abc/app.bsky.feed.post/123', cid: 'bafyreiabc...', }) ``` -------------------------------- ### Basic Agent Initialization Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/README.md Initialize an Agent with a CredentialSession after logging in. Requires account credentials. ```typescript import { Agent, CredentialSession } from '@atproto/api' const session = new CredentialSession(new URL('https://bsky.social')) await session.login(account) const agent = new Agent(session) ``` -------------------------------- ### Creating a Client from Another Client Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Explains how to instantiate a new Lex client based on an existing one, inheriting its configuration while allowing for specific overrides. This is useful for managing settings like labelers and headers. ```APIDOC ## Creating a Client from Another Client You can create a new `Client` instance from an existing client. The new client will share the same underlying configuration (authentication, headers, labelers, service proxy), with the ability to override specific settings. > [!NOTE] > > When you create a client from another client, the child client inherits the base client's configuration. On every request, the child client merges its own configuration with the base client's current configuration, with the child's settings taking precedence. Changes to the base client's configuration (like `baseClient.setLabelers()`) will be reflected in child client requests, but changes to child clients do not affect the base client. ```typescript import { Client } from '@atproto/lex' // Base client with authentication const baseClient = new Client(session) baseClient.setLabelers(['did:plc:labelerA', 'did:plc:labelerB']) baseClient.headers.set('x-app-version', '1.0.0') // Create a new client with additional configuration that will get merged with // baseClient's settings on every request. const configuredClient = new Client(baseClient, { labelers: ['did:plc:labelerC'], headers: { 'x-trace-id': 'abc123' }, }) ``` This pattern is particularly useful when you need to: - Configure labelers after authentication - Add application-specific headers - Create multiple clients with different configurations from the same session **Example: Configuring labelers after sign-in** ```typescript import { Client } from '@atproto/lex' import * as app from './lexicons/app.js' async function createBaseClient(session: OAuthSession) { // Create base client const client = new Client(session, { service: 'did:web:api.bsky.app#bsky_appview', }) // Fetch user preferences const { preferences } = await client.call(app.bsky.actor.getPreferences) // Extract labeler preferences const labelerPref = preferences.findLast((p) => app.bsky.actor.defs.labelersPref.check(p), ) const labelers = labelerPref?.labelers.map((l) => l.did) ?? [] // Configure the client with the user's preferred labelers client.setLabelers(labelers) return client } // Usage const baseClient = await createBaseClient(session) // Create a new client with a different service, but reusing the labelers // from the base client. const otherClient = new Client(baseClient, { service: 'did:web:com.example.other#other_service', }) // Whenever you update labelers on the base client, the other client will automatically // receive the same updates, since they share the same labeler set. ``` ``` -------------------------------- ### Procedure Handler Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex-server/README.md Explains how to implement procedure handlers for POST requests, which receive a request body. Includes an example for creating a new post. ```APIDOC ### Procedure Handler Procedures handle `POST` requests and receive a request body: ```typescript router.add(app.bsky.feed.post.create, async ({ input }) => { // input.body contains the parsed and validated request body const post = await db.createPost(input.body) return { body: { uri: post.uri, cid: post.cid } } }) ``` ``` -------------------------------- ### Defining and Using Actions Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex/lex/README.md Demonstrates how to define a custom Action and then invoke it using the client.call() method. ```APIDOC ## Define an Action An `Action` is a function with the signature: ```typescript type Action = ( client: Client, input: Input, options: CallOptions, ) => Output | Promise ``` Actions receive: - `client` - The Client instance (to make XRPC calls) - `input` - The input data for the action - `options` - Call options (signal) ### Example Action Definition ```typescript import { Action, Client, l } from '@atproto/lex' import * as app from './lexicons/app.js' export const likePost: Action< { uri: string; cid: string }, { uri: string; cid: string } > = async (client, { uri, cid }, options) => { client.assertAuthenticated() const result = await client.create( app.bsky.feed.like, { subject: { uri, cid }, createdAt: l.toDatetimeString(new Date()), }, options, ) return result } ``` ## Using Actions Actions are called using `client.call()`, the same method used for XRPC queries and procedures: ### Example Action Invocation ```typescript // Use the action const client = new Client(session) const like = await client.call(likePost, { uri: 'at://did:plc:abc/app.bsky.feed.post/123', cid: 'bafyreiabc...', }) ``` ```