diff --git a/package.json b/package.json
index 7f253022..3a5f1d85 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"test:types": "vitest typecheck"
},
"dependencies": {
+ "@types/ws": "^8.5.5",
"cookie-es": "^1.0.0",
"defu": "^6.1.2",
"destr": "^2.0.1",
@@ -39,7 +40,8 @@
"radix3": "^1.1.0",
"ufo": "^1.3.0",
"uncrypto": "^0.1.3",
- "unenv": "^1.7.4"
+ "unenv": "^1.7.4",
+ "ws": "^8.14.2"
},
"devDependencies": {
"0x": "^5.6.0",
diff --git a/playground/app.ts b/playground/app.ts
index ffa15e72..0f57d2b9 100644
--- a/playground/app.ts
+++ b/playground/app.ts
@@ -1,13 +1,36 @@
-import { createApp, createRouter, eventHandler } from "h3";
-
+import {
+ createApp,
+ eventHandler,
+ upgradeWebSocket,
+ isWebSocketUpgradeRequest,
+ isWebSocketEvent,
+ toWebSocketEvent,
+} from "h3";
export const app = createApp();
-const router = createRouter();
-app.use(router);
-
-router.get(
+app.use(
"/",
eventHandler((event) => {
- return { path: event.path, message: "Hello World!" };
+ if (isWebSocketEvent(event)) {
+ const wsEvent = toWebSocketEvent(event);
+ if (wsEvent.type === "connection") {
+ wsEvent.connection.send("pong");
+ } else if (wsEvent.type === "message") {
+ console.log("got", new TextDecoder().decode(wsEvent.message));
+ }
+ }
+ if (isWebSocketUpgradeRequest(event)) {
+ return upgradeWebSocket(event);
+ }
+
+ return `
Hello World!
`;
}),
);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 16b0dca4..cb60c944 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,9 @@ importers:
.:
dependencies:
+ '@types/ws':
+ specifier: ^8.5.5
+ version: 8.5.5
cookie-es:
specifier: ^1.0.0
version: 1.0.0
@@ -32,6 +35,9 @@ importers:
unenv:
specifier: ^1.7.4
version: 1.7.4
+ ws:
+ specifier: ^8.14.2
+ version: 8.14.2
devDependencies:
0x:
specifier: ^5.6.0
@@ -74,7 +80,7 @@ importers:
version: 1.19.3
listhen:
specifier: ^1.4.4
- version: 1.4.4
+ version: link:../listhen
node-fetch-native:
specifier: ^1.4.0
version: 1.4.0
@@ -898,6 +904,7 @@ packages:
cpu: [arm64]
os: [android]
requiresBuild: true
+ dev: false
optional: true
/@parcel/watcher-darwin-arm64@2.3.0:
@@ -906,6 +913,7 @@ packages:
cpu: [arm64]
os: [darwin]
requiresBuild: true
+ dev: false
optional: true
/@parcel/watcher-darwin-x64@2.3.0:
@@ -914,6 +922,7 @@ packages:
cpu: [x64]
os: [darwin]
requiresBuild: true
+ dev: false
optional: true
/@parcel/watcher-freebsd-x64@2.3.0:
@@ -922,6 +931,7 @@ packages:
cpu: [x64]
os: [freebsd]
requiresBuild: true
+ dev: false
optional: true
/@parcel/watcher-linux-arm-glibc@2.3.0:
@@ -930,6 +940,7 @@ packages:
cpu: [arm]
os: [linux]
requiresBuild: true
+ dev: false
optional: true
/@parcel/watcher-linux-arm64-glibc@2.3.0:
@@ -938,6 +949,7 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
+ dev: false
optional: true
/@parcel/watcher-linux-arm64-musl@2.3.0:
@@ -946,6 +958,7 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
+ dev: false
optional: true
/@parcel/watcher-linux-x64-glibc@2.3.0:
@@ -954,6 +967,7 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
+ dev: false
optional: true
/@parcel/watcher-linux-x64-musl@2.3.0:
@@ -962,6 +976,7 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
+ dev: false
optional: true
/@parcel/watcher-wasm@2.3.0:
@@ -971,6 +986,7 @@ packages:
is-glob: 4.0.3
micromatch: 4.0.5
napi-wasm: 1.1.0
+ dev: false
bundledDependencies:
- napi-wasm
@@ -980,6 +996,7 @@ packages:
cpu: [arm64]
os: [win32]
requiresBuild: true
+ dev: false
optional: true
/@parcel/watcher-win32-ia32@2.3.0:
@@ -988,6 +1005,7 @@ packages:
cpu: [ia32]
os: [win32]
requiresBuild: true
+ dev: false
optional: true
/@parcel/watcher-win32-x64@2.3.0:
@@ -996,6 +1014,7 @@ packages:
cpu: [x64]
os: [win32]
requiresBuild: true
+ dev: false
optional: true
/@parcel/watcher@2.3.0:
@@ -1019,6 +1038,7 @@ packages:
'@parcel/watcher-win32-arm64': 2.3.0
'@parcel/watcher-win32-ia32': 2.3.0
'@parcel/watcher-win32-x64': 2.3.0
+ dev: false
/@rollup/plugin-alias@5.0.0(rollup@3.28.1):
resolution: {integrity: sha512-l9hY5chSCjuFRPsnRm16twWBiSApl2uYFLsepQYwtBuAxNMQ/1dJqADld40P0Jkqm65GRTLy/AC6hnpVebtLsA==}
@@ -1190,7 +1210,6 @@ packages:
/@types/node@20.5.8:
resolution: {integrity: sha512-eajsR9aeljqNhK028VG0Wuw+OaY5LLxYmxeoXynIoE6jannr9/Ucd1LL0hSSoafk5LTYG+FfqsyGt81Q6Zkybw==}
- dev: true
/@types/normalize-package-data@2.4.1:
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
@@ -1240,6 +1259,12 @@ packages:
'@types/superagent': 4.1.18
dev: true
+ /@types/ws@8.5.5:
+ resolution: {integrity: sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==}
+ dependencies:
+ '@types/node': 20.5.8
+ dev: false
+
/@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.48.0)(typescript@5.2.2):
resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -1554,6 +1579,7 @@ packages:
/arch@2.2.0:
resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==}
+ dev: false
/argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@@ -2140,6 +2166,7 @@ packages:
arch: 2.2.0
execa: 5.1.1
is-wsl: 2.2.0
+ dev: false
/code-point-at@1.1.0:
resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==}
@@ -2276,6 +2303,7 @@ packages:
/cookie-es@1.0.0:
resolution: {integrity: sha512-mWYvfOLrfEc996hlKcdABeIiPHUPC6DM2QYZdGGOvhOTbA3tjm2eBwqlJpoFdjC89NI4Qt6h0Pu06Mp+1Pj5OQ==}
+ dev: false
/cookie-signature@1.0.6:
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
@@ -2599,6 +2627,7 @@ packages:
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
engines: {node: '>=0.10'}
hasBin: true
+ dev: false
/detective@5.2.1:
resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==}
@@ -3565,6 +3594,7 @@ packages:
/get-port-please@3.0.2:
resolution: {integrity: sha512-c14cAITf0E+uqdxGALvyYHwOL7UsnWcv3oDtgDAZksiVSGN87xlWVUWGZcmWQU3cICdaOxT+6LdQzUfK2ei1SA==}
+ dev: false
/get-port@7.0.0:
resolution: {integrity: sha512-mDHFgApoQd+azgMdwylJrv2DX47ywGq1i5VFJE7fZ0dttNq3iQMfsU4IvEgBHojA3KqEudyu7Vq+oN8kNaNkWw==}
@@ -3712,6 +3742,7 @@ packages:
ufo: 1.3.0
uncrypto: 0.1.3
unenv: 1.7.4
+ dev: false
/has-ansi@2.0.0:
resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==}
@@ -3854,6 +3885,7 @@ packages:
/http-shutdown@1.2.2:
resolution: {integrity: sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw==}
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
+ dev: false
/https-browserify@1.0.0:
resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==}
@@ -3995,6 +4027,7 @@ packages:
/iron-webcrypto@0.8.0:
resolution: {integrity: sha512-gScdcWHjTGclCU15CIv2r069NoQrys1UeUFFfaO1hL++ytLHkVw7N5nXJmFf3J2LEDMz1PkrvC0m62JEeu1axQ==}
+ dev: false
/is-arguments@1.1.1:
resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
@@ -4395,6 +4428,7 @@ packages:
ufo: 1.3.0
untun: 0.1.2
uqr: 0.1.2
+ dev: false
/local-pkg@0.4.3:
resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==}
@@ -4586,6 +4620,7 @@ packages:
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
engines: {node: '>=10.0.0'}
hasBin: true
+ dev: false
/mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
@@ -4775,6 +4810,7 @@ packages:
/napi-wasm@1.1.0:
resolution: {integrity: sha512-lHwIAJbmLSjF9VDRm9GoVOy9AGp3aIvkjv+Kvz9h16QR3uSVYH78PNQUnT2U4X53mhlnV2M7wrhibQ3GHicDmg==}
+ dev: false
/natural-compare-lite@1.4.0:
resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
@@ -4797,6 +4833,7 @@ packages:
/node-addon-api@7.0.0:
resolution: {integrity: sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==}
+ dev: false
/node-fetch-native@1.4.0:
resolution: {integrity: sha512-F5kfEj95kX8tkDhUCYdV8dg3/8Olx/94zB8+ZNthFs6Bz31UpUi8Xh40TN3thLwXgrwXry1pEg9lJ++tLWTcqA==}
@@ -4804,6 +4841,7 @@ packages:
/node-forge@1.3.1:
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
engines: {node: '>= 6.13.0'}
+ dev: false
/node-releases@2.0.13:
resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==}
@@ -5275,6 +5313,7 @@ packages:
/radix3@1.1.0:
resolution: {integrity: sha512-pNsHDxbGORSvuSScqNJ+3Km6QAVqk8CfsCBIEoDgpqLrkD2f3QM4I7d1ozJJ172OmIcoUcerZaNWqtLkRXTV3A==}
+ dev: false
/randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
@@ -6220,6 +6259,7 @@ packages:
/uncrypto@0.1.3:
resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
+ dev: false
/undeclared-identifiers@1.1.3:
resolution: {integrity: sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==}
@@ -6240,6 +6280,7 @@ packages:
mime: 3.0.0
node-fetch-native: 1.4.0
pathe: 1.1.1
+ dev: false
/universalify@2.0.0:
resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
@@ -6263,6 +6304,7 @@ packages:
citty: 0.1.3
consola: 3.2.3
pathe: 1.1.1
+ dev: false
/untyped@1.4.0:
resolution: {integrity: sha512-Egkr/s4zcMTEuulcIb7dgURS6QpN7DyqQYdf+jBtiaJvQ+eRsrtWUoX84SbvQWuLkXsOjM+8sJC9u6KoMK/U7Q==}
@@ -6296,6 +6338,7 @@ packages:
/uqr@0.1.2:
resolution: {integrity: sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==}
+ dev: false
/uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
@@ -6537,6 +6580,19 @@ packages:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: true
+ /ws@8.14.2:
+ resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+ dev: false
+
/xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
diff --git a/src/app.ts b/src/app.ts
index b7241c93..2dbc58ec 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -1,4 +1,5 @@
import { withoutTrailingSlash } from "ufo";
+import { WebSocketServer } from "ws";
import {
lazyEventHandler,
toEventHandler,
@@ -15,6 +16,9 @@ import {
sendWebResponse,
isWebResponse,
sendNoContent,
+ isWebSocketUpgradeRequest,
+ isWebSocketUpgradeResponse,
+ H3WebSocketEvent,
} from "./utils";
import type { EventHandler, LazyEventHandler } from "./types";
@@ -112,7 +116,8 @@ export function use(
export function createAppEventHandler(stack: Stack, options: AppOptions) {
const spacing = options.debug ? 2 : undefined;
- return eventHandler(async (event) => {
+ const ws = new WebSocketServer({ noServer: true });
+ const handler: EventHandler = eventHandler(async (event) => {
// Keep original incoming url accessable
event.node.req.originalUrl =
event.node.req.originalUrl || event.node.req.url || "/";
@@ -154,6 +159,13 @@ export function createAppEventHandler(stack: Stack, options: AppOptions) {
// 5. Try to handle return value
const _body = val === undefined ? undefined : await val;
if (_body !== undefined) {
+ if (
+ _body &&
+ isWebSocketUpgradeResponse(_body) &&
+ isWebSocketUpgradeRequest(event)
+ ) {
+ return handleWebSocketUpgrade(event, ws, handler);
+ }
const _response = { body: _body };
if (options.onBeforeResponse) {
await options.onBeforeResponse(event, _response);
@@ -185,6 +197,8 @@ export function createAppEventHandler(stack: Stack, options: AppOptions) {
await options.onAfterResponse(event, undefined);
}
});
+
+ return handler;
}
function normalizeLayer(input: InputLayer) {
@@ -208,6 +222,61 @@ function normalizeLayer(input: InputLayer) {
} as Layer;
}
+function handleWebSocketUpgrade(
+ event: H3Event,
+ ws: WebSocketServer,
+ handler: EventHandler,
+) {
+ return ws.handleUpgrade(
+ event.node.req,
+ event.node.req.socket,
+ Buffer.alloc(0),
+ (socket) => {
+ event.headers.delete("upgrade");
+ delete event.node.req.headers.upgrade;
+ handler(
+ new H3WebSocketEvent(event.node.req, event.node.res, {
+ type: "connection",
+ // @ts-ignore
+ connection: socket,
+ }),
+ );
+
+ socket.on("message", (message) => {
+ handler(
+ new H3WebSocketEvent(event.node.req, event.node.res, {
+ type: "message",
+ message,
+ // @ts-ignore
+ connection: socket,
+ }),
+ );
+ });
+
+ socket.on("close", () => {
+ handler(
+ new H3WebSocketEvent(event.node.req, event.node.res, {
+ type: "close",
+ // @ts-ignore
+ connection: socket,
+ }),
+ );
+ });
+
+ socket.on("error", (error) => {
+ handler(
+ new H3WebSocketEvent(event.node.req, event.node.res, {
+ type: "error",
+ error,
+ // @ts-ignore
+ connection: socket,
+ }),
+ );
+ });
+ },
+ );
+}
+
function handleHandlerResponse(event: H3Event, val: any, jsonSpace?: number) {
// Empty Content
if (val === null) {
diff --git a/src/event/event.ts b/src/event/event.ts
index d9774406..4fe4df92 100644
--- a/src/event/event.ts
+++ b/src/event/event.ts
@@ -1,7 +1,7 @@
import type { IncomingHttpHeaders } from "node:http";
import type { H3EventContext, HTTPMethod, EventHandlerRequest } from "../types";
import type { NodeIncomingMessage, NodeServerResponse } from "../adapters/node";
-import { sendWebResponse } from "../utils";
+import { sendWebResponse } from "../utils/response";
import { hasProp } from "../utils/internal/object";
export interface NodeEventContext {
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 4404d426..2c31314a 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -10,3 +10,4 @@ export * from "./response";
export * from "./sanitize";
export * from "./session";
export * from "./static";
+export * from "./websocket";
diff --git a/src/utils/websocket.ts b/src/utils/websocket.ts
new file mode 100644
index 00000000..72ce7b1a
--- /dev/null
+++ b/src/utils/websocket.ts
@@ -0,0 +1,79 @@
+import { NodeIncomingMessage, NodeServerResponse } from "../adapters";
+import { H3Event } from "../event";
+
+export class WebSocketUpgradeResponse {
+ status: number;
+ headers: Headers;
+ constructor(_status = 101, _headers: Headers = new Headers()) {
+ this.status = _status;
+ this.headers = _headers;
+ }
+}
+
+export function isWebSocketUpgradeRequest(event: H3Event): boolean {
+ return event.headers.get("upgrade") === "websocket";
+}
+
+export function isWebSocketUpgradeResponse(
+ response: any,
+): response is WebSocketUpgradeResponse {
+ return response instanceof WebSocketUpgradeResponse;
+}
+
+/** A WebSocket connected to the Party */
+export type Connection = WebSocket & {
+ /** Connection identifier */
+ // id: string;
+ // We would have been able to use Websocket::url
+ // but it's not available in the Workers runtime
+ // (rather, url is `null` when using WebSocketPair)
+ // It's also set as readonly, so we can't set it ourselves.
+ // Instead, we'll use the `uri` property.
+ // uri: string;
+};
+
+export type WebSocketEvent =
+ | {
+ type: "connection";
+ connection: Connection;
+ }
+ | {
+ type: "message";
+ message: string | ArrayBuffer | Buffer[];
+ connection: Connection;
+ }
+ | {
+ type: "error";
+ error: Error;
+ connection: Connection;
+ }
+ | {
+ type: "close";
+ connection: Connection;
+ };
+
+export class H3WebSocketEvent extends H3Event {
+ websocket: WebSocketEvent;
+ constructor(
+ req: NodeIncomingMessage,
+ res: NodeServerResponse,
+ wsEvent: WebSocketEvent,
+ ) {
+ super(req, res);
+ this.websocket = wsEvent;
+ }
+}
+
+export function upgradeWebSocket(event: H3Event): WebSocketUpgradeResponse {
+ return new WebSocketUpgradeResponse();
+}
+
+export function isWebSocketEvent(event: H3Event): event is H3WebSocketEvent {
+ // @ts-ignore
+ return !!event.websocket;
+}
+
+export function toWebSocketEvent(event: H3Event): WebSocketEvent {
+ // @ts-ignore
+ return event.websocket;
+}