apteryxxyz / next-ws

Add support for WebSockets in Next.js app directory.
https://npmjs.com/next-ws
131 stars 9 forks source link

Usage with a custom server #8

Closed GGAlanSmithee closed 2 days ago

GGAlanSmithee commented 8 months ago

Hi,

Is there an easy way to access the server socket when using a custom server?

Thanks for this awesome plugin and in advance for answering my question!

apteryxxyz commented 8 months ago

You want to be able to access the WebSocket server outside of a SOCKET route? There isn't currently anyway to do that I believe, but I'll look into it.

GGAlanSmithee commented 8 months ago

Thanks for your answer.

Yes, more specifically in a custom server.

I'm my case, I need to run some cronjobs at startup which needs to access the socket, but it could also be useful for the more general case where you want to use the connected event for whatever reason.

GGAlanSmithee commented 8 months ago

I haven't actually looked at this myself, but since (I guess) this package monkey patches nextjs with ws functionality, maybe the server sticker is already "there" at the time of startup?

apteryxxyz commented 8 months ago

I've tried a few methods but I'm able to make the WS server globally available. Do you have a Next.js custom server sort of template that I can use to test?

GGAlanSmithee commented 8 months ago

@apteryxxyz I will put up an repo right away.

interestingly, when actually testing this right now, it seems that next-ws is not compatible with next.js custom server at all atm.

Notice the different outcome in the image below when running next dev vs node server.js:

image

(which does make sense, since the server is not patched in the node server.js case. But it would be very interesting to have an api to be able to path it yourself. Or maybe I can deduce it from looking through the code)

GGAlanSmithee commented 8 months ago

Here is a repo to test with, just run npm run dev: https://github.com/GGAlanSmithee/nextjs-ws-custom-server

apteryxxyz commented 8 months ago

Yeah I get that error as well, it comes somewhere from app.prepare(). Next ws grabs the http server from the server options of the next server, aka the object you pass to the next function. Even if you do pass the custom server to that, it doesn't exist on the server options next ws receives. I'm not too sure why.

55Cancri commented 8 months ago

Not being able to use with custom server is also preventing me from adopting this package as well

apteryxxyz commented 8 months ago

I've just published an experimental version of Next WS (next-ws@experimental) that adds support for custom servers. It appears to work but it's not fully tested.

The way it works is by you assigning your own http server (and optionally ws server) to a global variable, which Next WS will check for when setting up the server. I'm not a fan of assigning to global, but I prefer this over having to patch more things in Next.js.

CustomHttpServer, CustomWsServer are two symbols that get exported from next-ws/server and can be used to assign your custom http server to the global object.

This is the setup I used:

// server.js
const { Server } = require('http');
const { WebSocketServer } = require('ws');
const { parse } = require('url');
const next = require('next');
const { CustomHttpServer, CustomWsServer } = require('next-ws/server');

const dev = process.env.NODE_ENV !== 'production';
const hostname = 'localhost';
const port = 3000;

const httpServer = new Server();
global[CustomHttpServer] = httpServer;
const wsServer = new WebSocketServer({ noServer: true });
global[CustomWsServer] = wsServer;

const app = next({ dev, hostname, port });
const handle = app.getRequestHandler();

app.prepare().then(() => {
  httpServer
    .on('request', async (req, res) => {
      const parsedUrl = parse(req.url, true);
      await handle(req, res, parsedUrl);
    })
    .listen(port, () => {
      console.log(` ▲ Ready on http://${hostname}:${port}`);
    });
});

Let me know if you try it and how it goes, I'll do more testing when I have more time.

55Cancri commented 8 months ago

Thanks so much man, it seems to be working for me as well, even when creating https and wss servers. Some things I noticed:

Otherwise, the changes look good to me and I will use like this. Thanks again for doing this! So sad nextjs doesn't natively support websockets and makes it so difficult to include.

apteryxxyz commented 8 months ago

you still must run pnpx next-ws-cli@latest patch, even for custom server

Yeah, there's a few reasons for this but the main is the Next WS needs access to the NodeNextServer. And there is no reliable (as in has everything Next WS needs) way that I know of to get that in the server.js file. There are a lot of "solutions" to the problems I encounter but they just cause more problems to arise, which is why I ended up going with using global.

the servers must be attached at the global level

The WebSocket server is setup when app.prepare is called, so the custom HTTP server needs to be defined before that.

package doesn't seem to have any esm exports

I've been meaning to replace compiling with TSC to tsup, I'll use that to add ESM exports. For the time being I could replace the CustomHttpServer symbol with a plain string so you don't have to grab it from the package in order to define.

Edit: I just remembered that Next.js has a ESM directory in their dist that Next WS doesn't patch (only patches the CommonJS versions). I not sure in which case these are used, I added type: "module" (and use next.config.mjs) to my test apps package.json and it still works. I'll have to look into that.

apteryxxyz commented 8 months ago

I'll just published a new next-ws@experimental version (1.0.1-experimental.2), that replaces the custom symbols with strings so you no longer have to grab the symbols from the next-ws package.

This is my new server.js file:

import { Server } from 'node:http';
import { parse } from 'node:url';
import next from 'next';
import { WebSocketServer } from 'ws';

const dev = process.env.NODE_ENV !== 'production';
const hostname = 'localhost';
const port = 3000;

const httpServer = new Server();
global['NextWS::CustomHttpServer'] = httpServer;
const wsServer = new WebSocketServer({ noServer: true });
global['NextWS::CustomWsServer'] = wsServer;

const app = next({ dev, hostname, port });
const handle = app.getRequestHandler();

app.prepare().then(() => {
  httpServer
    .on('request', async (req, res) => {
      const parsedUrl = parse(req.url, true);
      await handle(req, res, parsedUrl);
    })
    .listen(port, () => {
      console.log(` ▲ Ready on http://${hostname}:${port}`);
    });
});
GGAlanSmithee commented 7 months ago

Thanks so much for doing this @apteryxxyz. I haven't been able to test this just yet, because I got caught up in a lot of work but I intend to test it ASAP

Alex-Mastin commented 5 months ago

I'm finding I have a somewhat similar use-case. I'm building an app that needs an external source to be able to send messages to the WebSocket server, but I'm hitting a wall in getting that working. I tried the above solution, and was able to get it working locally, but since my project is built in standalone mode (for Docker) it wasn't super clear how to use the custom server alongside the server.js that gets generated by Next. Also didn't really love losing TypeScript support to have a custom server.