Skip to content

feat: native support for Websockets #12973

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

Draft
wants to merge 232 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
232 commits
Select commit Hold shift + click to select a range
d0b7f09
example crossws implementation with `hooks.server.js` websocketHooks …
LukeHagar Nov 7, 2024
b69b2e0
Migrated from hooks to server.js export named socket, validated funct…
LukeHagar Nov 8, 2024
aa69e1c
Formatting and fix a test
LukeHagar Nov 8, 2024
38c919c
removed global comment
LukeHagar Nov 8, 2024
2a022b5
regenerated types
LukeHagar Nov 8, 2024
c3a0bf7
removed some log statements
LukeHagar Nov 8, 2024
c86e4e9
cleaning up previous implementation
LukeHagar Nov 8, 2024
5858b49
Thoroughly tested handle implementation
LukeHagar Nov 9, 2024
9d56c50
Cleaning
LukeHagar Nov 9, 2024
46c8682
regenerated types and ran formatter
LukeHagar Nov 9, 2024
7791759
generate types
eltigerchino Nov 11, 2024
70202e3
adjusted implementation to only use responses and the updated crossws…
LukeHagar Jan 23, 2025
92e3e41
corrected example
LukeHagar Jan 23, 2025
db517f6
swapped from browser to onMount
LukeHagar Jan 23, 2025
a43c49b
cleaned log statements
LukeHagar Jan 23, 2025
6469816
added a docs page
LukeHagar Jan 24, 2025
ee0c6ee
updated node adapter
LukeHagar Jan 24, 2025
e737e02
fixed imports and resolve type
LukeHagar Jan 24, 2025
42e2f2d
updating adapters
LukeHagar Jan 24, 2025
0abeb17
Update documentation/docs/30-advanced/15-websockets.md
LukeHagar Jan 24, 2025
2a9971f
Update packages/kit/src/exports/index.js
LukeHagar Jan 24, 2025
cc58820
Update documentation/docs/30-advanced/15-websockets.md
LukeHagar Jan 24, 2025
29fdfec
ditching reject function for existing error
LukeHagar Jan 24, 2025
fda8a68
Update documentation/docs/30-advanced/15-websockets.md
LukeHagar Jan 24, 2025
ff58988
Update documentation/docs/30-advanced/15-websockets.md
LukeHagar Jan 24, 2025
1fed29d
Update documentation/docs/30-advanced/15-websockets.md
LukeHagar Jan 24, 2025
0ea72ab
Update documentation/docs/30-advanced/15-websockets.md
LukeHagar Jan 24, 2025
6408350
moved adapter integration, added response getter to HttpError
LukeHagar Jan 24, 2025
182a666
TABS
LukeHagar Jan 24, 2025
dda7298
corrected package.json versions
LukeHagar Jan 24, 2025
192840a
normalize on error and the response prop
LukeHagar Jan 24, 2025
ec0b797
Merge branch 'main' into crossws
LukeHagar Jan 24, 2025
f3bed08
recreated lockfile
LukeHagar Jan 24, 2025
2f11dca
ran formatter
LukeHagar Jan 24, 2025
3f2679e
fix lint errors
LukeHagar Jan 24, 2025
de37505
Update documentation/docs/25-build-and-deploy/99-writing-adapters.md
LukeHagar Jan 24, 2025
8dfad0c
fix lint errors
LukeHagar Jan 24, 2025
21cb866
added an s
LukeHagar Jan 24, 2025
f70aea1
correcting lockfile issues
LukeHagar Jan 24, 2025
c0a5e3d
regenerated types
LukeHagar Jan 24, 2025
a6bcf60
adjusting types
LukeHagar Jan 25, 2025
d3a48d7
cleaning a log
LukeHagar Jan 27, 2025
fa52645
adding ts comments
LukeHagar Feb 2, 2025
e63dfc3
adjusting ts comments
LukeHagar Feb 2, 2025
a0c54ab
adjusting lib for Generic error
LukeHagar Feb 3, 2025
c9457a4
adjusting ts version for Generic error
LukeHagar Feb 3, 2025
51d4729
updating lockfile
LukeHagar Feb 3, 2025
04b011d
updating generated types
LukeHagar Feb 3, 2025
2ab789f
downgrade typescript and address some types issues
benmccann Feb 3, 2025
4a6c8b3
upgrade @types/node to fix remaining types error
benmccann Feb 3, 2025
9bdfc04
adjusting tests for updated test app
LukeHagar Feb 3, 2025
894f38b
adjusting tests for updated test app
LukeHagar Feb 3, 2025
e7726a6
Update packages/kit/types/index.d.ts
LukeHagar Feb 3, 2025
8de07d1
Update packages/kit/src/exports/index.js
LukeHagar Feb 3, 2025
c18b4e8
Update packages/adapter-node/package.json
LukeHagar Feb 3, 2025
ddc9bc1
Update packages/adapter-cloudflare/package.json
LukeHagar Feb 3, 2025
80e14fe
Update packages/adapter-cloudflare-workers/package.json
LukeHagar Feb 3, 2025
3e58143
Update packages/adapter-node/src/index.js
LukeHagar Feb 3, 2025
7563cb3
Update packages/adapter-node/src/index.js
LukeHagar Feb 3, 2025
fd5b98b
Update packages/kit/src/exports/index.js
LukeHagar Feb 3, 2025
e79716c
updating lockfile
LukeHagar Feb 3, 2025
3f884b1
updating generated types
LukeHagar Feb 3, 2025
dea0952
re-export crossws Hooks type as Socket
eltigerchino Feb 3, 2025
25f039a
oopsie this should be +server.js instead of +page.server.js
eltigerchino Feb 3, 2025
4072369
Update 15-websockets.md
LukeHagar Feb 3, 2025
875536b
re-export Peer and Message types
eltigerchino Feb 3, 2025
84a1aa1
de-duplicate test id
eltigerchino Feb 3, 2025
93d40b2
revert server initialisation timing during dev
eltigerchino Feb 4, 2025
24f3b52
Update packages/kit/src/runtime/server/resolve.js
LukeHagar Feb 4, 2025
8c10aa4
Update packages/kit/src/runtime/server/resolve.js
eltigerchino Feb 4, 2025
52d607a
Update packages/kit/src/runtime/server/resolve.js
eltigerchino Feb 4, 2025
1ac7ccf
Update packages/kit/src/runtime/server/resolve.js
eltigerchino Feb 4, 2025
d9c4b1f
moving on call to after listen
LukeHagar Feb 4, 2025
6a3581b
restoring server options back to private
LukeHagar Feb 4, 2025
e762693
adjusting exports for node adapter
LukeHagar Feb 4, 2025
5c1f334
Update documentation/docs/30-advanced/15-websockets.md
eltigerchino Feb 5, 2025
8f98d61
revert typescript changes
eltigerchino Feb 5, 2025
ac788fb
Merge branch 'main' into pr/LukeHagar/12973
eltigerchino Feb 5, 2025
0a160fd
add Socket type to test app
eltigerchino Feb 5, 2025
d6f4ee5
spruce up docs
eltigerchino Feb 5, 2025
5e07cba
re-generate types
eltigerchino Feb 5, 2025
a0629ba
bump TS to 5.7
eltigerchino Feb 5, 2025
fef9dbe
add comment
eltigerchino Feb 5, 2025
940eb3d
follow mdn recommendation
eltigerchino Feb 5, 2025
e6caf6c
socket closes by itself if we navigate away
eltigerchino Feb 5, 2025
d437299
emphasise all websocket hooks are required
eltigerchino Feb 5, 2025
f203b8d
handle protocol upgrade to websocket
eltigerchino Feb 5, 2025
e6fed10
add ws to vite preview
eltigerchino Feb 5, 2025
f13af3b
i'm dumb
eltigerchino Feb 6, 2025
278c283
clarify
eltigerchino Feb 6, 2025
a458ff3
document node adapter upgradeHandler for custom servers
eltigerchino Feb 6, 2025
b766e06
formatting pass
LukeHagar Feb 6, 2025
3dc373c
tigthen up types
eltigerchino Feb 6, 2025
9597c7f
remove accept helper
eltigerchino Feb 6, 2025
9a839b5
de-duplicate respond logic
eltigerchino Feb 6, 2025
183117f
format
eltigerchino Feb 6, 2025
a9a3f9b
dynamically resolve hooks
eltigerchino Feb 6, 2025
e201b3b
comment
eltigerchino Feb 6, 2025
74937fd
remove cast and ensure headers are added to response
eltigerchino Feb 7, 2025
68bedce
move ws test pages from options-2 to basics
eltigerchino Feb 7, 2025
ec630b8
Update documentation/docs/30-advanced/15-websockets.md
LukeHagar Feb 8, 2025
19f273b
Update documentation/docs/30-advanced/15-websockets.md
eltigerchino Feb 10, 2025
8517855
Merge branch 'crossws' of https://github.com/lukehagar/kit into pr/Lu…
eltigerchino Feb 10, 2025
be4346d
add @since jsdoc
eltigerchino Feb 10, 2025
63cfe11
Merge branch 'main' into pr/LukeHagar/12973
eltigerchino Feb 10, 2025
7eb180f
revert installed typescript version
eltigerchino Feb 10, 2025
c8de055
re-generate types
eltigerchino Feb 10, 2025
6b06673
format
eltigerchino Feb 10, 2025
09c1e2a
revert typescript changes in lockfile
eltigerchino Feb 10, 2025
2fd6711
changeset
eltigerchino Feb 10, 2025
b03206d
re-generate types
eltigerchino Feb 10, 2025
d997737
add buttons to open/close websocket in test
eltigerchino Feb 10, 2025
06063f1
recursively adjusted TS Version back to 5.5.4
LukeHagar Feb 10, 2025
fef8521
Merge branch 'main' into crossws
LukeHagar Feb 10, 2025
0c1de43
updating lockfile after main merge
LukeHagar Feb 10, 2025
6411a4c
formatting run
LukeHagar Feb 10, 2025
db7f8c8
Regenerated types
LukeHagar Feb 10, 2025
23ad389
Update packages/kit/src/runtime/server/respond.js
LukeHagar Feb 10, 2025
3ffe264
removed unused hooks
LukeHagar Feb 10, 2025
367df8f
consolidating logic
LukeHagar Feb 10, 2025
3bc744d
bump crossws version
LukeHagar Feb 10, 2025
31d5617
formatting pass
LukeHagar Feb 10, 2025
fd05a21
correcting return types
LukeHagar Feb 10, 2025
bf9d9d3
restored handle_request overloads to make TS happy
LukeHagar Feb 10, 2025
ffadf9a
added overload comment
LukeHagar Feb 10, 2025
bb7aeb4
added verbatimModuleSyntax to package tsconfig
LukeHagar Feb 10, 2025
d4ca5eb
lowering TS version once more for package build errors
LukeHagar Feb 10, 2025
2b13bba
Update 15-websockets.md
LukeHagar Feb 11, 2025
427aeef
Update 15-websockets.md
LukeHagar Feb 11, 2025
2b6be27
Update 15-websockets.md
LukeHagar Feb 11, 2025
4f7b7ec
Update 15-websockets.md
LukeHagar Feb 11, 2025
f4e6c05
Update 15-websockets.md
LukeHagar Feb 11, 2025
be2a624
Update 15-websockets.md
LukeHagar Feb 11, 2025
651c15e
Update 15-websockets.md
LukeHagar Feb 11, 2025
c902fe8
Update public.d.ts
LukeHagar Feb 11, 2025
304e8a5
Update index.d.ts
LukeHagar Feb 11, 2025
63646f3
attempting to configure supports for websockets
LukeHagar Feb 11, 2025
64fff72
formatting pass
LukeHagar Feb 11, 2025
5570227
regenerated types
LukeHagar Feb 11, 2025
6e12953
adjusted supports usage
LukeHagar Feb 11, 2025
ac17702
regenerated types
LukeHagar Feb 11, 2025
6640525
rename test file from ts to js
eltigerchino Feb 12, 2025
c5c9f0a
ensure hooks call handleError when error is thrown
eltigerchino Feb 12, 2025
5ee9478
ensure dev updates work on websockets
eltigerchino Feb 12, 2025
74fcc83
dont reset test messages
eltigerchino Feb 12, 2025
2896ecf
Merge branch 'crossws' of https://github.com/lukehagar/kit into pr/Lu…
eltigerchino Feb 12, 2025
7905670
three dots instead of two
eltigerchino Feb 12, 2025
0560640
we no longer need to upgrade node types because TS is less than 5.7
eltigerchino Feb 12, 2025
a72bac6
inline getClientAddress
eltigerchino Feb 12, 2025
32981e0
only handle websocket upgrade requests
eltigerchino Feb 12, 2025
f7a88dd
clarify when kit hooks run
eltigerchino Feb 12, 2025
87c3071
remove verbatimModuleSyntax tsconfig option
eltigerchino Feb 12, 2025
5ec65b0
integrate adapter supports API
eltigerchino Feb 12, 2025
330c325
make adapter backwards compatible with older kit versions
eltigerchino Feb 12, 2025
9582975
no clue when the error hook actually runs
eltigerchino Feb 12, 2025
1c314ef
check feature when endpoint is accessed
eltigerchino Feb 12, 2025
0c497bb
clarify why we manually validate exports
eltigerchino Feb 12, 2025
51783ae
only handle upgrades if socket export exists
eltigerchino Feb 12, 2025
737d352
pass read and emulator to vite preview ws hooks resolver
eltigerchino Feb 12, 2025
fd956cb
Update documentation/docs/30-advanced/20-hooks.md
eltigerchino Feb 12, 2025
3977af6
revert blankspace removal
eltigerchino Feb 12, 2025
fbc9a8a
Merge branch 'crossws' of https://github.com/lukehagar/kit into pr/Lu…
eltigerchino Feb 12, 2025
7d0b219
better comment
eltigerchino Feb 12, 2025
448f46a
standardise node request and reject upgrade if socket doesn't exist
eltigerchino Feb 13, 2025
ba58ee7
add partial tests
eltigerchino Feb 13, 2025
fe48df9
fix handle hook returning response during upgrade
eltigerchino Feb 13, 2025
335f58c
fix vite preview server listening for upgrade
eltigerchino Feb 14, 2025
e933ea7
test unsuccessful upgrade requests
eltigerchino Feb 14, 2025
7f74b95
Merge branch 'main' into pr/LukeHagar/12973
eltigerchino Feb 14, 2025
12f7a97
format
eltigerchino Feb 14, 2025
7a6615e
fix test endpoint url
eltigerchino Feb 14, 2025
82e35ea
change response property to a getter
eltigerchino Feb 17, 2025
71cb0c5
generate types
eltigerchino Feb 17, 2025
5d65b6f
support redirect
eltigerchino Feb 18, 2025
e73415c
update error hook description
eltigerchino Feb 18, 2025
d122476
finish up tests
eltigerchino Feb 18, 2025
71376db
format
eltigerchino Feb 18, 2025
10e9274
respond with error if incorrect basepath of manifest error
eltigerchino Feb 18, 2025
5ff6324
remove response property from control classes
eltigerchino Feb 19, 2025
5595d34
reduce test flakiness
eltigerchino Feb 19, 2025
628323a
wait for error to be written to disk
eltigerchino Feb 19, 2025
16ddd0c
try waiting for open event before message
eltigerchino Feb 19, 2025
52677d7
better upgrade / reject tests
eltigerchino Feb 20, 2025
d6e64ab
client must have sent the same protocol to accept it
eltigerchino Feb 20, 2025
98c2021
include crossws as workers entry dep
eltigerchino Feb 20, 2025
5de28ec
trim header string values
eltigerchino Feb 20, 2025
0a80c3c
Merge branch 'main' into pr/LukeHagar/12973
eltigerchino Feb 24, 2025
13113c2
pass request event to upgrade hook and export publish and peers helpers
eltigerchino Mar 5, 2025
be8f67e
Merge branch 'main' into pr/LukeHagar/12973
eltigerchino Mar 5, 2025
5e2bfc8
oops
eltigerchino Mar 5, 2025
4d89bba
return error response format based on request accept header
eltigerchino Mar 5, 2025
b34d7c8
fix tests and try running in parallel on ci
eltigerchino Mar 6, 2025
4746851
fix doc
eltigerchino Mar 7, 2025
74e2a86
expect type error in docs
eltigerchino Mar 7, 2025
ef0619d
again
eltigerchino Mar 7, 2025
dfaa63d
add context to the upgrade hook parameter
eltigerchino Mar 10, 2025
56b91df
this property is already inherited
eltigerchino Mar 10, 2025
514fb6d
pass context to upgrade hook event and apply patches
eltigerchino Mar 10, 2025
4c0ee68
Merge branch 'main' into pr/LukeHagar/12973
eltigerchino Mar 10, 2025
2b36ccc
fix lint
eltigerchino Mar 10, 2025
13d11c5
generate types
eltigerchino Mar 10, 2025
c64ce20
oops
eltigerchino Mar 10, 2025
4bab022
refactor
eltigerchino Mar 10, 2025
6ca1034
close websockets during graceful shutdown
eltigerchino Mar 10, 2025
440e71c
change the upgrade param name to event
eltigerchino Mar 10, 2025
a1f9e05
set body if response is returned from upgrade
eltigerchino Mar 11, 2025
6931bfa
update type docs
eltigerchino Mar 11, 2025
802d925
update @since version
eltigerchino Mar 11, 2025
f46d2bf
fix lint
eltigerchino Mar 11, 2025
b99608e
update docs for writing adapters
eltigerchino Mar 11, 2025
1998d7b
fix links to doc
eltigerchino Mar 11, 2025
cf3361a
Merge branch 'main' into pr/LukeHagar/12973
eltigerchino Mar 17, 2025
ef444e2
Merge branch 'main' into pr/LukeHagar/12973
eltigerchino Mar 19, 2025
23b4f31
Merge branch 'main' into pr/LukeHagar/12973
eltigerchino Mar 26, 2025
20ee709
Merge branch 'main' into pr/LukeHagar/12973
eltigerchino Apr 7, 2025
0402340
fix route id and params typing
eltigerchino Apr 7, 2025
8eb64dc
add request event to peer
eltigerchino Apr 8, 2025
3de9608
Update .changeset/tricky-drinks-develop.md
eltigerchino Apr 11, 2025
2ec6f55
Update documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md
eltigerchino Apr 11, 2025
5fbc9c8
probably don't need this docs change
eltigerchino Apr 11, 2025
8cbc879
mentions the supports property
eltigerchino Apr 11, 2025
03bce3e
reduce indentation
eltigerchino Apr 11, 2025
b625c85
add cloudflare tests
eltigerchino Apr 11, 2025
53ca1fa
Update .changeset/two-islands-sleep.md
eltigerchino Apr 14, 2025
1d812e1
add node integration test suite
eltigerchino Apr 23, 2025
d65212c
Merge branch 'main' into pr/LukeHagar/12973
eltigerchino Apr 23, 2025
ed0b01d
these should be imported from HANDLER
eltigerchino Apr 23, 2025
276c828
add node tests
eltigerchino Apr 23, 2025
7db389f
Merge branch 'main' into pr/LukeHagar/12973
eltigerchino May 5, 2025
3059744
merge main
Rich-Harris May 5, 2025
08a3fcc
format
eltigerchino May 5, 2025
d8d803f
Merge branch 'crossws' of https://github.com/lukehagar/kit into pr/Lu…
eltigerchino May 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/tricky-drinks-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@sveltejs/adapter-cloudflare': minor
'@sveltejs/adapter-node': minor
'@sveltejs/kit': minor
---

