nitro icon indicating copy to clipboard operation
nitro copied to clipboard

WebSocket Support Tracker

Open pi0 opened this issue 1 year ago • 36 comments

Initial PR: https://github.com/unjs/nitro/pull/2170

Relavant docs:

  • https://h3.unjs.io/guide/websocket
  • https://crossws.unjs.io

Platform support

  • [ ] Dev server
    • [x] Runtime handlers
    • [ ] Dev handlers
  • [x] bun
  • [x] Cloudflare
    • [x] cloudflare-pages
    • [x] cloudflare-module
    • [x] cloudflare (worker)
  • [x] Deno
    • [x] deno-deploy
    • [x] deno-server
  • [ ] Node
    • [x] node-server
    • [x] node via websocket export
    • [ ] node-cluster

Needs investigation:

  • [ ] aws-lambda
  • [ ] AWS amplify
  • [ ] Firebase
  • [ ] Netlify
    • [ ] netlify-edge
    • [ ] netlify-lambda
  • [ ] stormkit
  • [ ] Vercel
    • [ ] vercel-lambda
    • [ ] vercel-edge
  • [ ] wnterjs
  • [ ] zerabur

pi0 avatar Feb 25 '24 20:02 pi0

After reviewing the Netlify documentation, I found no suitable offering that will properly support websocket servers at this time.

  1. Netlify Functions are killed off after 10 seconds. This limit can be increased to 26 seconds if you contact support.
  2. Netlify Edge-Functions have max 10 seconds of CPU time.
  3. Netlify Background Functions can run for 15 minutes, but they cannot send a response to the request that triggered them.

passionate-bram avatar Mar 04 '24 15:03 passionate-bram

Thanks for your investigation. Re Netlify, i have reached out and waiting for an answer if there is any viable current or future plans.

pi0 avatar Mar 04 '24 15:03 pi0

Is websocket still not available in the current development mode of the latest nuxt?

I try to access /websocket in development mode (pnpm run dev) and it's always connecting.

If I compile and start with node .output/server/index.mjs everything works fine.

https://github.com/StringKe/report-nuxt-webscoket

- Operating System: `Darwin`
- Node Version:     `v21.5.0`
- Nuxt Version:     `3.10.3`
- CLI Version:      `3.10.1`
- Nitro Version:    `2.9.3`
- Package Manager:  `[email protected]`
- Builder:          `-`
- User Config:      `nitro`
- Runtime Modules:  `-`
- Build Modules:    `-`

StringKe avatar Mar 15 '24 10:03 StringKe

@StringKe You need to use nuxi-nightly.

pi0 avatar Mar 15 '24 10:03 pi0

Are there plans to let us define which adapters defineWebsocketHandler uses internally? Specifically, in my case I want to use the crossws uWs adapter

https://github.com/unjs/nitro/pull/2170/files#diff-874de70a2b8dc27aa6a857ee5110f1b1ab675afc9e6b1191bebfb1119ad7c78fR7

Or is it possible to just import it manually and use it instead of defineWebsocketHandler?

daniandl avatar Mar 18 '24 11:03 daniandl

uws requires it's own server preset for Nitro to be supported. (because it is not based on node:http)

pi0 avatar Mar 18 '24 11:03 pi0

does crossws have an API to proactively close connection?

StringKe avatar Mar 19 '24 01:03 StringKe

Does nuxt support websocket when deployed on cloudflare pages? Everything is fine on localhost, but on Cloudflare I'm getting only status finished

indam1 avatar Mar 29 '24 20:03 indam1

Hello, I am trying to combine peer-server with my Nuxt application. Unfortunately, it doesn't work as expected. The reproduced issue is in nuxt/nuxt

In the peer-server middleware, ws is used to send the WebSocket message https://github.com/peers/peerjs-server/blob/master/src/services/webSocketServer/index.ts#L59

import { WebSocketServer as Server } from "ws";

const options: WebSocket.ServerOptions = {
	path: this.path,
	server,
};

this.socketServer = new Server(options);

this.socketServer.on("connection", (socket, req) => {
	this._onSocketConnection(socket, req);
});

Currently, Nuxt is unable to listen for web socket connection events as expected

FliPPeDround avatar Apr 02 '24 09:04 FliPPeDround

Does nuxt support websocket when deployed on cloudflare pages? Everything is fine on localhost, but on Cloudflare I'm getting only status finished

Hi did u manage to get this fixed?

meesvrh avatar Jun 30 '24 23:06 meesvrh

Thanks for your investigation. Re Netlify, i have reached out and waiting for an answer if there is any viable current or future plans.

Hi, @pi0 any updates on this from the netlify team?

pileroniel avatar Jul 02 '24 12:07 pileroniel

For netlify, you might use SSE (long polling) as much as know. @serhalp might give better answer if there is any plans for future WebSocket API.

pi0 avatar Jul 02 '24 12:07 pi0

👋🏼 I'm not aware of any plans to change Netlify's support for WebSockets. However, Server-Sent Events (SSE) work great with Netlify Edge Functions.

serhalp avatar Jul 29 '24 20:07 serhalp

