Skip to content

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 }); } };
}