feat: add support for WebSockets
5 changes: 5 additions & 0 deletions .changeset/two-islands-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/adapter-auto': patch
---

fix: add error message when using WebSockets as they are unsupported
13 changes: 9 additions & 4 deletions documentation/docs/25-build-and-deploy/40-adapter-node.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,12 @@ WantedBy=sockets.target

The adapter creates two files in your build directory — `index.js` and `handler.js`. Running `index.js` — e.g. `node build`, if you use the default build directory — will start a server on the configured port.

Alternatively, you can import the `handler.js` file, which exports a handler suitable for use with [Express](https://github.com/expressjs/express), [Connect](https://github.com/senchalabs/connect) or [Polka](https://github.com/lukeed/polka) (or even just the built-in [`http.createServer`](https://nodejs.org/dist/latest/docs/api/http.html#httpcreateserveroptions-requestlistener)) and set up your own server:
Alternatively, you can import the `handler.js` file, which exports handlers suitable for use with [Express](https://github.com/expressjs/express), [Connect](https://github.com/senchalabs/connect) or [Polka](https://github.com/lukeed/polka) (or even just the built-in [`http.createServer`](https://nodejs.org/dist/latest/docs/api/http.html#httpcreateserveroptions-requestlistener)) and set up your own server:

```js
// @errors: 2307 7006
/// file: my-server.js
import { handler } from './build/handler.js';
import { handler, upgradeHandler } from './build/handler.js';
import express from 'express';

const app = express();
Expand All @@ -256,10 +256,15 @@ app.get('/healthcheck', (req, res) => {
res.end('ok');
});

// let SvelteKit handle everything else, including serving prerendered pages and static assets
// let SvelteKit handle serving prerendered pages, static assets, and SSR
app.use(handler);

app.listen(3000, () => {
const server = app.listen(3000, () => {
console.log('listening on port 3000');
});

// let SvelteKit handle upgrades for WebSocket connections
server.on('upgrade', upgradeHandler);
```

If you're manually handling the `SIGTERM` and `SIGINT` signal events to implement your own graceful shutdown, you must use the `closeAllWebSockets` and the `terminateAllWebSockets` helpers imported from the `handler.js` file to gracefully close or immediately terminate all active WebSocket connections.
23 changes: 23 additions & 0 deletions documentation/docs/25-build-and-deploy/99-writing-adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,23 @@ export default function (options) {
// Return `true` if the route with the given `config` can use `read`
// from `$app/server` in production, return `false` if it can't.
// Or throw a descriptive error describing how to configure the deployment
},
webSockets: {
socket: () => {
// Return `true` if the production environment supports WebSockets,
// return `false` if it can't.
// Or throw a descriptive error describing how to configure the deployment
},
getPeers: ({ route }) => {
// Return `true` if the production environment supports WebSockets,
// return `false` if it can't.
// Or throw a descriptive error describing how to configure the deployment
},
Comment on lines +44 to +48
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more a note to self as i work through the PR than anything, but: the description here is identical to the one for socket. Are there cases where one would be supported but not the other? If so it might be helpful if the descriptions explain the difference

publish: ({ route }) => {
// Return `true` if the production environment supports coordination among
// multiple WebSockets, return `false` if it can't.
// Or throw a descriptive error describing how to configure the deployment
}
}
}
};
Expand All @@ -51,10 +68,16 @@ Within the `adapt` method, there are a number of things that an adapter should d
- Output code that:
- Imports `Server` from `${builder.getServerDirectory()}/index.js`
- Instantiates the app with a manifest generated with `builder.generateManifest({ relativePath })`
- Initialises the server by calling the `server.init({ env })` function
- Listens for requests from the platform, converts them to a standard [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) if necessary, calls the `server.respond(request, { getClientAddress })` function to generate a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) and responds with it
- expose any platform-specific information to SvelteKit via the `platform` option passed to `server.respond`
- Globally shims `fetch` to work on the target platform, if necessary. SvelteKit provides a `@sveltejs/kit/node/polyfills` helper for platforms that can use `undici`
- Bundle the output to avoid needing to install dependencies on the target platform, if necessary
- Put the user's static files and the generated JS/CSS in the correct location for the target platform

