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.

Revisions

  1. JesseTG revised this gist Aug 13, 2025. 2 changed files with 2 additions and 3 deletions.
    1 change: 1 addition & 0 deletions main.test.js
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,4 @@
    // 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';
    4 changes: 1 addition & 3 deletions package.json
    Original file line number Diff line number Diff line change
    @@ -6,8 +6,6 @@
    "dependencies": {
    "@fastify/websocket": "^11.2.0",
    "fastify-plugin": "^5.0.1",
    "fastify": "^5.5.0",
    "vite": "^7.1.2",
    "vitest": "^3.2.4"
    "fastify": "^5.5.0"
    }
    }
  2. JesseTG created this gist Aug 13, 2025.
    97 changes: 97 additions & 0 deletions main.test.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,97 @@
    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);
    });
    });
    13 changes: 13 additions & 0 deletions package.json
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,13 @@
    {
    "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",
    "vite": "^7.1.2",
    "vitest": "^3.2.4"
    }
    }