diff --git a/docs/.config/docs.yaml b/docs/.config/docs.yaml index af719b4a10..e8378ad615 100644 --- a/docs/.config/docs.yaml +++ b/docs/.config/docs.yaml @@ -5,7 +5,7 @@ description: Create web servers with everything you need and deploy them whereve github: unjs/nitro url: https://nitro.unjs.io themeColor: "red" -# automd: true +automd: true redirects: /deploy/node: /deploy/runtimes/node landing: diff --git a/docs/1.guide/8.websocket.md b/docs/1.guide/8.websocket.md new file mode 100644 index 0000000000..53c5ea6af1 --- /dev/null +++ b/docs/1.guide/8.websocket.md @@ -0,0 +1,97 @@ +--- +icon: cib:socket-io +--- + +# WebSocket + +> Nitro natively support a cross platform WebSocket API + +Nitro natively supports runtime agnostic [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) API using [CrossWS](https://crossws.unjs.io/) and [H3 WebSocket](https://h3.unjs.io/guide/websocket). + +: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). +> See [unjs/nitro#2171](https://github.com/unjs/nitro/issues/2171) for platform support status. + +## Usage + +Enable experimental flag first: + +::code-group +```ts [nitro.config.ts] +export default defineNitroConfig({ + experimental: { + websocket: true + } +}) +``` + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + nitro: { + experimental: { + websocket: true + } + } +}) +``` +:: + +Create a websocket handler in `routes/_ws.ts` or `server/routes/_ws.ts` for Nuxt). + + + +```ts [_ws.ts] +export default 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); + }, +}); + +``` + + + +> [!NOTE] +> Nitro allows you defining multiple websocket handlers using same routing of event handlers. + +Use a client to connect to server. Example: (`routes/websocket.ts` or `server/routes/websocket.ts` for Nuxt) + + + +```ts [index.ts] +export default defineEventHandler(() => { + return $fetch( + "https://raw.githubusercontent.com/unjs/crossws/main/examples/h3/public/index.html" + ); +}); + +``` + + + +Now you can try it on `/websocket` route! + +## 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) + +:read-more{to="https://h3.unjs.io/guide/websocket#server-sent-events-sse" title="SSE guide in H3"} diff --git a/docs/1.guide/99.nightly-channel.md b/docs/1.guide/99.nightly.md similarity index 100% rename from docs/1.guide/99.nightly-channel.md rename to docs/1.guide/99.nightly.md diff --git a/docs/bun.lockb b/docs/bun.lockb index c34b96d978..93032b28c0 100755 Binary files a/docs/bun.lockb and b/docs/bun.lockb differ diff --git a/examples/websocket/nitro.config.ts b/examples/websocket/nitro.config.ts new file mode 100644 index 0000000000..dec37bc189 --- /dev/null +++ b/examples/websocket/nitro.config.ts @@ -0,0 +1,5 @@ +export default defineNitroConfig({ + experimental: { + websocket: true, + }, +}); diff --git a/examples/websocket/package.json b/examples/websocket/package.json new file mode 100644 index 0000000000..fb3c547d5d --- /dev/null +++ b/examples/websocket/package.json @@ -0,0 +1,11 @@ +{ + "name": "example-hello-world", + "private": true, + "scripts": { + "dev": "nitro dev", + "build": "nitro build" + }, + "devDependencies": { + "nitropack": "latest" + } +} diff --git a/examples/websocket/routes/_ws.ts b/examples/websocket/routes/_ws.ts new file mode 100644 index 0000000000..d86f1f2fe8 --- /dev/null +++ b/examples/websocket/routes/_ws.ts @@ -0,0 +1,20 @@ +export default 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); + }, +}); diff --git a/examples/websocket/routes/index.ts b/examples/websocket/routes/index.ts new file mode 100644 index 0000000000..012875eebe --- /dev/null +++ b/examples/websocket/routes/index.ts @@ -0,0 +1,5 @@ +export default defineEventHandler(() => { + return $fetch( + "https://raw.githubusercontent.com/unjs/crossws/main/examples/h3/public/index.html" + ); +}); diff --git a/examples/websocket/tsconfig.json b/examples/websocket/tsconfig.json new file mode 100644 index 0000000000..43008af1c7 --- /dev/null +++ b/examples/websocket/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./.nitro/types/tsconfig.json" +} diff --git a/package.json b/package.json index 2f367f652a..4be5fc9ec5 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "citty": "^0.1.6", "consola": "^3.2.3", "cookie-es": "^1.0.0", + "crossws": "^0.2.3", "defu": "^6.1.4", "destr": "^2.0.3", "dot-prop": "^8.0.2", @@ -87,7 +88,7 @@ "fs-extra": "^11.2.0", "globby": "^14.0.1", "gzip-size": "^7.0.0", - "h3": "^1.11.0", + "h3": "^1.11.1", "hookable": "^5.5.3", "httpxy": "^0.1.5", "is-primitive": "^3.0.1", @@ -128,6 +129,7 @@ "@azure/static-web-apps-cli": "^1.1.6", "@cloudflare/workers-types": "^4.20240222.0", "@types/aws-lambda": "^8.10.134", + "@types/bun": "^1.0.7", "@types/estree": "^1.0.5", "@types/etag": "^1.8.3", "@types/fs-extra": "^11.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 90cf2807ba..b34bcc2a72 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,6 +69,9 @@ importers: cookie-es: specifier: ^1.0.0 version: 1.0.0 + crossws: + specifier: ^0.2.3 + version: 0.2.3 defu: specifier: ^6.1.4 version: 6.1.4 @@ -97,8 +100,8 @@ importers: specifier: ^7.0.0 version: 7.0.0 h3: - specifier: ^1.11.0 - version: 1.11.0 + specifier: ^1.11.1 + version: 1.11.1 hookable: specifier: ^5.5.3 version: 5.5.3 @@ -214,6 +217,9 @@ importers: '@types/aws-lambda': specifier: ^8.10.134 version: 8.10.134 + '@types/bun': + specifier: ^1.0.7 + version: 1.0.7 '@types/estree': specifier: ^1.0.5 version: 1.0.5 @@ -344,6 +350,12 @@ importers: specifier: link:../.. version: link:../.. + examples/websocket: + devDependencies: + nitropack: + specifier: link:../.. + version: link:../.. + packages: /@aashutoshrathi/word-wrap@1.2.6: @@ -2180,6 +2192,12 @@ packages: '@types/node': 20.11.20 dev: true + /@types/bun@1.0.7: + resolution: {integrity: sha512-zaPoQi+uBaqy7BwAh6HQ5dSt6H95XeejCSGEukXHYO32xIPdzPXJjNzmCJ64TWCpM4+R7WyPMdCnkZyETAZfuw==} + dependencies: + bun-types: 1.0.28 + dev: true + /@types/caseless@0.12.5: resolution: {integrity: sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==} requiresBuild: true @@ -2417,6 +2435,12 @@ packages: dev: true optional: true + /@types/ws@8.5.10: + resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} + dependencies: + '@types/node': 20.11.20 + dev: true + /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3157,6 +3181,13 @@ packages: semver: 7.6.0 dev: true + /bun-types@1.0.28: + resolution: {integrity: sha512-wQqbLYRM0YnsXZMFujbCr/9YxlEl51jshMXcJ2Y9wEuU7k6TKcX2KDh032k9oHfB1wH8/SleXboIsULMtFaAaA==} + dependencies: + '@types/node': 20.11.20 + '@types/ws': 8.5.10 + dev: true + /bundle-name@3.0.0: resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} engines: {node: '>=12'} @@ -3607,8 +3638,8 @@ packages: shebang-command: 2.0.0 which: 2.0.2 - /crossws@0.2.0: - resolution: {integrity: sha512-WW4qfY5ylZDzTPplWcMVh6dj3IXUme2yb1hGC4wWnAKEwL0txtiRrWdIctSAsDlcfm2udmH7GcH60IT5esY2Zw==} + /crossws@0.2.3: + resolution: {integrity: sha512-bAdbYPj/ej8+WCVRVnjEr54WYI/LncI25G/pJPZteO2CXUQFrhj5IOCscw9qsYKGKTWs9NkimUtTjdp9SoLD9A==} peerDependencies: uWebSockets.js: '*' peerDependenciesMeta: @@ -5381,11 +5412,11 @@ packages: duplexer: 0.1.2 dev: false - /h3@1.11.0: - resolution: {integrity: sha512-j0AFJlyJfYl5EhWk9aiFYaA1ZXKupsIPfQRm5xy7niBbQFSqdDFRkZ6Hzyg73srW/jt0AU5pB42xS2XXfHlBiA==} + /h3@1.11.1: + resolution: {integrity: sha512-AbaH6IDnZN6nmbnJOH72y3c5Wwh9P97soSVdGSBbcDACRdkC0FEWf25pzx4f/NuOCK6quHmW18yF2Wx+G4Zi1A==} dependencies: cookie-es: 1.0.0 - crossws: 0.2.0 + crossws: 0.2.3 defu: 6.1.4 destr: 2.0.3 iron-webcrypto: 1.0.0 @@ -6201,10 +6232,10 @@ packages: citty: 0.1.6 clipboardy: 4.0.0 consola: 3.2.3 - crossws: 0.2.0 + crossws: 0.2.3 defu: 6.1.4 get-port-please: 3.1.2 - h3: 1.11.0 + h3: 1.11.1 http-shutdown: 1.2.2 jiti: 1.21.0 mlly: 1.6.1 @@ -8824,7 +8855,7 @@ packages: anymatch: 3.1.3 chokidar: 3.6.0 destr: 2.0.3 - h3: 1.11.0 + h3: 1.11.1 ioredis: 5.3.2 listhen: 1.7.2 lru-cache: 10.2.0 diff --git a/src/deps.ts b/src/deps.ts index a9a85a1b86..ae66c1da49 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -17,4 +17,5 @@ export const nitroRuntimeDependencies = [ "unctx", "unenv", "unstorage", + "crossws", ]; diff --git a/src/dev/server.ts b/src/dev/server.ts index f892165c75..6f1587adb0 100644 --- a/src/dev/server.ts +++ b/src/dev/server.ts @@ -246,6 +246,17 @@ export function createDevServer(nitro: Nitro): NitroDevServer { let listeners: Listener[] = []; const _listen: NitroDevServer["listen"] = async (port, opts?) => { const listener = await listen(toNodeListener(app), { port, ...opts }); + listener.server.on("upgrade", (req, sock, head) => { + proxy.proxy.ws( + req, + sock as any, + { + target: getWorkerAddress(), + xfwd: true, + }, + head + ); + }); listeners.push(listener); return listener; }; diff --git a/src/rollup/config.ts b/src/rollup/config.ts index cb97f32697..f73bcf0dcf 100644 --- a/src/rollup/config.ts +++ b/src/rollup/config.ts @@ -202,6 +202,7 @@ export const getRollupConfig = (nitro: Nitro): RollupConfig => { "versions?.nitro": nitroPkg.version, // Internal _asyncContext: nitro.options.experimental.asyncContext, + _websocket: nitro.options.experimental.websocket, }; // Universal import.meta diff --git a/src/runtime/entries/bun.ts b/src/runtime/entries/bun.ts index 0f50d73b8d..b9e0c2cf54 100644 --- a/src/runtime/entries/bun.ts +++ b/src/runtime/entries/bun.ts @@ -1,23 +1,33 @@ import "#internal/nitro/virtual/polyfill"; +import type {} from "bun"; +import wsAdapter from "crossws/adapters/bun"; import { nitroApp } from "../app"; -// @ts-expect-error: Bun global +const ws = import.meta._websocket + ? wsAdapter(nitroApp.h3App.websocket) + : undefined; + const server = Bun.serve({ port: process.env.NITRO_PORT || process.env.PORT || 3000, - async fetch(request: Request) { - const url = new URL(request.url); + websocket: import.meta._websocket ? ws.websocket : undefined, + async fetch(req, server) { + if (import.meta._websocket && (await ws.handleUpgrade(req, server))) { + return; + } + + const url = new URL(req.url); let body; - if (request.body) { - body = await request.arrayBuffer(); + if (req.body) { + body = await req.arrayBuffer(); } return nitroApp.localFetch(url.pathname + url.search, { host: url.hostname, protocol: url.protocol, - headers: request.headers, - method: request.method, - redirect: request.redirect, + headers: req.headers, + method: req.method, + redirect: req.redirect, body, }); }, diff --git a/src/runtime/entries/cloudflare-module.ts b/src/runtime/entries/cloudflare-module.ts index 53db2c6010..bdbcc70f6a 100644 --- a/src/runtime/entries/cloudflare-module.ts +++ b/src/runtime/entries/cloudflare-module.ts @@ -8,21 +8,30 @@ import { // @ts-ignore Bundled by Wrangler // See https://github.com/cloudflare/kv-asset-handler#asset_manifest-required-for-es-modules import manifest from "__STATIC_CONTENT_MANIFEST"; +import wsAdapter from "crossws/adapters/cloudflare"; import { requestHasBody } from "../utils"; import { nitroApp } from "#internal/nitro/app"; import { useRuntimeConfig } from "#internal/nitro"; import { getPublicAssetMeta } from "#internal/nitro/virtual/public-assets"; +const ws = import.meta._websocket + ? wsAdapter(nitroApp.h3App.websocket) + : undefined; + interface CFModuleEnv { [key: string]: any; } export default { - async fetch( - request: Request, // CFRequest, - env: CFModuleEnv, - context: ExecutionContext - ) { + async fetch(request: Request, env: CFModuleEnv, context: ExecutionContext) { + // Websocket upgrade + if ( + import.meta._websocket && + request.headers.get("upgrade") === "websocket" + ) { + return ws.handleUpgrade(request as any, env, context); + } + try { // https://github.com/cloudflare/kv-asset-handler#es-modules return await getAssetFromKV( diff --git a/src/runtime/entries/cloudflare-pages.ts b/src/runtime/entries/cloudflare-pages.ts index 743b8e44ca..405855fc59 100644 --- a/src/runtime/entries/cloudflare-pages.ts +++ b/src/runtime/entries/cloudflare-pages.ts @@ -3,6 +3,7 @@ import type { Request as CFRequest, EventContext, } from "@cloudflare/workers-types"; +import wsAdapter from "crossws/adapters/cloudflare"; import { requestHasBody } from "#internal/nitro/utils"; import { nitroApp } from "#internal/nitro/app"; import { isPublicAssetURL } from "#internal/nitro/virtual/public-assets"; @@ -20,12 +21,24 @@ interface CFPagesEnv { [key: string]: any; } +const ws = import.meta._websocket + ? wsAdapter(nitroApp.h3App.websocket) + : undefined; + export default { async fetch( request: CFRequest, env: CFPagesEnv, context: EventContext ) { + // Websocket upgrade + if ( + import.meta._websocket && + request.headers.get("upgrade") === "websocket" + ) { + return ws.handleUpgrade(request as any, env, context); + } + const url = new URL(request.url); if (env.ASSETS /* !miniflare */ && isPublicAssetURL(url.pathname)) { return env.ASSETS.fetch(request); diff --git a/src/runtime/entries/cloudflare.ts b/src/runtime/entries/cloudflare.ts index 5277b5e141..eb16380f4e 100644 --- a/src/runtime/entries/cloudflare.ts +++ b/src/runtime/entries/cloudflare.ts @@ -4,6 +4,7 @@ import { mapRequestToAsset, } from "@cloudflare/kv-asset-handler"; import { withoutBase } from "ufo"; +import wsAdapter from "crossws/adapters/cloudflare"; import { requestHasBody } from "../utils"; import { nitroApp } from "#internal/nitro/app"; import { useRuntimeConfig } from "#internal/nitro"; @@ -13,7 +14,19 @@ addEventListener("fetch", (event: any) => { event.respondWith(handleEvent(event)); }); +const ws = import.meta._websocket + ? wsAdapter(nitroApp.h3App.websocket) + : undefined; + async function handleEvent(event: FetchEvent) { + // Websocket upgrade + if ( + import.meta._websocket && + event.request.headers.get("upgrade") === "websocket" + ) { + return ws.handleUpgrade(event.request as any, {}, event as any); + } + try { return await getAssetFromKV(event, { cacheControl: assetsCacheControl, diff --git a/src/runtime/entries/deno-deploy.ts b/src/runtime/entries/deno-deploy.ts index c23d34d390..e63a98d16d 100644 --- a/src/runtime/entries/deno-deploy.ts +++ b/src/runtime/entries/deno-deploy.ts @@ -1,5 +1,5 @@ import "#internal/nitro/virtual/polyfill"; -// @ts-ignore +import wsAdapter from "crossws/adapters/deno"; import { nitroApp } from "../app"; // https://deno.land/api?s=Deno.ServeHandlerInfo @@ -11,8 +11,17 @@ type ServeHandlerInfo = { }; }; -// @ts-expect-error unknown global Deno +const ws = import.meta._websocket + ? wsAdapter(nitroApp.h3App.websocket) + : undefined; + Deno.serve((request, info) => { + if ( + import.meta._websocket && + request.headers.get("upgrade") === "websocket" + ) { + return ws.handleUpgrade(request, info); + } return handleRequest(request, info); }); diff --git a/src/runtime/entries/deno-server.ts b/src/runtime/entries/deno-server.ts index 18eec92007..795c1fc37d 100644 --- a/src/runtime/entries/deno-server.ts +++ b/src/runtime/entries/deno-server.ts @@ -1,36 +1,31 @@ import "#internal/nitro/virtual/polyfill"; import destr from "destr"; +import wsAdapter from "crossws/adapters/deno"; import { nitroApp } from "../app"; import { useRuntimeConfig } from "#internal/nitro"; -// @ts-expect-error unknown global Deno if (Deno.env.get("DEBUG")) { - addEventListener("unhandledrejection", (event) => + addEventListener("unhandledrejection", (event: any) => console.error("[nitro] [dev] [unhandledRejection]", event.reason) ); - addEventListener("error", (event) => + addEventListener("error", (event: any) => console.error("[nitro] [dev] [uncaughtException]", event.error) ); } else { - addEventListener("unhandledrejection", (err) => + addEventListener("unhandledrejection", (err: any) => console.error("[nitro] [production] [unhandledRejection] " + err) ); - addEventListener("error", (event) => + addEventListener("error", (event: any) => console.error("[nitro] [production] [uncaughtException] " + event.error) ); } -// @ts-expect-error unknown global Deno // https://deno.land/api@v1.34.3?s=Deno.serve&unstable= Deno.serve( { - // @ts-expect-error unknown global Deno key: Deno.env.get("NITRO_SSL_KEY"), - // @ts-expect-error unknown global Deno cert: Deno.env.get("NITRO_SSL_CERT"), - // @ts-expect-error unknown global Deno port: destr(Deno.env.get("NITRO_PORT") || Deno.env.get("PORT")) || 3000, - // @ts-expect-error unknown global Deno hostname: Deno.env.get("NITRO_HOST") || Deno.env.get("HOST"), onListen: (opts) => { const baseURL = (useRuntimeConfig().app.baseURL || "").replace(/\/$/, ""); @@ -41,7 +36,19 @@ Deno.serve( handler ); -async function handler(request: Request) { +// Websocket support +const ws = import.meta._websocket + ? wsAdapter(nitroApp.h3App.websocket) + : undefined; + +async function handler(request: Request, info: any) { + if ( + import.meta._websocket && + request.headers.get("upgrade") === "websocket" + ) { + return ws.handleUpgrade(request, info); + } + const url = new URL(request.url); // https://deno.land/api?s=Body diff --git a/src/runtime/entries/nitro-dev.ts b/src/runtime/entries/nitro-dev.ts index 6aee45ac5f..dc5416f51b 100644 --- a/src/runtime/entries/nitro-dev.ts +++ b/src/runtime/entries/nitro-dev.ts @@ -12,6 +12,7 @@ import { toNodeListener, readBody, } from "h3"; +import wsAdapter from "crossws/adapters/node"; import { nitroApp } from "../app"; import { trapUnhandledNodeErrors } from "../utils"; import { runNitroTask } from "../task"; @@ -19,6 +20,11 @@ import { tasks } from "#internal/nitro/virtual/tasks"; const server = new Server(toNodeListener(nitroApp.h3App)); +if (import.meta._websocket) { + const { handleUpgrade } = wsAdapter(nitroApp.h3App.websocket); + server.on("upgrade", handleUpgrade); +} + function getAddress() { if ( provider === "stackblitz" || diff --git a/src/runtime/entries/node-server.ts b/src/runtime/entries/node-server.ts index 3ed6c48b90..a968ac4af6 100644 --- a/src/runtime/entries/node-server.ts +++ b/src/runtime/entries/node-server.ts @@ -4,6 +4,7 @@ import type { AddressInfo } from "node:net"; import { Server as HttpsServer } from "node:https"; import destr from "destr"; import { toNodeListener } from "h3"; +import wsAdapter from "crossws/adapters/node"; import { nitroApp } from "../app"; import { setupGracefulShutdown } from "../shutdown"; import { trapUnhandledNodeErrors } from "../utils"; @@ -51,4 +52,10 @@ trapUnhandledNodeErrors(); // Graceful shutdown setupGracefulShutdown(listener, nitroApp); +// Websocket support +if (import.meta._websocket) { + const { handleUpgrade } = wsAdapter(nitroApp.h3App.websocket); + server.on("upgrade", handleUpgrade); +} + export default {}; diff --git a/src/runtime/entries/node.ts b/src/runtime/entries/node.ts index fc8acb8755..974738765f 100644 --- a/src/runtime/entries/node.ts +++ b/src/runtime/entries/node.ts @@ -5,6 +5,11 @@ import { trapUnhandledNodeErrors } from "../utils"; export const listener = toNodeListener(nitroApp.h3App); +/** @experimental */ +export const websocket = import.meta._websocket + ? nitroApp.h3App.websocket + : undefined; + /** @deprecated use new `listener` export instead */ export const handler = listener; diff --git a/src/runtime/polyfill/deno-env.ts b/src/runtime/polyfill/deno-env.ts index b6af9830ff..a11725e20e 100644 --- a/src/runtime/polyfill/deno-env.ts +++ b/src/runtime/polyfill/deno-env.ts @@ -1,2 +1 @@ -// @ts-expect-error Deno global Object.assign(process.env, Deno.env.toObject()); diff --git a/src/runtime/utils.ts b/src/runtime/utils.ts index abbaba6e56..244f2dd4dd 100644 --- a/src/runtime/utils.ts +++ b/src/runtime/utils.ts @@ -171,5 +171,6 @@ export function toBuffer(data: ReadableStream | Readable | Uint8Array) { .on("error", reject); }); } + // @ts-ignore return Buffer.from(data as unknown as Uint16Array); } diff --git a/src/types/global.ts b/src/types/global.ts index f245b70aa1..6e72bd79db 100644 --- a/src/types/global.ts +++ b/src/types/global.ts @@ -2,6 +2,7 @@ import type { NitroOptions } from "./nitro"; export interface NitroStaticBuildFlags { _asyncContext?: boolean; + _websocket?: boolean; dev?: boolean; client?: boolean; nitro?: boolean; diff --git a/src/types/nitro.ts b/src/types/nitro.ts index 4e8cae047f..7b3629ad25 100644 --- a/src/types/nitro.ts +++ b/src/types/nitro.ts @@ -292,6 +292,12 @@ export interface NitroOptions extends PresetOptions { * @see https://github.com/unjs/nitro/pull/2043 */ envExpansion?: boolean; + /** + * Enable experimental WebSocket support + * + * @see https://h3.unjs.io/guide/websocket + */ + websocket?: boolean; }; future: { nativeSWR: boolean;