Reference source · 0.1.0-dev.11
https-server.ts
src/reference/https-server.ts · Download original file
import { createServer } from 'node:https';
import type { Server } from 'node:https';
import { readFileSync, mkdtempSync, rmSync, chmodSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { spawnSync } from 'node:child_process';
import type { ReferenceHost } from '../host.ts';
import type { Envelope } from '../envelope.ts';
import { parseObject, requireThat } from '../core.ts';
export async function startDemoServer(host: Pick<ReferenceHost, 'handle'>, clock: () => number, directory?: (resource: string, endpoint: string) => Record<string, any>): Promise<{ endpoint: string; ca: string; close: () => Promise<void> }> {
const dir = mkdtempSync(join(tmpdir(), 'oprp-tls-'));
const key = join(dir, 'key.pem'), cert = join(dir, 'cert.pem');
const generated = spawnSync('openssl', ['req', '-x509', '-newkey', 'rsa:2048', '-nodes', '-keyout', key, '-out', cert, '-days', '1', '-subj', '/CN=localhost', '-addext', 'subjectAltName=DNS:localhost,IP:127.0.0.1'], { stdio: 'pipe' });
if (generated.status !== 0) { rmSync(dir, { recursive: true, force: true }); throw new Error('Development TLS certificate generation failed'); }
chmodSync(key, 0o600);
const ca = readFileSync(cert, 'utf8');
const server: Server = createServer({ key: readFileSync(key), cert: ca, minVersion: 'TLSv1.3' }, async (request, response) => {
if (request.method === 'GET' && directory) {
const url = new URL(request.url ?? '/', 'https://localhost');
if (url.pathname === '/.well-known/webfinger') {
try {
const address = server.address(); requireThat(address && typeof address !== 'string');
const document = directory(url.searchParams.get('resource') ?? '', `https://127.0.0.1:${address.port}/oprp/exchange`);
response.writeHead(200, { 'content-type': 'application/jrd+json', 'cache-control': 'no-store' }); response.end(JSON.stringify(document));
} catch { response.writeHead(404); response.end(); }
return;
}
}
if (request.method !== 'POST' || request.url !== '/oprp/exchange' || request.headers['content-type'] !== 'application/oprp+json') { response.writeHead(404); response.end(); return; }
try {
const chunks: Buffer[] = []; let length = 0;
for await (const chunk of request) {
length += chunk.length; requireThat(length <= 1600000, 'message_too_large'); chunks.push(Buffer.from(chunk));
}
const result = host.handle(parseObject(Buffer.concat(chunks)) as Envelope, clock());
response.writeHead(200, { 'content-type': 'application/oprp+json', 'cache-control': 'no-store' });
response.end(JSON.stringify(result));
} catch { response.writeHead(400, { 'cache-control': 'no-store' }); response.end(); }
});
server.requestTimeout = 10000; server.headersTimeout = 10000;
try { await new Promise<void>((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); }); }
catch (e) { rmSync(dir, { recursive: true, force: true }); throw e; }
const address = server.address(); requireThat(address && typeof address !== 'string');
return { endpoint: `https://127.0.0.1:${address.port}/oprp/exchange`, ca,
close: async () => { await new Promise<void>((resolve, reject) => server.close(e => e ? reject(e) : resolve())); rmSync(dir, { recursive: true, force: true }); } };
}