@open-draft/test-server
Version:
HTTP/HTTPS testing server for your tests.
88 lines • 3.5 kB
JavaScript
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