@nodefony/http 10.0.0-beta.2 → 10.0.0-beta.4

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.
Files changed (139) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  2. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
  3. package/dist/index.js +19 -11
  4. package/dist/nodefony/command/assetsPublishCommand.js +5 -3
  5. package/dist/nodefony/command/certificatesCommand.js +5 -3
  6. package/dist/nodefony/command/networkCommand.js +5 -3
  7. package/dist/nodefony/command/proxyGenerateCommand.js +8 -3
  8. package/dist/nodefony/config/config.js +45 -6
  9. package/dist/nodefony/config/defineModuleConfig.js +2 -3
  10. package/dist/nodefony/interfaces/IReverseProxy.js +1 -0
  11. package/dist/nodefony/service/HttpAdminApi.js +2 -3
  12. package/dist/nodefony/service/ProfilerAdminApi.js +1 -2
  13. package/dist/nodefony/service/audit-logger.js +22 -16
  14. package/dist/nodefony/service/certificates.js +228 -19
  15. package/dist/nodefony/service/error-renderer.js +30 -13
  16. package/dist/nodefony/service/http-kernel.js +331 -82
  17. package/dist/nodefony/service/pretty-request-logger.js +5 -4
  18. package/dist/nodefony/service/request-logger.js +24 -16
  19. package/dist/nodefony/service/reverse-proxy.js +336 -0
  20. package/dist/nodefony/service/servers/clientError.js +22 -3
  21. package/dist/nodefony/service/servers/server-h2c.js +126 -0
  22. package/dist/nodefony/service/servers/server-http.js +5 -8
  23. package/dist/nodefony/service/servers/server-https.js +12 -24
  24. package/dist/nodefony/service/servers/server-static.js +7 -10
  25. package/dist/nodefony/service/servers/server-websocket-secure.js +18 -18
  26. package/dist/nodefony/service/servers/server-websocket.js +18 -18
  27. package/dist/nodefony/service/servers/serverShutdown.js +78 -18
  28. package/dist/nodefony/service/servers/wsHeartbeat.js +32 -17
  29. package/dist/nodefony/service/sessions/sessions-service.js +34 -20
  30. package/dist/nodefony/service/trace.js +1 -2
  31. package/dist/nodefony/service/upload/upload-service.js +5 -8
  32. package/dist/nodefony/service/x509.js +1 -2
  33. package/dist/nodefony/src/assets/collectAssets.js +1 -2
  34. package/dist/nodefony/src/assets/prebuiltUi.js +1 -2
  35. package/dist/nodefony/src/context/Context.js +51 -32
  36. package/dist/nodefony/src/context/domainMatcher.js +21 -4
  37. package/dist/nodefony/src/context/forwarded.js +3 -4
  38. package/dist/nodefony/src/context/http/HttpContext.js +98 -46
  39. package/dist/nodefony/src/context/http/Request.js +34 -38
  40. package/dist/nodefony/src/context/http/Response.js +39 -7
  41. package/dist/nodefony/src/context/http/SseStream.js +349 -0
  42. package/dist/nodefony/src/context/http/parser.js +36 -10
  43. package/dist/nodefony/src/context/http/urlFastPath.js +1 -2
  44. package/dist/nodefony/src/context/http2/Request.js +4 -8
  45. package/dist/nodefony/src/context/http2/Response.js +22 -9
  46. package/dist/nodefony/src/context/metaData.js +1 -2
  47. package/dist/nodefony/src/context/requestId.js +1 -2
  48. package/dist/nodefony/src/context/responseEnded.js +26 -0
  49. package/dist/nodefony/src/context/transport.js +45 -0
  50. package/dist/nodefony/src/context/trustProxy.js +1 -2
  51. package/dist/nodefony/src/context/websocket/Response.js +42 -6
  52. package/dist/nodefony/src/context/websocket/WebsocketContext.js +94 -20
  53. package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
  54. package/dist/nodefony/src/context/websocket/websocketUrl.js +20 -0
  55. package/dist/nodefony/src/context/websocket/wsBackpressure.js +1 -2
  56. package/dist/nodefony/src/context/websocket/wsCloseReason.js +1 -2
  57. package/dist/nodefony/src/context/websocket/wsLogContent.js +1 -2
  58. package/dist/nodefony/src/cookies/cookie.js +3 -7
  59. package/dist/nodefony/src/errors/httpError.js +1 -2
  60. package/dist/nodefony/src/errors/thrownMessage.js +1 -2
  61. package/dist/nodefony/src/profiler/FrameProfile.js +1 -2
  62. package/dist/nodefony/src/profiler/Profiler.js +108 -30
  63. package/dist/nodefony/src/profiler/aggregate.js +422 -0
  64. package/dist/nodefony/src/proxy/forward.js +500 -0
  65. package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
  66. package/dist/nodefony/src/proxy/rules.js +188 -0
  67. package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +1 -2
  68. package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +1 -2
  69. package/dist/nodefony/src/rateLimit/rateLimitFilters.js +1 -2
  70. package/dist/nodefony/src/servers/http2Limits.js +50 -0
  71. package/dist/nodefony/src/servers/kernelServers.js +12 -3
  72. package/dist/nodefony/src/servers/portBinder.js +41 -10
  73. package/dist/nodefony/src/servers/upgradeDispatch.js +128 -0
  74. package/dist/nodefony/src/session/session.js +30 -15
  75. package/dist/nodefony/src/session/sessionStoreFailure.js +1 -2
  76. package/dist/nodefony/src/session/storage/MemorySessionStorage.js +1 -2
  77. package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +1 -2
  78. package/dist/nodefony/src/session/storage/sessionFilters.js +1 -2
  79. package/dist/nodefony/src/session/storage/sessionSort.js +1 -2
  80. package/dist/types/index.d.ts +12 -3
  81. package/dist/types/nodefony/config/config.d.ts +16 -0
  82. package/dist/types/nodefony/interfaces/IContext.d.ts +65 -4
  83. package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +1 -1
  84. package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
  85. package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +67 -0
  86. package/dist/types/nodefony/interfaces/IRouting.d.ts +26 -0
  87. package/dist/types/nodefony/interfaces/ISecurity.d.ts +6 -0
  88. package/dist/types/nodefony/interfaces/index.d.ts +3 -2
  89. package/dist/types/nodefony/service/audit-logger.d.ts +2 -1
  90. package/dist/types/nodefony/service/certificates.d.ts +147 -5
  91. package/dist/types/nodefony/service/error-renderer.d.ts +32 -1
  92. package/dist/types/nodefony/service/http-kernel.d.ts +157 -15
  93. package/dist/types/nodefony/service/reverse-proxy.d.ts +156 -0
  94. package/dist/types/nodefony/service/servers/clientError.d.ts +16 -0
  95. package/dist/types/nodefony/service/servers/server-h2c.d.ts +51 -0
  96. package/dist/types/nodefony/service/servers/server-http.d.ts +3 -3
  97. package/dist/types/nodefony/service/servers/server-https.d.ts +4 -4
  98. package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +2 -3
  99. package/dist/types/nodefony/service/servers/server-websocket.d.ts +2 -3
  100. package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
  101. package/dist/types/nodefony/service/servers/wsHeartbeat.d.ts +26 -0
  102. package/dist/types/nodefony/service/sessions/sessions-service.d.ts +19 -1
  103. package/dist/types/nodefony/src/context/Context.d.ts +35 -6
  104. package/dist/types/nodefony/src/context/domainMatcher.d.ts +11 -1
  105. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +48 -1
  106. package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
  107. package/dist/types/nodefony/src/context/http/Response.d.ts +14 -0
  108. package/dist/types/nodefony/src/context/http/SseStream.d.ts +155 -0
  109. package/dist/types/nodefony/src/context/http/parser.d.ts +18 -2
  110. package/dist/types/nodefony/src/context/http2/Request.d.ts +1 -1
  111. package/dist/types/nodefony/src/context/http2/Response.d.ts +15 -5
  112. package/dist/types/nodefony/src/context/responseEnded.d.ts +20 -0
  113. package/dist/types/nodefony/src/context/transport.d.ts +53 -0
  114. package/dist/types/nodefony/src/context/websocket/Response.d.ts +18 -2
  115. package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +57 -7
  116. package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
  117. package/dist/types/nodefony/src/context/websocket/websocketUrl.d.ts +16 -0
  118. package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +13 -12
  119. package/dist/types/nodefony/src/profiler/Profiler.d.ts +69 -15
  120. package/dist/types/nodefony/src/profiler/aggregate.d.ts +215 -0
  121. package/dist/types/nodefony/src/proxy/forward.d.ts +168 -0
  122. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
  123. package/dist/types/nodefony/src/proxy/rules.d.ts +88 -0
  124. package/dist/types/nodefony/src/servers/http2Limits.d.ts +37 -0
  125. package/dist/types/nodefony/src/servers/kernelServers.d.ts +10 -1
  126. package/dist/types/nodefony/src/servers/portBinder.d.ts +29 -5
  127. package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +69 -0
  128. package/dist/types/nodefony/src/session/session.d.ts +2 -0
  129. package/docs/cookies.md +85 -78
  130. package/docs/index.md +5 -1
  131. package/docs/observabilite.md +81 -81
  132. package/docs/rate-limit.md +83 -83
  133. package/docs/reverse-proxy.md +522 -0
  134. package/docs/servers.md +378 -230
  135. package/docs/session.md +180 -180
  136. package/docs/sse.md +287 -0
  137. package/docs/upload.md +120 -117
  138. package/package.json +10 -11
  139. package/dist/_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js +0 -6
