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: websocket support #671

Merged
merged 19 commits into from
Feb 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ dist
lib
.nuxt
.output
docs/**/*.md
2 changes: 1 addition & 1 deletion docs/.config/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description:
github: unjs/h3
themeColor: amber
url: https://h3.unjs.io
# automd: true # HMR seems unstable
automd: true
redirects: {}
landing:
heroLinks:
Expand Down
115 changes: 115 additions & 0 deletions docs/1.guide/6.websocket.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
icon: cib:socket-io
---

# WebSockets

> H3 has built-in support for cross platform WebSocket and SSE.

H3 natively supports runtime agnostic [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) API using [CrossWS](https://crossws.unjs.io/).

::tip
You can define WebSocket handlers in your exisiting [Event Handlers](/guide/event-handler) to define multiple websocket handlers, dynamically matched with your same route defenitions!
::

:read-more{title="WebSocket in MDN" to="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket"}

:read-more{title="CrossWS" to="https://crossws.unjs.io/"}

> [!IMPORTANT]
> WebSockets support is currently experimental and available in [nightly channel](/guide/nightly).

## Usage

### Example

> [!TIP]
> Yuu can use `npx listhen --ws -w websocket.ts` to run this example

<!-- automd:file code src="../../examples/websocket.ts" -->

```ts [websocket.ts]
import { createApp, defineEventHandler, defineWebSocketHandler } from "h3";

export const app = createApp();

app.use(
defineEventHandler(() =>
fetch(
"https://raw.githubusercontent.com/unjs/crossws/main/examples/h3/public/index.html",
).then((r) => r.text()),
),
);

app.use(
"/_ws",
defineWebSocketHandler({
open(peer) {
console.log("[ws] open", peer);
},

message(peer, message) {
console.log("[ws] message", peer, message);
if (message.text().includes("ping")) {
peer.send("pong");
}
},

close(peer, event) {
console.log("[ws] close", peer, event);
},

error(peer, error) {
console.log("[ws] error", peer, error);
},
}),
);

```

<!-- /automd -->

## Server Sent Events (SSE)

As an alternative to WebSockets, you can use [Server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events).

H3 has a built-in API to create server-sent events using `createEventStream(event)` utility.

> [!IMPORTANT]
> SSE support is currently experimental and available in [nightly channel](/guide/nightly).

### Example

<!-- automd:file code src="../../examples/server-sent-events.ts" -->

```ts [server-sent-events.ts]
import { createApp, createRouter, eventHandler, createEventStream } from "h3";

export const app = createApp();

const router = createRouter();
app.use(router);

router.get(
"/",
eventHandler((event) => {
const eventStream = createEventStream(event);

// Send a message every second
const interval = setInterval(async () => {
await eventStream.push("Hello world");
}, 1000);

// cleanup the interval and close the stream when the connection is terminated
eventStream.onClosed(async () => {
clearInterval(interval);
await eventStream.close();
});

return eventStream.send();
}),
);

```

<!-- /automd -->
15 changes: 15 additions & 0 deletions docs/3.adapters/1.node.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,18 @@ Run this command to run your servers:
```sh
npx --yes listhen ./app.ts
```

## WebSocket support

:read-more{to="https://crossws.unjs.io/adapters/node"}

> [!TIP]
> When using listhen method, websocket is supported out of the box!

```ts
import wsAdapter from "crossws/adapters/node";

const { handleUpgrade } = wsAdapter(app.websocket);

server.on("upgrade", handleUpgrade);
```
24 changes: 24 additions & 0 deletions docs/3.adapters/bun.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,27 @@ Now, your can run Bun server:
```bash
bun --bun ./server.mjs
```

## WebSocket support

:read-more{to="https://crossws.unjs.io/adapters/bun"}

```ts
import wsAdapter from "crossws/adapters/cloudflare";

const { websocket, handleUpgrade } = wsAdapter(app.websocket);

const handler = toWebHandler(app)

const server = Bun.serve({
port: 3000,
websocket,
fetch(req, server) {
if (await handleUpgrade(req, server)) {
return;
}
return handler(req)
}
});
```

21 changes: 21 additions & 0 deletions docs/3.adapters/cloudflare.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,27 @@ To deploy, use `wrangler deploy`:
npx wrangler deploy
```

## WebSocket support

:read-more{to="https://crossws.unjs.io/adapters/cloudflare"}

```ts
import wsAdapter from "crossws/adapters/cloudflare";

const { handleUpgrade } = wsAdapter(app.websocket);

export default {
async fetch(request, env, ctx) {
if (request.headers.get("upgrade") === "websocket") {
return handleUpgrade(request, env, context);
}
return handler(request, {
cloudflare: { env, ctx },
});
},
};
```

---

::read-more
Expand Down
19 changes: 19 additions & 0 deletions docs/3.adapters/deno.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,25 @@ To deploy, use `deployctl deploy`:
deployctl deploy --prod --exclude=node_modules --import-map=./import_map.json ./deno.mjs
```

## WebSocket support

:read-more{to="https://crossws.unjs.io/adapters/deno"}

```ts
import wsAdapter from "crossws/adapters/deno";

const handler = toWebHandler(app)

const { handleUpgrade } = wsAdapter(app.websocket);

Deno.serve(request => {
if (request.headers.get("upgrade") === "websocket") {
return handleUpgrade(request);
}
return handler(request)
})
```

---

::read-more
Expand Down
Binary file modified docs/bun.lockb
Binary file not shown.
35 changes: 35 additions & 0 deletions examples/websocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { createApp, defineEventHandler, defineWebSocketHandler } from "h3";

export const app = createApp();

app.use(
defineEventHandler(() =>
fetch(
"https://raw.githubusercontent.com/unjs/crossws/main/examples/h3/public/index.html",
).then((r) => r.text()),
),
);

app.use(
"/_ws",
defineWebSocketHandler({
open(peer) {
console.log("[ws] open", peer);
},

message(peer, message) {
console.log("[ws] message", peer, message);
if (message.text().includes("ping")) {
peer.send("pong");
}
},

close(peer, event) {
console.log("[ws] close", peer, event);
},

error(peer, error) {
console.log("[ws] error", peer, error);
},
}),
);
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
},
"dependencies": {
"cookie-es": "^1.0.0",
"crossws": "^0.2.0",
"defu": "^6.1.4",
"destr": "^2.0.3",
"iron-webcrypto": "^1.0.0",
Expand All @@ -56,7 +57,7 @@
"express": "^4.18.2",
"get-port": "^7.0.0",
"jiti": "^1.21.0",
"listhen": "^1.6.0",
"listhen": "^1.7.2",
"node-fetch-native": "^1.6.2",
"prettier": "^3.2.5",
"react": "^18.2.0",
Expand Down
Loading
Loading