### Setup SPA Project Dependencies (Bash)
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/OAUTH.md
Installs essential development dependencies for an atproto OAuth client SPA, including the OAuth client, API library, Parcel bundler, and a static file reporter. These commands initialize the project and prepare it for bundling.
```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 (Bash)
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/OAUTH.md
Starts the Parcel development server, which will bundle the SPA and serve the `src/index.html` file. It watches for file changes and automatically reloads the application.
```bash
npx parcel src/index.html
```
--------------------------------
### Start Mock UI Server (pnpm)
Source: https://github.com/bluesky-social/atproto/blob/main/packages/oauth/oauth-provider-ui/CONTRIBUTING.md
Starts the mock UI server using pnpm. This server is built with Vite and serves the UI at http://localhost:5173. Use the provided URLs to live-preview different UI pages.
```shell
pnpm dev:ui
```
--------------------------------
### Run Development Environment
Source: https://github.com/bluesky-social/atproto/blob/main/README.md
Command to set up and run the development environment for the AT Protocol project. Requires global installation of `jq` and `docker`.
```shell
make run-dev-env
```
--------------------------------
### Install @atproto/api Package
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/README.md
Installs the @atproto/api package using yarn. This is the first step to using the ATProtocol client in your application.
```sh
yarn add @atproto/api
```
--------------------------------
### Client Metadata JSON Example
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/OAUTH.md
Example JSON structure for client metadata used in OAuth authentication with atproto. It includes fields like client_id, client_name, redirect_uris, and scope, which identify the application to the authorization server.
```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": "atproto",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none",
"application_type": "web",
"dpop_bound_access_tokens": true
}
```
--------------------------------
### Basic SPA TypeScript Entry Point (TypeScript)
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/OAUTH.md
A simple TypeScript file that logs a message to the console. This is the main script that will be executed by the browser when the HTML file is loaded.
```typescript
console.log('Hello from atproto OAuth example app!')
```
--------------------------------
### Create SPA HTML Entry Point (HTML)
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/OAUTH.md
Provides the basic HTML structure for the SPA, including meta tags for character set and viewport, and links to the main TypeScript module. This serves as the entry point for the client-side application.
```html
My First OAuth App
Loading...
```
--------------------------------
### Authenticate and Fetch Profile with Agent
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/OAUTH.md
Uses the authenticated session to create an `Agent` instance for interacting with the ATProto API. It includes functionality to fetch the user's profile and provides UI elements 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)
}
```
--------------------------------
### Initialize Bluesky BrowserOAuthClient
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/OAUTH.md
Configures the `BrowserOAuthClient` for handling OAuth flows in a browser environment. It requires a client ID and a handle resolver endpoint, such as 'https://bsky.social/'.
```TypeScript
import { Agent } from '@atproto/api'
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)
```
--------------------------------
### Configure Parcel Bundler (.parcelrc)
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/OAUTH.md
Defines the Parcel bundler configuration, extending the default settings and adding a reporter for copying static files. This ensures that static assets are correctly handled during the build process.
```json
{
"extends": ["@parcel/config-default"],
"reporters": ["...", "parcel-reporter-static-files-copy"]
}
```
--------------------------------
### Initiate OAuth Authorization Flow
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/OAUTH.md
Prompts the user for their ATProto handle and initiates the OAuth authorization process. It generates an authorization URL and redirects the user to the authorization server. Includes a timeout to mitigate browser back-forward cache issues.
```TypeScript
if (!session) {
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'),
)
})
}
```
--------------------------------
### Run bsky appview service
Source: https://github.com/bluesky-social/atproto/blob/main/services/bsky/README.md
Command to start the bsky appview service using Node.js. Requires specific environment variables to be set for proper operation.
```shell
node api.js
```
--------------------------------
### Check Authentication Status
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/OAUTH.md
Initializes the OAuth client to detect if the user is already authenticated. If authenticated, it logs the user's DID and session details.
```TypeScript
const result = await oauthClient.init()
if (result) {
if ('state' in result) {
console.log('The user was just redirected back from the authorization page')
}
console.log(`The user is currently signed in as ${result.session.did}`)
}
const session = result?.session
// TO BE CONTINUED
```
--------------------------------
### Open ngrok Tunnel (Bash)
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/OAUTH.md
Opens a secure tunnel to your local development server using ngrok, exposing it to the internet. This is necessary for the atproto OAuth flow, which requires a publicly accessible URL for the redirect URI.
```bash
ngrok http 1234
```
--------------------------------
### Initialize and Start Firehose Subscription
Source: https://github.com/bluesky-social/atproto/blob/main/packages/sync/README.md
Demonstrates initializing the Firehose class with an IdResolver and service endpoint. It covers handling different event types (identity, account, create, update, delete), errors, and filtering collections. The firehose verifies signatures and repo proofs by default.
```typescript
import { Firehose } from '@atproto/sync'
import { IdResolver } from '@atproto/identity'
const idResolver = new IdResolver()
const firehose = new Firehose({
idResolver,
service: 'wss://bsky.network',
handleEvt: async (evt) => {
if (evt.event === 'identity') {
// ...
} else if (evt.event === 'account') {
// ...
} else if (evt.event === 'create') {
// ...
} else if (evt.event === 'update') {
// ...
} else if (evt.event === 'delete') {
// ...
}
},
onError: (err) => {
console.error(err)
},
filterCollections: ['com.myexample.app'],
})
firehose.start()
// on service shutdown
await firehose.destroy()
```
--------------------------------
### REPL API Commands
Source: https://github.com/bluesky-social/atproto/blob/main/packages/dev-env/README.md
Provides methods for interacting with the local ATProto development environment. These commands allow developers to start and stop PDS instances, create users, and retrieve service clients.
```APIDOC
REPL API:
status()
List the currently active servers.
startPds(port?: number)
Create a new PDS instance. Data is stored in memory.
Parameters:
port: The port number for the PDS instance (optional).
stop(port: number)
Stop the server at the given port.
Parameters:
port: The port number of the server to stop.
mkuser(handle: string, pdsPort?: number)
Create a new user.
Parameters:
handle: The handle for the new user.
pdsPort: The port of the PDS instance to associate the user with (optional).
user(handle: string): ServiceClient
Get the ServiceClient for the given user.
Parameters:
handle: The handle of the user.
Returns:
ServiceClient: A client object for interacting with the user's PDS.
```
--------------------------------
### Node.js Express Example for Serving Bundled Assets
Source: https://github.com/bluesky-social/atproto/blob/main/packages/internal/rollup-plugin-bundle-manifest/README.md
This JavaScript example shows how to use the generated bundle manifest in a Node.js Express application to serve static assets. It parses the manifest file to dynamically set `Content-Type` and `Content-Length` headers based on asset metadata, and serves the asset content from the manifest's data field.
```javascript
const assetManifest = require('./dist/bundle-manifest.json')
const app = express()
app.use((req, res, next) => {
const asset = assetManifest[req.path.slice(1)]
if (!asset) return next()
res.setHeader('Content-Type', asset.mime)
res.setHeader('Content-Length', asset.data.length)
res.end(Buffer.from(asset.data, 'base64'))
})
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
buildCSP(assetManifest), // Not provided here
)
// Serve the index.html file
res.sendFile('index.html')
})
```
--------------------------------
### Attaching a Profiler (Chrome DevTools)
Source: https://github.com/bluesky-social/atproto/blob/main/packages/README.md
Step-by-step guide to profiling benchmark execution using Chrome DevTools. This involves launching the benchmark in debug mode and connecting via the Chrome inspector.
```APIDOC
Profiler Attachment Guide:
1. **Launch Profiling**: Execute `pnpm bench:profile` to start benchmarks with `--inspect-brk`.
2. **Open Inspector**: Navigate to `about://inspect` in your Chrome browser.
3. **Connect to Process**: Select the Node.js process that corresponds to the running benchmark.
4. **Navigate to Performance Tab**: Switch to the 'Performance' tab within the inspector.
5. **Start Recording**: Press the record button to resume execution and begin profiling.
6. **Wait and Finish**: Allow the benchmarks to complete, then stop the recording.
```
--------------------------------
### Initiate OAuth Flow
Source: https://github.com/bluesky-social/atproto/blob/main/packages/oauth/oauth-client-browser/README.md
Starts the OAuth flow by resolving user information (handle, DID, or PDS URL) and redirecting the user to the OAuth server. Handles potential cancellations or navigation back.
```typescript
try {
await client.signIn('my.handle.com', {
state: 'some value needed later',
prompt: 'none', // Attempt to sign in without user interaction (SSO)
ui_locales: 'fr-CA fr en', // Only supported by some OAuth servers (requires OpenID Connect support + i18n support)
signal: new AbortController().signal, // Optional, allows to cancel the sign in (and destroy the pending authorization, for better security)
})
console.log('Never executed')
} catch (err) {
console.log('The user aborted the authorization process by navigating "back"')
}
```
--------------------------------
### Configure Client Metadata (JSON)
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/OAUTH.md
Defines the client metadata required for the atproto OAuth flow. This JSON file specifies details like the client ID (using the ngrok hostname), name, URIs, and supported grant types. Ensure the `client_id` and URIs are updated with your specific ngrok URL.
```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
}
```
--------------------------------
### AT Protocol URIs: Edge Cases and Behavior
Source: https://github.com/bluesky-social/atproto/blob/main/interop-test-files/syntax/aturi_syntax_invalid.txt
Examples of AT Protocol URIs demonstrating specific behaviors and handling of edge cases, including variations in path components and identifiers.
```APIDOC
AT Protocol URI Examples:
- at://user.bsky.social//
- at://user.bsky.social//com.atproto.feed.post
- at://user.bsky.social/com.atproto.feed.post//
- at://did:plc:asdf123/com.atproto.feed.post/asdf123/more/more'
- at://did:plc:asdf123/short/stuff
- at://did:plc:asdf123/12345
These URIs illustrate specific behaviors and edge case handling within the AT Protocol URI specification.
```
--------------------------------
### AT Protocol URIs: Edge Cases and Behavior
Source: https://github.com/bluesky-social/atproto/blob/main/packages/syntax/tests/interop-files/aturi_syntax_invalid.txt
Examples of AT Protocol URIs demonstrating specific behaviors and handling of edge cases, including variations in path components and identifiers.
```APIDOC
AT Protocol URI Examples:
- at://user.bsky.social//
- at://user.bsky.social//com.atproto.feed.post
- at://user.bsky.social/com.atproto.feed.post//
- at://did:plc:asdf123/com.atproto.feed.post/asdf123/more/more'
- at://did:plc:asdf123/short/stuff
- at://did:plc:asdf123/12345
These URIs illustrate specific behaviors and edge case handling within the AT Protocol URI specification.
```
--------------------------------
### Configure @atproto-labs/rollup-plugin-bundle-manifest
Source: https://github.com/bluesky-social/atproto/blob/main/packages/internal/rollup-plugin-bundle-manifest/README.md
This JavaScript code demonstrates how to integrate the `@atproto-labs/rollup-plugin-bundle-manifest` into a Rollup build configuration file (`rollup.config.js`). It shows the basic setup, including specifying the output manifest file name and an option to embed asset data.
```javascript
// rollup.config.js
import bundleManifest from '@atproto-labs/rollup-plugin-bundle-manifest'
export default {
input: 'src/index.js',
output: {
dir: 'dist',
format: 'es',
},
plugins: [
bundleManifest({
name: 'bundle-manifest.json',
// Optional: should the asset data be embedded (as base64 string) in the manifest?
data: false,
}),
],
}
```
--------------------------------
### Configure Application Labelers
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/docs/moderation.md
Shows how to configure the Bluesky agent with a list of application-specific labelers. This is typically done once during application setup to define default moderation services.
```typescript
BskyAgent.configure({
appLabelers: ['did:web:my-labeler.com'],
})
```
--------------------------------
### Using Moderation UI Contexts for Decisions
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/docs/moderation.md
Example demonstrating how to use the `ui()` method with different contexts to conditionally render or modify content based on moderation flags.
```typescript
const mod = moderatePost(post, moderationOptions)
if (mod.ui('contentList').filter) {
// dont show the post
}
if (mod.ui('contentList').blur) {
// cover the post with the explanation from mod.ui('contentList').blurs[0]
if (mod.ui('contentList').noOverride) {
// dont allow the cover to be removed
}
}
if (mod.ui('contentMedia').blur) {
// 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
}
}
if (mod.ui('avatar').blur) {
// cover the avatar with the explanation from mod.ui('avatar').blurs[0]
if (mod.ui('avatar').noOverride) {
// dont allow the cover to be removed
}
}
for (const alert of mod.ui('contentList').alerts) {
// render this alert
}
for (const inform of mod.ui('contentList').informs) {
// render this inform
}
```
--------------------------------
### JavaScript UI Mock Data Initialization
Source: https://github.com/bluesky-social/atproto/blob/main/packages/oauth/oauth-provider-frontend/account.html
Initializes mock data structures using JavaScript Maps to simulate user accounts, devices, OAuth clients, and session data. This setup is crucial for the UI to function during development without a backend.
```javascript
import { API_ENDPOINT_PREFIX } from '@atproto/oauth-provider-api'
// PDS branding configuration
history.replaceState(history.state, '', '/account')
const devices = new Map([
[
'device1',
{
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
ipAddress: '192.0.0.1',
lastSeenAt: new Date().toISOString(),
},
],
[
'device2',
{
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
ipAddress: '192.0.0.1',
lastSeenAt: '2024-11-26T02:32:15.233Z',
},
],
])
const accounts = new Map([
{
sub: 'did:plc:3jpt2mvvsumj2r7eqk4gzzjz',
email: 'eric@foobar.com',
email_verified: true,
name: 'Eric',
preferred_username: 'esb.lol',
picture: 'https://cdn.bsky.app/img/avatar/plain/did:plc:3jpt2mvvsumj2r7eqk4gzzjz/bafkreiaexnb3bkzbaxktm5q3l3txyweflh3smcruigesvroqjrqxec4zv4@jpeg',
},
{
sub: 'did:plc:dpajgwmnecpdyjyqzjzm6bnb',
email: 'eric@foobar.com',
email_verified: true,
name: 'Tom Sawyeeeeeeeeeee',
preferred_username: 'test.esb.lol',
picture: 'https://cdn.bsky.app/img/avatar/plain/did:plc:dpajgwmnecpdyjyqzjzm6bnb/bafkreia6dx7fhoi6fxwfpgm7jrxijpqci7ap53wpilkpazojwvqlmgud2m@jpeg',
},
{
sub: 'did:plc:matttmattmattmattmattmat',
email: 'matthieu@foobar.com',
email_verified: true,
name: 'Matthieu',
preferred_username: 'matthieu.bsky.test',
picture: /* @type {sting|undefined} */ (undefined),
},
].map((a) => [a.sub, a]))
const clients = new Map([
{
client_id: 'https://bsky.app/oauth-client.json',
client_name: 'Bluesky',
client_uri: 'https://bsky.app',
logo_uri: 'https://web-cdn.bsky.app/static/apple-touch-icon.png',
},
].map((c) => [c.client_id, c]))
// Unable to load metadata for this client: clients.set('https://example.com/oauth-client.json', undefined)
const accountDeviceSessions = new Map([
[
'device1',
[
{
sub: 'did:plc:3jpt2mvvsumj2r7eqk4gzzjz',
remember: true,
loginRequired: true,
},
{
sub: 'did:plc:dpajgwmnecpdyjyqzjzm6bnb',
remember: false,
loginRequired: false,
},
],
],
[
'device2',
[
{
sub: 'did:plc:3jpt2mvvsumj2r7eqk4gzzjz',
remember: true,
loginRequired: false,
},
],
],
])
const accountOAuthSessions = new Map([
[
'did:plc:3jpt2mvvsumj2r7eqk4gzzjz',
[
{
tokenId: 'token1',
createdAt: '2023-10-01T00:00:00.000Z',
updatedAt: '2025-10-01T00:00:00.000Z',
clientId: 'https://bsky.app/oauth-client.json',
scope: 'atproto transition:generic transition:chat.bsky',
},
],
],
[
'did:plc:dpajgwmnecpdyjyqzjzm6bnb',
[
{
tokenId: 'token2',
createdAt: '2023-10-01T00:00:00.000Z',
updatedAt: '2023-10-01T00:00:00.000Z',
clientId: 'https://bsky.app/oauth-client.json',
scope: 'atproto transition:generic transition:email transition:chat.bsky',
},
{
tokenId: 'token3',
createdAt: '2024-08-01T00:00:00.000Z',
updatedAt: '2025-10-01T00:00:00.000Z',
clientId: 'https://example.com/oauth-client.json',
scope: /* @type {string|undefined} */ (undefined),
},
],
],
])
const currentDeviceId = 'device1' // Simulate that this device is "device1"
```
--------------------------------
### AT Protocol URIs: Strict Path Enforcement
Source: https://github.com/bluesky-social/atproto/blob/main/packages/syntax/tests/interop-files/aturi_syntax_invalid.txt
Examples of AT Protocol URIs that adhere to strict path requirements, ensuring specific segments are present and correctly ordered for resource resolution.
```APIDOC
AT Protocol URIs with Strict Paths:
- at://did:plc:asdf123/com.atproto.feed.post/asdf123/asdf
This URI exemplifies the strict path validation rules applied to AT Protocol identifiers.
```
--------------------------------
### AT Protocol URIs: Strict Path Enforcement
Source: https://github.com/bluesky-social/atproto/blob/main/interop-test-files/syntax/aturi_syntax_invalid.txt
Examples of AT Protocol URIs that adhere to strict path requirements, ensuring specific segments are present and correctly ordered for resource resolution.
```APIDOC
AT Protocol URIs with Strict Paths:
- at://did:plc:asdf123/com.atproto.feed.post/asdf123/asdf
This URI exemplifies the strict path validation rules applied to AT Protocol identifiers.
```
--------------------------------
### Moderation Workflow Example
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/docs/moderation.md
Demonstrates a typical workflow for moderating content. It involves fetching user preferences using `agent.getPreferences()` and then passing these preferences, along with the user's DID and custom label definitions, to a moderation function like `moderatePost`.
```typescript
const prefs = await agent.getPreferences()
moderatePost(post, {
userDid: /*...*/,
prefs: prefs.moderationPrefs,
labelDefs: /*...*/
})
```
--------------------------------
### Initiate OAuth Authorization Flow
Source: https://github.com/bluesky-social/atproto/blob/main/packages/oauth/oauth-client/README.md
Starts the OAuth authorization process by generating a URL for the user to visit. This URL includes parameters like the client's DID, state, desired scope, and UI locales. The user must be redirected to this URL to grant consent.
```ts
const url = await client.authorize('foo.bsky.team', {
state: '434321',
prompt: 'consent',
scope: 'email',
ui_locales: 'fr',
})
```
--------------------------------
### Implement ATProto Session Store
Source: https://github.com/bluesky-social/atproto/blob/main/packages/oauth/oauth-client-node/README.md
Provides an example implementation for the `sessionStore` interface, which is crucial for persisting OAuth session data like access tokens and refresh tokens. This typically involves database operations for saving, retrieving, and deleting session information.
```ts
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)
},
}
```
--------------------------------
### Disallowed ATProto URI Paths
Source: https://github.com/bluesky-social/atproto/blob/main/packages/syntax/tests/interop-files/aturi_syntax_invalid.txt
Examples of ATProto URIs that are considered invalid due to the presence of single-dot or double-dot path segments. These patterns are disallowed to maintain security and prevent path traversal issues.
```ATProto
at://did:plc:asdf123/com.atproto.feed.post/.
```
```ATProto
at://did:plc:asdf123/com.atproto.feed.post/..
```
--------------------------------
### Disallowed ATProto URI Paths
Source: https://github.com/bluesky-social/atproto/blob/main/interop-test-files/syntax/aturi_syntax_invalid.txt
Examples of ATProto URIs that are considered invalid due to the presence of single-dot or double-dot path segments. These patterns are disallowed to maintain security and prevent path traversal issues.
```ATProto
at://did:plc:asdf123/com.atproto.feed.post/.
```
```ATProto
at://did:plc:asdf123/com.atproto.feed.post/..
```
--------------------------------
### Docker Compose: Persistent PostgreSQL Service
Source: https://github.com/bluesky-social/atproto/blob/main/packages/dev-infra/README.md
Configures the `db` service in `docker-compose.yaml` for a persistent PostgreSQL database. It runs on port `5432` and uses a Docker volume named `pg_atp_db` for data persistence. To start fresh, the volume must be manually removed.
```docker-compose
docker compose up db -d # start container
docker compose stop db # stop container
docker compose rm db # remove container
docker volume rm pg_atp_db # remove volume
```
--------------------------------
### Fetch Label Definitions and Service Information
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/docs/moderation.md
Demonstrates how to fetch custom label definitions from labeler services. This involves calling `agent.getPreferences()` to get user settings, then `agent.getLabelDefinitions()` using those preferences, and periodically fetching service definitions using `app.bsky.labeler.getService()`.
```typescript
import { AtpAgent } from '@atproto/api'
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)
// To sync definitions periodically:
// const serviceInfo = await agent.getLabelerService('did:plc:1234...') // or getServices for batch
// Cache serviceInfo.policies.labelValueDefinitions
```
--------------------------------
### Docker Compose: Persistent Redis Service
Source: https://github.com/bluesky-social/atproto/blob/main/packages/dev-infra/README.md
Configures the `redis` service in `docker-compose.yaml` for a persistent Redis instance. It runs on port `6379` and uses a Docker volume named `atp_redis` for data persistence. To start fresh, the volume must be manually removed.
```docker-compose
# Configuration is within docker-compose.yaml, commands are standard docker compose commands.
# Example: docker compose up redis -d
# Example: docker volume rm atp_redis
```
--------------------------------
### Parse, Create, and Validate Atproto NSIDs in TypeScript
Source: https://github.com/bluesky-social/atproto/blob/main/packages/syntax/README.md
This example illustrates the usage of the 'NSID' class from '@atproto/syntax' for handling NameSpaced IDs. It shows how to parse an existing NSID string, create a new NSID from components, access its authority and name parts, convert it back to a string, and validate NSID strings using 'NSID.isValid'.
```typescript
import { NSID } from '@atproto/syntax'
const id1 = NSID.parse('com.example.foo')
id1.authority // => 'example.com'
id1.name // => 'foo'
id1.toString() // => 'com.example.foo'
const id2 = NSID.create('example.com', 'foo')
id2.authority // => 'example.com'
id2.name // => 'foo'
id2.toString() // => 'com.example.foo'
const id3 = NSID.create('example.com', 'someRecord')
id3.authority // => 'example.com'
id3.name // => 'someRecord'
id3.toString() // => 'com.example.someRecord'
NSID.isValid('com.example.foo') // => true
NSID.isValid('com.example.someRecord') // => true
NSID.isValid('example.com/foo') // => false
NSID.isValid('foo') // => false
```
--------------------------------
### Show All Commands
Source: https://github.com/bluesky-social/atproto/blob/main/README.md
Command to display all available commands and their descriptions within the project's Makefile.
```shell
make help
```
--------------------------------
### Instantiate XrpcClient with OAuthSession
Source: https://github.com/bluesky-social/atproto/blob/main/packages/oauth/oauth-client/README.md
Demonstrates how to create an XrpcClient instance using an OAuthSession obtained after user sign-in. It includes defining lexicons and authenticating the user.
```ts
import { Lexicons } from '@atproto/lexicon'
import { OAuthClient } from '@atproto/oauth-client' // or "@atproto/oauth-client-browser" or "@atproto/oauth-client-node"
import { XrpcClient } from '@atproto/xrpc'
// Define your lexicons
const myLexicon = new Lexicons([
{
lexicon: 1,
id: 'com.example.query',
defs: {
main: {
// ...
},
},
},
])
// Describe your app's oauth client
const oauthClient = new OAuthClient({
// ...
})
// Authenticate the user
const oauthSession = await oauthClient.restore('did:plc:123')
// Instantiate a client using the `oauthSession` as fetch handler object
const client = new XrpcClient(oauthSession, myLexicon)
// Make authenticated calls
const response = await client.call('com.example.query')
```
--------------------------------
### Initialize and Call XrpcClient
Source: https://github.com/bluesky-social/atproto/blob/main/packages/xrpc/README.md
Demonstrates initializing the XrpcClient with a service URL and Lexicon definitions, then making a call to a defined method. It shows how to access the response encoding and body for the 'io.example.ping' query.
```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'}
```
--------------------------------
### Lexicon CLI Tool Usage
Source: https://github.com/bluesky-social/atproto/blob/main/packages/lex-cli/README.md
Provides the general usage syntax and lists all available commands for the Lexicon CLI tool. It details options for help and version, along with commands for creating new schemas, generating markdown, TypeScript objects, and TypeScript APIs.
```Shell
Usage: lex [options] [command]
Lexicon CLI
Options:
-V, --version output the version number
-h, --help display help for command
Commands:
new [options] [outfile] Create a new schema json file
gen-md Generate markdown documentation
gen-ts-obj Generate a TS file that exports an array of schemas
gen-api Generate a TS client API
gen-server Generate a TS server API
help [command] display help for command
```
--------------------------------
### Run Benchmarks
Source: https://github.com/bluesky-social/atproto/blob/main/packages/README.md
Command to execute all benchmarks within the project. This command is applicable to packages that contain benchmark configurations.
```bash
pnpm bench
```
--------------------------------
### Instantiate BrowserOAuthClient by Loading Metadata
Source: https://github.com/bluesky-social/atproto/blob/main/packages/oauth/oauth-client-browser/README.md
Shows how to instantiate the BrowserOAuthClient by asynchronously loading the client metadata from a specified URL. This approach is flexible but may have performance implications compared to embedding metadata.
```typescript
import { BrowserOAuthClient } from '@atproto/oauth-client-browser'
const client = await BrowserOAuthClient.load({
clientId: 'https://my-app.com/client-metadata.json',
// ... other configuration options
})
```
--------------------------------
### AT Protocol Direct API Calls (TypeScript)
Source: https://github.com/bluesky-social/atproto/blob/main/packages/api/README.md
Demonstrates making direct AT Protocol API calls using reverse-DNS names for methods like `com.atproto.repo.createRecord` and `app.bsky.feed.post.create`. Shows how to interact with collections and specific record types, offering more granular control than convenience wrappers.
```typescript
const res1 = await agent.com.atproto.repo.createRecord({
did: alice.did,
collection: 'app.bsky.feed.post',
record: {
$type: 'app.bsky.feed.post',
text: 'Hello, world!',
createdAt: new Date().toISOString(),
},
})
const res2 = await agent.com.atproto.repo.listRecords({
repo: alice.did,
collection: 'app.bsky.feed.post',
})
const res3 = await agent.app.bsky.feed.post.create(
{ repo: alice.did },
{
text: 'Hello, world!',
createdAt: new Date().toISOString(),
},
)
const res4 = await agent.app.bsky.feed.post.list({ repo: alice.did })
```
--------------------------------
### Profile Benchmarks
Source: https://github.com/bluesky-social/atproto/blob/main/packages/README.md
Command to run benchmarks with Node.js inspector enabled for profiling. Execution pauses until a debugger is attached.
```bash
pnpm bench:profile
```
--------------------------------
### AT Protocol Packages Overview
Source: https://github.com/bluesky-social/atproto/blob/main/README.md
This table lists the primary TypeScript packages available in the AT Protocol reference implementation, along with links to their documentation and NPM registry pages. These packages cover various aspects of the protocol, from client libraries to core schema definitions.
```APIDOC
Package List:
- @atproto/api: Client library for interacting with AT Protocol APIs.
- Docs: [README](./packages/api/README.md)
- NPM: [](https://www.npmjs.com/package/@atproto/api)
- @atproto/common-web: Shared code and helpers runnable in web browsers.
- Docs: [README](./packages/common-web/README.md)
- NPM: [](https://www.npmjs.com/package/@atproto/common-web)
- @atproto/common: Shared code and helpers not compatible with web browsers.
- Docs: [README](./packages/common/README.md)
- NPM: [](https://www.npmjs.com/package/@atproto/common)
- @atproto/crypto: Cryptographic signing and key serialization utilities.
- Docs: [README](./packages/crypto/README.md)
- NPM: [](https://www.npmjs.com/package/@atproto/crypto)
- @atproto/identity: DID and handle resolution services.
- Docs: [README](./packages/identity/README.md)
- NPM: [](https://www.npmjs.com/package/@atproto/identity)
- @atproto/lexicon: Schema definition language for AT Protocol.
- Docs: [README](./packages/lexicon/README.md)
- NPM: [](https://www.npmjs.com/package/@atproto/lexicon)
- @atproto/repo: Data storage structure, including MST (Merkle Signature Tree).
- Docs: [README](./packages/repo/README.md)
- NPM: [](https://www.npmjs.com/package/@atproto/repo)
- @atproto/syntax: String parsers for AT Protocol identifiers.
- Docs: [README](./packages/syntax/README.md)
- NPM: [](https://www.npmjs.com/package/@atproto/syntax)
- @atproto/xrpc: Client-side HTTP API helpers for XRPC.
- Docs: [README](./packages/xrpc/README.md)
- NPM: [](https://www.npmjs.com/package/@atproto/xrpc)
- @atproto/xrpc-server: Server-side HTTP API helpers for XRPC.
- Docs: [README](./packages/xrpc-server/README.md)
- NPM: [](https://www.npmjs.com/package/@atproto/xrpc-server)
```
--------------------------------
### Silent Sign-In and Callback Handling
Source: https://github.com/bluesky-social/atproto/blob/main/packages/oauth/oauth-client/README.md
Demonstrates creating a login URL for silent sign-in and handling the callback endpoint, including retrying failed silent sign-in attempts by removing the 'prompt=none' parameter.
```TypeScript
async function createLoginUrl(handle: string, state?: string): string {
return client.authorize(handle, {
state,
// Use "prompt=none" to attempt silent sign-in
prompt: 'none',
})
}
async function handleCallback(params: URLSearchParams) {
try {
return await client.callback(params)
} catch (err) {
// Silent sign-in failed, retry without prompt=none
if (
err instanceof OAuthCallbackError &&
['login_required', 'consent_required'].includes(err.params.get('error'))
) {
// Do *not* use prompt=none when retrying (to avoid infinite redirects)
const url = await client.authorize(handle, { state: err.state })
// Allow calling code to catch the error and redirect the user to the new URL
return new MyLoginRequiredError(url)
}
throw err
}
}
```
--------------------------------
### AT Protocol Services Overview
Source: https://github.com/bluesky-social/atproto/blob/main/README.md
Details on the core TypeScript services implementing the AT Protocol. These include the Personal Data Server (PDS) for hosting user data and the AppView (bsky) for handling microblogging application logic.
```APIDOC
TypeScript Services:
- pds (Personal Data Server):
- Hosts repo content for AT Protocol accounts.
- Primary implementation code is in `packages/pds`.
- Runtime wrapper is in `services/pds`.
- For self-hosting directions, see [bluesky-social/pds](https://github.com/bluesky-social/pds).
- bsky (AppView):
- Implements the `app.bsky.*` API endpoints for the microblogging application.
- Runs on the main network at `api.bsky.app`.
- Primary implementation code is in `packages/bsky`.
- Runtime wrapper is in `services/bsky`.
```
--------------------------------
### Configure Node OAuth Client and Expose Metadata
Source: https://github.com/bluesky-social/atproto/blob/main/packages/oauth/oauth-client-node/README.md
Demonstrates initializing the NodeOAuthClient with application metadata, keys, and storage interfaces. It also shows how to set up Express routes to expose client metadata and JWKS for the OAuth server.
```typescript
import { NodeOAuthClient, Session } from '@atproto/oauth-client-node'
import { JoseKey } from '@atproto/jwk-jose'
import express from 'express'
import { Agent } from '@atproto/api'
// Assume requestLock and NodeSavedState are defined elsewhere
// const requestLock = ...
// type NodeSavedState = { ... }
const client = new NodeOAuthClient({
// This object will be used to build the payload of the /client-metadata.json
// endpoint metadata, exposing the client metadata to the OAuth server.
clientMetadata: {
// Must be a URL that will be exposing this metadata
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/callback'],
grant_types: ['authorization_code', 'refresh_token'],
scope: 'atproto transition:generic',
response_types: ['code'],
application_type: 'web',
token_endpoint_auth_method: 'private_key_jwt',
token_endpoint_auth_signing_alg: 'RS256',
dpop_bound_access_tokens: true,
jwks_uri: 'https://my-app.com/jwks.json',
},
// Used to authenticate the client to the token endpoint. Will be used to
// build the jwks object to be exposed on the "jwks_uri" endpoint.
keyset: await Promise.all([
JoseKey.fromImportable(process.env.PRIVATE_KEY_1, 'key1'),
JoseKey.fromImportable(process.env.PRIVATE_KEY_2, 'key2'),
JoseKey.fromImportable(process.env.PRIVATE_KEY_3, 'key3'),
]),
// Interface to store authorization state data (during authorization flows)
stateStore: {
async set(key: string, internalState: any): Promise { /* ... */ },
async get(key: string): Promise { /* ... */ },
async del(key: string): Promise { /* ... */ },
},
// Interface to store authenticated session data
sessionStore: {
async set(sub: string, session: Session): Promise { /* ... */ },
async get(sub: string): Promise { /* ... */ },
async del(sub: string): Promise { /* ... */ },
},
// A lock to prevent concurrent access to the session store. Optional if only one instance is running.
requestLock,
})
const app = express()
// Expose the metadata and jwks
app.get('/client-metadata.json', (req, res) => res.json(client.clientMetadata))
app.get('/jwks.json', (req, res) => res.json(client.jwks))
```