chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import {
CID,
cborEncodeDagCbor,
cidToString,
createBlobCid,
createCid,
} from '@pdsjs/core/repo';
import { beforeEach, describe, expect, it } from 'vitest';
import { createSiteHandler } from '../src/handler.js';
import { clearManifestCache } from '../src/manifest.js';
const DID = 'did:plc:handlertest';
const DOMAIN = 'example.com';
/**
* Build a handler over an in-memory site. Returns the handler plus call
* counters so tests can assert which storage reads happened.
* @param {Object} [opts]
* @param {Record<string, {content: string, mimeType?: string, contentType?: string, cacheControl?: string, originTemplate?: boolean}>} [opts.files]
* @param {string} [opts.fallback]
* @param {string} [opts.site]
*/
async function setup(opts = {}) {
const siteName = opts.site || 'home';
/** @type {Map<string, {data: Uint8Array, mimeType: string}>} */
const blobStore = new Map();
const fileEntries = [];
const cids = /** @type {Record<string, string>} */ ({});
for (const [path, spec] of Object.entries(opts.files || {})) {
const data = new TextEncoder().encode(spec.content);
const cidBytes = await createBlobCid(data);
const cid = cidToString(cidBytes);
cids[path] = cid;
const mimeType = spec.mimeType ?? 'application/octet-stream';
blobStore.set(cid, { data, mimeType });
const entry = /** @type {Record<string, unknown>} */ ({
path,
blob: {
$type: 'blob',
ref: new CID(cidBytes),
mimeType,
size: data.length,
},
});
if (spec.contentType) entry.contentType = spec.contentType;
if (spec.cacheControl) entry.cacheControl = spec.cacheControl;
if (spec.originTemplate) entry.originTemplate = true;
fileEntries.push(entry);
}
const record = /** @type {Record<string, unknown>} */ ({
$type: 'dev.pdsjs.site.deploy',
files: fileEntries,
createdAt: '2026-08-06T00:00:00.000Z',
});
if (opts.fallback) record.fallback = opts.fallback;
const bytes = cborEncodeDagCbor(record);
const recordCid = cidToString(await createCid(bytes));
const calls = { getRecord: 0, blobGet: 0 };
const handler = createSiteHandler({
actorStorage: /** @type {any} */ ({
getRecord: async (/** @type {string} */ uri) => {
calls.getRecord++;
return uri === `at://${DID}/dev.pdsjs.site.deploy/${siteName}`
? { cid: recordCid, value: bytes }
: null;
},
}),
blobs: /** @type {any} */ ({
get: async (/** @type {string} */ _did, /** @type {string} */ cid) => {
calls.blobGet++;
return blobStore.get(cid) || null;
},
}),
getDid: async () => DID,
siteDomain: DOMAIN,
});
/** @param {string} urlStr @param {RequestInit} [init] */
const fetch = (urlStr, init) => {
const request = new Request(urlStr, init);
return handler(request, new URL(urlStr));
};
return { fetch, calls, cids };
}
beforeEach(() => clearManifestCache());
describe('createSiteHandler', () => {
it('throws on missing ctx entries', () => {
expect(() => createSiteHandler(/** @type {any} */ ({}))).toThrow(
/actorStorage/,
);
});
it('returns null for hostnames outside the site domain', async () => {
const { fetch } = await setup({
files: { 'index.html': { content: 'hi', mimeType: 'text/html' } },
});
expect(await fetch('https://pds.other.dev/index.html')).toBeNull();
expect(await fetch('https://other.org/')).toBeNull();
});
it('serves the apex site at the apex and by subdomain rkey', async () => {
const { fetch } = await setup({
files: {
'index.html': { content: '<h1>hi</h1>', mimeType: 'text/html' },
},
});
const apex = await fetch('https://example.com/');
expect(apex?.status).toBe(200);
expect(await apex?.text()).toBe('<h1>hi</h1>');
expect(apex?.headers.get('content-type')).toBe('text/html');
const sub = await fetch('https://home.example.com/index.html');
expect(sub?.status).toBe(200);
});
it('404s an unknown site and multi-label subdomains', async () => {
const { fetch } = await setup({
files: { 'index.html': { content: 'hi', mimeType: 'text/html' } },
});
expect((await fetch('https://nope.example.com/'))?.status).toBe(404);
expect((await fetch('https://a.b.example.com/'))?.status).toBe(404);
});
it('redirects www to the apex when no www site is deployed', async () => {
const { fetch } = await setup({
files: { 'index.html': { content: 'hi', mimeType: 'text/html' } },
});
const response = await fetch('https://www.example.com/a/b?q=1');
expect(response?.status).toBe(301);
expect(response?.headers.get('location')).toBe(
'https://example.com/a/b?q=1',
);
});
it('serves a deployed www site instead of redirecting', async () => {
const { fetch } = await setup({
site: 'www',
files: {
'index.html': { content: 'the www site', mimeType: 'text/html' },
},
});
const response = await fetch('https://www.example.com/');
expect(response?.status).toBe(200);
expect(await response?.text()).toBe('the www site');
});
it('fills the origin token in an origin-templated file', async () => {
const { fetch } = await setup({
files: {
'client-metadata.json': {
content:
'{"client_id":"__PDSJS_ORIGIN__/client-metadata.json","redirect_uris":["__PDSJS_ORIGIN__/callback"]}',
mimeType: 'application/json',
originTemplate: true,
},
},
});
const res = await fetch(`https://${DOMAIN}/client-metadata.json`);
expect(res?.status).toBe(200);
// The blob cid no longer names the transformed bytes.
expect(res?.headers.get('ETag')).toBeNull();
const body = await res?.json();
expect(body.client_id).toBe(`https://${DOMAIN}/client-metadata.json`);
expect(body.redirect_uris).toEqual([`https://${DOMAIN}/callback`]);
const head = await fetch(`https://${DOMAIN}/client-metadata.json`, {
method: 'HEAD',
});
expect(Number(head?.headers.get('Content-Length'))).toBeGreaterThan(0);
});
it('answers handle verification on site hostnames', async () => {
const { fetch } = await setup({ files: {} });
const response = await fetch('https://example.com/.well-known/atproto-did');
expect(response?.status).toBe(200);
expect(await response?.text()).toBe(DID);
// A browser app reads this from the handle's own hostname.
expect(response?.headers.get('access-control-allow-origin')).toBe('*');
});
it('answers a preflight for handle verification', async () => {
const { fetch } = await setup({ files: {} });
const response = await fetch(
'https://example.com/.well-known/atproto-did',
{ method: 'OPTIONS' },
);
expect(response?.status).toBe(204);
expect(response?.headers.get('access-control-allow-origin')).toBe('*');
});
it('405s non-GET/HEAD including OPTIONS, with no CORS headers', async () => {
const { fetch } = await setup({
files: { 'index.html': { content: 'hi', mimeType: 'text/html' } },
});
for (const method of ['POST', 'PUT', 'DELETE', 'OPTIONS']) {
const response = await fetch('https://example.com/', { method });
expect(response?.status).toBe(405);
expect(response?.headers.get('allow')).toBe('GET, HEAD');
expect(response?.headers.get('access-control-allow-origin')).toBeNull();
}
});
it('rejects traversal and undecodable paths', async () => {
const { fetch } = await setup({
files: { 'index.html': { content: 'hi', mimeType: 'text/html' } },
});
// Dot segments, literal or percent-encoded, never reach the handler:
// WHATWG URL parsing collapses both, so these are ordinary misses. The
// resolvePath guard behind them is defense-in-depth for non-URL callers.
expect((await fetch('https://example.com/../secrets'))?.status).toBe(404);
expect((await fetch('https://example.com/a/%2e%2e/b'))?.status).toBe(404);
expect((await fetch('https://example.com/%zz'))?.status).toBe(400);
expect((await fetch('https://example.com/a%5cb'))?.status).toBe(400);
});
it('serves directory indexes and redirects slashless directories', async () => {
const { fetch } = await setup({
files: {
'index.html': { content: 'root', mimeType: 'text/html' },
'docs/index.html': { content: 'docs', mimeType: 'text/html' },
},
});
expect(await (await fetch('https://example.com/docs/'))?.text()).toBe(
'docs',
);
const redirect = await fetch('https://example.com/docs?q=1');
expect(redirect?.status).toBe(301);
expect(redirect?.headers.get('location')).toBe('/docs/?q=1');
});
it('serves the SPA fallback with status 200', async () => {
const { fetch } = await setup({
files: { 'index.html': { content: 'app', mimeType: 'text/html' } },
fallback: 'index.html',
});
const response = await fetch('https://example.com/some/deep/route');
expect(response?.status).toBe(200);
expect(await response?.text()).toBe('app');
});
it('404s unmatched paths without a fallback', async () => {
const { fetch } = await setup({
files: { 'index.html': { content: 'hi', mimeType: 'text/html' } },
});
const response = await fetch('https://example.com/missing.png');
expect(response?.status).toBe(404);
expect(response?.headers.get('cache-control')).toBe('no-store');
});
it('serves files with an open CORS origin', async () => {
const { fetch } = await setup({
files: { 'index.html': { content: 'hi', mimeType: 'text/html' } },
});
const response = await fetch('https://example.com/');
expect(response?.headers.get('access-control-allow-origin')).toBe('*');
});
it('sends a strong ETag and answers If-None-Match with a blobless 304', async () => {
const { fetch, calls, cids } = await setup({
files: { 'index.html': { content: 'hi', mimeType: 'text/html' } },
});
const first = await fetch('https://example.com/');
const etag = first?.headers.get('etag');
expect(etag).toBe(`"${cids['index.html']}"`);
expect(calls.blobGet).toBe(1);
const conditional = await fetch('https://example.com/', {
headers: { 'If-None-Match': /** @type {string} */ (etag) },
});
expect(conditional?.status).toBe(304);
expect(conditional?.headers.get('etag')).toBe(etag);
expect(calls.blobGet).toBe(1);
const weak = await fetch('https://example.com/', {
headers: { 'If-None-Match': `W/${etag}, "other"` },
});
expect(weak?.status).toBe(304);
expect(calls.blobGet).toBe(1);
});
it('answers HEAD from the manifest without reading the blob', async () => {
const { fetch, calls } = await setup({
files: { 'index.html': { content: 'hello', mimeType: 'text/html' } },
});
const response = await fetch('https://example.com/', { method: 'HEAD' });
expect(response?.status).toBe(200);
expect(response?.headers.get('content-length')).toBe('5');
expect(await response?.text()).toBe('');
expect(calls.blobGet).toBe(0);
});
it('applies MIME precedence: manifest override, stored mime, extension map', async () => {
const { fetch } = await setup({
files: {
'styled.css': {
content: 'a{}',
mimeType: 'text/css',
contentType: 'text/css; charset=iso-8859-1',
},
'plain.css': { content: 'b{}', mimeType: 'text/css' },
'sniffed.js': {
content: 'let x',
mimeType: 'application/octet-stream',
},
'mystery.bin': { content: '?', mimeType: 'application/octet-stream' },
},
});
const styled = await fetch('https://example.com/styled.css');
expect(styled?.headers.get('content-type')).toBe(
'text/css; charset=iso-8859-1',
);
const plain = await fetch('https://example.com/plain.css');
expect(plain?.headers.get('content-type')).toBe('text/css');
const sniffed = await fetch('https://example.com/sniffed.js');
expect(sniffed?.headers.get('content-type')).toBe(
'text/javascript; charset=utf-8',
);
const mystery = await fetch('https://example.com/mystery.bin');
expect(mystery?.headers.get('content-type')).toBe(
'application/octet-stream',
);
});
it('applies cacheControl overrides and the revalidating default', async () => {
const { fetch } = await setup({
files: {
'app.hash123.js': {
content: 'x',
mimeType: 'text/javascript',
cacheControl: 'public, max-age=31536000, immutable',
},
'index.html': { content: 'hi', mimeType: 'text/html' },
},
});
const hashed = await fetch('https://example.com/app.hash123.js');
expect(hashed?.headers.get('cache-control')).toBe(
'public, max-age=31536000, immutable',
);
const index = await fetch('https://example.com/');
expect(index?.headers.get('cache-control')).toBe(
'public, max-age=60, must-revalidate',
);
expect(index?.headers.get('x-content-type-options')).toBe('nosniff');
expect(index?.headers.get('strict-transport-security')).toBe(
'max-age=31536000',
);
expect(index?.headers.get('content-security-policy')).toBeNull();
expect(index?.headers.get('content-disposition')).toBeNull();
});
it('500s in plain text when serving throws', async () => {
const handler = createSiteHandler({
actorStorage: /** @type {any} */ ({
getRecord: async () => {
throw new Error('storage exploded');
},
}),
blobs: /** @type {any} */ ({ get: async () => null }),
getDid: async () => DID,
siteDomain: DOMAIN,
});
const url = 'https://example.com/';
const response = await handler(new Request(url), new URL(url));
expect(response?.status).toBe(500);
expect(response?.headers.get('content-type')).toContain('text/plain');
});
});
describe('repo blobs by CID', () => {
it('serves media blobs under their stored mime, immutable and sandboxed', async () => {
const { fetch, cids } = await setup({
files: { 'photo.webp': { content: 'img-bytes', mimeType: 'image/webp' } },
});
const response = await fetch(
`https://example.com/.blobs/${cids['photo.webp']}`,
);
expect(response?.status).toBe(200);
expect(response?.headers.get('content-type')).toBe('image/webp');
expect(response?.headers.get('cache-control')).toBe(
'public, max-age=31536000, immutable',
);
expect(response?.headers.get('content-security-policy')).toContain(
'sandbox',
);
expect(await response?.text()).toBe('img-bytes');
});
it('forces non-media mimes to octet-stream', async () => {
const { fetch, cids } = await setup({
files: {
'page.html': { content: '<script>1</script>', mimeType: 'text/html' },
},
});
const response = await fetch(
`https://example.com/.blobs/${cids['page.html']}`,
);
expect(response?.headers.get('content-type')).toBe(
'application/octet-stream',
);
});
it('answers If-None-Match with 304 and no blob read', async () => {
const { fetch, calls, cids } = await setup({
files: { 'photo.webp': { content: 'img', mimeType: 'image/webp' } },
});
const cid = cids['photo.webp'];
const response = await fetch(`https://example.com/.blobs/${cid}`, {
headers: { 'If-None-Match': `"${cid}"` },
});
expect(response?.status).toBe(304);
expect(response?.headers.get('content-type')).toBeNull();
expect(calls.blobGet).toBe(0);
});
it('404s an unknown or malformed CID', async () => {
const { fetch } = await setup({
files: { 'photo.webp': { content: 'img', mimeType: 'image/webp' } },
});
const missing = await fetch(
'https://example.com/.blobs/bafkreiaaaaaaaaaaaaaaaaaaaaaaaa',
);
expect(missing?.status).toBe(404);
const malformed = await fetch('https://example.com/.blobs/NOT-A-CID');
expect(malformed?.status).toBe(404);
});
});