Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Native support for web sockets #1491

Open
AndreasHald opened this issue May 19, 2021 · 67 comments · May be fixed by #12973
Open

Native support for web sockets #1491

AndreasHald opened this issue May 19, 2021 · 67 comments · May be fixed by #12973
Labels
feature / enhancement New feature or request p1-important SvelteKit cannot be used by a large number of people, basic functionality is missing, etc. size:large significant feature with tricky design questions and multi-day implementation
Milestone

Comments

@AndreasHald
Copy link

We've been attempting to migrate our app from sapper to sveltekit and ran into the problem of web sockets, in sapper we could simply attach the WebSocket server directly to the express server and be done with it, but as per Rich Harris' comments here and here here in sveltekit we don't want to expose direct access to the middlwares thus promoting the node adapter over the serverless ones.

However seeing as some of the serverless providers are starting to support web sockets (cloudflare) (begin) (AWS Lambda) should sveltekit provide a way to handle these connections directly?

A native approach to this could also potentially provide a tool to solve this issue and this

Describe the solution you'd like
An interesting solution could be to expose functions in endpoints in the same way we handle http requests, and then let the adapters handle the rest

routes/api/index.ts

// Handle post requests
export const post: RequestHandler = async (request) => {....}
// Handle web socket requests
export const ws: WebSocketRequestHandler = async (request) => {....}

A concern with this approach could be mixing protocols, within an endpoint could be confusing. Another could be if this becomes too complicated since as far as I know the initial handshake to start a WebSocket connection is handled over http, would this then be handled directly by sveltekit which could be considered too much "magic" since it would have to intercept a get request that would then not make it to the get handler in the endpoint. Or should we let the user implement the get endpoint and return the WebSocket pair and upgrade header.

Describe alternatives you've considered
The way we go about it today is a small script that manipulates the build output of sveltekit as such however this does not work in dev mode which is a pain point.

const instance = createServer({ render: app.render }).listen(PORT, HOST, (err) => {
	if (err) {
		console.log('error', err);
	} else {
		console.log(`Listening on port ${PORT}`);
	}
});
app.server.installSubscriptionHandlers(instance);
export { instance };

Is this a feature that you feel would suit sveltekit? are there any considerations I've overlooked?

@Gregcop1
Copy link

@AndreasHald Have you ever heard of Mercure?

@AndreasHald
Copy link
Author

@AndreasHald Have you ever heard of Mercure?

Can't say i had. The idea looks interesting, but afaik sveltekit doesn't yet support SSE yet either.

We are pretty invested in Apollo Graphql server / client, and i don't think they have a transport layer for mercury, but correct me if i'm wrong.

@dunglas
Copy link

dunglas commented Jun 3, 2021

Hi! Mercure creator here.

sveltekit doesn't yet support SSE yet either

Basically, as long as you delegate to a Mercure hub (a server implementing the protocol), you don't have to deal with SSE directly. The app just have to do a POST request to the hub to broadcast an update to all connected clients.

Supporting this part of the protocol perfectly fits with serverless environments because you can delegate the long running connection (that is by nature incompatible with serverless) to a dedicated component, even if the serverless service itself is not compatible with SSE, Websockets and the like.

We are pretty invested in Apollo Graphql server / client, and i don't think they have a transport layer for mercury

I don't know Apollo very well. I know that API Platform has native support for GraphQL subscriptions through Mercure, and AFAIU there is no need for client-side code. Using the native EventSource JS class looks enough. But maybe that a better integration with Apollo could be created?

Anyway, I would love to see support for the protocol land in SvelteKit. Let me know if I can help in any way to make this happen (I can try to create a transport for Apollo for instance).

@cayter
Copy link

cayter commented Jul 10, 2021

I had the same requirement and initially got some dirty workarounds to make it work with just SvelteKit but it's not maintainable. I have come to understand the direction SvelteKit is being steered towards(i.e. serverless rendering) that makes it quite tough to be fully compatible with the traditional NodeJS SSR + Vite itself would still lead us to a lot of bundling issues for server-side libraries. After playing with SvelteKit for a few weeks, I decided to build a set of toolkit on top of SvelteKit and bring out the best possible monolith DX. For more details, please find my comment here.

