~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/atlibs

0
0
main
106 lines · 7.0 KB Raw History
// A local fixture registry for the atlibs CLI (no network): a fake PLC, handle resolver and PDS
// (node:http) for alice.test. publish(name) puts examples/rust/<name>'s `cargo wasm` build, its
// module.json and its records/<session>.json as alice.test's records. It also answers an http
// composition handler (POST /handler), app-password sign-in with refresh-token rotation (auth counts
// the calls), and the writes `atlibs publish` makes (listRecords, putRecord/createRecord, uploadBlob).
// `env` points the CLI at it: ATLIBS_PLC_URL, ATLIBS_HANDLE_RESOLVER, ATLIBS_ALLOW_PRIVATE.
//
//   const registry = await fixtureRegistry();
//   await registry.publish("compose");
//   // atlibs add alice.test/compose, with registry.env
//   registry.close();
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import { createServer } from "node:http";
import { modulePin } from "../../javascript/src/run-module/index.js";

const ROOT = new URL("../../", import.meta.url);
export const DID = "did:plc:alicetest";
const sha256 = (bytes) => createHash("sha256").update(bytes).digest();
// CIDv1 base32: raw (blobs) or dag-cbor (records; any stable hash will do for a fake PDS)
export function cid(codec, bytes) {
	const raw = [0x01, codec, 0x12, 0x20, ...sha256(bytes)];
	let bits = 0, value = 0, out = "b";
	for (const byte of raw) {
		value = (value << 8) | byte;
		bits += 8;
		while (bits >= 5) out += "abcdefghijklmnopqrstuvwxyz234567"[(value >>> (bits -= 5)) & 31];
	}
	return bits ? out + "abcdefghijklmnopqrstuvwxyz234567"[(value << (5 - bits)) & 31] : out;
}