Where possible, we recommend putting the adapter output under the `build/` directory with any intermediate output placed under `.svelte-kit/[adapter-name]`.

If your environment supports WebSockets, you will need to configure the `supports.webSockets` property returned by the adapter and integrate [`crossws`](https://crossws.unjs.io/adapters) into your adapter by outputting code that:

- Initialises the server with the `crossws` adapter's `peers` and `publish` utilities by calling the `server.init({ env, peers, publish })` function
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there also something about supports that we should mention here?

Copy link
Member

@eltigerchino eltigerchino Apr 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added "you will need to configure the supports.webSockets property returned by the adapter" in the paragraph above. Is that descriptive enough? (the property is explained in more detail in the example adapter code block shown earlier)

- Listens for requests from the platform that have an `Upgrade: websocket` header, converts them to a standard `Request` if necessary, calls the `server.resolveWebSocketHooks(request, { getClientAddress })` function to resolve the WebSocket hooks, and passes it to the `crossws` adapter's `resolve` option.
137 changes: 137 additions & 0 deletions documentation/docs/30-advanced/15-websockets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
title: WebSockets
---

[WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) provide a way to open a bidirectional communication channel between a client and a server. SvelteKit uses [`crossws`](https://crossws.unjs.io/) to provide a consistent interface across different platforms.

## Hooks

A `+server.js` file can export a `socket` object with [hooks](https://crossws.unjs.io/guide/hooks), all optional, to handle the different stages of the WebSocket lifecycle.

```js
/** @type {import('./$types').Socket} **/
export const socket = {
upgrade(event) {
// ...
},
open(peer) {
// ...
},
message(peer, message) {
// ...
},
close(peer, details) {
// ...
},
error(peer, error) {
// ...
}
};
```

### upgrade

The `upgrade` hook is called before a WebSocket connection is established. It receives a [`RequestEvent`](@sveltejs-kit#RequestEvent) object that has an additional [`context`](https://crossws.unjs.io/guide/peer#peercontext) property to store abitrary information that is shared with that connection's [`Peer`](https://crossws.unjs.io/guide/peer) object.

You can use the [`error`](@sveltejs-kit#error) function imported from `@sveltejs/kit` to easily reject connections. Requests will be auto-accepted if the `upgrade` hook is not defined or does not `error`.

```js
import { error } from "@sveltejs/kit";

/** @type {import('./$types').Socket} **/
export const socket = {
upgrade({ request }) {
if (request.headers.get('origin') !== 'my_allowed_origin') {
// Reject the WebSocket connection by throwing an error
error(403, 'Forbidden');
}
}
};
```

### open

The `open` hook is called when a WebSocket connection is opened. It receives a [`Peer`](https://crossws.unjs.io/guide/peer) object to allow interacting with connected clients.

```js
/** @type {import('./$types').Socket} **/
export const socket = {
open(peer) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be a deviation from the crossws API but if we used this signature instead we would be able to access the RequestEvent without adding the event property to the peer object (which is also a deviation from the crossws API, but a more subtle one, and potentially a more breaky one if crossws were to later add an event property of their own to peer)

Suggested change
open(peer) {
open({ peer, event }) {

Similarly for message etc — could be message({ peer, message, event }) and so on

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the experience this destructing provides, and love it today with the existing parts of kit

// ...
}
};
```

### message

The `message` hook is called when a message is received from the client. It receives the [`Peer`](https://crossws.unjs.io/guide/peer) object to allow interacting with connected clients and the [`Message`](https://crossws.unjs.io/guide/message) object which contains data from the client.

```js
/** @type {import('./$types').Socket} **/
export const socket = {
message(peer, message) {
// ...
}
};
```

### close

The `close` hook is called when a WebSocket connection is closed. It receives the [`Peer`](https://crossws.unjs.io/guide/peer) object to allow interacting with connected clients and the close details object which contains the [WebSocket connection close code](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code#value) and reason.

```js
/** @type {import('./$types').Socket} **/
export const socket = {
close(peer, details) {
// ...
}
};
```

### error

The `error` hook is called when a connection with a WebSocket has been closed due to an error. It receives the [`Peer`](https://crossws.unjs.io/guide/peer) object to allow interacting with connected clients and the error.

```js
/** @type {import('./$types').Socket} **/
export const socket = {
error(peer, error) {
// ...
}
};
```

## Accessing `RequestEvent` through `Peer`

The [`Peer`](https://crossws.unjs.io/guide/peer) object has been extended to include the [`RequestEvent`](@sveltejs-kit#RequestEvent) object from the initial upgrade request. It can be accessed through the `peer.event` property.
Comment on lines +104 to +106
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see prior note on tweaking the socket signature rather than monkey-patching peer


## `getPeers` and `publish`

The [`getPeers`]($app-server#getPeers) and [`publish`]($app-server#publish) functions from `$app/server` can be used to interact with your WebSocket connections from anywhere on the server.
Comment on lines +108 to +110
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand these functions — aren't they specific to a socket? publish takes a topic but it's not clear how I would subscribe to a topic in the first place

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok @eltigerchino has educated me here on how these work. My feeling is that we shouldn't expose getPeers and publish, because if getPeers returns all peers for all socket exports, then it becomes a source of bugs. Say I'm using getPeers somewhere in my app and then another developers adds another +server.ts with a socket export. Suddenly I'll start broadcasting to the wrong peers.

Instead I think we should probably encourage people to handle peers manually, if you want to interact with them from outside the socket handlers:

// src/lib/topics/foo.ts
export const peers = new Set();
// src/routes/foo/+server.ts
import { peers } from '$lib/topics/foo';

export const socket = {
	open({ peer }) {
		peers.add(peer);
    peer.send('hello');
	},

	close({ peer }) {
		peers.delete(peer);
	}
};
// src/routes/elsewhere/+server.ts
import { peers } from '$lib/topics/foo';

export async function POST({ request }) {
	const message = await request.json();

	for (const peer of peers) {
		peer.send(message);
	}
}

One question we're not too sure about: will this work with Durable Objects, or is there some magic for saving/restoring that we don't have access to?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Durable objects have internal persistent state. As either Sqlite like db or KV. It also has in memory store. In example bellow I think sessions is in memory.

https://github.com/cloudflare/workers-chat-demo/blob/master/src/chat.mjs

Often I want to store state in the DO so new joiners get the current state from the DO the incremental state via WS.

So very helpful if DO can be exposed on platform.env.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Helpful if there is a general way to package DOs with Sveltekit through adapter-cloudflare as there are many nice DO things: MCP servers, partykit.

I'd really like to be able to put HelloWorld.durableObject.ts in a route folder and have it added to the worker output by the build.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


## Connecting from the client

To connect to a WebSocket endpoint, you can use the [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket) constructor in the browser.

```svelte
<script>
import { onMount } from 'svelte';

onMount(() => {
// To connect to src/routes/ws/+server.js
const socket = new WebSocket(`/ws`);

socket.addEventListener("open", (event) => {
socket.send("Hello Server!");
});

// ...
});
</script>
```

See [the WebSocket documentation on MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) for more details.

## Compatibility

Please refer to the crossws [`Peer` object compatibility table](https://crossws.unjs.io/guide/peer#compatibility) and [`Message` object compatibility table](https://crossws.unjs.io/guide/message#adapter-support) to know what is supported in different runtime environments.
3 changes: 2 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ export default [
},
ignores: [
'packages/adapter-cloudflare/test/apps/**/*',
'packages/adapter-node/test/apps/**/*',
'packages/adapter-node/rollup.config.js',
'packages/adapter-node/tests/smoke.spec_disabled.js',
'packages/adapter-node/smoke.spec_disabled.js',
'packages/adapter-static/test/apps/**/*',
'packages/create-svelte/shared/**/*',
'packages/create-svelte/templates/**/*',
Expand Down
15 changes: 15 additions & 0 deletions packages/adapter-auto/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,21 @@ export default () => ({
supports_error(
'The read function imported from $app/server only works in certain environments'
);
},
webSockets: {
socket: () => {
supports_error('The socket export only works in environments that support WebSockets');
},
getPeers: () => {
supports_error(
'The getPeers function imported from $app/server only works in environments that support WebSockets'
);
},
publish: () => {
supports_error(
'The publish function imported from $app/server only works in environments that support WebSockets'
);
}
}
}
});
Expand Down
16 changes: 15 additions & 1 deletion packages/adapter-cloudflare/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import process from 'node:process';
import { fileURLToPath } from 'node:url';
import { getPlatformProxy, unstable_readConfig } from 'wrangler';

const name = '@sveltejs/adapter-cloudflare';

/** @type {import('./index.js').default} */
export default function (options = {}) {
return {
name: '@sveltejs/adapter-cloudflare',
name,
async adapt(builder) {
if (existsSync('_routes.json')) {
throw new Error(
Expand Down Expand Up @@ -160,6 +162,18 @@ export default function (options = {}) {
return prerender ? emulated.prerender_platform : emulated.platform;
}
};
},
supports: {
webSockets: {
socket: () => true,
getPeers: () => true,
publish: ({ route }) => {
// TODO: allow WebSocket integration with Durable Objects using crossws/adapters/cloudflare-durable
throw new Error(
`${name}: Cannot use \`publish\` from \`$app/server\` in route \`${route.id}\` because Cloudflare Workers cannot coordinate among multiple WebSocket connections without Durable Objects`
);
}
}
}
};
}
Expand Down
1 change: 1 addition & 0 deletions packages/adapter-cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
},
"dependencies": {
"@cloudflare/workers-types": "^4.20250415.0",
"crossws": "^0.3.4",
"worktop": "0.8.0-next.18"
},
"devDependencies": {
Expand Down
59 changes: 45 additions & 14 deletions packages/adapter-cloudflare/src/worker.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Server } from 'SERVER';
import { manifest, prerendered, base_path } from 'MANIFEST';
import * as Cache from 'worktop/cfw.cache';
import crossws from 'crossws/adapters/cloudflare';

const server = new Server(manifest);

Expand All @@ -9,6 +10,17 @@ const app_path = `/${manifest.appPath}`;
const immutable = `${app_path}/immutable/`;
const version_file = `${app_path}/version.json`;

/** @type {import('crossws').ResolveHooks} */
let resolve_websocket_hooks;
/** @type {import('crossws/adapters/cloudflare').CloudflareAdapter} */
let ws;

if (server.resolveWebSocketHooks) {
ws = crossws({
resolve: (req) => resolve_websocket_hooks(req)
});
}

export default {
/**
* @param {Request} req
Expand All @@ -17,11 +29,38 @@ export default {
* @returns {Promise<Response>}
*/
async fetch(req, env, context) {
const options = /** @satisfies {Parameters<typeof server.respond>[1]} */ ({
platform: {
env,
context,
// @ts-expect-error webworker types from worktop are not compatible with Cloudflare Workers types
caches,
// @ts-expect-error the type is correct but ts is confused because platform.cf uses the type from index.ts while req.cf uses the type from index.d.ts
cf: req.cf
},
getClientAddress() {
return req.headers.get('cf-connecting-ip');
}
});

await server.init({
// @ts-expect-error env contains environment variables and bindings
env
env,
peers: ws?.peers,
publish: ws?.publish
});

if (req.headers.get('upgrade') === 'websocket' && ws) {
const hooks = await server.resolveWebSocketHooks(
req,
// @ts-ignore
options
);
resolve_websocket_hooks = () => hooks;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a possible race condition here? If two upgrade requests came in at the same moment, could the resolve_websocket_hooks called by the ws instance's resolve hook be (incorrectly) shared by the two requests?

It feels like if we had the ability to create the ws instance here instead of sharing it between all requests, we'd be able to eliminate this risk and also reduce some of the indirection. But IIUC we can only call it once otherwise peers can't talk to each other

// @ts-ignore wtf is Cloudflare doing to these types
return ws.handleUpgrade(req, env, context);
}

// skip cache if "cache-control: no-cache" in request
let pragma = req.headers.get('cache-control') || '';
let res = !pragma.includes('no-cache') && (await Cache.lookup(req));
Expand Down Expand Up @@ -67,19 +106,11 @@ export default {
});
} else {
// dynamically-generated pages
res = await server.respond(req, {
platform: {
env,
context,
// @ts-expect-error webworker types from worktop are not compatible with Cloudflare Workers types
caches,
// @ts-expect-error the type is correct but ts is confused because platform.cf uses the type from index.ts while req.cf uses the type from index.d.ts
cf: req.cf
},
getClientAddress() {
return req.headers.get('cf-connecting-ip');
}
});
res = await server.respond(
req,
// @ts-ignore wtf is Cloudflare doing to these types
options
);
}

// write to `Cache` only if response is not an error,
Expand Down
Loading
Loading