Skip to content

Commit

Permalink
feat: websocket support (#671)
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 authored Feb 25, 2024
1 parent 2d10070 commit 0e930ef
Show file tree
Hide file tree
Showing 16 changed files with 359 additions and 21 deletions.
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

0 comments on commit 0e930ef

Please sign in to comment.