@pi0 is there an example somewhere that instructs how to set up websockets with the cloudflare-module Nitro preset?

When using the example from the docs, if I run locally in Nuxt (via nuxt dev) the tabs communicate properly; however, when I build and then run wrangler dev there is no communication between separate tabs.

I can see each peer is available (the number is incremented for each connected client if I output on the page) but I can not receive messages between them after calling subscribe/publish accordingly.

I see the following note on the CrossWS Pub / Sub page:

Native pub/sub is currently only available for Bun, Node.js (uWebSockets) and Cloudflare (durable objects)

but there are no instructions as to how to actually hook it up (here in the Nitro docs) to Cloudflare Durable Objects?

If you can point me in the right direction I can also push up a PR for docs.


Alternatively, if you have another (non-experimental) suggestion on how to implement similar messaging, I'd be open to try.

adamdehaven avatar Aug 05 '24 20:08 adamdehaven

Durable-ojbect support is on the progress (sorry for misleading docs, that section is not released yet)

pi0 avatar Aug 06 '24 07:08 pi0

Durable-ojbect support is on the progress (sorry for misleading docs, that section is not released yet)

😅 Well I feel better that I couldn't get it working now. Tag me if I can help test/implement/document

adamdehaven avatar Aug 06 '24 13:08 adamdehaven

Hello, is there a way to access the event similar to defineEventHandler or eventHandler in Nuxt? I'm asking because I'm trying to retrieve a router parameter, and all server utility functions require the event parameter.

bpbastos avatar Aug 22 '24 15:08 bpbastos

Hello, is there a way to access the event similar to defineEventHandler or eventHandler in Nuxt? I'm asking because I'm trying to retrieve a router parameter, and all server utility functions require the event parameter.

A route parameter from the websocket route? what's your use case?

Barbapapazes avatar Aug 22 '24 21:08 Barbapapazes

I'm trying to create a proxy for Parse Server's LiveQueries and need to pass parameters to initialize the SDK and perform the necessary queries. I'm not sure if this is the best approach, as I'm just starting out with WebSockets.

bpbastos avatar Aug 25 '24 13:08 bpbastos

Yeah, if it possible, please: add possibility to read params from url. It's might be useful to authenticate user with one-time/signed token.

Possible solution may be second (or first, ws still experimental feature anyway) argument to open() hook

UPD:

it's possible to parse params from peer.url string:

export default defineWebSocketHandler({
  open(peer) {
    console.log(peer.url); // `/some/usr/TOKEN/ws`
  },
});

samenick avatar Oct 19 '24 23:10 samenick

What about platforms not mentioned, (e.g. Azure) are they omitted for any reason?

AgileInteractive avatar Oct 31 '24 00:10 AgileInteractive

@pi0 is there an example somewhere that instructs how to set up websockets with the cloudflare-module Nitro preset?

When using the example from the docs, if I run locally in Nuxt (via nuxt dev) the tabs communicate properly; however, when I build and then run wrangler dev there is no communication between separate tabs.

I can see each peer is available (the number is incremented for each connected client if I output on the page) but I can not receive messages between them after calling subscribe/publish accordingly.

I see the following note on the CrossWS Pub / Sub page:

Native pub/sub is currently only available for Bun, Node.js (uWebSockets) and Cloudflare (durable objects)

but there are no instructions as to how to actually hook it up (here in the Nitro docs) to Cloudflare Durable Objects?

If you can point me in the right direction I can also push up a PR for docs.

Alternatively, if you have another (non-experimental) suggestion on how to implement similar messaging, I'd be open to try.

hey, currently i'm also facing the same issue, connections works pretty well in local dev nuxt dev. However can't see incoming data when running with wrangler dev. Any lead on this ?

tahirmahmudzade avatar Nov 05 '24 20:11 tahirmahmudzade

@pi0Existe algum exemplo em algum lugar que instrua como configurar websockets com a cloudflare-modulepredefinição Nitro? Ao usar o exemplo da documentação , se eu executar localmente no Nuxt (via nuxt dev), as guias se comunicam corretamente; no entanto, quando eu compilo e executo, wrangler devnão há comunicação entre guias separadas. Posso ver que cada um peerestá disponível (o número é incrementado para cada cliente conectado se eu exibir na página), mas não consigo receber mensagens entre eles depois de fazer a chamada subscribe/publishcorrespondente. Vejo a seguinte nota na CrossWS Pub / Subpágina :

O pub/sub nativo está disponível atualmente apenas para Bun , Node.js (uWebSockets) e Cloudflare (objetos duráveis)

mas não há instruções sobre como conectá-lo (aqui nos documentos do Nitro) aos Objetos Duráveis ​​do Cloudflare? Se você puder me indicar a direção certa, também posso aumentar o PR dos documentos. Alternativamente, se você tiver outra sugestão (não experimental) sobre como implementar mensagens semelhantes, eu estaria aberto a tentar.

ei, atualmente também estou enfrentando o mesmo problema, as conexões funcionam muito bem no dev local nuxt dev. No entanto, não consigo ver os dados de entrada ao executar com wrangler dev. Alguma pista sobre isso?