export async function fixtureRegistry({ port = 0 } = {}) {
	const records = new Map(); // "collection/rkey" -> { cid, value }
	const blobs = new Map();
	const auth = { refreshes: 0, creates: 0 };
	const seen = []; // the requests' paths
	let base;
	const put = (collection, rkey, value) => {
		const entry = { cid: cid(0x71, Buffer.from(JSON.stringify(value))), value };
		records.set(`${collection}/${rkey}`, entry);
		return { uri: `at://${DID}/${collection}/${rkey}`, cid: entry.cid };
	};
	async function publish(name) {
		const wasm = await readFile(new URL(`target/wasm32-unknown-unknown/release/${name.replaceAll("-", "_")}.wasm`, ROOT));
		const spec = JSON.parse(await readFile(new URL(`examples/rust/${name}/module.json`, ROOT), "utf8"));
		const blob = cid(0x55, wasm);
		blobs.set(blob, wasm);
		const session = spec.session && put("com.fedfork.exec.oauth.clientMetadata", spec.session, JSON.parse(await readFile(new URL(`records/${spec.session}.json`, ROOT), "utf8")));
		const record = { $type: "com.fedfork.exec.wasm.module", ...spec, ...(session && { session }), module: { $type: "blob", ref: { $link: blob }, mimeType: "application/wasm", size: wasm.length }, abi: "emdash-wasm-json-v1", actions: spec.actions ?? [], createdAt: "2026-11-08T00:00:00.000Z" };
		// as `atlibs publish` writes it: at its name and at its version (<name>.<32 hex of its pin>)
		put("com.fedfork.exec.wasm.module", `${name}.${(await modulePin({ ...spec, module: wasm })).slice(0, 32)}`, record);
		put("com.fedfork.exec.wasm.module", name, record);
		return { wasm, blob };
	}

	const server = createServer((req, res) => {
		const url = new URL(req.url, base);
		seen.push(url.pathname);
		const json = (status, body) => res.writeHead(status, { "content-type": "application/json" }).end(JSON.stringify(body));
		if (url.pathname === `/plc/${DID}`) return json(200, { id: DID, alsoKnownAs: ["at://alice.test"], service: [{ id: "#atproto_pds", type: "AtprotoPersonalDataServer", serviceEndpoint: base }] });
		if (url.pathname === "/xrpc/com.atproto.identity.resolveHandle") return url.searchParams.get("handle") === "alice.test" ? json(200, { did: DID }) : json(400, { error: "InvalidRequest" });
		if (url.pathname === "/xrpc/com.atproto.sync.getBlob") return blobs.has(url.searchParams.get("cid")) ? res.end(blobs.get(url.searchParams.get("cid"))) : json(404, { error: "BlobNotFound" });
		if (url.pathname === "/xrpc/com.atproto.repo.getRecord") {
			const found = records.get(`${url.searchParams.get("collection")}/${url.searchParams.get("rkey")}`);
			return found ? json(200, { uri: `at://${DID}/${url.searchParams.get("collection")}/${url.searchParams.get("rkey")}`, ...found }) : json(400, { error: "RecordNotFound", message: "Could not locate record" });
		}
		// an http handler (atlibs run --handler confirm=http://127.0.0.1/handler): answers with what it got
		if (url.pathname === "/handler" && req.method === "POST") {
			let body = "";
			req.on("data", (chunk) => (body += chunk));
			return req.on("end", () => json(200, { approved: true, got: JSON.parse(body), auth: req.headers.authorization ?? null }));
		}
		// sign-in (the shared-session test): each refresh token is taken once (rotation), a1 is expired, then invalid
		const late = (status, body) => setTimeout(() => json(status, body), 10); // concurrent runs overlap
		const bearer = req.headers.authorization ?? "";
		const didDoc = { service: [{ id: "#atproto_pds", serviceEndpoint: base }] };
		if (url.pathname === "/xrpc/com.atproto.server.createSession") return auth.creates++, late(200, { did: DID, handle: "alice.test", accessJwt: "a9", refreshJwt: "r9", didDoc });
		if (url.pathname === "/xrpc/com.atproto.server.refreshSession")
			return ++auth.refreshes === 1 && bearer === "Bearer r1" ? late(200, { did: DID, handle: "alice.test", accessJwt: "a2", refreshJwt: "r2", didDoc }) : late(400, { error: "ExpiredToken", message: "refresh token already used" });
		if (url.pathname === "/xrpc/com.atproto.repo.deleteRecord") return bearer === "Bearer a1" ? (auth.refreshes ? late(401, { error: "InvalidToken" }) : late(400, { error: "ExpiredToken" })) : late(200, {});
		// publishing (the `atlibs publish` test): listRecords, putRecord/createRecord, uploadBlob
		if (url.pathname === "/xrpc/com.atproto.repo.listRecords") {
			const prefix = `${url.searchParams.get("collection")}/`;
			return json(200, { records: [...records].filter(([key]) => key.startsWith(prefix)).map(([key, r]) => ({ uri: `at://${DID}/${key}`, ...r })) });
		}
		if (req.method === "POST" && /^\/xrpc\/com\.atproto\.repo\.(put|create)Record$/.test(url.pathname) || url.pathname === "/xrpc/com.atproto.repo.uploadBlob") {
			const chunks = [];
			req.on("data", (chunk) => chunks.push(chunk));
			return req.on("end", () => {
				const body = Buffer.concat(chunks);
				if (url.pathname.endsWith("uploadBlob")) {
					blobs.set(cid(0x55, body), body);
					return json(200, { blob: { $type: "blob", ref: { $link: cid(0x55, body) }, mimeType: req.headers["content-type"], size: body.length } });
				}
				const { collection, rkey, record } = JSON.parse(body);
				json(200, put(collection, rkey, record));
			});
		}
		json(501, { error: "MethodNotImplemented" });
	});
	await new Promise((resolve) => server.listen(port, "127.0.0.1", resolve));
	base = `http://127.0.0.1:${server.address().port}`;
	const env = { ATLIBS_PLC_URL: `${base}/plc`, ATLIBS_HANDLE_RESOLVER: base, ATLIBS_ALLOW_PRIVATE: "1" };
	return { base, env, records, blobs, auth, seen, put, publish, close: () => server.close() };
}