Though I haven't added:

  • export const ws to support websocket
  • export const graphql to support GraphQL
  • export const grpc to support GRPC (haven't dive deep into this yet but will be very much needed for successful startups who wants to slowly move some monolith pieces out to another service)

but yes, the above are all planned! I'm just excited to see this post here as I was wondering the same why we can't support export const ws and export const graphql! Good job.

@AndreasHald
Copy link
Author

@cayter sounds interesting, i think we will migrate to use SSE when 1568 is resolved, as i understand there is a PR ready.

How would you go about implementing export const graphql.

Svelte kit supports the graphql standard very well out og the box. Because graphql specifies a single endpont just use a single file /graphql/index.js that supports the spec

We use apollo server without any issues.

I had the same requirement and initially got some dirty workarounds to make it work with just SvelteKit but it's not maintainable. I have come to understand the direction SvelteKit is being steered towards(i.e. serverless rendering) that makes it quite tough to be fully compatible with the traditional NodeJS SSR + Vite itself would still lead us to a lot of bundling issues for server-side libraries. After playing with SvelteKit for a few weeks, I decided to build a set of toolkit on top of SvelteKit and bring out the best possible monolith DX. For more details, please find my comment here.

Though I haven't added:

  • export const ws to support websocket

  • export const graphql to support GraphQL

  • export const grpc to support GRPC (haven't dive deep into this yet but will be very much needed for successful startups who wants to slowly move some monolith pieces out to another service)

but yes, the above are all planned! I'm just excited to see this post here as I was wondering the same why we can't support export const ws and export const graphql! Good job.

@cayter
Copy link

cayter commented Jul 10, 2021

Svelte kit supports the graphql standard very well out og the box. Because graphql specifies a single endpont just use a single file /graphql/index.js that supports the spec We use apollo server without any issues.

I probably need to give it a try again, I bumped into issues where Vite was bundling some of the server-side dependencies in the client bundle and broke the page rendering. But yeah I was thinking of supporting graphql (with websocket subscription) easily via export const graphql though I haven't really come to this step yet, will share more once I'm onto it and will very likely be adopting:

@benmccann benmccann added the p1-important SvelteKit cannot be used by a large number of people, basic functionality is missing, etc. label Aug 4, 2021
@benmccann
Copy link
Member

I think we will be exposing a way to add middleware in adapter-node (#2051), but if anyone wants to take a stab at adding more generic support that also support the serverless platforms, that still sounds valuable

@benmccann benmccann removed this from the 1.0 milestone Aug 4, 2021
@Xananax
Copy link

Xananax commented Sep 8, 2021

So is there some guideline regarding how to integrate sockets with a svelte-kit app? As far as I understand, the path of least resistance at the moment is to use adapter-node, and add a file that uses the built svelte site:

import { assetsMiddleware, prerenderedMiddleware, kitMiddleware } from './build/middlewares.js';
import polka from 'polka';
import Socket from 'socket.io';

const { PORT=3000 } = process.env;

const app = polka();

app.use(assetsMiddleware, prerenderedMiddleware, kitMiddleware);

const { server } = polka().listen(PORT, () => console.log(`> Running on localhost:${PORT}`))

const io = Socket(server);
io.on('connection', socket => {
  console.log('connection')
})

I didn't try this yet, but it seems to be the recommended path. It bothers me somewhat because it isn't integrated in the build system (won't have access to the same environment variables and import.meta goodies, won't be using typescript unless I set up a separate build, ...).

But assuming I go with that, but I am not sure how access to all the session information that I have in my svelte-kit app. I am also not very clear on changing the state of the socket server based on url calls. Should I send two requests from each URL in my svelte-app (one normal http call and one to the websocket server?) Should I add a middleware to adapter-node?

@matths
Copy link
Contributor

matths commented Sep 24, 2021

I didn't try this yet, but it seems to be the recommended path. It bothers me somewhat because it isn't integrated in the build system

@Xananax, well I did. And actually it is easy to integrate with the build process. You can specify your custom server as entryPoint in your svelte.config.js file. Your can read about that here: https://github.com/sveltejs/kit/tree/master/packages/adapter-node#entryPoint

An example:

  • Put your custom server at your [YOUR_PROJECT_DIR]/server/index.js right next to [YOUR_PROJECT_DIR]/src
  • Do not forget to add polka to your dev dependencies and to run npm installagain
  • Add entryPoint: 'entryPoint: 'server/index.js',' in your adapter option within the kit.adapter section of your svelte.config.js
  • run npm build and see the magic happen.

@benmccann

This comment has been minimized.

@bluepuma77

This comment was marked as off-topic.

@bfanger
Copy link
Contributor

bfanger commented Oct 30, 2021

Workaround

Inject socket.io (or another websockets implementation) into the vite DEV server via the vite config:

import { Server } from "socket.io";
export default {
 plugins: [
    sveltekit(),
    {
      name: "multiplayer",
      configureServer(server) {
          const io = new Server(server.httpServer));
          // do websocket stuff
      },
    },
  ]
}

https://github.com/bfanger/multiplayer-dice-game/blob/main/vite.config.ts

For PROD use the node-adapter to generate the middleware and which you'll include in a custom server:
https://github.com/bfanger/multiplayer-dice-game/blob/main/server.js

@glics
Copy link

glics commented Nov 9, 2021

Using adapter-node as middleware along with a websocket server outside of sveltekit means that, if for example you are using TypeScript and .ts database models (i.e.: my current structure is lib/db/models/[...].ts, fetched from endpoints), you'll need a lot of extra steps to make these usable. In my case this makes it not worth it to even support real time updates :/

@sjmueller

This comment was marked as off-topic.

@benmccann
Copy link
Member

It bothers me somewhat because it isn't integrated in the build system (won't have access to the same environment variables and import.meta goodies, won't be using typescript unless I set up a separate build, ...).

This should now be addressed since #2931. Previously, adapter-node had a second bundling step using esbuild. Now Vite does everything

@bluepuma77
Copy link

@benmccann It would be great if we could get a quick description (or link to it) how we can use WebSocket with SvelteKit.

Specifically for those web developers not so much involved with the inner workings of SvelteKit and Vite.

@Rich-Harris Rich-Harris added the feature / enhancement New feature or request label Mar 6, 2022
@DaniilKubarenko

This comment was marked as off-topic.

@Rich-Harris Rich-Harris added this to the post-1.0 milestone Apr 25, 2022
@superboss224
Copy link

superboss224 commented May 28, 2022

As of now, Cloudflare Workers, Deno Deploy and AWS are supporting WebSockets
There is gonna be more and more WebSocket support on serverless platforms, having some native and first-class WebSocket support is something we truly need to make immersive experiences that can go realtime

@kevin192291

This comment was marked as off-topic.

@VirgileD

This comment was marked as off-topic.

@cayter
Copy link

cayter commented Jul 19, 2022

Just FYI, for those who are not running on serverless technology, you can rely on this to setup your own HTTP server alongside the websocket server. I had successfully setup a Fastify server + tRPC with subscriptions.

@faulander
Copy link

Can someone point me to a working solution to implement only the client into SvelteKit? Background: DIRECTUS now also has websocket support and currently i am polling the database. Having a subscription would be awesome here, but i seem to be unable to get the client implementation to work.

@Trackhe
Copy link

Trackhe commented Jun 17, 2023

Can someone point me to a working solution to implement only the client into SvelteKit? Background: DIRECTUS now also has websocket support and currently i am polling the database. Having a subscription would be awesome here, but i seem to be unable to get the client implementation to work.

I guess this https://joyofcode.xyz/using-websockets-with-sveltekit is actually the best what you can find.

@s-petey
Copy link

s-petey commented Jun 19, 2023

@faulander

Can someone point me to a working solution to implement only the client into SvelteKit? Background: DIRECTUS now also has websocket support and currently i am polling the database. Having a subscription would be awesome here, but i seem to be unable to get the client implementation to work.

I've made a few different repositories with svelte-kit and some web socket logic. Please ignore my bad ts-ignores.
https://github.com/llmaboi/planning-poker-sveltekit-trpc/blob/main/src/routes/room/%5BroomId%5D/%5BdisplayId%5D/%2Bpage.svelte#L46-L70

@TomDo1234
Copy link

Can someone point me to a working solution to implement only the client into SvelteKit? Background: DIRECTUS now also has websocket support and currently i am polling the database. Having a subscription would be awesome here, but i seem to be unable to get the client implementation to work.

Just install socket io and import that in the client side script tags

@faulander
Copy link

No, that doesn't work. socket.io only supports socket.io

@alanxp
Copy link

alanxp commented Jul 13, 2023

https://github.com/llmaboi/planning-poker-sveltekit-trpc/blob/main/src/routes/room/%5BroomId%5D/%5BdisplayId%5D/%2Bpage.svelte#L46-L70

even throught thats the best option for now, its not made with typescript and that adds a whole new level of complexity

@s-petey
Copy link

s-petey commented Jul 13, 2023

https://github.com/llmaboi/planning-poker-sveltekit-trpc/blob/main/src/routes/room/%5BroomId%5D/%5BdisplayId%5D/%2Bpage.svelte#L46-L70

even throught thats the best option for now, its not made with typescript and that adds a whole new level of complexity

I agree I wish there was a more type safe way I thought of to do this, maybe in future I can, but at least for now there is a workable way. I could probably get it to be more type safe, but would require additional work.

@ghost

This comment was marked as off-topic.

@alanxp

This comment was marked as off-topic.

@benmccann
Copy link
Member

benmccann commented Aug 24, 2023

I understand there's a lot of interest in this, but please don't comment expressing interest. Instead use the thumbs up on the issue. Commenting with expressions of interest makes it harder to find the comments discussing how to implement this and we do need to be able to easily find those details to come up with a design for this. I've hidden all comments that are not relevant to the implementation of the feature.

The core team won't be implementing this in the near future as we're currently focused on a very exciting set of changes in Svelte 5 as detailed in our roadmap: https://svelte.dev/roadmap. However, we will periodically switch back to adding major new features to SvelteKit in the future. You can read more about our process here: https://github.com/sveltejs/svelte/blob/master/CONTRIBUTING.md#our-process.

In team meantime, we do still welcome folks chiming in with technical details that may help this get implemented. Examples that we can follow would be very helpful. Also helping to compile which platforms/adapters can and cannot support the feature. It looks like Node, Cloudflare, Deno, and AWS support web sockets (#1491 (comment)) while Vercel and Netlify do not. We're continuing to review PRs from the community if anyone would like to take a stab at this!

@PhatDump
Copy link

This seems like a very well-done implementation that provides control of the WebSocket server to SvelteKit's server logic:
https://github.com/suhaildawood/SvelteKit-integrated-WebSocket/tree/main

Surprised nobody has linked to this , yet.

@Loizzus
Copy link

Loizzus commented Sep 13, 2023

Bun has a socket server built in. Thoughts on whether or not it could be a viable alternative to node.js and solve our socket issue?

@carlosV2
Copy link

Bun implements uWebsockets natively so, using it's NodeJS package should get you very close to it in NodeJS land. Besides, I believe the problem SvelteKit has is more about how to standardise this feature for any potential adapter and not so much about finding a backend that supports websockets. That said, I guess Bun adds for another backend with full support so the scale can start leaning towards those solutions.

@ADIOP55550
Copy link

ADIOP55550 commented Oct 6, 2023

Bun has a socket server built in. Thoughts on whether or not it could be a viable alternative to node.js and solve our socket issue?

Also they suggest sveltekit-adapter-bun, which has websocket support built-in via their Exposed Websocket Server.
I'm planning to use it soon so I will see if it works.

Edit:
⚠️ As far as I can see this solution is only working after building the app (no hmr) so it will not be a good solution for me

@PimTournaye

This comment was marked as off-topic.

@benmccann
Copy link
Member

We just merged #11649, which adds a supports feature as part of the adapter API to allow adapters to indicate whether they support an optional feature. This could potentially be leveraged for future web socket support allowing us to indicate that adapter-node and adapter-cloudflare support websockets while the other adapters in this repo don't.

@ashwin31
Copy link

ashwin31 commented Mar 9, 2024

We just merged #11649, which adds a supports feature as part of the adapter API to allow adapters to indicate whether they support an optional feature. This could potentially be leveraged for future web socket support allowing us to indicate that adapter-node and adapter-cloudflare support websockets while the other adapters in this repo don't.

then we can add socket.io server to sveltekit like how we do with express js ?
i am running express js + socket.io server separately because sveltekit is not supporting socket.io server.

@craigcosmo

This comment was marked as duplicate.

@aolose
Copy link
Contributor

aolose commented Apr 10, 2024

About using websocket with adapter-node, I have an example to share here and it works well. I wrote a vite plugin with monkey patches to hack the node:http package to get the httpServer. Then you can use your favorite websocket framework normally.

As for adapter-cloudflare you can refer to this.

In fact, the need for websockets represents the user's desire to handle raw requests before entering sveltekit. It is true that incorrect handling can break sveltekit, but the user will be responsible for this.

I hope Sveltekit can provide a hook like import { onMount } from 'svelte'

It could look like below, and it will be handled before the request is processed by sveltekit .

import {onServerMount} from 'svelteKit';
import {WebSocketServer} from 'ws';

onServerMount((httpServerInstance, preRequestHandle) => {
    // for adapter-node
    const ws = new WebSocketServer(httpServerInstance)
    ws.on('connection',(ws)=>{

    })

    // such as cloudflare
    preRequestHandle((req, res, next) => {
        const upgradeHeader = request.headers.get('Upgrade');
        if (!upgradeHeader || upgradeHeader !== 'websocket')return next()
        const webSocketPair = new WebSocketPair();
        const client = webSocketPair[0],
            server = webSocketPair[1];
        server.accept()
        server.on('connection',(ws)=>{

        })
        res.status(101).set('webSocket',client)
    })
})

@jacobbogers
Copy link

Hi I just wanted to chime in

Websockets where specifically designed to piggy back on existing http request response functionality.

There are 2 questions if we want to implement this:

  1. how would a websocket connection be exposed to kit. I think making it look like we currently use api routes would be a good idea and very developer friendly. aka a Specific handler name.
  2. what needs to change in kit code to make above possible

Firstly:
What is a websocket request? what happens when someone types new WebSocket('/chat') for example.
In that case we see just a normal GET request with specific "upgrade headers" on the server side.

Note that the parameter in WebSocket is an actual url/path , this could be the route path to routes/chat/+server.ts

So how can we implement websockets keeping the feel of api routes?

I see 2 possibilities:

  1. kit detects GET request (+ upgrade headers), creates a context for the duration of the websocket lifespan and whenever a data is sent over the line it calls wsEventData(data), in routes/chat/+server.ts. the return value would be the response to said data. The difficulty in this approach is how would the server send data to the client if the server initiates the send... I think therefor 2. (next point) is more appropreate

  2. kit detects GET request (+ upgrade headers), and calls the function UPGRADE in routes/chat/+server.ts after successfull network upgrade.

// called AFTER kit has upgraded the GET request to websocket protocol and ws "open" event is issued
export function UPGRADE(
     event: RequestEvent, // original event for the ws upgrade
     ws: WebSocket  // 
 ) {
     
  
    ws.onmessage = e => {...}
    ws.onerror = e => {...}
    ws.onclose = e => {...}
         
    // WebSocket has  `reqId` property 
    // ws is stored in a global object so if you want to send something you need to fetch the websocket from a global map
}

@ADIOP55550
Copy link

ADIOP55550 commented Jun 21, 2024

For me it would make sense to have it a little more explicit. I am thinking of something like

// /src/routes/.../+server.ts
export const websocket: WebSocketHandler = async ({wss}: {wss:WebSocketServer}) => {
   wss.on("connection", (socket) => {
      socket.on("open", ()=>{/*...*/});
      socket.on("message", ()=>{/*...*/});
      socket.on("close", ()=>{/*...*/});
      socket.on("error", ()=>{/*...*/});

      // It would also allow for server initiated conversation
      socket.emit(/*...*/);
   }
   // Broadcasting to all clients would be possible
   wss.broadcast(/*...*/);
};

OR the bun way:

export const handleWebsocket = {
    message: (ws, msg) => {
        ws.send(msg);
    },
    //optionally, doesnt have to be set - can be used to deny websocket
    upgrade: (req, upgrade) => {
        upgrade(req, {data:1});
    }
}

Edit: now I have second thoughts on whether this is the best form for it, but it feels natural. The downside I see would be that this exported websocket function would be called only once (on server startup) and then all the action would happen inside the callback (wss.on)

@MahmoodKhalil57
Copy link

jacobbogers

Hi I just wanted to chime in

Websockets where specifically designed to piggy back on existing http request response functionality.

any implementation should take this in mind

@LukeHagar
Copy link
Contributor

Hi I just wanted to chime in

Websockets where specifically designed to piggy back on existing http request response functionality.

There are 2 questions if we want to implement this:

  1. how would a websocket connection be exposed to kit. I think making it look like we currently use api routes would be a good idea and very developer friendly. aka a Specific handler name.
  2. what needs to change in kit code to make above possible

Firstly: What is a websocket request? what happens when someone types new WebSocket('/chat') for example. In that case we see just a normal GET request with specific "upgrade headers" on the server side.

Note that the parameter in WebSocket is an actual url/path , this could be the route path to routes/chat/+server.ts

So how can we implement websockets keeping the feel of api routes?

I see 2 possibilities:

  1. kit detects GET request (+ upgrade headers), creates a context for the duration of the websocket lifespan and whenever a data is sent over the line it calls wsEventData(data), in routes/chat/+server.ts. the return value would be the response to said data. The difficulty in this approach is how would the server send data to the client if the server initiates the send... I think therefor 2. (next point) is more appropreate
  2. kit detects GET request (+ upgrade headers), and calls the function UPGRADE in routes/chat/+server.ts after successfull network upgrade.
// called AFTER kit has upgraded the GET request to websocket protocol and ws "open" event is issued
export function UPGRADE(
     event: RequestEvent, // original event for the ws upgrade
     ws: WebSocket  // 
 ) {
     
  
    ws.onmessage = e => {...}
    ws.onerror = e => {...}
    ws.onclose = e => {...}
         
    // WebSocket has  `reqId` property 
    // ws is stored in a global object so if you want to send something you need to fetch the websocket from a global map
}

Assuming that the request that would trigger the UPGRADE function here is still processed by hooks (server, and client), for handling things like auth guards, or populating relevant data. I think this is a pretty elegant solution.

We also want to ensure solutions for handling sets of connections, or accessing a specific connection is well supported.

// Broadcast message to a specific user's clients
function broadcastMessage(userID: string, message: any) {
  if (clients[userID]) {
    clients[userID].forEach((socketID) => {
      io.to(socketID).emit("message", message);
    });
  }
}

This is still the biggest/only thing missing for me using SK day to day.

@Padi2312

This comment was marked as duplicate.

@happydog-bot
Copy link

After looking into this more deeply - could we consider both WebSocket and WebTransport support?

WebTransport (built on HTTP/3) offers some compelling benefits like improved connection handling through QUIC and better multiplexing. While it's not yet supported in Safari, a WebSocket fallback pattern would provide complete browser coverage while letting us take advantage of WebTransport's improvements where available.

Since Deno 2 has strong HTTP/3 capabilities, it would be interesting to explore how SvelteKit could leverage both technologies to give developers options for real-time communication needs. Having both would be the absolute bleeding edge and best opportunity for developers and UIs.

@izakdvlpr

This comment was marked as duplicate.

@benmccann
Copy link
Member

benmccann commented Nov 6, 2024

I just went through the implementations offered by various vendors.

Deno, Bun, Node, and Nitro appear to use a WebSocket API which matches the browser spec (example of using it). Whenever we can use APIs adhering to the spec that is best

Cloudflare seem to have their own API. It's slightly different from the spec. E.g. it uses accept instead of open.

Meanwhile, AWS Lambda appears to go completely off script with an API totally different from anything else and has $connect, $disconnect, and $message routes.

I found https://crossws.unjs.io/, which tries to introduce an API that works across various implementations

@LukeHagar LukeHagar linked a pull request Nov 8, 2024 that will close this issue
13 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature / enhancement New feature or request p1-important SvelteKit cannot be used by a large number of people, basic functionality is missing, etc. size:large significant feature with tricky design questions and multi-day implementation
Projects
None yet
Development

Successfully merging a pull request may close this issue.