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

feat: native support for Websockets #12973

Open
wants to merge 67 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 64 commits
Commits
Show all changes
67 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
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
2 changes: 2 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 @@ -58,3 +58,5 @@ Within the `adapt` method, there are a number of things that an adapter should d
- 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]`.

To add WebSockets to a SvelteKit adapter, you will need to handle upgrading the connection within the adapter. The crossws [adapter integration guides](https://crossws.unjs.io/adapters) may be a helpful reference.
126 changes: 126 additions & 0 deletions documentation/docs/30-advanced/15-websockets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
title: WebSockets
---

## The `socket` object

[WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) provide a way to open a bidirectional communication channel between the client and server.

SvelteKit accepts a `socket` object in `+server.js` files that you can use to handle WebSocket connections to different routes in your app.
LukeHagar marked this conversation as resolved.
Show resolved Hide resolved

The shape of this socket object directly corresponds to the [Hooks](https://crossws.unjs.io/guide/hooks) type in `crossws` as this is the package being used to handle cross-platform WebSocket connections.

```js
/** @type {import('@sveltejs/kit').Socket} **/
export const socket = {
LukeHagar marked this conversation as resolved.
Show resolved Hide resolved
upgrade(req) {
// ...
},

open(peer) {
// ...
},

message(peer, message) {
// ...
},

close(peer, event) {
// ...
},

error(peer, error) {
// ...
}
};
```

### Upgrade

The `upgrade` hook is called when a WebSocket connection is established, and can be used to accept or reject the connection attempt.

Additionally, SvelteKit provides a WebSocket specific `accept` helper function alongside the existing `error` function used for HTTP errors to easily accept or reject connections.

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

/** @type {import('@sveltejs/kit').Socket} **/
export const socket = {
upgrade(req) {
// Accept the WebSocket connection with a return
return accept();

// Reject the WebSocket connection with a standard SvelteKit error
error(401, 'unauthorized');
}

// ...
};
```

### Open