@@ -1,16 +1,14 @@
1
1
  import { asServersConfig, configuredServerPort } from "../../src/servers/kernelServers.js";
2
- import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorate.js";
4
- import http_kernel_default from "../http-kernel.js";
5
- import { handleClientError } from "./clientError.js";
2
+ import __decorate from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
3
+ import { handleClientError, handleSessionError } from "./clientError.js";
6
4
  import { createDrainTerminator } from "./serverShutdown.js";
7
5
  import { bindWithFallback, buildBindPlan } from "../../src/servers/portBinder.js";
8
- import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateParam.js";
9
- import { Module, Service, extend, inject, nodefonyError } from "nodefony";
6
+ import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorateParam.js";
7
+ import { http2ServerLimits, setHttp2SessionTimeout } from "../../src/servers/http2Limits.js";
8
+ import { Inject, Service, extend, nodefonyError } from "nodefony";
10
9
  import http2 from "node:http2";
11
10
  import https from "node:https";
12
- import { TLSSocket } from "node:tls";
13
- //#region nodefony/service/servers/server-https.ts
11
+ // nodefony/service/servers/server-https.ts
14
12
  let ServerHttps = class ServerHttps extends Service {
15
13
  httpKernel;
16
14
  httpTerminator = null;
@@ -98,7 +96,6 @@ let ServerHttps = class ServerHttps extends Service {
98
96
  createServerH2() {
99
97
  return new Promise((resolve, reject) => {
100
98
  try {
101
- const h2Cfg = this.module.options.http2 ?? {};
102
99
  const opt = extend({
103
100
  allowHTTP1: true,
104
101
  rejectUnauthorized: this.options.rejectUnauthorized,
@@ -106,23 +103,14 @@ let ServerHttps = class ServerHttps extends Service {
106
103
  cert: this.httpKernel.serviceCerticats?.cert,
107
104
  ca: this.httpKernel.serviceCerticats?.ca ? this.httpKernel.serviceCerticats.ca : void 0
108
105
  });
109
- if (h2Cfg.maxSessionMemory) opt.maxSessionMemory = h2Cfg.maxSessionMemory;
110
- if (h2Cfg.maxConcurrentStreams) opt.settings = {
111
- ...opt.settings,
112
- maxConcurrentStreams: h2Cfg.maxConcurrentStreams
113
- };
106
+ Object.assign(opt, http2ServerLimits(this.module.options.http2));
114
107
  this.server = http2.createSecureServer(opt);
115
108
  this.httpTerminator = this.terminator();
116
- if (this.options.timeout) this.server.setTimeout(this.options.timeout, () => {
109
+ setHttp2SessionTimeout(this.server, this.options.timeout, () => {
117
110
  this.fire("onTimeout", this);
118
111
  });
119
112
  this.server.on("request", (request, response) => {
120
- let alpnProtocol = false;
121
- const sessionSocket = request.stream?.session?.socket;
122
- if (sessionSocket instanceof TLSSocket) alpnProtocol = sessionSocket.alpnProtocol;
123
- if (request.socket instanceof TLSSocket) alpnProtocol = request.socket.alpnProtocol;
124
- if (alpnProtocol === "h2") this.httpKernel.onHttpRequest(request, response, "http2");
125
- else this.httpKernel.onHttpRequest(request, response, "https");
113
+ this.httpKernel.onHttpRequest(request, response, this.type);
126
114
  });
127
115
  this.listenWithPolicy().then(() => resolve(this.server)).catch((e) => {
128
116
  const error = e instanceof Error ? e : new Error(String(e));
@@ -139,7 +127,7 @@ let ServerHttps = class ServerHttps extends Service {
139
127
  }
140
128
  });
141
129
  this.server.on("sessionError", (error) => {
142
- this.log(error, "ERROR", "HTTP2 Server sessionError");
130
+ handleSessionError(error, (e) => this.log(e, "ERROR", "HTTP2 Server sessionError"));
143
131
  });
144
132
  this.server.on("streamError", (error) => {
145
133
  this.log(error, "ERROR", "HTTP2 Server streamError");
@@ -157,6 +145,7 @@ let ServerHttps = class ServerHttps extends Service {
157
145
  * `EADDRINUSE` de repli, qui ne sont pas des pannes).
158
146
  */
159
147
  async listenWithPolicy() {
148
+ if (this.server === null) throw new Error("Server https not created");
160
149
  const { address, shiftedFrom } = await bindWithFallback(this.server, this.domain, buildBindPlan("https", asServersConfig(this.module.kernel?.options.servers), this.module.kernel?.environment));
161
150
  this.infos = address;
162
151
  this.port = address.port;
@@ -197,7 +186,6 @@ let ServerHttps = class ServerHttps extends Service {
197
186
  if (this.infos) this.log(`Server Listen on ${this.scheme}://${this.infos.address}:${this.infos.port} Family: ${this.infos.family} Protocol : ${this.protocol}`);
198
187
  }
199
188
  };
200
- ServerHttps = __decorate([__decorateParam(1, inject("HttpKernel")), __decorateMetadata("design:paramtypes", [typeof Module === "undefined" ? Object : Module, typeof http_kernel_default === "undefined" ? Object : http_kernel_default])], ServerHttps);
189
+ ServerHttps = __decorate([__decorateParam(1, Inject("HttpKernel"))], ServerHttps);
201
190
  var server_https_default = ServerHttps;
202
- //#endregion
203
191
  export { server_https_default as default };
@@ -1,13 +1,15 @@
1
+ import { normalizePrefix } from "../../src/proxy/rules.js";
2
+ import { requestAuthority } from "../../src/context/transport.js";
1
3
  import { Service, extend } from "nodefony";
2
4
  import http from "node:http";
5
+ import http2 from "node:http2";
3
6
  import { URL } from "node:url";
4
7
  import { existsSync } from "node:fs";
5
8
  import { join } from "node:path";
6
9
  import mime from "mime-types";
7
- import http2 from "node:http2";
8
10
  import tls from "node:tls";
9
11
  import serveStatic from "serve-static";
10
- //#region nodefony/service/servers/server-static.ts
12
+ // nodefony/service/servers/server-static.ts
11
13
  const defaultOptions = {
12
14
  cacheControl: true,
13
15
  maxAge: 345600
@@ -109,14 +111,10 @@ var Statics = class extends Service {
109
111
  * @param dir dossier absolu à servir
110
112
  */
111
113
  addMount(prefix, dir) {
112
- let p = prefix.trim();
113
- if (!p.startsWith("/")) p = `/${p}`;
114
- if (!p.endsWith("/")) p = `${p}/`;
115
- p = p.replace(/\/{2,}/g, "/");
116
- const server = serveStatic(dir, extend({}, this.defaultOptions));
114
+ const p = normalizePrefix(prefix);
117
115
  const entry = {
118
116
  prefix: p,
119
- server,
117
+ server: serveStatic(dir, extend({}, this.defaultOptions)),
120
118
  dir
121
119
  };
122
120
  const i = this.mounts.findIndex((m) => m.prefix === p);
@@ -151,7 +149,7 @@ var Statics = class extends Service {
151
149
  return scheme + "://" + host;
152
150
  } else if (request instanceof http2.Http2ServerRequest) {
153
151
  scheme = request.socket instanceof tls.TLSSocket ? "https" : "http";
154
- host = request.headers[":authority"];
152
+ host = requestAuthority(request.headers);
155
153
  return scheme + "://" + host;
156
154
  }
157
155
  throw new Error(`Bad request type`);
@@ -184,5 +182,4 @@ var Statics = class extends Service {
184
182
  return Promise.resolve(response);
185
183
  }
186
184
  };
187
- //#endregion
188
185
  export { Statics as default };
@@ -1,24 +1,25 @@
1
+ import { startHeartbeat } from "./wsHeartbeat.js";
1
2
  import { asServersConfig, configuredServerPort } from "../../src/servers/kernelServers.js";
2
- import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorate.js";
4
- import http_kernel_default from "../http-kernel.js";
5
- import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateParam.js";
6
- import { startHeartbeat, trackPong } from "./wsHeartbeat.js";
7
- import { Module, Service, inject } from "nodefony";
3
+ import __decorate from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
4
+ import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorateParam.js";
5
+ import { attachUpgradeDispatch } from "../../src/servers/upgradeDispatch.js";
6
+ import { Inject, Service } from "nodefony";
8
7
  import Ws, { WebSocketServer } from "ws";
9
- //#region nodefony/service/servers/server-websocket-secure.ts
8
+ // nodefony/service/servers/server-websocket-secure.ts
10
9
  let WebsocketSecure = class WebsocketSecure extends Service {
11
10
  httpKernel;
12
11
  module;
13
12
  ready = false;
14
13
  server = null;
14
+ /** Retire l'écouteur `upgrade` posé par {@link attachUpgradeDispatch}. */
15
+ #detachUpgrade = null;
15
16
  port;
16
17
  domain;
17
18
  protocol = "1.1";
18
19
  family = null;
19
20
  scheme = "wss";
20
21
  address = null;
21
- type = "websocket-secure";
22
+ type = "wss";
22
23
  infos = null;
23
24
  /** Timer keep-alive (UN par serveur). `null` si désactivé ou pas démarré. */
24
25
  heartbeatTimer = null;
@@ -46,10 +47,12 @@ let WebsocketSecure = class WebsocketSecure extends Service {
46
47
  }
47
48
  this.server = new WebSocketServer({
48
49
  ...this.options,
49
- server: serverHttps.server,
50
+ noServer: true,
50
51
  clientTracking: true
51
52
  });
52
- this.server.on("connection", this.onConnection.bind(this));
53
+ this.#detachUpgrade = attachUpgradeDispatch(serverHttps.server, this.server, () => this.httpKernel.reverseProxy, (req) => this.httpKernel.websocketUpgradeRefusal(req), (req, admission) => {
54
+ this.httpKernel.onWebsocketUpgrade(req, admission, this.type).catch(() => {});
55
+ });
53
56
  this.heartbeatTimer = startHeartbeat(this.server, this.options);
54
57
  this.kernel?.prependOnceListener("onTerminate", this.terminate.bind(this));
55
58
  this.ready = true;
@@ -61,12 +64,6 @@ let WebsocketSecure = class WebsocketSecure extends Service {
61
64
  }
62
65
  });
63
66
  }
64
- onConnection(ws, req) {
65
- trackPong(ws);
66
- this.httpKernel.onWebsocketRequest(ws, req, this.type).catch(() => {
67
- process.nextTick(() => {});
68
- });
69
- }
70
67
  terminate() {
71
68
  return new Promise((resolve, reject) => {
72
69
  if (this.heartbeatTimer) {
@@ -83,6 +80,8 @@ let WebsocketSecure = class WebsocketSecure extends Service {
83
80
  });
84
81
  setTimeout(() => {
85
82
  try {
83
+ this.#detachUpgrade?.();
84
+ this.#detachUpgrade = null;
86
85
  this.server?.close();
87
86
  this.log(` SHUTDOWN WEBSOCKET Server is listening on DOMAIN : ${this.domain} PORT : ${this.port}`, "INFO");
88
87
  return resolve(true);
@@ -92,6 +91,8 @@ let WebsocketSecure = class WebsocketSecure extends Service {
92
91
  }, 300);
93
92
  return;
94
93
  }
94
+ this.#detachUpgrade?.();
95
+ this.#detachUpgrade = null;
95
96
  return resolve(true);
96
97
  });
97
98
  }
@@ -99,7 +100,6 @@ let WebsocketSecure = class WebsocketSecure extends Service {
99
100
  if (this.infos) this.log(`Server Listen on ${this.scheme}://${this.infos.address}:${this.infos.port} Family: ${this.infos.family} Protocol : ${this.protocol}`);
100
101
  }
101
102
  };
102
- WebsocketSecure = __decorate([__decorateParam(1, inject("HttpKernel")), __decorateMetadata("design:paramtypes", [typeof Module === "undefined" ? Object : Module, typeof http_kernel_default === "undefined" ? Object : http_kernel_default])], WebsocketSecure);
103
+ WebsocketSecure = __decorate([__decorateParam(1, Inject("HttpKernel"))], WebsocketSecure);
103
104
  var server_websocket_secure_default = WebsocketSecure;
104
- //#endregion
105
105
  export { server_websocket_secure_default as default };
@@ -1,24 +1,25 @@
1
+ import { startHeartbeat } from "./wsHeartbeat.js";
1
2
  import { asServersConfig, configuredServerPort } from "../../src/servers/kernelServers.js";
2
- import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorate.js";
4
- import http_kernel_default from "../http-kernel.js";
5
- import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateParam.js";
6
- import { startHeartbeat, trackPong } from "./wsHeartbeat.js";
7
- import { Module, Service, inject } from "nodefony";
3
+ import __decorate from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
4
+ import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorateParam.js";
5
+ import { attachUpgradeDispatch } from "../../src/servers/upgradeDispatch.js";
6
+ import { Inject, Service } from "nodefony";
8
7
  import Ws, { WebSocketServer } from "ws";
9
- //#region nodefony/service/servers/server-websocket.ts
8
+ // nodefony/service/servers/server-websocket.ts
10
9
  let Websocket = class Websocket extends Service {
11
10
  httpKernel;
12
11
  module;
13
12
  ready = false;
14
13
  server = null;
14
+ /** Retire l'écouteur `upgrade` posé par {@link attachUpgradeDispatch}. */
15
+ #detachUpgrade = null;
15
16
  port;
16
17
  domain;
17
18
  protocol = "1.1";
18
19
  family = null;
19
20
  scheme = "ws";
20
21
  address = null;
21
- type = "websocket";
22
+ type = "ws";
22
23
  infos = null;
23
24
  /** Timer keep-alive (UN par serveur). `null` si désactivé ou pas démarré. */
24
25
  heartbeatTimer = null;
@@ -46,10 +47,12 @@ let Websocket = class Websocket extends Service {
46
47
  }
47
48
  this.server = new WebSocketServer({
48
49
  ...this.options,
49
- server: serverHttp.server,
50
+ noServer: true,
50
51
  clientTracking: true
51
52
  });
52
- this.server.on("connection", this.onConnection.bind(this));
53
+ this.#detachUpgrade = attachUpgradeDispatch(serverHttp.server, this.server, () => this.httpKernel.reverseProxy, (req) => this.httpKernel.websocketUpgradeRefusal(req), (req, admission) => {
54
+ this.httpKernel.onWebsocketUpgrade(req, admission, this.type).catch(() => {});
55
+ });
53
56
  this.heartbeatTimer = startHeartbeat(this.server, this.options);
54
57
  this.kernel?.prependOnceListener("onTerminate", this.terminate.bind(this));
55
58
  this.ready = true;
@@ -61,12 +64,6 @@ let Websocket = class Websocket extends Service {
61
64
  }
62
65
  });
63
66
  }
64
- onConnection(ws, req) {
65
- trackPong(ws);
66
- this.httpKernel.onWebsocketRequest(ws, req, this.type).catch(() => {
67
- process.nextTick(() => {});
68
- });
69
- }
70
67
  terminate() {
71
68
  return new Promise((resolve, reject) => {
72
69
  if (this.heartbeatTimer) {
@@ -83,6 +80,8 @@ let Websocket = class Websocket extends Service {
83
80
  });
84
81
  setTimeout(() => {
85
82
  try {
83
+ this.#detachUpgrade?.();
84
+ this.#detachUpgrade = null;
86
85
  this.server?.close();
87
86
  this.log(` SHUTDOWN WEBSOCKET Server is listening on DOMAIN : ${this.domain} PORT : ${this.port}`, "INFO");
88
87
  return resolve(true);
@@ -92,6 +91,8 @@ let Websocket = class Websocket extends Service {
92
91
  }, 300);
93
92
  return;
94
93
  }
94
+ this.#detachUpgrade?.();
95
+ this.#detachUpgrade = null;
95
96
  return resolve(true);
96
97
  });
97
98
  }
@@ -99,7 +100,6 @@ let Websocket = class Websocket extends Service {
99
100
  if (this.infos) this.log(`Server Listen on ${this.scheme}://${this.infos.address}:${this.infos.port} Family: ${this.infos.family} Protocol : ${this.protocol}`);
100
101
  }
101
102
  };
102
- Websocket = __decorate([__decorateParam(1, inject("HttpKernel")), __decorateMetadata("design:paramtypes", [typeof Module === "undefined" ? Object : Module, typeof http_kernel_default === "undefined" ? Object : http_kernel_default])], Websocket);
103
+ Websocket = __decorate([__decorateParam(1, Inject("HttpKernel"))], Websocket);
103
104
  var server_websocket_default = Websocket;
104
- //#endregion
105
105
  export { server_websocket_default as default };
@@ -1,31 +1,91 @@
1
- import { createHttpTerminator } from "http-terminator";
2
- //#region nodefony/service/servers/serverShutdown.ts
1
+ import http from "node:http";
2
+ import https from "node:https";
3
+ import tls from "node:tls";
4
+ // nodefony/service/servers/serverShutdown.ts
3
5
  /**
4
- * Fabrique le terminator de drain graceful d'un serveur HTTP/HTTPS/HTTP2.
6
+ * Fabrique l'arrêt gracieux d'un serveur HTTP/1.1, HTTPS, HTTP/2 sur TLS ou h2c.
5
7
  *
6
- * Au `terminate()` (shutdown SIGTERM/docker stop) : les requêtes in-flight se
7
- * terminent (header `connection: close` injecté sur les réponses en cours), les
8
- * sockets idle sont fermées immédiatement, et tout ce qui reste après
9
- * `shutdownTimeout` ms est détruit de force. Le terminator appelle lui-même
10
- * `server.close()` — ne pas le rappeler derrière.
8
+ * Au `terminate()` (SIGTERM, `docker stop`), le serveur cesse d'accepter, puis :
9
+ * - **HTTP/2** : chaque session reçoit un GOAWAY (`session.close()`) — les flux
10
+ * en vol se terminent, les nouveaux sont refusés, et le client sait qu'il doit
11
+ * rouvrir ailleurs au lieu de constater une coupure ;
12
+ * - **HTTP/1.1** : la réponse en vol part avec `Connection: close`, la socket
13
+ * inactive est fermée tout de suite, une requête qui arrive encore sur une
14
+ * socket gardée ouverte reçoit aussi `Connection: close`.
15
+ *
16
+ * Ce qui reste après `shutdownTimeout` ms est détruit. `server.close()` est
17
+ * appelé ici : ne pas le rappeler derrière.
11
18
  *
12
19
  * ⚠️ Les sockets WebSocket upgradées (sans réponse HTTP en cours) sont détruites
13
- * SANS frame Close par le terminator → les serveurs WS doivent fermer leurs
14
- * clients (close 1001) AVANT ce drain. Garanti par l'ordre des listeners
15
- * `onTerminate` : WS/WSS s'attachent en `prependOnceListener`, les serveurs
16
- * HTTP en `once`.
20
+ * SANS frame Close → les serveurs WS doivent fermer leurs clients (close 1001)
21
+ * AVANT ce drain. Garanti par l'ordre des listeners `onTerminate` : WS/WSS
22
+ * s'attachent en `prependOnceListener`, les serveurs HTTP en `once`.
17
23
  *
18
- * @param server - serveur Node à drainer (créé, pas forcément listening)
24
+ * @param server - serveur Node à drainer, créé AVANT son `listen` (le suivi des
25
+ * connexions commence ici)
19
26
  * @param shutdownTimeout - délai de drain en ms avant destruction forcée
20
- * @returns le terminator à invoquer au shutdown
27
+ * @returns l'arrêt à invoquer au shutdown
21
28
  */
22
29
  function createDrainTerminator(server, shutdownTimeout) {
23
- return createHttpTerminator({
24
- server,
25
- gracefulTerminationTimeout: shutdownTimeout ?? 5e3
30
+ const timeout = shutdownTimeout ?? 5e3;
31
+ const emitter = server;
32
+ const speaksHttp2 = !(server instanceof http.Server || server instanceof https.Server);
33
+ const overTls = server instanceof tls.Server;
34
+ const sockets = /* @__PURE__ */ new Set();
35
+ emitter.on("connection", (socket) => track(sockets, socket));
36
+ let http1Sockets = null;
37
+ if (!speaksHttp2) http1Sockets = overTls ? /* @__PURE__ */ new Set() : sockets;
38
+ else if (overTls) http1Sockets = /* @__PURE__ */ new Set();
39
+ if (overTls && http1Sockets !== null) {
40
+ const set = http1Sockets;
41
+ emitter.on("secureConnection", (socket) => {
42
+ if (socket.alpnProtocol !== "h2") track(set, socket);
43
+ });
44
+ }
45
+ let draining = false;
46
+ const sessions = speaksHttp2 ? /* @__PURE__ */ new Set() : null;
47
+ if (sessions !== null) emitter.on("session", (session) => {
48
+ if (draining) {
49
+ session.close();
50
+ return;
51
+ }
52
+ sessions.add(session);
53
+ session.once("close", () => sessions.delete(session));
54
+ });
55
+ let terminating = null;
56
+ const drain = () => new Promise((resolve) => {
57
+ draining = true;
58
+ const deadline = setTimeout(() => {
59
+ if (sessions !== null) for (const s of sessions) s.destroy();
60
+ for (const socket of sockets) socket.destroy();
61
+ }, timeout);
62
+ server.close(() => {
63
+ clearTimeout(deadline);
64
+ resolve();
65
+ });
66
+ if (sessions !== null) for (const s of sessions) s.close();
67
+ if (http1Sockets !== null) {
68
+ emitter.on("request", closeAfterResponse);
69
+ for (const socket of http1Sockets) {
70
+ const response = socket._httpMessage;
71
+ if (!response) socket.destroy();
72
+ else if (!response.headersSent) response.setHeader("connection", "close");
73
+ }
74
+ }
26
75
  });
76
+ return { terminate() {
77
+ terminating ??= drain();
78
+ return terminating;
79
+ } };
80
+ }
81
+ function track(set, socket) {
82
+ set.add(socket);
83
+ socket.once("close", () => set.delete(socket));
84
+ }
85
+ /** Requête HTTP/1.1 reçue pendant le drain : la connexion ne survivra pas à sa réponse. */
86
+ function closeAfterResponse(request, response) {
87
+ if (request.httpVersionMajor === 1 && !response.headersSent) response.setHeader("connection", "close");
27
88
  }
28
89
  /** Drain par défaut (ms) — même valeur que le défaut Zod `servers.*.shutdownTimeout`. */
29
90
  const DEFAULT_SHUTDOWN_TIMEOUT = 5e3;
30
- //#endregion
31
91
  export { DEFAULT_SHUTDOWN_TIMEOUT, createDrainTerminator };
@@ -1,5 +1,5 @@
1
1
  import Ws from "ws";
2
- //#region nodefony/service/servers/wsHeartbeat.ts
2
+ // nodefony/service/servers/wsHeartbeat.ts
3
3
  /**
4
4
  * Arme le suivi keep-alive d'UNE connexion : initialise l'horodatage de vie et
5
5
  * attache l'unique listener `pong` qui le rafraîchit.
@@ -19,6 +19,35 @@ const trackPong = (ws) => {
19
19
  });
20
20
  };
21
21
  /**
22
+ * Un tick du keep-alive : pingue les connexions muettes depuis `interval`, et
23
+ * coupe celles dont le ping attend sa réponse depuis plus de `grace`.
24
+ *
25
+ * Fonction PURE du temps : l'horloge est passée en argument, si bien que la
26
+ * règle se prouve pas à pas, sans minuteur réel — un banc qui la prouverait à
27
+ * l'horloge murale confondrait une boucle d'événements chargée avec un zombie.
28
+ *
29
+ * @param clients - les connexions du serveur
30
+ * @param now - l'instant du tick (`performance.now()` en production)
31
+ * @param interval - délai sans signe de vie avant un ping, ms
32
+ * @param grace - délai accordé au pong avant de couper, ms
33
+ */
34
+ const heartbeatTick = (clients, now, interval, grace) => {
35
+ for (const client of clients) {
36
+ if (client.readyState !== Ws.OPEN) continue;
37
+ const sock = client;
38
+ sock._nfLastPong ??= now;
39
+ const pingedAt = sock._nfPingedAt ?? 0;
40
+ if (pingedAt > sock._nfLastPong) {
41
+ if (now - pingedAt > grace) client.terminate();
42
+ continue;
43
+ }
44
+ if (now - sock._nfLastPong >= interval) {
45
+ sock._nfPingedAt = now;
46
+ client.ping();
47
+ }
48
+ }
49
+ };
50
+ /**
22
51
  * Démarre le heartbeat keep-alive du serveur : détecte les connexions zombies
23
52
  * (half-open — TCP encore ouvert mais le pair a disparu sans frame Close) et les
24
53
  * `terminate()` pour libérer slot mémoire + descripteur de fichier.
@@ -42,23 +71,9 @@ const startHeartbeat = (server, options) => {
42
71
  if (interval <= 0) return null;
43
72
  const grace = options.keepaliveGracePeriod ?? 0;
44
73
  const timer = setInterval(() => {
45
- const now = performance.now();
46
- for (const client of server.clients) {
47
- if (client.readyState !== Ws.OPEN) continue;
48
- const sock = client;
49
- sock._nfLastPong ??= now;
50
- if ((sock._nfPingedAt ?? 0) > sock._nfLastPong) {
51
- if (now - sock._nfPingedAt > grace) client.terminate();
52
- continue;
53
- }
54
- if (now - sock._nfLastPong >= interval) {
55
- sock._nfPingedAt = now;
56
- client.ping();
57
- }
58
- }
74
+ heartbeatTick(server.clients, performance.now(), interval, grace);
59
75
  }, Math.max(250, grace > 0 ? Math.min(interval, grace) : interval));
60
76
  timer.unref();
61
77
  return timer;
62
78
  };
63
- //#endregion
64
- export { startHeartbeat, trackPong };
79
+ export { heartbeatTick, startHeartbeat, trackPong };
@@ -1,14 +1,12 @@
1
1
  import { SESSION_FACETS } from "../../src/session/storage/sessionFilters.js";
2
- import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorate.js";
4
- import http_kernel_default from "../http-kernel.js";
5
- import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateParam.js";
2
+ import __decorate from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
3
+ import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorateParam.js";
6
4
  import Session from "../../src/session/session.js";
7
5
  import MemorySessionStorage from "../../src/session/storage/MemorySessionStorage.js";
8
6
  import RevocationGuardStorage from "../../src/session/storage/RevocationGuardStorage.js";
9
- import { AUTO_STORE, EMPTY_INFRA, GcScheduler, Module, Nodefony, RequestContext, Service, countFacets, extend, inject, injectable, readStoreConnector, readStoreLocation, resolveAutoStore, runNeedsExternalServices } from "nodefony";
7
+ import { AUTO_STORE, EMPTY_INFRA, GcScheduler, Inject, Nodefony, RequestContext, Service, countFacets, extend, injectable, readStoreConnector, readStoreLocation, resolveAutoStore, storeConnectivity } from "nodefony";
10
8
  import { createHash, createHmac } from "node:crypto";
11
- //#region nodefony/service/sessions/sessions-service.ts
9
+ // nodefony/service/sessions/sessions-service.ts
12
10
  var _SessionsService;
13
11
  /**
14
12
  * Convertit un timestamp de session (`Date` en mémoire, **string ISO** après
@@ -143,7 +141,7 @@ let SessionsService = class SessionsService extends Service {
143
141
  const configured = storeName;
144
142
  let reason = `store explicitement configuré ("${configured}")`;
145
143
  if (storeName === AUTO_STORE) {
146
- const auto = resolveAutoStore("session", this.kernel?.infra ?? EMPTY_INFRA, _SessionsService.storageHandlers(), "memory", runNeedsExternalServices(this.kernel));
144
+ const auto = resolveAutoStore("session", this.kernel?.infra ?? EMPTY_INFRA, _SessionsService.storageHandlers(), "memory", storeConnectivity(this.kernel));
147
145
  storeName = auto.store;
148
146
  reason = auto.reason;
149
147
  this.log(`session.store "auto" → "${storeName}" (${auto.reason})`, "INFO");
@@ -219,7 +217,7 @@ let SessionsService = class SessionsService extends Service {
219
217
  return resolve(context.session);
220
218
  }
221
219
  }
222
- let inst = null;
220
+ let inst;
223
221
  try {
224
222
  context.sessionStarting = true;
225
223
  inst = this.createSession(this.defaultSessionName);
@@ -481,23 +479,44 @@ let SessionsService = class SessionsService extends Service {
481
479
  return destroyed;
482
480
  }
483
481
  /**
484
- * « Déconnexion partout » : détruit TOUTES les sessions d'un utilisateur (scan
485
- * O(N) — pas d'index inverse, acceptable en admin). Renvoie le nombre détruit.
482
+ * Détruit TOUTES les sessions dont le contenu satisfait `match` — la primitive
483
+ * des révocations groupées (« déconnecter partout », déconnexion demandée par
484
+ * un fournisseur d'identité). Ni journal ni audit : l'appelant, qui connaît le
485
+ * motif, les émet.
486
+ *
487
+ * @remarks Quand la méthode rend la main, il ne reste AUCUNE session qui
488
+ * satisfait `match` (au plus `MAX_LOGOUT_PASSES` passages). Une révocation qui
489
+ * en laisse une seule n'est pas une imprécision, c'est une faille.
490
+ *
491
+ * @param filter - restriction poussée au store (`user` : WHERE indexable) ;
492
+ * `undefined` parcourt tout le parc.
493
+ * @param match - prédicat sur la session sérialisée — il doit RE-VÉRIFIER ce
494
+ * que `filter` exprime, un store pouvant l'ignorer.
495
+ * @returns le nombre de sessions détruites.
496
+ * @throws Error si le store actif ne sait pas énumérer ses sessions.
486
497
  */
487
- async destroyByUser(identifier, actor) {
498
+ async destroyWhere(filter, match) {
488
499
  const storage = this.enumerable();
489
500
  let destroyed = 0;
490
501
  for (let pass = 0; pass < MAX_LOGOUT_PASSES; pass += 1) {
491
502
  let destroyedThisPass = 0;
492
- await this.eachSessionRecord({ user: identifier }, async (rec) => {
493
- if (rec.data.user !== identifier) return false;
503
+ await this.eachSessionRecord(filter, async (rec) => {
504
+ if (!match(rec.data)) return false;
494
505
  if (await storage.destroy(rec.id)) destroyedThisPass += 1;
495
506
  return false;
496
507
  });
497
508
  destroyed += destroyedThisPass;
498
509
  if (destroyedThisPass === 0) break;
499
- if (pass === 9) this.log(`sessions: logout-all interrompu après ${MAX_LOGOUT_PASSES} passages — user=${identifier} (révocation potentiellement incomplète)`, "WARNING");
510
+ if (pass === 9) this.log(`sessions: révocation groupée interrompue après ${MAX_LOGOUT_PASSES} passages — filtre=${JSON.stringify(filter ?? {})} (révocation potentiellement incomplète)`, "WARNING");
500
511
  }
512
+ return destroyed;
513
+ }
514
+ /**
515
+ * « Déconnexion partout » : détruit TOUTES les sessions d'un utilisateur (scan
516
+ * O(N) — pas d'index inverse, acceptable en admin). Renvoie le nombre détruit.
517
+ */
518
+ async destroyByUser(identifier, actor) {
519
+ const destroyed = await this.destroyWhere({ user: identifier }, (data) => data.user === identifier);
501
520
  if (destroyed > 0) {
502
521
  this.log(`sessions revoked by admin (logout-all) — user=${identifier} count=${destroyed} actor=${actor ?? "admin"}`, "INFO");
503
522
  this.emitAudit({
@@ -577,12 +596,7 @@ let SessionsService = class SessionsService extends Service {
577
596
  this.get("auditService")?.record(event);
578
597
  }
579
598
  };
580
- SessionsService = _SessionsService = __decorate([
581
- injectable(),
582
- __decorateParam(1, inject("HttpKernel")),
583
- __decorateMetadata("design:paramtypes", [typeof Module === "undefined" ? Object : Module, typeof http_kernel_default === "undefined" ? Object : http_kernel_default])
584
- ], SessionsService);
599
+ SessionsService = _SessionsService = __decorate([injectable(), __decorateParam(1, Inject("HttpKernel"))], SessionsService);
585
600
  SessionsService.registerStorage("memory", MemorySessionStorage);
586
601
  var sessions_service_default = SessionsService;
587
- //#endregion
588
602
  export { computeSessionRef, sessions_service_default as default, toSessionSummary };
@@ -1,5 +1,5 @@
1
1
  import { randomFillSync } from "node:crypto";
2
- //#region nodefony/service/trace.ts
2
+ // nodefony/service/trace.ts
3
3
  /**
4
4
  * W3C Trace Context — parse + generate `traceparent` headers (P2.7).
5
5
  *
@@ -69,5 +69,4 @@ function resolveTraceparent(header) {
69
69
  const hex = randomHex(24);
70
70
  return `00-${hex.slice(0, 32)}-${hex.slice(32)}-01`;
71
71
  }
72
- //#endregion
73
72
  export { parseTraceparent, resolveTraceparent };
@@ -1,12 +1,10 @@
1
- import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js";
2
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorate.js";
3
- import http_kernel_default from "../http-kernel.js";
4
- import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateParam.js";
5
- import { Cli, FileClass, Module, Service, inject } from "nodefony";
1
+ import __decorate from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
2
+ import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorateParam.js";
3
+ import { Cli, FileClass, Inject, Service } from "nodefony";
6
4
  import fs from "node:fs";
7
5
  import fsp from "node:fs/promises";
8
6
  import path from "node:path";
9
- //#region nodefony/service/upload/upload-service.ts
7
+ // nodefony/service/upload/upload-service.ts
10
8
  /** Test d'existence non bloquant (`fsp.access`) — remplace `existsSync`. */
11
9
  const existsAsync = async (p) => {
12
10
  try {
@@ -58,7 +56,7 @@ let upload = class upload extends Service {
58
56
  throw new Error(`Syslog not ready`);
59
57
  }
60
58
  };
61
- upload = __decorate([__decorateParam(1, inject("HttpKernel")), __decorateMetadata("design:paramtypes", [typeof Module === "undefined" ? Object : Module, typeof http_kernel_default === "undefined" ? Object : http_kernel_default])], upload);
59
+ upload = __decorate([__decorateParam(1, Inject("HttpKernel"))], upload);
62
60
  var UploadedFile = class UploadedFile extends FileClass {
63
61
  parsedFile;
64
62
  size;
@@ -168,5 +166,4 @@ var UploadedFile = class UploadedFile extends FileClass {
168
166
  }
169
167
  };
170
168
  var upload_service_default = upload;
171
- //#endregion
172
169
  export { UploadedFile, upload_service_default as default, upload };
@@ -1,6 +1,6 @@
1
1
  import { isIPv4, isIPv6 } from "node:net";
2
2
  import { createHash, sign } from "node:crypto";
3
- //#region nodefony/service/x509.ts
3
+ // nodefony/service/x509.ts
4
4
  /**
5
5
  * Fabrique et lecture de certificats X.509 par `node:crypto` seul — aucune
6
6
  * dépendance tierce.
@@ -320,5 +320,4 @@ async function createSelfSignedCertificate(spec) {
320
320
  const signature = await signAsync(spec.hash, tbs, spec.privateKey);
321
321
  return toPem(sequence(tbs, algorithm, bitString(signature)));
322
322
  }
323
- //#endregion
324
323
  export { SHA1_WITH_RSA_OID, createSelfSignedCertificate, distinguishedNameField, ipAddressBytes, parseSubjectAltName, signatureAlgorithmName, signatureAlgorithmOid };