Skip to content

Instantly share code, notes, and snippets.

@JesseTG
Last active August 13, 2025 19:50
Show Gist options
  • Select an option

  • Save JesseTG/f6c1b9f836e90117d49c76d629d9d0a1 to your computer and use it in GitHub Desktop.

Select an option

Save JesseTG/f6c1b9f836e90117d49c76d629d9d0a1 to your computer and use it in GitHub Desktop.
Fastify Websockets Issue
// Clone this Gist repo, install the packages with `npm install`, then run the test with `node --test`. The test should hang.
import { it, afterEach, describe, beforeEach } from 'node:test';
import Fastify from 'fastify';
import fastifyPlugin from 'fastify-plugin';
import fastifyWebSocket from '@fastify/websocket';
import WebSocket from 'ws';
/** @param {FastifyInstance} fastify */
async function plugin(fastify) {
fastify.addHook('onClose', async (instance) => {
for (const client of instance.websocketServer.clients) {
client.close(WebSocketStatus.GOING_AWAY);
}
});
await fastify.register(fastifyWebSocket, {
options: {
clientTracking: true
}
});
fastify.get('/signal', {
websocket: true,
preHandler: [ /* some handlers for authentication and validation (can post if relevant) */ ],
}, async (socket, request) => {
socket.on('message', async (data) => {
try {
console.log("Got a message");
const message = JSON.parse(data.toString());
console.log(message);
} catch (error) {
if (error instanceof SyntaxError) {
// If the message isn't valid JSON...
socket.close(1007, 'Invalid JSON format');
return;
}
}
});
});
}
const signalling = fastifyPlugin(plugin, {
name: 'signalling',
fastify: '5.x'
});
/** @returns {Promise<FastifyInstance>} */
async function buildApp() {
const fastify = Fastify();
await fastify.register(signalling);
return fastify;
}
describe('Signalling Endpoint Protocol', async () => {
/** @type {FastifyInstance} */
let app;
/** @type {WebSocket} */
let ws;
beforeEach(async () => {
app = await buildApp();
await app.ready();
ws = await app.injectWS('/signal');
});
afterEach(async () => {
ws?.terminate();
await app.close();
});
it.only('disconnects if the client sends malformed JSON', async (t) => {
/**
* @type {{promise: Promise<WebSocketStatus>, resolve: (value?: WebSocketStatus | PromiseLike<WebSocketStatus>) => void, reject: (reason?: any) => void}}
*/
const {promise, resolve, reject} = Promise.withResolvers();
ws.once('close', resolve);
ws.once('error', reject);
ws.send('fgsfds', (error) => {
if (error) {
console.error("Error sending malformed JSON:", error);
reject(error);
} else {
console.log("Malformed JSON sent successfully");
}
});
t.assert.equal(await promise, 1007); // invalid frame payload data
// hang begins here
// execution continues here after the test times out
await t.waitFor(() => ws.readyState);
});
});
{
"name": "vitest-websocket-bug",
"version": "0.0.0",
"scripts": {},
"private": true,
"dependencies": {
"@fastify/websocket": "^11.2.0",
"fastify-plugin": "^5.0.1",
"fastify": "^5.5.0"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment