nitro-nightly 3.0.1-20251109-184848-3a5337c9 → 3.0.1-20251109-233730-ab0fad99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -68,7 +68,6 @@ function baseBuildConfig(nitro) {
68
68
  "versions.nitro": "",
69
69
  "versions?.nitro": "",
70
70
  _asyncContext: nitro.options.experimental.asyncContext,
71
- _websocket: nitro.options.experimental.websocket,
72
71
  _tasks: nitro.options.experimental.tasks
73
72
  };
74
73
  const replacements = {
@@ -637,7 +636,8 @@ function featureFlags(nitro) {
637
636
  hasRoutedMiddleware: nitro.routing.routedMiddleware.hasRoutes(),
638
637
  hasGlobalMiddleware: nitro.routing.globalMiddleware.length > 0,
639
638
  hasPlugins: nitro.options.plugins.length > 0,
640
- hasHooks: nitro.options.features?.runtimeHooks ?? nitro.options.plugins.length > 0
639
+ hasHooks: nitro.options.features?.runtimeHooks ?? nitro.options.plugins.length > 0,
640
+ hasWebSocket: nitro.options.features?.websocket ?? nitro.options.experimental.websocket ?? false
641
641
  };
642
642
  return Object.entries(featureFlags$1).map(([key, value]) => `export const ${key} = ${Boolean(value)};`).join("\n");
643
643
  } }, nitro.vfs);
@@ -13,7 +13,7 @@ import "../_libs/tinyglobby.mjs";
13
13
  import "../_libs/compatx.mjs";
14
14
  import "../_libs/klona.mjs";
15
15
  import { r as a } from "../_libs/std-env.mjs";
16
- import "../_chunks/DPeGJhnS.mjs";
16
+ import "../_chunks/Bb_Lq1zU.mjs";
17
17
  import "../_libs/escape-string-regexp.mjs";
18
18
  import "../_libs/tsconfck.mjs";
19
19
  import "../_libs/dot-prop.mjs";
@@ -2,7 +2,7 @@ import { C as join$1, D as relative$1, O as resolve$1, S as isAbsolute$1, b as d
2
2
  import { f as sanitizeFilePath } from "../_libs/local-pkg.mjs";
3
3
  import { t as formatCompatibilityDate } from "../_libs/compatx.mjs";
4
4
  import { n as T, r as a } from "../_libs/std-env.mjs";
5
- import { a as createNitro, n as prepare, r as copyPublicAssets } from "../_chunks/DPeGJhnS.mjs";
5
+ import { a as createNitro, n as prepare, r as copyPublicAssets } from "../_chunks/Bb_Lq1zU.mjs";
6
6
  import { n as prettyPath } from "../_chunks/DTJoprWj.mjs";
7
7
  import { i as scanHandlers } from "../_chunks/CYzvH-lo.mjs";
8
8
  import { n as writeBuildInfo, t as getBuildInfo } from "./common.mjs";
@@ -280,6 +280,10 @@ async function configureViteDevServer(ctx, server) {
280
280
  const nitroEnv$1 = server.environments.nitro;
281
281
  const nitroConfigFile = nitro$1.options._c12.configFile;
282
282
  if (nitroConfigFile) server.config.configFileDependencies.push(nitroConfigFile);
283
+ if (nitro$1.options.features.websocket ?? nitro$1.options.experimental.websocket) server.httpServer.on("upgrade", (req, socket, head) => {
284
+ if (req.url?.startsWith("/?token")) return;
285
+ ctx.devWorker?.upgrade(req, socket, head);
286
+ });
283
287
  const reload = debounce(async () => {
284
288
  await scanHandlers(nitro$1);
285
289
  nitro$1.routing.sync();
@@ -43,6 +43,7 @@ const NitroDefaults = {
43
43
  serverDir: "{{ output.dir }}/server",
44
44
  publicDir: "{{ output.dir }}/public"
45
45
  },
46
+ features: {},
46
47
  experimental: {},
47
48
  future: {},
48
49
  storage: {},
package/dist/_dev.mjs CHANGED
@@ -125,7 +125,9 @@ var NodeDevWorker = class {
125
125
  return this.#proxy.proxy.ws(req, socket, {
126
126
  target: this.#address,
127
127
  xfwd: true
128
- }, head);
128
+ }, head).catch((error) => {
129
+ consola$1.error("WebSocket proxy error:", error);
130
+ });
129
131
  }
