@nodefony/http 10.0.0-beta.5 → 10.0.0-beta.7
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.
- package/README.md +24 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorateParam.js +1 -1
- package/dist/index.js +30 -26
- package/dist/nodefony/command/proxyGenerateCommand.js +2 -2
- package/dist/nodefony/config/config.js +29 -4
- package/dist/nodefony/grpc/GrpcCall.js +298 -0
- package/dist/nodefony/grpc/GrpcEndpoint.js +528 -0
- package/dist/nodefony/grpc/index.js +4 -0
- package/dist/nodefony/interfaces/IRpcCall.js +1 -0
- package/dist/nodefony/service/ProfilerAdminApi.js +5 -2
- package/dist/nodefony/service/audit-logger.js +52 -6
- package/dist/nodefony/service/error-renderer.js +47 -13
- package/dist/nodefony/service/http-kernel.js +306 -49
- package/dist/nodefony/service/pretty-request-logger.js +5 -3
- package/dist/nodefony/service/request-logger.js +21 -6
- package/dist/nodefony/service/reverse-proxy.js +8 -3
- package/dist/nodefony/service/servers/clientError.js +53 -4
- package/dist/nodefony/service/servers/server-h2c.js +6 -6
- package/dist/nodefony/service/servers/server-http.js +6 -4
- package/dist/nodefony/service/servers/server-https.js +14 -7
- package/dist/nodefony/service/servers/server-static.js +1 -6
- package/dist/nodefony/service/servers/server-websocket-secure.js +7 -11
- package/dist/nodefony/service/servers/server-websocket.js +7 -11
- package/dist/nodefony/service/servers/serverShutdown.js +43 -6
- package/dist/nodefony/service/sessions/sessions-service.js +7 -7
- package/dist/nodefony/service/upload/upload-service.js +2 -2
- package/dist/nodefony/src/assets/prebuiltUi.js +23 -5
- package/dist/nodefony/src/context/Context.js +12 -1
- package/dist/nodefony/src/context/http/ByteStream.js +364 -0
- package/dist/nodefony/src/context/http/HttpContext.js +139 -32
- package/dist/nodefony/src/context/http/Request.js +111 -22
- package/dist/nodefony/src/context/http/Response.js +459 -37
- package/dist/nodefony/src/context/http/SseStream.js +83 -148
- package/dist/nodefony/src/context/http/StreamLifecycle.js +582 -0
- package/dist/nodefony/src/context/http/parser.js +29 -11
- package/dist/nodefony/src/context/http/urlFastPath.js +80 -1
- package/dist/nodefony/src/context/http/webResponse.js +56 -0
- package/dist/nodefony/src/context/http2/Request.js +1 -1
- package/dist/nodefony/src/context/http2/Response.js +55 -12
- package/dist/nodefony/src/context/requestTrace.js +6 -2
- package/dist/nodefony/src/context/rpcOutcome.js +20 -0
- package/dist/nodefony/src/context/transport.js +20 -1
- package/dist/nodefony/src/context/websocket/Response.js +15 -3
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +86 -18
- package/dist/nodefony/src/context/websocket/websocketUrl.js +7 -0
- package/dist/nodefony/src/context/websocket/wsBackpressure.js +15 -15
- package/dist/nodefony/src/context/websocket/wsFrameRate.js +71 -0
- package/dist/nodefony/src/context/websocket/wsLogContent.js +55 -17
- package/dist/nodefony/src/cookies/cookie.js +57 -14
- package/dist/nodefony/src/errors/httpError.js +58 -25
- package/dist/nodefony/src/grpc/cors.js +59 -0
- package/dist/nodefony/src/grpc/limits.js +16 -0
- package/dist/nodefony/src/grpc/refusal.js +271 -0
- package/dist/nodefony/src/profiler/Profiler.js +14 -1
- package/dist/nodefony/src/profiler/aggregate.js +2 -2
- package/dist/nodefony/src/proxy/forward.js +109 -9
- package/dist/nodefony/src/proxy/generateProxyConfig.js +97 -17
- package/dist/nodefony/src/proxy/rules.js +29 -14
- package/dist/nodefony/src/servers/http2Goaway.js +76 -0
- package/dist/nodefony/src/servers/http2Limits.js +41 -15
- package/dist/nodefony/src/servers/http2StreamGrace.js +100 -0
- package/dist/nodefony/src/servers/receiveDeadline.js +177 -0
- package/dist/nodefony/src/servers/upgradeDispatch.js +62 -4
- package/dist/types/index.d.ts +11 -2
- package/dist/types/nodefony/config/config.d.ts +14 -0
- package/dist/types/nodefony/grpc/GrpcCall.d.ts +142 -0
- package/dist/types/nodefony/grpc/GrpcEndpoint.d.ts +98 -0
- package/dist/types/nodefony/grpc/index.d.ts +15 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +9 -0
- package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +14 -0
- package/dist/types/nodefony/interfaces/IRequest.d.ts +2 -1
- package/dist/types/nodefony/interfaces/IRequestTrace.d.ts +10 -0
- package/dist/types/nodefony/interfaces/IRouting.d.ts +19 -0
- package/dist/types/nodefony/interfaces/IRpcCall.d.ts +83 -0
- package/dist/types/nodefony/interfaces/ISecurity.d.ts +27 -2
- package/dist/types/nodefony/interfaces/IUpload.d.ts +51 -4
- package/dist/types/nodefony/interfaces/index.d.ts +2 -1
- package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +2 -1
- package/dist/types/nodefony/service/audit-logger.d.ts +33 -3
- package/dist/types/nodefony/service/error-renderer.d.ts +27 -5
- package/dist/types/nodefony/service/http-kernel.d.ts +36 -3
- package/dist/types/nodefony/service/request-logger.d.ts +10 -0
- package/dist/types/nodefony/service/servers/clientError.d.ts +3 -2
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +7 -5
- package/dist/types/nodefony/src/assets/prebuiltUi.d.ts +9 -4
- package/dist/types/nodefony/src/context/Context.d.ts +9 -1
- package/dist/types/nodefony/src/context/http/ByteStream.d.ts +109 -0
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +99 -3
- package/dist/types/nodefony/src/context/http/Request.d.ts +46 -9
- package/dist/types/nodefony/src/context/http/Response.d.ts +188 -8
- package/dist/types/nodefony/src/context/http/SseStream.d.ts +33 -8
- package/dist/types/nodefony/src/context/http/StreamLifecycle.d.ts +106 -0
- package/dist/types/nodefony/src/context/http/parser.d.ts +17 -3
- package/dist/types/nodefony/src/context/http/urlFastPath.d.ts +40 -0
- package/dist/types/nodefony/src/context/http/webResponse.d.ts +31 -0
- package/dist/types/nodefony/src/context/http2/Response.d.ts +40 -5
- package/dist/types/nodefony/src/context/requestTrace.d.ts +3 -1
- package/dist/types/nodefony/src/context/rpcOutcome.d.ts +11 -0
- package/dist/types/nodefony/src/context/transport.d.ts +17 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +6 -1
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +48 -0
- package/dist/types/nodefony/src/context/websocket/websocketUrl.d.ts +4 -0
- package/dist/types/nodefony/src/context/websocket/wsBackpressure.d.ts +12 -2
- package/dist/types/nodefony/src/context/websocket/wsFrameRate.d.ts +69 -0
- package/dist/types/nodefony/src/context/websocket/wsLogContent.d.ts +16 -8
- package/dist/types/nodefony/src/cookies/cookie.d.ts +20 -2
- package/dist/types/nodefony/src/errors/httpError.d.ts +29 -4
- package/dist/types/nodefony/src/grpc/cors.d.ts +32 -0
- package/dist/types/nodefony/src/grpc/limits.d.ts +14 -0
- package/dist/types/nodefony/src/grpc/refusal.d.ts +136 -0
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +11 -1
- package/dist/types/nodefony/src/proxy/forward.d.ts +37 -2
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +15 -2
- package/dist/types/nodefony/src/proxy/rules.d.ts +20 -0
- package/dist/types/nodefony/src/servers/http2Goaway.d.ts +41 -0
- package/dist/types/nodefony/src/servers/http2Limits.d.ts +27 -14
- package/dist/types/nodefony/src/servers/http2StreamGrace.d.ts +34 -0
- package/dist/types/nodefony/src/servers/receiveDeadline.d.ts +77 -0
- package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +41 -1
- package/docs/byte-stream.md +406 -0
- package/docs/cookies.md +13 -3
- package/docs/diagnostics-channel.md +45 -1
- package/docs/index.md +8 -0
- package/docs/reverse-proxy.md +15 -3
- package/docs/servers.md +67 -43
- package/docs/session.md +3 -3
- package/docs/sse.md +27 -20
- package/docs/upload.md +43 -17
- package/package.json +38 -18
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { severityFromStatus, severityOfHttp, statusOf } from "./audit-logger.js";
|
|
2
|
+
import { rpcSegment } from "./request-logger.js";
|
|
1
3
|
import { handshakeRefusalStatus } from "../src/context/websocket/WebsocketContext.js";
|
|
2
|
-
import { severityFromStatus, statusOf } from "./audit-logger.js";
|
|
3
4
|
import { accessStatusFamily, logColor } from "nodefony";
|
|
4
5
|
// nodefony/service/pretty-request-logger.ts
|
|
5
6
|
/**
|
|
@@ -27,9 +28,10 @@ var PrettyRequestLogger = class {
|
|
|
27
28
|
const status = statusOf(ctx) ?? (err ? 500 : null);
|
|
28
29
|
const method = ctx.method ?? "?";
|
|
29
30
|
const duration = ctx.stopClock();
|
|
31
|
+
const rpc = ctx.rpc ?? null;
|
|
30
32
|
return {
|
|
31
|
-
text: `${logColor.cyan(method.padEnd(4))} ${colorizeStatus(status)} ${ctx.url} ${logColor.blackBright(formatDuration(duration))} ${logColor.blackBright(ctx.remoteAddress ?? "-")} ` + logColor.magenta(`[${shortId(ctx.requestId)}]`) + (err ? ` ${logColor.red(err.message)}` : ""),
|
|
32
|
-
severity:
|
|
33
|
+
text: `${logColor.cyan(method.padEnd(4))} ${colorizeStatus(status)} ${ctx.url} ${logColor.blackBright(formatDuration(duration))} ${logColor.blackBright(ctx.remoteAddress ?? "-")} ` + logColor.magenta(`[${shortId(ctx.requestId)}]`) + (rpc === null ? "" : ` ${rpcSegment(rpc)}`) + (err ? ` ${logColor.red(err.message)}` : ""),
|
|
34
|
+
severity: severityOfHttp(ctx, status),
|
|
33
35
|
msgid: "req"
|
|
34
36
|
};
|
|
35
37
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { severityFromRpcCode, severityFromStatus } from "./audit-logger.js";
|
|
1
2
|
import { handshakeRefusalStatus } from "../src/context/websocket/WebsocketContext.js";
|
|
2
|
-
import { severityFromStatus } from "./audit-logger.js";
|
|
3
3
|
import { logColor } from "nodefony";
|
|
4
4
|
// nodefony/service/request-logger.ts
|
|
5
5
|
/**
|
|
@@ -11,6 +11,18 @@ const statusOf = (context) => {
|
|
|
11
11
|
return context.response?.statusCode;
|
|
12
12
|
};
|
|
13
13
|
/**
|
|
14
|
+
* Segment d'un appel RPC sur la ligne d'accès : `RPC : <méthode> <NOM>`
|
|
15
|
+
* (ADR-0026 D10 point 4) — le statut HTTP reste à sa place. Sans code (appel
|
|
16
|
+
* jamais commencé), la méthode seule.
|
|
17
|
+
*
|
|
18
|
+
* @param rpc - le résultat de l'appel.
|
|
19
|
+
* @returns le segment, sans espace de tête.
|
|
20
|
+
*/
|
|
21
|
+
function rpcSegment(rpc) {
|
|
22
|
+
const name = rpc.codeName;
|
|
23
|
+
return name === null ? `${logColor.cyan("RPC")} : ${rpc.method}` : `${logColor.cyan("RPC")} : ${rpc.method} ${name}`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
14
26
|
* Default Nodefony per-request logger — preserves the legacy colored format.
|
|
15
27
|
*
|
|
16
28
|
* HTTP success: `URL : <url> FROM : <remote> ORIGIN : <host> ID : <uuid>`
|
|
@@ -23,21 +35,24 @@ const statusOf = (context) => {
|
|
|
23
35
|
*/
|
|
24
36
|
var DefaultRequestLogger = class {
|
|
25
37
|
renderHttp(context, error) {
|
|
26
|
-
const
|
|
38
|
+
const rpc = context.rpc ?? null;
|
|
39
|
+
const txt = `${logColor.cyan("URL")} : ${context.url} ${logColor.cyan("FROM")} : ${context.remoteAddress} ${logColor.cyan("ORIGIN")} : ${context.originUrl?.host} ${logColor.cyan("ID")} : ${context.requestId}` + (rpc === null ? "" : ` ${rpcSegment(rpc)}`);
|
|
40
|
+
const code = rpc === null ? null : rpc.code;
|
|
27
41
|
const err = error ?? context.error;
|
|
28
42
|
if (err) {
|
|
29
43
|
const errCode = err.code ?? statusOf(context) ?? 500;
|
|
30
44
|
const msgid = `${context.serverType} ${logColor.magenta(errCode)} ${logColor.red(context.method ?? "")}`;
|
|
31
45
|
return {
|
|
32
46
|
text: context.kernel?.environment === "production" ? `${txt} ${err}` : `${txt}\n ${err}`,
|
|
33
|
-
severity: "ERROR",
|
|
47
|
+
severity: code === null ? "ERROR" : severityFromRpcCode(code),
|
|
34
48
|
msgid
|
|
35
49
|
};
|
|
36
50
|
}
|
|
51
|
+
const msgid = `${context.serverType} ${logColor.magenta(statusOf(context) ?? "")} ${context.method}`;
|
|
37
52
|
return {
|
|
38
53
|
text: txt,
|
|
39
|
-
severity: "INFO",
|
|
40
|
-
msgid
|
|
54
|
+
severity: code === null ? "INFO" : severityFromRpcCode(code),
|
|
55
|
+
msgid
|
|
41
56
|
};
|
|
42
57
|
}
|
|
43
58
|
renderWebsocket(context, error, acceptedProtocol) {
|
|
@@ -59,4 +74,4 @@ var DefaultRequestLogger = class {
|
|
|
59
74
|
};
|
|
60
75
|
}
|
|
61
76
|
};
|
|
62
|
-
export { DefaultRequestLogger as default };
|
|
77
|
+
export { DefaultRequestLogger as default, rpcSegment };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { forwardRequest, forwardUpgrade, refuseMalformedHandshake, refuseUpgrade } from "../src/proxy/forward.js";
|
|
1
2
|
import { isAmbiguousPath, normalizePrefix, parseProxyOrigin, proxyMountProblems, websocketHandshakeProblem } from "../src/proxy/rules.js";
|
|
2
3
|
import { requestAuthority } from "../src/context/transport.js";
|
|
3
|
-
import { forwardRequest, forwardUpgrade, refuseUpgrade } from "../src/proxy/forward.js";
|
|
4
4
|
import { Service } from "nodefony";
|
|
5
5
|
import http from "node:http";
|
|
6
6
|
import { randomBytes } from "node:crypto";
|
|
@@ -67,7 +67,7 @@ var ReverseProxy = class extends Service {
|
|
|
67
67
|
this.defaultTimeoutMs = section?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
68
68
|
this.connectTimeoutMs = section?.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
69
69
|
this.maxSockets = section?.maxSockets ?? DEFAULT_MAX_SOCKETS;
|
|
70
|
-
this.kernel?.
|
|
70
|
+
this.kernel?.once("onTerminate", () => this.closeAll());
|
|
71
71
|
for (const [prefix, mount] of Object.entries(section?.mounts ?? {})) this.#add(prefix, mount, "config");
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
@@ -275,10 +275,15 @@ var ReverseProxy = class extends Service {
|
|
|
275
275
|
refuseUpgrade(socket, 421, "Misdirected Request");
|
|
276
276
|
return true;
|
|
277
277
|
}
|
|
278
|
-
if (hostname === null || isAmbiguousPath(req.url ?? "/")
|
|
278
|
+
if (hostname === null || isAmbiguousPath(req.url ?? "/")) {
|
|
279
279
|
refuseUpgrade(socket, 400, "Bad Request");
|
|
280
280
|
return true;
|
|
281
281
|
}
|
|
282
|
+
const problem = websocketHandshakeProblem(req.method, req.headers);
|
|
283
|
+
if (problem !== null) {
|
|
284
|
+
refuseMalformedHandshake(socket, problem);
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
282
287
|
const secure = req.socket instanceof TLSSocket;
|
|
283
288
|
if (rules !== void 0 && !rules.isWebsocketOriginAllowed(req.headers.origin, hostname, secure)) {
|
|
284
289
|
refuseUpgrade(socket, 403, "Forbidden");
|
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
import { isPeerGoneError } from "../../src/context/websocket/Response.js";
|
|
2
|
+
import { STATUS_CODES } from "node:http";
|
|
2
3
|
// nodefony/service/servers/clientError.ts
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
+
* Le statut que Node lui-même aurait répondu (`_http_server.js#socketOnError`),
|
|
6
|
+
* hors débordement d'en-têtes ({@link overflowStatusLine}) ; 400 sinon.
|
|
7
|
+
* Brancher un écouteur `clientError` retire cette réponse par défaut : sans
|
|
8
|
+
* cette table, un délai de réception dépassé partait en `400 Bad Request` —
|
|
9
|
+
* le client lisait sa requête malformée alors qu'elle était seulement lente.
|
|
10
|
+
*/
|
|
11
|
+
const CLIENT_ERROR_STATUS = /* @__PURE__ */ new Map([["ERR_HTTP_REQUEST_TIMEOUT", 408], ["HPE_CHUNK_EXTENSIONS_OVERFLOW", 413]]);
|
|
12
|
+
function statusLineOf(status) {
|
|
13
|
+
return `${status} ${STATUS_CODES[status] ?? ""}`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* La réponse en vol sur cette socket a-t-elle déjà écrit ses en-têtes ? La
|
|
17
|
+
* garde même de Node (`socketOnError` : `_httpMessage._headerSent`), lue par
|
|
18
|
+
* réflexion — deux champs internes, sans équivalent public sur la socket.
|
|
19
|
+
*/
|
|
20
|
+
function inFlightHeaderSent(socket) {
|
|
21
|
+
const message = Reflect.get(socket, "_httpMessage");
|
|
22
|
+
return message !== null && typeof message === "object" && Reflect.get(message, "_headerSent") === true;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Gère l'event `clientError` d'un `http.Server` / `https.Server`, et des connexions
|
|
26
|
+
* HTTP/1.1 d'un serveur HTTP/2 à `allowHTTP1`.
|
|
5
27
|
*
|
|
6
28
|
* ⚠️ Doc Node (`http.Server` event `'clientError'`) : dès qu'un listener
|
|
7
29
|
* `clientError` est attaché, Node **cesse de fermer le socket automatiquement**
|
|
@@ -13,9 +35,36 @@ import { isPeerGoneError } from "../../src/context/websocket/Response.js";
|
|
|
13
35
|
* @param socket - flux client brut (l'event `clientError` fournit un `Duplex`).
|
|
14
36
|
*/
|
|
15
37
|
function handleClientError(error, socket) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
socket
|
|
38
|
+
const code = "code" in error ? error.code : void 0;
|
|
39
|
+
if (code === "ECONNRESET" || !socket.writable) return;
|
|
40
|
+
if (inFlightHeaderSent(socket)) {
|
|
41
|
+
socket.destroy();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const statusLine = code === "HPE_HEADER_OVERFLOW" ? overflowStatusLine(error) : statusLineOf(CLIENT_ERROR_STATUS.get(code) ?? 400);
|
|
45
|
+
socket.end(`HTTP/1.1 ${statusLine}\r\nConnection: close\r\nContent-Length: 0\r\n\r\n`);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Le statut d'un `HPE_HEADER_OVERFLOW` : Node lève la même erreur pour une
|
|
49
|
+
* cible trop longue et pour des en-têtes trop longs, alors que la norme les
|
|
50
|
+
* distingue — 414 pour la cible (RFC 9112 §3), 431 pour les en-têtes
|
|
51
|
+
* (RFC 6585 §5).
|
|
52
|
+
*
|
|
53
|
+
* Le parseur ne constate le débordement qu'à la FIN d'un élément (la cible,
|
|
54
|
+
* une valeur d'en-tête), au point `bytesParsed` du paquet reçu (`rawPacket`).
|
|
55
|
+
* Si la ligne de requête était finie, son `CRLF` — ou celui d'un en-tête —
|
|
56
|
+
* précède ce point : ce sont les en-têtes qui débordent. Sinon, c'est la cible.
|
|
57
|
+
* Constaté sur Node 26, en un paquet comme en plusieurs. Faute de ces deux
|
|
58
|
+
* champs, 431 : le statut d'avant.
|
|
59
|
+
*/
|
|
60
|
+
function overflowStatusLine(error) {
|
|
61
|
+
const raw = "rawPacket" in error ? error.rawPacket : void 0;
|
|
62
|
+
const parsed = "bytesParsed" in error ? error.bytesParsed : void 0;
|
|
63
|
+
if (Buffer.isBuffer(raw) && typeof parsed === "number") {
|
|
64
|
+
const eol = raw.indexOf("\r\n");
|
|
65
|
+
if (eol === -1 || eol >= parsed) return "414 URI Too Long";
|
|
66
|
+
}
|
|
67
|
+
return "431 Request Header Fields Too Large";
|
|
19
68
|
}
|
|
20
69
|
/**
|
|
21
70
|
* Gère l'event `sessionError` d'un serveur HTTP/2 (h2c ou TLS) : un pair PARTI
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { asServersConfig, configuredServerPort, isH2cEnabled } from "../../src/servers/kernelServers.js";
|
|
2
|
-
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
|
|
3
|
-
import { handleSessionError } from "./clientError.js";
|
|
4
1
|
import { createDrainTerminator } from "./serverShutdown.js";
|
|
5
|
-
import {
|
|
6
|
-
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorateParam.js";
|
|
2
|
+
import { handleSessionError } from "./clientError.js";
|
|
7
3
|
import { http2ServerLimits, setHttp2SessionTimeout } from "../../src/servers/http2Limits.js";
|
|
4
|
+
import { bindWithFallback, buildBindPlan, h2cPortConflict } from "../../src/servers/portBinder.js";
|
|
5
|
+
import { asServersConfig, configuredServerPort, isH2cEnabled } from "../../src/servers/kernelServers.js";
|
|
6
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorateParam.js";
|
|
7
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorate.js";
|
|
8
8
|
import { BootConfigurationError, Inject, Service, nodefonyError } from "nodefony";
|
|
9
9
|
import http2 from "node:http2";
|
|
10
10
|
// nodefony/service/servers/server-h2c.ts
|
|
@@ -98,7 +98,7 @@ let ServerH2c = class ServerH2c extends Service {
|
|
|
98
98
|
server.close();
|
|
99
99
|
reject(error);
|
|
100
100
|
});
|
|
101
|
-
this.
|
|
101
|
+
this.httpKernel.addShutdownStep("drain", async () => {
|
|
102
102
|
if (!this.server) return;
|
|
103
103
|
try {
|
|
104
104
|
await this.httpTerminator?.terminate();
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { asServersConfig, configuredServerPort } from "../../src/servers/kernelServers.js";
|
|
2
|
-
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
|
|
3
1
|
import { handleClientError } from "./clientError.js";
|
|
2
|
+
import { routeSocketTimeout } from "../../src/context/http/HttpContext.js";
|
|
4
3
|
import { createDrainTerminator } from "./serverShutdown.js";
|
|
5
4
|
import { bindWithFallback, buildBindPlan } from "../../src/servers/portBinder.js";
|
|
6
|
-
import
|
|
5
|
+
import { asServersConfig, configuredServerPort } from "../../src/servers/kernelServers.js";
|
|
6
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorateParam.js";
|
|
7
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorate.js";
|
|
7
8
|
import { Inject, Service, extend, nodefonyError } from "nodefony";
|
|
8
9
|
import http from "node:http";
|
|
9
10
|
// nodefony/service/servers/server-http.ts
|
|
@@ -41,6 +42,7 @@ let ServerHttp = class ServerHttp extends Service {
|
|
|
41
42
|
this.server = http.createServer(opt);
|
|
42
43
|
this.httpTerminator = createDrainTerminator(this.server, this.options.shutdownTimeout);
|
|
43
44
|
if (this.options.maxHeadersCount) this.server.maxHeadersCount = this.options.maxHeadersCount;
|
|
45
|
+
this.server.on("timeout", routeSocketTimeout);
|
|
44
46
|
if (this.options.timeout) {
|
|
45
47
|
this.server.setTimeout(this.options.timeout, () => {
|
|
46
48
|
this.fire("onTimeout", this);
|
|
@@ -67,7 +69,7 @@ let ServerHttp = class ServerHttp extends Service {
|
|
|
67
69
|
this.reportBindError(error);
|
|
68
70
|
reject(error);
|
|
69
71
|
});
|
|
70
|
-
this.
|
|
72
|
+
this.httpKernel.addShutdownStep("drain", async () => {
|
|
71
73
|
if (!this.server) return;
|
|
72
74
|
try {
|
|
73
75
|
await this.httpTerminator?.terminate();
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { asServersConfig, configuredServerPort } from "../../src/servers/kernelServers.js";
|
|
2
|
-
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
|
|
3
|
-
import { handleClientError, handleSessionError } from "./clientError.js";
|
|
4
1
|
import { createDrainTerminator } from "./serverShutdown.js";
|
|
5
|
-
import { bindWithFallback, buildBindPlan } from "../../src/servers/portBinder.js";
|
|
6
|
-
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorateParam.js";
|
|
7
2
|
import { http2ServerLimits, setHttp2SessionTimeout } from "../../src/servers/http2Limits.js";
|
|
3
|
+
import { handleClientError, handleSessionError } from "./clientError.js";
|
|
4
|
+
import { routeSocketTimeout } from "../../src/context/http/HttpContext.js";
|
|
5
|
+
import { bindWithFallback, buildBindPlan } from "../../src/servers/portBinder.js";
|
|
6
|
+
import { asServersConfig, configuredServerPort } from "../../src/servers/kernelServers.js";
|
|
7
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorateParam.js";
|
|
8
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorate.js";
|
|
8
9
|
import { Inject, Service, extend, nodefonyError } from "nodefony";
|
|
9
10
|
import http2 from "node:http2";
|
|
10
11
|
import https from "node:https";
|
|
@@ -57,6 +58,7 @@ let ServerHttps = class ServerHttps extends Service {
|
|
|
57
58
|
const server = https.createServer(opt);
|
|
58
59
|
this.server = server;
|
|
59
60
|
this.httpTerminator = this.terminator();
|
|
61
|
+
server.on("timeout", routeSocketTimeout);
|
|
60
62
|
if (this.options.timeout) {
|
|
61
63
|
server.setTimeout(this.options.timeout, () => {
|
|
62
64
|
this.fire("onTimeout", this);
|
|
@@ -74,7 +76,7 @@ let ServerHttps = class ServerHttps extends Service {
|
|
|
74
76
|
this.server.on("request", (request, response) => {
|
|
75
77
|
this.httpKernel.onHttpRequest(request, response, this.type);
|
|
76
78
|
});
|
|
77
|
-
this.
|
|
79
|
+
this.httpKernel.addShutdownStep("drain", async () => {
|
|
78
80
|
if (!this.server) return;
|
|
79
81
|
try {
|
|
80
82
|
await this.httpTerminator?.terminate();
|
|
@@ -106,6 +108,7 @@ let ServerHttps = class ServerHttps extends Service {
|
|
|
106
108
|
Object.assign(opt, http2ServerLimits(this.module.options.http2));
|
|
107
109
|
this.server = http2.createSecureServer(opt);
|
|
108
110
|
this.httpTerminator = this.terminator();
|
|
111
|
+
this.server.on("timeout", routeSocketTimeout);
|
|
109
112
|
setHttp2SessionTimeout(this.server, this.options.timeout, () => {
|
|
110
113
|
this.fire("onTimeout", this);
|
|
111
114
|
});
|
|
@@ -117,7 +120,7 @@ let ServerHttps = class ServerHttps extends Service {
|
|
|
117
120
|
this.reportBindError(error);
|
|
118
121
|
reject(error);
|
|
119
122
|
});
|
|
120
|
-
this.
|
|
123
|
+
this.httpKernel.addShutdownStep("drain", async () => {
|
|
121
124
|
if (!this.server) return;
|
|
122
125
|
try {
|
|
123
126
|
await this.httpTerminator?.terminate();
|
|
@@ -129,6 +132,10 @@ let ServerHttps = class ServerHttps extends Service {
|
|
|
129
132
|
this.server.on("sessionError", (error) => {
|
|
130
133
|
handleSessionError(error, (e) => this.log(e, "ERROR", "HTTP2 Server sessionError"));
|
|
131
134
|
});
|
|
135
|
+
this.server.on("clientError", (e, socket) => {
|
|
136
|
+
this.fire("onClientError", e, socket);
|
|
137
|
+
handleClientError(e, socket);
|
|
138
|
+
});
|
|
132
139
|
this.server.on("streamError", (error) => {
|
|
133
140
|
this.log(error, "ERROR", "HTTP2 Server streamError");
|
|
134
141
|
});
|
|
@@ -6,7 +6,6 @@ import http2 from "node:http2";
|
|
|
6
6
|
import { URL } from "node:url";
|
|
7
7
|
import { existsSync } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import mime from "mime-types";
|
|
10
9
|
import tls from "node:tls";
|
|
11
10
|
import serveStatic from "serve-static";
|
|
12
11
|
// nodefony/service/servers/server-static.ts
|
|
@@ -174,11 +173,7 @@ var Statics = class extends Service {
|
|
|
174
173
|
break;
|
|
175
174
|
}
|
|
176
175
|
}
|
|
177
|
-
for (const ele of Object.values(this.servers))
|
|
178
|
-
await this.getStatic(ele, request, response);
|
|
179
|
-
const type = mime.lookup(pathname);
|
|
180
|
-
response.setHeader("Content-Type", type);
|
|
181
|
-
}
|
|
176
|
+
for (const ele of Object.values(this.servers)) await this.getStatic(ele, request, response);
|
|
182
177
|
return Promise.resolve(response);
|
|
183
178
|
}
|
|
184
179
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { startHeartbeat } from "./wsHeartbeat.js";
|
|
2
1
|
import { asServersConfig, configuredServerPort } from "../../src/servers/kernelServers.js";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
2
|
+
import { startHeartbeat } from "./wsHeartbeat.js";
|
|
3
|
+
import { attachUpgradeDispatch, createNodefonyWebSocketServer } from "../../src/servers/upgradeDispatch.js";
|
|
4
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorateParam.js";
|
|
5
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorate.js";
|
|
6
6
|
import { Inject, Service } from "nodefony";
|
|
7
|
-
import Ws
|
|
7
|
+
import Ws from "ws";
|
|
8
8
|
// nodefony/service/servers/server-websocket-secure.ts
|
|
9
9
|
let WebsocketSecure = class WebsocketSecure extends Service {
|
|
10
10
|
httpKernel;
|
|
@@ -45,16 +45,12 @@ let WebsocketSecure = class WebsocketSecure extends Service {
|
|
|
45
45
|
this.family = this.infos.family;
|
|
46
46
|
this.protocol = serverHttps.protocol;
|
|
47
47
|
}
|
|
48
|
-
this.server =
|
|
49
|
-
...this.options,
|
|
50
|
-
noServer: true,
|
|
51
|
-
clientTracking: true
|
|
52
|
-
});
|
|
48
|
+
this.server = createNodefonyWebSocketServer(this.options);
|
|
53
49
|
this.#detachUpgrade = attachUpgradeDispatch(serverHttps.server, this.server, () => this.httpKernel.reverseProxy, (req) => this.httpKernel.websocketUpgradeRefusal(req), (req, admission) => {
|
|
54
50
|
this.httpKernel.onWebsocketUpgrade(req, admission, this.type).catch(() => {});
|
|
55
51
|
});
|
|
56
52
|
this.heartbeatTimer = startHeartbeat(this.server, this.options);
|
|
57
|
-
this.
|
|
53
|
+
this.httpKernel.addShutdownStep("dismiss", () => this.terminate());
|
|
58
54
|
this.ready = true;
|
|
59
55
|
this.module.fire("onServersReady", this.type, this);
|
|
60
56
|
return resolve(this.server);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { startHeartbeat } from "./wsHeartbeat.js";
|
|
2
1
|
import { asServersConfig, configuredServerPort } from "../../src/servers/kernelServers.js";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
2
|
+
import { startHeartbeat } from "./wsHeartbeat.js";
|
|
3
|
+
import { attachUpgradeDispatch, createNodefonyWebSocketServer } from "../../src/servers/upgradeDispatch.js";
|
|
4
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorateParam.js";
|
|
5
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorate.js";
|
|
6
6
|
import { Inject, Service } from "nodefony";
|
|
7
|
-
import Ws
|
|
7
|
+
import Ws from "ws";
|
|
8
8
|
// nodefony/service/servers/server-websocket.ts
|
|
9
9
|
let Websocket = class Websocket extends Service {
|
|
10
10
|
httpKernel;
|
|
@@ -45,16 +45,12 @@ let Websocket = class Websocket extends Service {
|
|
|
45
45
|
this.family = this.infos.family;
|
|
46
46
|
this.protocol = serverHttp.protocol;
|
|
47
47
|
}
|
|
48
|
-
this.server =
|
|
49
|
-
...this.options,
|
|
50
|
-
noServer: true,
|
|
51
|
-
clientTracking: true
|
|
52
|
-
});
|
|
48
|
+
this.server = createNodefonyWebSocketServer(this.options);
|
|
53
49
|
this.#detachUpgrade = attachUpgradeDispatch(serverHttp.server, this.server, () => this.httpKernel.reverseProxy, (req) => this.httpKernel.websocketUpgradeRefusal(req), (req, admission) => {
|
|
54
50
|
this.httpKernel.onWebsocketUpgrade(req, admission, this.type).catch(() => {});
|
|
55
51
|
});
|
|
56
52
|
this.heartbeatTimer = startHeartbeat(this.server, this.options);
|
|
57
|
-
this.
|
|
53
|
+
this.httpKernel.addShutdownStep("dismiss", () => this.terminate());
|
|
58
54
|
this.ready = true;
|
|
59
55
|
this.module.fire("onServersReady", this.type, this);
|
|
60
56
|
return resolve(this.server);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ANNOUNCE_CAP_MS, announceSessionClose } from "../../src/servers/http2Goaway.js";
|
|
1
2
|
import http from "node:http";
|
|
2
3
|
import https from "node:https";
|
|
3
4
|
import tls from "node:tls";
|
|
@@ -6,9 +7,11 @@ import tls from "node:tls";
|
|
|
6
7
|
* Fabrique l'arrêt gracieux d'un serveur HTTP/1.1, HTTPS, HTTP/2 sur TLS ou h2c.
|
|
7
8
|
*
|
|
8
9
|
* Au `terminate()` (SIGTERM, `docker stop`), le serveur cesse d'accepter, puis :
|
|
9
|
-
* - **HTTP/2** : chaque session reçoit un GOAWAY
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* - **HTTP/2** : chaque session reçoit un GOAWAY en deux temps — d'abord à
|
|
11
|
+
* `2^31-1` ({@link announceShutdown}), puis, un aller-retour plus tard, le
|
|
12
|
+
* définitif (`session.close()`). Les flux en vol se terminent, les nouveaux
|
|
13
|
+
* sont refusés, et le client sait qu'il doit rouvrir ailleurs au lieu de
|
|
14
|
+
* constater une coupure ;
|
|
12
15
|
* - **HTTP/1.1** : la réponse en vol part avec `Connection: close`, la socket
|
|
13
16
|
* inactive est fermée tout de suite, une requête qui arrive encore sur une
|
|
14
17
|
* socket gardée ouverte reçoit aussi `Connection: close`.
|
|
@@ -18,8 +21,8 @@ import tls from "node:tls";
|
|
|
18
21
|
*
|
|
19
22
|
* ⚠️ Les sockets WebSocket upgradées (sans réponse HTTP en cours) sont détruites
|
|
20
23
|
* SANS frame Close → les serveurs WS doivent fermer leurs clients (close 1001)
|
|
21
|
-
* AVANT ce drain. Garanti par
|
|
22
|
-
*
|
|
24
|
+
* AVANT ce drain. Garanti par `HttpKernel.drainTraffic` (ADR-0027 D2) : l'étape
|
|
25
|
+
* `dismiss` (1001) est attendue avant l'étape `drain`.
|
|
23
26
|
*
|
|
24
27
|
* @param server - serveur Node à drainer, créé AVANT son `listen` (le suivi des
|
|
25
28
|
* connexions commence ici)
|
|
@@ -59,6 +62,9 @@ function createDrainTerminator(server, shutdownTimeout) {
|
|
|
59
62
|
if (sessions !== null) for (const s of sessions) s.destroy();
|
|
60
63
|
for (const socket of sockets) socket.destroy();
|
|
61
64
|
}, timeout);
|
|
65
|
+
announceShutdown(sessions, Math.min(timeout, ANNOUNCE_CAP_MS), () => closeAll(resolve, deadline));
|
|
66
|
+
});
|
|
67
|
+
const closeAll = (resolve, deadline) => {
|
|
62
68
|
server.close(() => {
|
|
63
69
|
clearTimeout(deadline);
|
|
64
70
|
resolve();
|
|
@@ -72,12 +78,43 @@ function createDrainTerminator(server, shutdownTimeout) {
|
|
|
72
78
|
else if (!response.headersSent) response.setHeader("connection", "close");
|
|
73
79
|
}
|
|
74
80
|
}
|
|
75
|
-
}
|
|
81
|
+
};
|
|
76
82
|
return { terminate() {
|
|
77
83
|
terminating ??= drain();
|
|
78
84
|
return terminating;
|
|
79
85
|
} };
|
|
80
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Annonce l'arrêt à chaque session HTTP/2 SANS écarter un flux déjà en route —
|
|
89
|
+
* l'annonce en deux temps de RFC 9113 §6.8, session par session
|
|
90
|
+
* ({@link announceSessionClose}, la même règle que la fermeture d'une session
|
|
91
|
+
* inactive). Le GOAWAY définitif (`session.close()`) suit dans `done`. Un pair
|
|
92
|
+
* qui ne répond pas au PING est réglé au délai `cap`.
|
|
93
|
+
*
|
|
94
|
+
* @param sessions - sessions HTTP/2 ouvertes, `null` pour un serveur HTTP/1.1
|
|
95
|
+
* @param cap - délai maximal d'attente des échos (ms)
|
|
96
|
+
* @param done - appelé UNE fois, quand tous les échos sont revenus ou au délai
|
|
97
|
+
*/
|
|
98
|
+
function announceShutdown(sessions, cap, done) {
|
|
99
|
+
let pending = 1;
|
|
100
|
+
let finished = false;
|
|
101
|
+
const finish = () => {
|
|
102
|
+
if (finished) return;
|
|
103
|
+
finished = true;
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
done();
|
|
106
|
+
};
|
|
107
|
+
const settle = () => {
|
|
108
|
+
if (--pending === 0) finish();
|
|
109
|
+
};
|
|
110
|
+
const timer = setTimeout(finish, cap);
|
|
111
|
+
if (sessions !== null) for (const session of sessions) {
|
|
112
|
+
if (session.closed || session.destroyed) continue;
|
|
113
|
+
pending++;
|
|
114
|
+
announceSessionClose(session, settle);
|
|
115
|
+
}
|
|
116
|
+
settle();
|
|
117
|
+
}
|
|
81
118
|
function track(set, socket) {
|
|
82
119
|
set.add(socket);
|
|
83
120
|
socket.once("close", () => set.delete(socket));
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { SESSION_FACETS } from "../../src/session/storage/sessionFilters.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";
|
|
4
1
|
import Session from "../../src/session/session.js";
|
|
5
2
|
import MemorySessionStorage from "../../src/session/storage/MemorySessionStorage.js";
|
|
6
3
|
import RevocationGuardStorage from "../../src/session/storage/RevocationGuardStorage.js";
|
|
4
|
+
import { SESSION_FACETS } from "../../src/session/storage/sessionFilters.js";
|
|
5
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorateParam.js";
|
|
6
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorate.js";
|
|
7
7
|
import { AUTO_STORE, EMPTY_INFRA, GcScheduler, Inject, Nodefony, RequestContext, Service, countFacets, extend, injectable, readStoreConnector, readStoreLocation, resolveAutoStore, storeConnectivity } from "nodefony";
|
|
8
8
|
import { createHash, createHmac } from "node:crypto";
|
|
9
9
|
// nodefony/service/sessions/sessions-service.ts
|
|
@@ -125,10 +125,6 @@ let SessionsService = class SessionsService extends Service {
|
|
|
125
125
|
this.module = module;
|
|
126
126
|
this.certificates = this.get("certificates");
|
|
127
127
|
this.defaultSessionName = this.options.name;
|
|
128
|
-
this.once("onTerminate", () => {
|
|
129
|
-
this.gcScheduler?.stop();
|
|
130
|
-
if (this.storage) this.storage.close();
|
|
131
|
-
});
|
|
132
128
|
}
|
|
133
129
|
async init() {
|
|
134
130
|
this.secret = this.createSecret();
|
|
@@ -184,6 +180,10 @@ let SessionsService = class SessionsService extends Service {
|
|
|
184
180
|
onError: (e) => this.log(e, "WARNING", "SESSION-GC")
|
|
185
181
|
});
|
|
186
182
|
this.gcScheduler.start();
|
|
183
|
+
this.kernel?.once("onTerminate", () => {
|
|
184
|
+
this.gcScheduler?.stop();
|
|
185
|
+
this.storage?.close();
|
|
186
|
+
});
|
|
187
187
|
});
|
|
188
188
|
return this.storage;
|
|
189
189
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorateParam.js";
|
|
2
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorate.js";
|
|
3
3
|
import { Cli, FileClass, Inject, Service } from "nodefony";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import fsp from "node:fs/promises";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cspNonceMetaTag } from "nodefony";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
// nodefony/src/assets/prebuiltUi.ts
|
|
@@ -67,8 +68,8 @@ function resolveUiDelivery(opts) {
|
|
|
67
68
|
* requête. Le SPA fallback reste au controller du module (routes littérales,
|
|
68
69
|
* cf. StudioController).
|
|
69
70
|
*
|
|
70
|
-
* Perf : l'index est lu UNE fois (lazy, caché)
|
|
71
|
-
*
|
|
71
|
+
* Perf : l'index est lu UNE fois (lazy, caché) et découpé UNE fois autour des
|
|
72
|
+
* emplacements du nonce ; un rendu ne coûte qu'un `join`.
|
|
72
73
|
*/
|
|
73
74
|
var PrebuiltUi = class {
|
|
74
75
|
publicPath;
|
|
@@ -76,6 +77,8 @@ var PrebuiltUi = class {
|
|
|
76
77
|
indexPath;
|
|
77
78
|
/** Template HTML caché — `null` tant que non lu (lazy). */
|
|
78
79
|
template = null;
|
|
80
|
+
/** Index découpé autour des emplacements du nonce — `null` tant que non calculé. */
|
|
81
|
+
nonceParts = null;
|
|
79
82
|
constructor(opts) {
|
|
80
83
|
let p = opts.publicPath.trim();
|
|
81
84
|
if (!p.startsWith("/")) p = `/${p}`;
|
|
@@ -107,8 +110,11 @@ var PrebuiltUi = class {
|
|
|
107
110
|
* Document HTML d'entrée de l'UI (index pré-buildé par Vite).
|
|
108
111
|
*
|
|
109
112
|
* @param nonce nonce CSP de la requête (`Context.cspNonce`) — injecté sur
|
|
110
|
-
* chaque `<script
|
|
111
|
-
*
|
|
113
|
+
* chaque `<script>`, `src` compris : sous `'strict-dynamic'`
|
|
114
|
+
* (ADR-0024 §C1), `'self'` est ignoré et seul le nonce autorise
|
|
115
|
+
* une balise ; ce qu'elle importe ensuite hérite de la confiance.
|
|
116
|
+
* Annoncé aussi par `<meta property="csp-nonce">` en tête de
|
|
117
|
+
* `<head>` (ADR-0024 §C2) : les `<style>` créés à l'exécution.
|
|
112
118
|
* @returns le HTML, ou un commentaire HTML fail-loud si l'index est absent
|
|
113
119
|
*/
|
|
114
120
|
renderIndex(nonce) {
|
|
@@ -118,7 +124,19 @@ var PrebuiltUi = class {
|
|
|
118
124
|
return `<!DOCTYPE html><!-- prebuilt UI index missing: ${this.indexPath} -->`;
|
|
119
125
|
}
|
|
120
126
|
if (!nonce) return this.template;
|
|
121
|
-
|
|
127
|
+
this.nonceParts ??= splitAroundNonce(this.template);
|
|
128
|
+
return this.nonceParts.join(nonce);
|
|
122
129
|
}
|
|
123
130
|
};
|
|
131
|
+
/** Repère des emplacements du nonce : un octet nul ne figure dans aucun HTML. */
|
|
132
|
+
const NONCE_SLOT = "\0";
|
|
133
|
+
/**
|
|
134
|
+
* Découpe un index HTML autour des emplacements du nonce : l'attribut de chaque
|
|
135
|
+
* `<script>` et la balise meta posée juste après l'ouverture de `<head>` (rien
|
|
136
|
+
* si le document n'en a pas — les `<script>` restent signés).
|
|
137
|
+
*/
|
|
138
|
+
function splitAroundNonce(template) {
|
|
139
|
+
const marked = template.replaceAll("<script", `<script nonce="${NONCE_SLOT}"`).replace(/<head\b[^>]*>/i, (open) => open + cspNonceMetaTag(NONCE_SLOT));
|
|
140
|
+
return Object.freeze(marked.split(NONCE_SLOT));
|
|
141
|
+
}
|
|
124
142
|
export { PrebuiltUi, resolveUiDelivery };
|
|
@@ -3,9 +3,9 @@ import { protocolNameOf } from "./transport.js";
|
|
|
3
3
|
import { resolveTimingMode } from "./timingMode.js";
|
|
4
4
|
import Cookie, { cookiesParser } from "../cookies/cookie.js";
|
|
5
5
|
import { RequestContext, Service, logColor } from "nodefony";
|
|
6
|
+
import { AsyncResource } from "node:async_hooks";
|
|
6
7
|
import { randomFillSync, randomUUID } from "node:crypto";
|
|
7
8
|
import { performance } from "node:perf_hooks";
|
|
8
|
-
import { AsyncResource } from "node:async_hooks";
|
|
9
9
|
// nodefony/src/context/Context.ts
|
|
10
10
|
const colorLogEvent = () => logColor.cyanBgBlack("EVENT CONTEXT");
|
|
11
11
|
const PERF_PROBE_SUB = process.env.NF_PERF_PROBE === "1";
|
|
@@ -356,6 +356,17 @@ var Context = class extends Service {
|
|
|
356
356
|
holdRequestStore(store) {
|
|
357
357
|
this.requestStore = store;
|
|
358
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* L'utilisateur authentifié de l'unité de travail, lu sur son magasin et
|
|
361
|
+
* non sur l'ALS : valable là où la bulle n'est plus active (rendu d'une
|
|
362
|
+
* erreur, démontage). Même règle que `RequestContext.getAuthenticatedUser`.
|
|
363
|
+
*
|
|
364
|
+
* @returns l'utilisateur connecté, ou `null` (anonyme, hors zone, nettoyé).
|
|
365
|
+
*/
|
|
366
|
+
authenticatedUser() {
|
|
367
|
+
const store = this.requestStore;
|
|
368
|
+
return store === null ? null : RequestContext.authenticatedUserOf(store);
|
|
369
|
+
}
|
|
359
370
|
clean() {
|
|
360
371
|
this.cleaned = true;
|
|
361
372
|
this.httpKernel = null;
|