UNPKG

@open-draft/test-server

Version:

HTTP/HTTPS testing server for your tests.

88 lines 3.5 kB
import { WebSocketServer } from 'ws'; import {} from '@hono/node-server'; import { DeferredPromise } from '@open-draft/deferred-promise'; import { kServer, getServerUrl, kServers, kEmitter, toRelativePathname, } from './createTestHttpServer.js'; import { randomUUID } from 'node:crypto'; function buildWebSocketApi(protocol, server, pathname) { const baseUrl = new URL(toRelativePathname(pathname), getServerUrl(protocol, server)); // Always set the protocol to the WebSocket protocol // to save the upgrade roundtrip on requests. baseUrl.protocol = baseUrl.protocol.replace('http', 'ws'); return { url() { return baseUrl; }, }; } export function createWebSocketMiddleware(options) { const emitter = Reflect.get(options.server, kEmitter); const pathname = options.pathname ?? `/ws/${randomUUID()}`; const wss = new WebSocketServer({ noServer: true, path: pathname, }); const handleUpgrade = (request, socket, head) => { if (request.url !== pathname) { return; } wss.handleUpgrade(request, socket, head, (ws) => { wss.emit('connection', ws, request); }); }; const subscriptions = []; const addUpgradeListener = (server) => { server.on('upgrade', handleUpgrade); // Store the removal of the event listeners so it can be replayed // during the cleanup without having to reference the `server` again. subscriptions.push(() => server.removeListener('upgrade', handleUpgrade)); }; // First, see if the test server already has server instances running. // E.g. when calling this middleware inside of a test. const servers = Reflect.get(options.server, kServers); servers.forEach((server) => { addUpgradeListener(server); }); // Add a listener to whenever the test server starts listening. // This is handy when adding this middleware to a test server in the setup phase. emitter.on('listen', (server) => { addUpgradeListener(server); }); return { async [Symbol.asyncDispose]() { await this.close(); }, on: wss.on.bind(wss), once: wss.once.bind(wss), off: wss.off.bind(wss), get ws() { const server = Reflect.get(options.server.http, kServer); return buildWebSocketApi('http', server, pathname); }, get wss() { const server = Reflect.get(options.server.https, kServer); return buildWebSocketApi('https', server, pathname); }, async close() { let effect; while ((effect = subscriptions.pop())) { effect(); } if (wss.clients.size === 0) { return; } const pendingClientClosures = []; wss.clients.forEach((client) => { client.close(); const clientClosedPromise = new DeferredPromise(); pendingClientClosures.push(clientClosedPromise); client.once('close', clientClosedPromise.resolve); }); await Promise.all(pendingClientClosures); /** * @note I don't think `wss.close()` is necessary since we want to * keep the actual HTTP server running even if the WebSocket middleware is disposed of. */ }, }; } //# sourceMappingURL=createTestWebSocketServer.js.map