The `open` hook is called when a WebSocket connection is opened. It receives the [peer](https://crossws.unjs.io/guide/peer) WebSocket object as a parameter.

```js
/** @type {import('@sveltejs/kit').Socket} **/
export const socket = {
open(peer) {
// ...
}
};
```

### Message

The `message` hook is called when a message is received from the client. It receives the [peer](https://crossws.unjs.io/guide/peer) WebSocket object and the [message](https://crossws.unjs.io/guide/message) as parameters.

```js
/** @type {import('@sveltejs/kit').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) WebSocket object and the close event as parameters.

```js
/** @type {import('@sveltejs/kit').Socket} **/
export const socket = {
close(peer, event) {
// ...
}
};
```

### Error

The `error` hook is called when a WebSocket connection error occurs. It receives the [peer](https://crossws.unjs.io/guide/peer) WebSocket object and the error as parameters.

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

## Connecting from the client

To connect to a WebSocket endpoint in SvelteKit, you can use the native `WebSocket` class in the browser.

```js
// To connect to src/routes/ws/+server.js
const socket = new WebSocket(`/ws`);
LukeHagar marked this conversation as resolved.
Show resolved Hide resolved
```

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

## Compatibility

SvelteKit uses [`unjs/crossws`](https://crossws.unjs.io) to handle WebSocket connections. Please refer to their [compatibility table](https://crossws.unjs.io/guide/peer#compatibility) for the peer object in different runtime environments.
2 changes: 1 addition & 1 deletion packages/adapter-auto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"devDependencies": {
"@sveltejs/kit": "workspace:^",
"@sveltejs/vite-plugin-svelte": "^5.0.1",
"@types/node": "^18.19.48",
"@types/node": "^20.17.16",
"typescript": "^5.3.3",
"vitest": "^3.0.1"
},
Expand Down
11 changes: 11 additions & 0 deletions packages/adapter-cloudflare-workers/files/entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Server } from 'SERVER';
import { manifest, prerendered, base_path } from 'MANIFEST';
import { getAssetFromKV, mapRequestToAsset } from '@cloudflare/kv-asset-handler';
import static_asset_manifest_json from '__STATIC_CONTENT_MANIFEST';
import crossws from 'crossws/adapters/cloudflare';

const static_asset_manifest = JSON.parse(static_asset_manifest_json);

const server = new Server(manifest);
Expand All @@ -20,6 +22,15 @@ export default {
async fetch(req, env, context) {
await server.init({ env });

const ws = crossws({
resolve: server.resolve()
});

if (req.headers.get('upgrade') === 'websocket') {
// @ts-ignore wtf is Cloudflare doing to these types
return ws.handleUpgrade(req, env, context);
}

const url = new URL(req.url);

// static assets
Expand Down
5 changes: 3 additions & 2 deletions packages/adapter-cloudflare-workers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@
"check": "tsc --skipLibCheck"
},
"dependencies": {
"@cloudflare/workers-types": "^4.20231121.0",
"@cloudflare/workers-types": "^4.20250129.0",
"@iarna/toml": "^2.2.5",
"crossws": "^0.3.3",
"esbuild": "^0.24.0"
},
"devDependencies": {
"@cloudflare/kv-asset-handler": "^0.3.0",
"@sveltejs/kit": "workspace:^",
"@types/node": "^18.19.48",
"@types/node": "^20.17.16",
"typescript": "^5.3.3"
},
"peerDependencies": {
Expand Down
3 changes: 2 additions & 1 deletion packages/adapter-cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@
},
"dependencies": {
"@cloudflare/workers-types": "^4.20241106.0",
"crossws": "^0.3.3",
"esbuild": "^0.24.0",
"worktop": "0.8.0-next.18"
},
"devDependencies": {
"@sveltejs/kit": "workspace:^",
"@types/node": "^18.19.48",
"@types/node": "^20.17.16",
"@types/ws": "^8.5.10",
"typescript": "^5.3.3"
},
Expand Down
12 changes: 12 additions & 0 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 @@ -11,9 +12,20 @@ const version_file = `${app_path}/version.json`;

/** @type {import('worktop/cfw').Module.Worker<{ ASSETS: import('worktop/cfw.durable').Durable.Object }>} */
const worker = {
// @ts-ignore wtf is Cloudflare doing to these types
async fetch(req, env, context) {
// @ts-ignore
await server.init({ env });

const ws = crossws({
resolve: server.resolve()
});

if (req.headers.get('upgrade') === 'websocket') {
// @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
2 changes: 1 addition & 1 deletion packages/adapter-netlify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"@rollup/plugin-node-resolve": "^16.0.0",
"@sveltejs/kit": "workspace:^",
"@sveltejs/vite-plugin-svelte": "^5.0.1",
"@types/node": "^18.19.48",
"@types/node": "^20.17.16",
"@types/set-cookie-parser": "^2.4.7",
"rollup": "^4.14.2",
"typescript": "^5.3.3",
Expand Down
1 change: 1 addition & 0 deletions packages/adapter-node/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ declare module 'ENV' {

declare module 'HANDLER' {
export const handler: import('polka').Middleware;
export const resolve: import('crossws').ResolveHooks;
}

declare module 'MANIFEST' {
Expand Down
3 changes: 2 additions & 1 deletion packages/adapter-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
"@polka/url": "^1.0.0-next.28",
"@sveltejs/kit": "workspace:^",
"@sveltejs/vite-plugin-svelte": "^5.0.1",
"@types/node": "^18.19.48",
"@types/node": "^20.17.16",
"crossws": "^0.3.3",
"polka": "^1.0.0-next.28",
"sirv": "^3.0.0",
"typescript": "^5.3.3",
Expand Down
2 changes: 2 additions & 0 deletions packages/adapter-node/src/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,5 @@ export const handler = sequence(
ssr
].filter(Boolean)
);

export const resolve = server.resolve;
9 changes: 8 additions & 1 deletion packages/adapter-node/src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import process from 'node:process';
import { handler } from 'HANDLER';
import { handler, resolve } from 'HANDLER';
import { env } from 'ENV';
import polka from 'polka';
import crossws from 'crossws/adapters/node';

export const path = env('SOCKET_PATH', false);
export const host = env('HOST', '0.0.0.0');
Expand Down Expand Up @@ -33,6 +34,12 @@ let idle_timeout_id;

const server = polka().use(handler);

const ws = crossws({
resolve
});

server.server.on('upgrade', ws.handleUpgrade);

if (socket_activation) {
server.listen({ fd: SD_LISTEN_FDS_START }, () => {
console.log(`Listening on file descriptor ${SD_LISTEN_FDS_START}`);
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-static/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"@playwright/test": "^1.44.1",
"@sveltejs/kit": "workspace:^",
"@sveltejs/vite-plugin-svelte": "^5.0.1",
"@types/node": "^18.19.48",
"@types/node": "^20.17.16",
"sirv": "^3.0.0",
"svelte": "^5.2.9",
"typescript": "^5.3.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-vercel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"devDependencies": {
"@sveltejs/kit": "workspace:^",
"@sveltejs/vite-plugin-svelte": "^5.0.1",
"@types/node": "^18.19.48",
"@types/node": "^20.17.16",
"typescript": "^5.3.3",
"vitest": "^3.0.1"
},
Expand Down
4 changes: 2 additions & 2 deletions packages/enhanced-img/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@
},
"devDependencies": {
"@types/estree": "^1.0.5",
"@types/node": "^18.19.48",
"@types/node": "^20.17.16",
"rollup": "^4.27.4",
"svelte": "^5.2.9",
"typescript": "^5.6.3",
"typescript": "^5.3.3",
Copy link
Member

@eltigerchino eltigerchino Feb 3, 2025

Choose a reason for hiding this comment

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

This was changed to ^5.6.3 in https://github.com/sveltejs/kit/pull/12822/files#r1803038073 to support top-level jsdoc type imports. We can probably return it back to ^5.3.3 but replace the import statement with inline type imports in the file

/** @import { AST } from 'svelte/compiler' */

"vite": "^6.0.11",
"vitest": "^3.0.1"
},
Expand Down
3 changes: 2 additions & 1 deletion packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"dependencies": {
"@types/cookie": "^0.6.0",
"cookie": "^0.6.0",
"crossws": "^0.3.3",
"devalue": "^5.1.0",
"esm-env": "^1.2.2",
"import-meta-resolve": "^4.1.0",
Expand All @@ -34,7 +35,7 @@
"@playwright/test": "^1.44.1",
"@sveltejs/vite-plugin-svelte": "^5.0.1",
"@types/connect": "^3.4.38",
"@types/node": "^18.19.48",
"@types/node": "^20.17.16",
"@types/set-cookie-parser": "^2.4.7",
"dts-buddy": "^0.5.4",
"rollup": "^4.14.2",
Expand Down
12 changes: 12 additions & 0 deletions packages/kit/src/exports/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,18 @@ export function redirect(status, location) {
);
}

const acceptResponse = new Response(null, {
status: 200
});

/**
* Accepts a WebSocket upgrade request. When called during request handling, SvelteKit will accept the WebSocket upgrade request.
* @return {Response} This response instructs SvelteKit to accept the WebSocket upgrade request.
*/
export function accept() {
return acceptResponse;
}

/**
* Checks whether this is a redirect thrown by {@link redirect}.
* @param {unknown} e The object to check.
Expand Down
7 changes: 7 additions & 0 deletions packages/kit/src/exports/public.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1277,6 +1277,7 @@ export class Server {
constructor(manifest: SSRManifest);
init(options: ServerInitOptions): Promise<void>;
respond(request: Request, options: RequestOptions): Promise<Response>;
resolve(): import('crossws').ResolveHooks;
}

export interface ServerInitOptions {
Expand Down Expand Up @@ -1464,6 +1465,12 @@ export type SubmitFunction<
}) => void)
>;

/**
* Shape of the `export const socket = {..}` object in `+server.js`.
* See [WebSockets](https://svelte.dev/docs/kit/websockets) for more information.
*/
export type Socket = import('crossws').Hooks;

/**
* The type of `export const snapshot` exported from a page or layout component.
*/
Expand Down
Loading
Loading