Any news ?

estige avatar Jan 12 '25 01:01 estige

Just tried using this feature in node with nuxt, it seems ws doesn't support sending messages asynchronously.

WS Implementation

export default defineWebSocketHandler({
  open(peer) {
    ready("Connected to peer: ", peer.id);
  },

  async message(peer, message) {
    const config = useRuntimeConfig();
    if (message.text() === "get-data") {
      await $fetch(`${config.public.apiBaseUrl}/frontend-counters`).then(
        peer.send,
      );
    }
  },

  close(peer, event) {
    info(`Peer \`(${peer.id})\` disconnected. Reason: ${event.code}`);
  },

  error(peer, error) {
    fail("An error occured: ", peer.id, error);
  },
});

I keep getting this error in preview server

[unhandledRejection] TypeError: Cannot read properties of undefined (reading '_internal')
    at send (.output/server/chunks/nitro/nitro.mjs:5266:10)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async message (.output/server/chunks/routes/api/websocket.mjs:33:7)
ℹ Peer (331f477a-20ca-4ecc-adf7-1ad37d96448e) disconnected. Reason: 1000                                                                                                                            server:ws 9:45:24 AM
[unhandledRejection] TypeError: Cannot read properties of undefined (reading '_internal')
    at send (.output/server/chunks/nitro/nitro.mjs:5266:10)

And some times the dev server doesn't start

Meta

The preview server is node-server, on a mac, with node v20.19 (also tested on node 22)

splitwarechef avatar May 20 '25 08:05 splitwarechef

@splitwarechef Please consider sharing a minimal reproduction with nitro 🙏🏼

pi0 avatar May 20 '25 09:05 pi0

See here: https://stackblitz.com/~/github.com/splitwarechef/test-nitro-websocket

Absolute paths don't work on stackblitz so I suggest you try this on your local instead.

Edit

The github repo is public, you're free to clone

splitwarechef avatar May 20 '25 17:05 splitwarechef

Thanks for reproduction @splitwarechef 🙏🏼 (please consider using nitro only reproductions in the future not nuxt and also use new issues )

Issue is that you are calling peer method with wrong context:

---       await $fetch(`/api/counter`).then(peer.send);
+++       await $fetch(`/api/counter`).then(message => peer.send(message));

pi0 avatar May 20 '25 21:05 pi0

Any idea when the cloudflare-durable preset might be ready for production (and docs)? I'd love to cook something up in my production app 👨🏼‍🍳

adamdehaven avatar May 20 '25 23:05 adamdehaven

Thanks for reproduction @splitwarechef 🙏🏼 (please consider using nitro only reproductions in the future not nuxt and also use new issues )

Issue is that you are calling peer method with wrong context:

--- await $fetch(/api/counter).then(peer.send); +++ await $fetch(/api/counter).then(message => peer.send(message));

Thank you @pi0. Also, noted!.

splitwarechef avatar May 22 '25 10:05 splitwarechef

Not exactly standard, but I'm using a bastardized version in server middleware to wrap the nuxt node server to create a Websocket that can be integrated with trpc. I was wondering if there were any plans to be able to let us define the server it uses e.g. if I want to switch completely to uwebsockets etc and integrate with the trpc uwebsockets package, just throwing this out there in case there are other options I'm not aware of

import type { CreateWSSContextFnOptions } from "@trpc/server/adapters/ws";
import type { Server } from "node:http";

import { getSynchronizedFunction } from "#shared/util/getSynchronizedFunction";
import { createCallerFactory } from "@@/server/trpc";
import { createContext } from "@@/server/trpc/context";
import { trpcRouter } from "@@/server/trpc/routers";
import { userRouter } from "@@/server/trpc/routers/user";
import { applyWSSHandler } from "@trpc/server/adapters/ws";
import { WebSocketServer } from "ws";

export default defineEventHandler((event) => {
  if (global.websocketServer) return;

  const server =
    event.node.res.socket && "server" in event.node.res.socket ? (event.node.res.socket.server as Server) : null;
  if (!server) return;

  const wss = new WebSocketServer({ server });
  const handler = applyWSSHandler({
    createContext,
    keepAlive: { enabled: true },
    router: trpcRouter,
    wss,
  });
  const createCaller = createCallerFactory(userRouter);

  wss.on(
    "connection",
    getSynchronizedFunction(async (ws, req) => {
      const context = createContext({ req, res: ws } as CreateWSSContextFnOptions);
      const caller = createCaller(context);
      await caller.connect();
      console.log(`Connection opened, client size: ${wss.clients.size}`);
      ws.once(
        "close",
        getSynchronizedFunction(async () => {
          await caller.disconnect();
          console.log(`Connection closed, client size: ${wss.clients.size}`);
        }),
      );
    }),
  );
  process.on("SIGTERM", () => {
    handler.broadcastReconnectNotification();
    wss.close();
  });

  console.log("WebSocket Server is listening");
  global.websocketServer = wss;
});

Q16solver avatar Jun 12 '25 02:06 Q16solver