130
132
  sendMessage(message) {
131
133
  if (!this.#worker) throw new Error("Dev worker should be initialized before sending messages.");
package/dist/_presets.mjs CHANGED
@@ -529,17 +529,9 @@ async function writeCFPagesRedirects(nitro) {
529
529
  }
530
530
  async function enableNodeCompat(nitro) {
531
531
  nitro.options.cloudflare ??= {};
532
- if (nitro.options.cloudflare.deployConfig === void 0 && p === "cloudflare_workers") nitro.options.cloudflare.deployConfig = true;
533
- if (nitro.options.cloudflare.nodeCompat === void 0) {
534
- const { config } = await readWranglerConfig(nitro);
535
- const userCompatibilityFlags = new Set(config?.compatibility_flags || []);
536
- if (userCompatibilityFlags.has("nodejs_compat") || userCompatibilityFlags.has("nodejs_compat_v2") || nitro.options.cloudflare.deployConfig) nitro.options.cloudflare.nodeCompat = true;
537
- }
538
- if (!nitro.options.cloudflare.nodeCompat) {
539
- if (nitro.options.cloudflare.nodeCompat === void 0) nitro.logger.warn("[cloudflare] Node.js compatibility is not enabled.");
540
- return;
541
- }
542
- nitro.options.unenv.push(unencCfNodeCompat);
532
+ nitro.options.cloudflare.deployConfig ??= true;
533
+ nitro.options.cloudflare.nodeCompat ??= true;
534
+ if (nitro.options.cloudflare.nodeCompat) nitro.options.unenv.push(unencCfNodeCompat);
543
535
  }
544
536
  const extensionParsers = {
545
537
  ".json": h,
@@ -751,7 +743,6 @@ const cloudflareModule = defineNitroPreset({
751
743
  hooks: {
752
744
  "build:before": async (nitro) => {
753
745
  await enableNodeCompat(nitro);
754
- if (nitro.options.builder?.includes("rolldown")) nitro.options.minify = false;
755
746
  },
756
747
  async compiled(nitro) {
757
748
  await writeWranglerConfig(nitro, "module");
package/dist/builder.mjs CHANGED
@@ -13,7 +13,7 @@ import "./_libs/tinyglobby.mjs";
13
13
  import "./_libs/compatx.mjs";
14
14
  import "./_libs/klona.mjs";
15
15
  import "./_libs/std-env.mjs";
16
- import { a as createNitro, c as loadOptions, i as build, n as prepare, o as listTasks, r as copyPublicAssets, s as runTask, t as prerender } from "./_chunks/DPeGJhnS.mjs";
16
+ import { a as createNitro, c as loadOptions, i as build, n as prepare, o as listTasks, r as copyPublicAssets, s as runTask, t as prerender } from "./_chunks/Bb_Lq1zU.mjs";
17
17
  import "./_libs/escape-string-regexp.mjs";
18
18
  import "./_libs/tsconfck.mjs";
19
19
  import "./_libs/dot-prop.mjs";
@@ -1,12 +1,14 @@
1
1
  import "#nitro-internal-pollyfills";
2
- import { useNitroApp, useNitroHooks } from "nitro/app";
3
- import { startScheduleRunner } from "nitro/~internal/runtime/task";
4
- import { trapUnhandledErrors } from "nitro/~internal/runtime/error/hooks";
5
2
  import { Server } from "node:http";
6
3
  import { parentPort, threadId } from "node:worker_threads";
7
4
  import wsAdapter from "crossws/adapters/node";
8
5
  import { toNodeHandler } from "srvx/node";
9
6
  import { getSocketAddress, isSocketSupported } from "get-port-please";
7
+ import { useNitroApp, useNitroHooks } from "nitro/app";
8
+ import { startScheduleRunner } from "nitro/~internal/runtime/task";
9
+ import { trapUnhandledErrors } from "nitro/~internal/runtime/error/hooks";
10
+ import { resolveWebsocketHooks } from "nitro/~internal/runtime/app";
11
+ import { hasWebSocket } from "#nitro-internal-virtual/feature-flags";
10
12
  // Listen for shutdown signal from runner
11
13
  parentPort?.on("message", (msg) => {
12
14
  if (msg && msg.event === "shutdown") {
@@ -23,9 +25,8 @@ listen().catch((error) => {
23
25
  return shutdown();
24
26
  });
25
27
  // https://crossws.unjs.io/adapters/node
26
- if (import.meta._websocket) {
27
- // @ts-expect-error
28
- const { handleUpgrade } = wsAdapter(nitroApp.h3App.websocket);
28
+ if (hasWebSocket) {
29
+ const { handleUpgrade } = wsAdapter({ resolve: resolveWebsocketHooks });
29
30
  server.on("upgrade", handleUpgrade);
30
31
  }
31
32
  // Scheduled tasks
@@ -1,15 +1,27 @@
1
1
  import "#nitro-internal-pollyfills";
2
2
  import { serve } from "srvx/bun";
3
+ import wsAdapter from "crossws/adapters/bun";
3
4
  import { useNitroApp } from "nitro/app";
4
5
  import { startScheduleRunner } from "nitro/~internal/runtime/task";
5
6
  import { trapUnhandledErrors } from "nitro/~internal/runtime/error/hooks";
7
+ import { resolveWebsocketHooks } from "nitro/~internal/runtime/app";
8
+ import { hasWebSocket } from "#nitro-internal-virtual/feature-flags";
6
9
  const port = Number.parseInt(process.env.NITRO_PORT || process.env.PORT || "") || 3e3;
7
10
  const host = process.env.NITRO_HOST || process.env.HOST;
8
11
  const cert = process.env.NITRO_SSL_CERT;
9
12
  const key = process.env.NITRO_SSL_KEY;
10
13
  // const socketPath = process.env.NITRO_UNIX_SOCKET; // TODO
11
- // if (import.meta._websocket) // TODO
12
14
  const nitroApp = useNitroApp();
15
+ let _fetch = nitroApp.fetch;
16
+ const ws = hasWebSocket ? wsAdapter({ resolve: resolveWebsocketHooks }) : undefined;
17
+ if (hasWebSocket) {
18
+ _fetch = (req) => {
19
+ if (req.headers.get("upgrade") === "websocket") {
20
+ return ws.handleUpgrade(req, req.runtime.bun.server);
21
+ }
22
+ return nitroApp.fetch(req);
23
+ };
24
+ }
13
25
  serve({
14
26
  port,
15
27
  hostname: host,
@@ -17,7 +29,8 @@ serve({
17
29
  cert,
18
30
  key
19
31
  } : undefined,
20
- fetch: nitroApp.fetch
32
+ fetch: _fetch,
33
+ bun: { websocket: hasWebSocket ? ws?.websocket : undefined }
21
34
  });
22
35
  trapUnhandledErrors();
23
36
  // Scheduled tasks
@@ -1,9 +1,11 @@
1
1
  import "#nitro-internal-pollyfills";
2
2
  import { DurableObject } from "cloudflare:workers";
3
3
  import wsAdapter from "crossws/adapters/cloudflare";
4
+ import { createHandler, fetchHandler } from "./_module-handler.mjs";
4
5
  import { useNitroApp, useNitroHooks } from "nitro/app";
5
6
  import { isPublicAssetURL } from "#nitro-internal-virtual/public-assets";
6
- import { createHandler, fetchHandler } from "./_module-handler.mjs";
7
+ import { resolveWebsocketHooks } from "nitro/~internal/runtime/app";
8
+ import { hasWebSocket } from "#nitro-internal-virtual/feature-flags";
7
9
  const DURABLE_BINDING = "$DurableObject";
8
10
  const DURABLE_INSTANCE = "server";
9
11
  const nitroApp = useNitroApp();
@@ -16,7 +18,8 @@ const getDurableStub = (env) => {
16
18
  const id = binding.idFromName(DURABLE_INSTANCE);
17
19
  return binding.get(id);
18
20
  };
19
- const ws = import.meta._websocket ? wsAdapter({
21
+ const ws = hasWebSocket ? wsAdapter({
22
+ resolve: resolveWebsocketHooks,
20
23
  instanceName: DURABLE_INSTANCE,
21
24
  bindingName: DURABLE_BINDING
22
25
  }) : undefined;
@@ -29,7 +32,7 @@ export default createHandler({ fetch(request, env, context, url, ctxExt) {
29
32
  ctxExt.durableFetch = (req = request) => getDurableStub(env).fetch(req);
30
33
  // Websocket upgrade
31
34
  // https://crossws.unjs.io/adapters/cloudflare#durable-objects
32
- if (import.meta._websocket && request.headers.get("upgrade") === "websocket") {
35
+ if (hasWebSocket && request.headers.get("upgrade") === "websocket") {
33
36
  return ws.handleUpgrade(request, env, context);
34
37
  }
35
38
  } });
@@ -40,12 +43,12 @@ export class $DurableObject extends DurableObject {
40
43
  state,
41
44
  env
42
45
  }));
43
- if (import.meta._websocket) {
46
+ if (hasWebSocket) {
44
47
  ws.handleDurableInit(this, state, env);
45
48
  }
46
49
  }
47
50
  fetch(request) {
48
- if (import.meta._websocket && request.headers.get("upgrade") === "websocket") {
51
+ if (hasWebSocket && request.headers.get("upgrade") === "websocket") {
49
52
  return ws.handleDurableUpgrade(this, request);
50
53
  }
51
54
  // Main handler
@@ -56,12 +59,12 @@ export class $DurableObject extends DurableObject {
56
59
  this.ctx.waitUntil(nitroHooks.callHook("cloudflare:durable:alarm", this));
57
60
  }
58
61
  async webSocketMessage(client, message) {
59
- if (import.meta._websocket) {
62
+ if (hasWebSocket) {
60
63
  return ws.handleDurableMessage(this, client, message);
61
64
  }
62
65
  }
63
66
  async webSocketClose(client, code, reason, wasClean) {
64
- if (import.meta._websocket) {
67
+ if (hasWebSocket) {
65
68
  return ws.handleDurableClose(this, client, code, reason, wasClean);
66
69
  }
67
70
  }
@@ -1,10 +1,10 @@
1
1
  import "#nitro-internal-pollyfills";
2
2
  import wsAdapter from "crossws/adapters/cloudflare";
3
- import { useNitroApp } from "nitro/app";
4
3
  import { isPublicAssetURL } from "#nitro-internal-virtual/public-assets";
5
4
  import { createHandler } from "./_module-handler.mjs";
6
- const nitroApp = useNitroApp();
7
- const ws = import.meta._websocket ? wsAdapter(nitroApp.h3App.websocket) : undefined;
5
+ import { resolveWebsocketHooks } from "nitro/~internal/runtime/app";
6
+ import { hasWebSocket } from "#nitro-internal-virtual/feature-flags";
7
+ const ws = hasWebSocket ? wsAdapter({ resolve: resolveWebsocketHooks }) : undefined;
8
8
  export default createHandler({ fetch(request, env, context, url) {
9
9
  // Static assets fallback (optional binding)
10
10
  if (env.ASSETS && isPublicAssetURL(url.pathname)) {
@@ -12,7 +12,7 @@ export default createHandler({ fetch(request, env, context, url) {
12
12
  }
13
13
  // Websocket upgrade
14
14
  // https://crossws.unjs.io/adapters/cloudflare
15
- if (import.meta._websocket && request.headers.get("upgrade") === "websocket") {
15
+ if (hasWebSocket && request.headers.get("upgrade") === "websocket") {
16
16
  return ws.handleUpgrade(request, env, context);
17
17
  }
18
18
  } });
@@ -1,10 +1,12 @@
1
1
  import "#nitro-internal-pollyfills";
2
+ import wsAdapter from "crossws/adapters/cloudflare";
2
3
  import { useNitroApp } from "nitro/app";
3
4
  import { isPublicAssetURL } from "#nitro-internal-virtual/public-assets";
4
5
  import { runCronTasks } from "nitro/~internal/runtime/task";
5
- import wsAdapter from "crossws/adapters/cloudflare";
6
+ import { resolveWebsocketHooks } from "nitro/~internal/runtime/app";
7
+ import { hasWebSocket } from "#nitro-internal-virtual/feature-flags";
6
8
  const nitroApp = useNitroApp();
7
- const ws = import.meta._websocket ? wsAdapter(nitroApp.h3App.websocket) : undefined;
9
+ const ws = hasWebSocket ? wsAdapter({ resolve: resolveWebsocketHooks }) : undefined;
8
10
  export default {
9
11
  async fetch(cfReq, env, context) {
10
12
  // srvx compatibility
@@ -17,7 +19,7 @@ export default {
17
19
  req.waitUntil = context.waitUntil.bind(context);
18
20
  // Websocket upgrade
19
21
  // https://crossws.unjs.io/adapters/cloudflare
20
- if (import.meta._websocket && cfReq.headers.get("upgrade") === "websocket") {
22
+ if (hasWebSocket && cfReq.headers.get("upgrade") === "websocket") {
21
23
  return ws.handleUpgrade(cfReq, env, context);
22
24
  }
23
25
  const url = new URL(cfReq.url);
@@ -1,8 +1,10 @@
1
1
  import "#nitro-internal-pollyfills";
2
- import { useNitroApp } from "nitro/app";
3
2
  import wsAdapter from "crossws/adapters/deno";
3
+ import { useNitroApp } from "nitro/app";
4
+ import { resolveWebsocketHooks } from "nitro/~internal/runtime/app";
5
+ import { hasWebSocket } from "#nitro-internal-virtual/feature-flags";
4
6
  const nitroApp = useNitroApp();
5
- const ws = import.meta._websocket ? wsAdapter(nitroApp.h3App.websocket) : undefined;
7
+ const ws = hasWebSocket ? wsAdapter({ resolve: resolveWebsocketHooks }) : undefined;
6
8
  // TODO: Migrate to srvx to provide request IP
7
9
  Deno.serve((denoReq, info) => {
8
10
  // srvx compatibility
@@ -11,7 +13,7 @@ Deno.serve((denoReq, info) => {
11
13
  req.runtime.deno ??= { info };
12
14
  // TODO: Support remoteAddr
13
15
  // https://crossws.unjs.io/adapters/deno
14
- if (import.meta._websocket && req.headers.get("upgrade") === "websocket") {
16
+ if (hasWebSocket && req.headers.get("upgrade") === "websocket") {
15
17
  return ws.handleUpgrade(req, info);
16
18
  }
17
19
  return nitroApp.fetch(req);
@@ -1,15 +1,27 @@
1
1
  import "#nitro-internal-pollyfills";
2
2
  import { serve } from "srvx/deno";
3
+ import wsAdapter from "crossws/adapters/deno";
3
4
  import { useNitroApp } from "nitro/app";
4
5
  import { startScheduleRunner } from "nitro/~internal/runtime/task";
5
6
  import { trapUnhandledErrors } from "nitro/~internal/runtime/error/hooks";
7
+ import { resolveWebsocketHooks } from "nitro/~internal/runtime/app";
8
+ import { hasWebSocket } from "#nitro-internal-virtual/feature-flags";
6
9
  const port = Number.parseInt(process.env.NITRO_PORT || process.env.PORT || "") || 3e3;
7
10
  const host = process.env.NITRO_HOST || process.env.HOST;
8
11
  const cert = process.env.NITRO_SSL_CERT;
9
12
  const key = process.env.NITRO_SSL_KEY;
10
13
  // const socketPath = process.env.NITRO_UNIX_SOCKET; // TODO
11
14
  const nitroApp = useNitroApp();
12
- // if (import.meta._websocket) // TODO
15
+ let _fetch = nitroApp.fetch;
16
+ if (hasWebSocket) {
17
+ const { handleUpgrade } = wsAdapter({ resolve: resolveWebsocketHooks });
18
+ _fetch = (req) => {
19
+ if (req.headers.get("upgrade") === "websocket") {
20
+ return handleUpgrade(req, req.runtime.deno.info);
21
+ }
22
+ return nitroApp.fetch(req);
23
+ };
24
+ }
13
25
  serve({
14
26
  port,
15
27
  hostname: host,
@@ -17,7 +29,7 @@ serve({
17
29
  cert,
18
30
  key
19
31
  } : undefined,
20
- fetch: nitroApp.fetch
32
+ fetch: _fetch
21
33
  });
22
34
  trapUnhandledErrors();
23
35
  // Scheduled tasks
@@ -1,9 +1,12 @@
1
1
  import "#nitro-internal-pollyfills";
2
2
  import cluster from "node:cluster";
3
- import { serve } from "srvx/node";
3
+ import { NodeRequest, serve } from "srvx/node";
4
+ import wsAdapter from "crossws/adapters/node";
4
5
  import { useNitroApp } from "nitro/app";
5
6
  import { startScheduleRunner } from "nitro/~internal/runtime/task";
6
7
  import { trapUnhandledErrors } from "nitro/~internal/runtime/error/hooks";
8
+ import { resolveWebsocketHooks } from "nitro/~internal/runtime/app";
9
+ import { hasWebSocket } from "#nitro-internal-virtual/feature-flags";
7
10
  const port = Number.parseInt(process.env.NITRO_PORT || process.env.PORT || "") || 3e3;
8
11
  const host = process.env.NITRO_HOST || process.env.HOST;
9
12
  const cert = process.env.NITRO_SSL_CERT;
@@ -13,9 +16,8 @@ const clusterId = cluster.isWorker && process.env.WORKER_ID;
13
16
  if (clusterId) {
14
17
  console.log(`Worker #${clusterId} started`);
15
18
  }
16
- // if (import.meta._websocket) // TODO
17
19
  const nitroApp = useNitroApp();
18
- serve({
20
+ const server = serve({
19
21
  port,
20
22
  hostname: host,
21
23
  tls: cert && key ? {
@@ -26,6 +28,24 @@ serve({
26
28
  silent: clusterId ? clusterId !== "1" : undefined,
27
29
  fetch: nitroApp.fetch
28
30
  });
31
+ if (hasWebSocket) {
32
+ const { handleUpgrade } = wsAdapter({ resolve: resolveWebsocketHooks });
33
+ server.node.server.on("upgrade", (req, socket, head) => {
34
+ handleUpgrade(
35
+ req,
36
+ socket,
37
+ head,
38
+ // @ts-expect-error (upgrade is not typed)
39
+ new NodeRequest({
40
+ req,
41
+ upgrade: {
42
+ socket,
43
+ head
44
+ }
45
+ })
46
+ );
47
+ });
48
+ }
29
49
  trapUnhandledErrors();
30
50
  // Scheduled tasks
31
51
  if (import.meta._tasks) {
@@ -1,5 +1,3 @@
1
1
  import "#nitro-internal-pollyfills";
2
2
  export declare const middleware: unknown;
3
- // TODO
4
- /** @experimental */
5
- export declare const websocket: unknown;
3
+ export declare const handleUpgrade: unknown;
@@ -1,12 +1,14 @@
1
1
  import "#nitro-internal-pollyfills";
2
2
  import { toNodeHandler } from "srvx/node";
3
+ import wsAdapter from "crossws/adapters/node";
3
4
  import { useNitroApp } from "nitro/app";
4
5
  import { startScheduleRunner } from "nitro/~internal/runtime/task";
6
+ import { resolveWebsocketHooks } from "nitro/~internal/runtime/app";
7
+ import { hasWebSocket } from "#nitro-internal-virtual/feature-flags";
5
8
  const nitroApp = useNitroApp();
6
9
  export const middleware = toNodeHandler(nitroApp.fetch);
7
- // TODO
8
- /** @experimental */
9
- export const websocket = import.meta._websocket ? undefined : undefined;
10
+ const ws = hasWebSocket ? wsAdapter({ resolve: resolveWebsocketHooks }) : undefined;
11
+ export const handleUpgrade = ws?.handleUpgrade;
10
12
  // Scheduled tasks
11
13
  if (import.meta._tasks) {
12
14
  startScheduleRunner();
@@ -1,16 +1,18 @@
1
1
  import "#nitro-internal-pollyfills";
2
- import { serve } from "srvx/node";
2
+ import { NodeRequest, serve } from "srvx/node";
3
+ import wsAdapter from "crossws/adapters/node";
3
4
  import { useNitroApp } from "nitro/app";
4
5
  import { startScheduleRunner } from "nitro/~internal/runtime/task";
5
6
  import { trapUnhandledErrors } from "nitro/~internal/runtime/error/hooks";
7
+ import { resolveWebsocketHooks } from "nitro/~internal/runtime/app";
8
+ import { hasWebSocket } from "#nitro-internal-virtual/feature-flags";
6
9
  const port = Number.parseInt(process.env.NITRO_PORT || process.env.PORT || "") || 3e3;
7
10
  const host = process.env.NITRO_HOST || process.env.HOST;
8
11
  const cert = process.env.NITRO_SSL_CERT;
9
12
  const key = process.env.NITRO_SSL_KEY;
10
13
  // const socketPath = process.env.NITRO_UNIX_SOCKET; // TODO
11
- // if (import.meta._websocket) // TODO
12
14
  const nitroApp = useNitroApp();
13
- serve({
15
+ const server = serve({
14
16
  port,
15
17
  hostname: host,
16
18
  tls: cert && key ? {
@@ -19,6 +21,24 @@ serve({
19
21
  } : undefined,
20
22
  fetch: nitroApp.fetch
21
23
  });
24
+ if (hasWebSocket) {
25
+ const { handleUpgrade } = wsAdapter({ resolve: resolveWebsocketHooks });
26
+ server.node.server.on("upgrade", (req, socket, head) => {
27
+ handleUpgrade(
28
+ req,
29
+ socket,
30
+ head,
31
+ // @ts-expect-error (upgrade is not typed)
32
+ new NodeRequest({
33
+ req,
34
+ upgrade: {
35
+ socket,
36
+ head
37
+ }
38
+ })
39
+ );
40
+ });
41
+ }
22
42
  trapUnhandledErrors();
23
43
  // Scheduled tasks
24
44
  if (import.meta._tasks) {
@@ -1,6 +1,6 @@
1
1
  import type { NitroApp, NitroRuntimeHooks } from "nitro/types";
2
- import type { ServerRequestContext } from "srvx";
3
- import type { H3EventContext } from "h3";
2
+ import type { ServerRequest, ServerRequestContext } from "srvx";
3
+ import type { H3EventContext, WebSocketHooks } from "h3";
4
4
  import { HookableCore } from "hookable";
5
5
  declare global {
6
6
  var __nitro__: NitroApp | undefined;
@@ -8,4 +8,5 @@ declare global {
8
8
  export declare function useNitroApp(): NitroApp;
9
9
  export declare function useNitroHooks(): HookableCore<NitroRuntimeHooks>;
10
10
  export declare function serverFetch(resource: string | URL | Request, init?: RequestInit, context?: ServerRequestContext | H3EventContext): Promise<Response>;
11
+ export declare function resolveWebsocketHooks(req: ServerRequest): Promise<Partial<WebSocketHooks>>;
11
12
  export declare function fetch(resource: string | URL | Request, init?: RequestInit, context?: ServerRequestContext | H3EventContext): Promise<Response>;
@@ -30,6 +30,11 @@ export function serverFetch(resource, init, context) {
30
30
  return Promise.reject(error);
31
31
  }
32
32
  }
33
+ export async function resolveWebsocketHooks(req) {
34
+ // https://github.com/h3js/h3/blob/c11ca743d476e583b3b47de1717e6aae92114357/src/utils/ws.ts#L37
35
+ const hooks = (await serverFetch(req)).crossws;
36
+ return hooks || {};
37
+ }
33
38
  export function fetch(resource, init, context) {
34
39
  if (typeof resource === "string" && resource.charCodeAt(0) === 47) {
35
40
  return serverFetch(resource, init, context);
@@ -1,6 +1,16 @@
1
1
  import "#nitro-internal-pollyfills";
2
+ import wsAdapter from "crossws/adapters/node";
3
+
2
4
  import { useNitroApp } from "nitro/app";
5
+ import { resolveWebsocketHooks } from "nitro/~internal/runtime/app";
6
+ import { hasWebSocket } from "#nitro-internal-virtual/feature-flags";
3
7
 
4
8
  const nitroApp = useNitroApp();
5
9
 
6
10
  export const fetch = nitroApp.fetch;
11
+
12
+ const ws = hasWebSocket
13
+ ? wsAdapter({ resolve: resolveWebsocketHooks })
14
+ : undefined;
15
+
16
+ export const handleUpgrade = ws?.handleUpgrade;
@@ -187,6 +187,11 @@ if (workerData.server) {
187
187
  })
188
188
  );
189
189
 
190
+ server.on("upgrade", (req, socket, head) => {
191
+ const handleUpgrade = envs["nitro"]?.entry?.handleUpgrade;
192
+ handleUpgrade?.(req, socket, head);
193
+ });
194
+
190
195
  parentPort.on("message", async (message) => {
191
196
  if (message?.type === "full-reload") {
192
197
  await reload();
@@ -2861,6 +2861,10 @@ interface NitroOptions extends PresetOptions {
2861
2861
  * By default this feature will be enabled if there is at least one nitro plugin.
2862
2862
  */
2863
2863
  runtimeHooks: boolean;
2864
+ /**
2865
+ * Enable WebSocket support
2866
+ */
2867
+ websocket: boolean;
2864
2868
  };
2865
2869
  /**
2866
2870
  * @experimental Requires `experimental.wasm` to work
@@ -2896,9 +2900,11 @@ interface NitroOptions extends PresetOptions {
2896
2900
  */
2897
2901
  envExpansion?: boolean;
2898
2902
  /**
2899
- * Enable experimental WebSocket support
2903
+ * Enable WebSocket support
2900
2904
  *
2901
2905
  * @see https://nitro.build/guide/websocket
2906
+ *
2907
+ * @deprecated use `features.websocket` instead.
2902
2908
  */
2903
2909
  websocket?: boolean;
2904
2910
  /**
@@ -3099,7 +3105,6 @@ interface NitroRuntimeConfig {
3099
3105
  //#region src/types/global.d.ts
3100
3106
  interface NitroStaticBuildFlags {
3101
3107
  _asyncContext?: boolean;
3102
- _websocket?: boolean;
3103
3108
  _tasks?: boolean;
3104
3109
  dev?: boolean;
3105
3110
  client?: boolean;
package/dist/vite.mjs CHANGED
@@ -13,7 +13,7 @@ import "./_libs/tinyglobby.mjs";
13
13
  import "./_libs/compatx.mjs";
14
14
  import "./_libs/klona.mjs";
15
15
  import "./_libs/std-env.mjs";
16
- import "./_chunks/DPeGJhnS.mjs";
16
+ import "./_chunks/Bb_Lq1zU.mjs";
17
17
  import "./_libs/escape-string-regexp.mjs";
18
18
  import "./_libs/tsconfck.mjs";
19
19
  import "./_libs/dot-prop.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitro-nightly",
3
- "version": "3.0.1-20251109-184848-3a5337c9",
3
+ "version": "3.0.1-20251109-233730-ab0fad99",
4
4
  "description": "Build and Deploy Universal JavaScript Servers",
5
5
  "homepage": "https://nitro.build",
6
6
  "repository": "nitrojs/nitro",