2

I'm trying to create a simple WebSocket server that will run in a SvelteKit application. I found this tutorial online which shows how to do it using Socket.io, however I would like to use the ws module instead.

This is the vite.config.ts file that I've come up with so far:

import type { UserConfig } from 'vite';

import { sveltekit } from '@sveltejs/kit/vite';

import { WebSocketServer } from "ws";

const webSocketServer = {
    name: "webSocketServer",
    configureServer: () => {
        const webSocketServer = new WebSocketServer({
            port: 8080
        });
    
        webSocketServer.on("connection", (socket, request) => {
            socket.on("message", (data, isBinary) => {
                console.log(`Recieved ${data}`);
            });
        
            socket.send("test from server");
        });
    }
}

const config: UserConfig = {
    plugins: [sveltekit(), webSocketServer]
};

export default config;

I can connect to this WebSocket server from the frontend (in +page.svelte, for example) and send messages between them.

But every time I make a change to my server's code and save the file, I get an error saying that "there's already something listening on port 8080" and my Vite dev server terminates. If I then start back up my Vite dev server by running npm run dev then it will all work fine again, until I make any change to the WebSocket server code and save the file, and then the same thing will repeat.

I would rather not have to re-start my Vite dev server every time I make a change to my WebSocket server's code. If possible, I would rather Vite automatically shut down my old webSocket server to make room for the new one before booting it up each time so that they won't conflict.

The tutorial I linked above shows this code snippet:

import adapter from '@sveltejs/adapter-node'
import preprocess from 'svelte-preprocess'

import { Server } from 'socket.io'

const webSocketServer = {
  name: 'webSocketServer',
  configureServer(server) {
    const io = new Server(server.httpServer)

    io.on('connection', (socket) => {
      socket.emit('eventFromServer', 'Hello, World ')
    })
  },
}

/** @type {import('@sveltejs/kit').Config} */
const config = {
  preprocess: preprocess(),
  kit: {
    adapter: adapter(),
    vite: {
      plugins: [webSocketServer],
    },
  },
}

export default config

This snippet is using an old SvelteKit version where Vite configuration was done inside the svelte.config.js file, so the layout is a little different, but it seems they're simply spinning up their Socket.io server directly inside the configureServer method just like I am, but they're tapping into the existing Vite http server instead of creating a new one like I am. I tried doing this and I still get the same problem. Every time I try httpServer.listen(8080); on Vite's http server I'm told that the server was already listening on a port and the dev server terminates. I also tried manually creating an http server using require("http").createServer() and using that, but (unsurprisingly) that also did not work and acted identically to my initial attempt shown at the beginning of this question.

The tutorial seems to be booting up a Socket.io server the same way I'm trying to boot up mine, but they don't seem to be running into any conflicts like I am. I checked Socket.io's documentation to see if perhaps the Server constructor has a built-in failsafe to make sure it doesn't listen on a port if it's already listening on that port (and avoid creating the error), but the docs didn't give any information in that regard, so I'm still unsure as to what's going on there.

Is there any way to do what I'm trying to do, or am I going about this entirely the wrong way? I can't find hardly any information about this anywhere on the internet.

And also, what's going on in the Socket.io example that allows it to work where mine won't? Is Socket.io doing something special?

Here's my attempt at using Vite's built-in http server. This behaves the same as my other attempts. vite.config.ts

import type { UserConfig } from 'vite';

import { sveltekit } from '@sveltejs/kit/vite';

import { WebSocketServer } from "ws";

const webSocketServer = {
    name: "webSocketServer",
    configureServer: (server: any) => {
        const httpServer = server.httpServer;
        const webSocketServer = new WebSocketServer({
            noServer: true
        });
    
        webSocketServer.on("connection", (socket, request) => {
            socket.on("message", (data, isBinary) => {
                console.log(`Recieved ${data}`);
            });
        
            socket.send("hi c:");
        });

        httpServer.on("upgrade", (request: any, socket: any, head: any) => {
            webSocketServer.handleUpgrade(request, socket, head, socket => {
                webSocketServer.emit("connection", socket, request);
            });
        });

        httpServer.listen(8080);
    }
}

const config: UserConfig = {
    plugins: [sveltekit(), webSocketServer]
};

export default config;
OOPS Studio
  • 732
  • 1
  • 8
  • 25
  • The adapter / socketio example attaches socketio to the server created in '@sveltejs/adapter-node'. so startup/shutdown is all handled by the adapter – Matt Feb 02 '23 at 05:55
  • @Matt May I ask then why I couldn't get it to work when I attempted to attach to that very same server? I included my attempt's code in an edit to my question. Do you see any glaring issues? (I had to use a lot of explicit `any`s so TypeScript wouldn't complain. That alone is a red flag to me) I am using `@sveltejs/adapter-node` in my `svelte.config.js` file. – OOPS Studio Feb 02 '23 at 06:06
  • Can you pass `{ server }` or `{ server: httpServer }` the the constructor? and skip the upgrade/listen. – Matt Feb 02 '23 at 06:26
  • I'll post my answer, but I think I've written for the blog API rather than your updated one. Feel free to edit it – Matt Feb 02 '23 at 06:26
  • To address your question, I believe the issue relates to trying to control a server from the config. In the latest case `httpServer.listen(8080);` when vite is in control of startup shutdown. In the blog, node-adapter in control of startup/shutdown. You just want to piggy back WS on what sveltekit is providing you – Matt Feb 02 '23 at 06:30

1 Answers1

1

ws supports initialising with an existing server like socket.io

Don't store the application logic in the vite/svelte config, as it will be needed for the production build when vite is not available (I'm not familiar with sveltekit though so maybe it does some magic?). Create a standalone file for the ws setup:

import { WebSocketServer } from "ws";

export const configureServer = (server) => {
  const webSocketServer = new WebSocketServer({
    server: server.httpServer,
  });
    
  webSocketServer.on("connection", (socket, request) => {
    socket.on("message", (data, isBinary) => {
      console.log(`Recieved ${data}`);
    });
        
    socket.send("test from server");
  });
}

export const webSocketServer = {
    name: "webSocketServer",
    configureServer,
}

Then you should be able to use your version of webSocketServer like the socket.io example as they both attach to the '@sveltejs/adapter-node' server.

import adapter from '@sveltejs/adapter-node'
import { webSocketServer } from './sockets.js';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    adapter: adapter(),
    vite: {
      plugins: [webSocketServer],
    },
  },
}

export default config

configureServer can now be reused when you setup the custom server for production.

Matt
  • 68,711
  • 7
  • 155
  • 158
  • Your edit used `server.httpServer`. Does that work? – Matt Feb 02 '23 at 06:35
  • Using `server.httpServer` it gives me this error: `RangeError: Invalid WebSocket frame: invalid status code 42745` Additionally: `WS_ERR_INVALID_CLOSE_CODE` and `statusCode: 1002` – OOPS Studio Feb 02 '23 at 06:37
  • Hi, I had the same problem wit the ws library. I use socket.io now for server and cliënt. It looks like the socket classes are not compatible. So also use the Socket from socket.io-cliënt in the browser and not the native WebSocket. Works like a charm now with sveltekit. – Ronald Brinkerink Feb 24 '23 at 23:24