@jsenv/server 17.6.5 → 17.6.6

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.
@@ -7309,11 +7309,14 @@ const TIMING_NOOP = () => {
7309
7309
  * - `GET /@jsenv/server/*`, serving this package's own client files;
7310
7310
  * - `/.internal/alive.websocket` and `/.internal/alive.eventsource`, which a client
7311
7311
  * subscribes to in order to reload when the server restarts.
7312
- * @param {boolean|Object} [params.serverTiming=false] - `true` or `{ minDuration }` to send
7313
- * `server-timing` response headers: the time to start responding, the routing of each
7314
- * plugin, what routes measure with `helpers.timing` and the `timing` a response hands
7315
- * back. `minDuration` (ms) drops the entries that took less: 0 keeps everything (what a
7316
- * test wants), a human reading devtools usually wants the sub-millisecond noise gone.
7312
+ * @param {boolean|Function|Object} [params.serverTiming=false] - `true`, `(request) =>
7313
+ * boolean` or `{ enabled, minDuration }` to send `server-timing` response headers: the
7314
+ * time to start responding, the routing of each plugin, what routes measure with
7315
+ * `helpers.timing` and the `timing` a response hands back. `enabled` can be a function
7316
+ * too: it is asked once per request, so a server can measure for a client it recognizes
7317
+ * (a header, a cookie, an origin) and stay silent for everyone else. `minDuration` (ms)
7318
+ * drops the entries that took less: 0 keeps everything (what a test wants), a human
7319
+ * reading devtools usually wants the sub-millisecond noise gone.
7317
7320
  * @param {number} [params.requestWaitingMs=0] - Call `requestWaitingCallback` when a request
7318
7321
  * still has no response after that many ms (0 disables).
7319
7322
  * @param {Function} [params.requestWaitingCallback] - `({ request, requestWaitingMs })`, logs a warning by default.
@@ -7670,13 +7673,16 @@ const startServer = async ({
7670
7673
  sendResponseOperation.addAbortSignal(receiveRequestOperation.signal);
7671
7674
  return [receiveRequestOperation, sendResponseOperation];
7672
7675
  };
7673
- const serverTimingMinDuration =
7674
- serverTiming && typeof serverTiming === "object"
7675
- ? serverTiming.minDuration || 0
7676
- : 0;
7676
+ const { serverTimingEnabled, serverTimingMinDuration } =
7677
+ resolveServerTiming(serverTiming);
7677
7678
  const getResponseProperties = async (request) => {
7679
+ // Asked once per request, on what the client sent: an api can then measure
7680
+ // for everyone and disclose to a token, a cookie, an origin — instead of
7681
+ // choosing between measuring nothing and telling every client what it does
7682
+ // internally.
7683
+ const timingEnabled = serverTimingEnabled(request);
7678
7684
  const timings = {};
7679
- const timing = serverTiming
7685
+ const timing = timingEnabled
7680
7686
  ? (name) => {
7681
7687
  const start = performance.now();
7682
7688
  timings[name] = null;
@@ -7711,7 +7717,7 @@ const startServer = async ({
7711
7717
 
7712
7718
  let headersToInject;
7713
7719
  const finalizeResponseProperties = (responseProperties) => {
7714
- if (serverTiming) {
7720
+ if (timingEnabled) {
7715
7721
  startRespondingTiming.end();
7716
7722
  // A response can hand back measures of its own (a `timing` property —
7717
7723
  // durations keyed by description, null for a plain marker): they join
@@ -8233,6 +8239,36 @@ const PROCESS_TEARDOWN_EVENTS_MAP = {
8233
8239
  exit: STOP_REASON_PROCESS_EXIT,
8234
8240
  };
8235
8241
 
8242
+ const SERVER_TIMING_DISABLED = () => false;
8243
+ const SERVER_TIMING_ENABLED = () => true;
8244
+
8245
+ const resolveServerTiming = (serverTiming) => {
8246
+ if (typeof serverTiming === "function") {
8247
+ return {
8248
+ serverTimingEnabled: serverTiming,
8249
+ serverTimingMinDuration: 0,
8250
+ };
8251
+ }
8252
+ if (serverTiming && typeof serverTiming === "object") {
8253
+ const { enabled = true, minDuration = 0 } = serverTiming;
8254
+ return {
8255
+ serverTimingEnabled:
8256
+ typeof enabled === "function"
8257
+ ? enabled
8258
+ : enabled
8259
+ ? SERVER_TIMING_ENABLED
8260
+ : SERVER_TIMING_DISABLED,
8261
+ serverTimingMinDuration: minDuration,
8262
+ };
8263
+ }
8264
+ return {
8265
+ serverTimingEnabled: serverTiming
8266
+ ? SERVER_TIMING_ENABLED
8267
+ : SERVER_TIMING_DISABLED,
8268
+ serverTimingMinDuration: 0,
8269
+ };
8270
+ };
8271
+
8236
8272
  const internalErrorHtmlFileUrl = import.meta.resolve("./client/error_handler/500.html");
8237
8273
  let internalErrorHtmlTemplate;
8238
8274
  const readInternalErrorHtmlTemplate = () => {
@@ -8421,6 +8457,9 @@ class ProgressiveResponse {
8421
8457
  * so here we just need to add the CORS headers to the response
8422
8458
  */
8423
8459
 
8460
+ const TIMING_ALLOW_ORIGIN_DISABLED = () => false;
8461
+ const TIMING_ALLOW_ORIGIN_ENABLED = () => true;
8462
+
8424
8463
  const jsenvAccessControlAllowedHeaders = ["x-requested-with"];
8425
8464
 
8426
8465
  const jsenvAccessControlAllowedMethods = [
@@ -8453,8 +8492,10 @@ const jsenvAccessControlAllowedMethods = [
8453
8492
  * @param {boolean} [params.accessControlAllowCredentials=false] - Send
8454
8493
  * `access-control-allow-credentials: true`.
8455
8494
  * @param {number} [params.accessControlMaxAge=600] - Seconds a browser may cache the preflight.
8456
- * @param {boolean} [params.timingAllowOrigin=false] - Send `timing-allow-origin` so the
8457
- * allowed origin can read resource timing.
8495
+ * @param {boolean|Function} [params.timingAllowOrigin=false] - Send `timing-allow-origin`
8496
+ * so the allowed origin can read resource timing (the `server-timing` header included).
8497
+ * A `(request) => boolean` is asked once per request, so it can be the very test that
8498
+ * decides `startServer({ serverTiming })`.
8458
8499
  * @returns {Object|Array} The plugin, or `[]` when CORS stays disabled.
8459
8500
  */
8460
8501
  const serverPluginCORS = ({
@@ -8482,6 +8523,12 @@ const serverPluginCORS = ({
8482
8523
  const allowedOriginChecker = createAllowedOriginChecker(
8483
8524
  accessControlAllowedOrigins,
8484
8525
  );
8526
+ const timingAllowOriginEnabled =
8527
+ typeof timingAllowOrigin === "function"
8528
+ ? timingAllowOrigin
8529
+ : timingAllowOrigin
8530
+ ? TIMING_ALLOW_ORIGIN_ENABLED
8531
+ : TIMING_ALLOW_ORIGIN_DISABLED;
8485
8532
 
8486
8533
  return {
8487
8534
  name: "jsenv:cors",
@@ -8496,7 +8543,7 @@ const serverPluginCORS = ({
8496
8543
  accessControlAllowRequestHeaders,
8497
8544
  accessControlAllowCredentials,
8498
8545
  accessControlMaxAge,
8499
- timingAllowOrigin,
8546
+ timingAllowOriginEnabled,
8500
8547
  });
8501
8548
  return {
8502
8549
  headers: accessControlHeaders,
@@ -8550,7 +8597,7 @@ const originPatternToRegExp = (originPattern) => {
8550
8597
  // https://www.w3.org/TR/cors/
8551
8598
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
8552
8599
  const generateAccessControlHeaders = ({
8553
- request: { headers },
8600
+ request,
8554
8601
  allowedOriginChecker,
8555
8602
  accessControlAllowRequestOrigin,
8556
8603
  accessControlAllowedMethods,
@@ -8561,8 +8608,9 @@ const generateAccessControlHeaders = ({
8561
8608
  // by default OPTIONS request can be cache for a long time, it's not going to change soon ?
8562
8609
  // we could put a lot here, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
8563
8610
  accessControlMaxAge = 600,
8564
- timingAllowOrigin,
8611
+ timingAllowOriginEnabled,
8565
8612
  } = {}) => {
8613
+ const { headers } = request;
8566
8614
  const vary = [];
8567
8615
 
8568
8616
  // Access-Control-Allow-Origin must be a single value (not a list).
@@ -8626,7 +8674,9 @@ const generateAccessControlHeaders = ({
8626
8674
  ? { "access-control-allow-credentials": true }
8627
8675
  : {}),
8628
8676
  "access-control-max-age": accessControlMaxAge,
8629
- ...(timingAllowOrigin ? { "timing-allow-origin": allowOrigin } : {}),
8677
+ ...(timingAllowOriginEnabled(request)
8678
+ ? { "timing-allow-origin": allowOrigin }
8679
+ : {}),
8630
8680
  ...(vary.length ? { vary: vary.join(", ") } : {}),
8631
8681
  };
8632
8682
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/server",
3
- "version": "17.6.5",
3
+ "version": "17.6.6",
4
4
  "type": "module",
5
5
  "description": "A modern Node.js HTTP server with declarative routing, content negotiation, and WebSocket support",
6
6
  "repository": {
@@ -3,6 +3,9 @@
3
3
  * so here we just need to add the CORS headers to the response
4
4
  */
5
5
 
6
+ const TIMING_ALLOW_ORIGIN_DISABLED = () => false;
7
+ const TIMING_ALLOW_ORIGIN_ENABLED = () => true;
8
+
6
9
  export const jsenvAccessControlAllowedHeaders = ["x-requested-with"];
7
10
 
8
11
  export const jsenvAccessControlAllowedMethods = [
@@ -35,8 +38,10 @@ export const jsenvAccessControlAllowedMethods = [
35
38
  * @param {boolean} [params.accessControlAllowCredentials=false] - Send
36
39
  * `access-control-allow-credentials: true`.
37
40
  * @param {number} [params.accessControlMaxAge=600] - Seconds a browser may cache the preflight.
38
- * @param {boolean} [params.timingAllowOrigin=false] - Send `timing-allow-origin` so the
39
- * allowed origin can read resource timing.
41
+ * @param {boolean|Function} [params.timingAllowOrigin=false] - Send `timing-allow-origin`
42
+ * so the allowed origin can read resource timing (the `server-timing` header included).
43
+ * A `(request) => boolean` is asked once per request, so it can be the very test that
44
+ * decides `startServer({ serverTiming })`.
40
45
  * @returns {Object|Array} The plugin, or `[]` when CORS stays disabled.
41
46
  */
42
47
  export const serverPluginCORS = ({
@@ -64,6 +69,12 @@ export const serverPluginCORS = ({
64
69
  const allowedOriginChecker = createAllowedOriginChecker(
65
70
  accessControlAllowedOrigins,
66
71
  );
72
+ const timingAllowOriginEnabled =
73
+ typeof timingAllowOrigin === "function"
74
+ ? timingAllowOrigin
75
+ : timingAllowOrigin
76
+ ? TIMING_ALLOW_ORIGIN_ENABLED
77
+ : TIMING_ALLOW_ORIGIN_DISABLED;
67
78
 
68
79
  return {
69
80
  name: "jsenv:cors",
@@ -78,7 +89,7 @@ export const serverPluginCORS = ({
78
89
  accessControlAllowRequestHeaders,
79
90
  accessControlAllowCredentials,
80
91
  accessControlMaxAge,
81
- timingAllowOrigin,
92
+ timingAllowOriginEnabled,
82
93
  });
83
94
  return {
84
95
  headers: accessControlHeaders,
@@ -132,7 +143,7 @@ const originPatternToRegExp = (originPattern) => {
132
143
  // https://www.w3.org/TR/cors/
133
144
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
134
145
  const generateAccessControlHeaders = ({
135
- request: { headers },
146
+ request,
136
147
  allowedOriginChecker,
137
148
  accessControlAllowRequestOrigin,
138
149
  accessControlAllowedMethods,
@@ -143,8 +154,9 @@ const generateAccessControlHeaders = ({
143
154
  // by default OPTIONS request can be cache for a long time, it's not going to change soon ?
144
155
  // we could put a lot here, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
145
156
  accessControlMaxAge = 600,
146
- timingAllowOrigin,
157
+ timingAllowOriginEnabled,
147
158
  } = {}) => {
159
+ const { headers } = request;
148
160
  const vary = [];
149
161
 
150
162
  // Access-Control-Allow-Origin must be a single value (not a list).
@@ -208,7 +220,9 @@ const generateAccessControlHeaders = ({
208
220
  ? { "access-control-allow-credentials": true }
209
221
  : {}),
210
222
  "access-control-max-age": accessControlMaxAge,
211
- ...(timingAllowOrigin ? { "timing-allow-origin": allowOrigin } : {}),
223
+ ...(timingAllowOriginEnabled(request)
224
+ ? { "timing-allow-origin": allowOrigin }
225
+ : {}),
212
226
  ...(vary.length ? { vary: vary.join(", ") } : {}),
213
227
  };
214
228
  };
@@ -129,11 +129,14 @@ const TIMING_NOOP = () => {
129
129
  * - `GET /@jsenv/server/*`, serving this package's own client files;
130
130
  * - `/.internal/alive.websocket` and `/.internal/alive.eventsource`, which a client
131
131
  * subscribes to in order to reload when the server restarts.
132
- * @param {boolean|Object} [params.serverTiming=false] - `true` or `{ minDuration }` to send
133
- * `server-timing` response headers: the time to start responding, the routing of each
134
- * plugin, what routes measure with `helpers.timing` and the `timing` a response hands
135
- * back. `minDuration` (ms) drops the entries that took less: 0 keeps everything (what a
136
- * test wants), a human reading devtools usually wants the sub-millisecond noise gone.
132
+ * @param {boolean|Function|Object} [params.serverTiming=false] - `true`, `(request) =>
133
+ * boolean` or `{ enabled, minDuration }` to send `server-timing` response headers: the
134
+ * time to start responding, the routing of each plugin, what routes measure with
135
+ * `helpers.timing` and the `timing` a response hands back. `enabled` can be a function
136
+ * too: it is asked once per request, so a server can measure for a client it recognizes
137
+ * (a header, a cookie, an origin) and stay silent for everyone else. `minDuration` (ms)
138
+ * drops the entries that took less: 0 keeps everything (what a test wants), a human
139
+ * reading devtools usually wants the sub-millisecond noise gone.
137
140
  * @param {number} [params.requestWaitingMs=0] - Call `requestWaitingCallback` when a request
138
141
  * still has no response after that many ms (0 disables).
139
142
  * @param {Function} [params.requestWaitingCallback] - `({ request, requestWaitingMs })`, logs a warning by default.
@@ -490,13 +493,16 @@ export const startServer = async ({
490
493
  sendResponseOperation.addAbortSignal(receiveRequestOperation.signal);
491
494
  return [receiveRequestOperation, sendResponseOperation];
492
495
  };
493
- const serverTimingMinDuration =
494
- serverTiming && typeof serverTiming === "object"
495
- ? serverTiming.minDuration || 0
496
- : 0;
496
+ const { serverTimingEnabled, serverTimingMinDuration } =
497
+ resolveServerTiming(serverTiming);
497
498
  const getResponseProperties = async (request) => {
499
+ // Asked once per request, on what the client sent: an api can then measure
500
+ // for everyone and disclose to a token, a cookie, an origin — instead of
501
+ // choosing between measuring nothing and telling every client what it does
502
+ // internally.
503
+ const timingEnabled = serverTimingEnabled(request);
498
504
  const timings = {};
499
- const timing = serverTiming
505
+ const timing = timingEnabled
500
506
  ? (name) => {
501
507
  const start = performance.now();
502
508
  timings[name] = null;
@@ -531,7 +537,7 @@ export const startServer = async ({
531
537
 
532
538
  let headersToInject;
533
539
  const finalizeResponseProperties = (responseProperties) => {
534
- if (serverTiming) {
540
+ if (timingEnabled) {
535
541
  startRespondingTiming.end();
536
542
  // A response can hand back measures of its own (a `timing` property —
537
543
  // durations keyed by description, null for a plain marker): they join
@@ -1057,3 +1063,33 @@ const PROCESS_TEARDOWN_EVENTS_MAP = {
1057
1063
  beforeExit: STOP_REASON_PROCESS_BEFORE_EXIT,
1058
1064
  exit: STOP_REASON_PROCESS_EXIT,
1059
1065
  };
1066
+
1067
+ const SERVER_TIMING_DISABLED = () => false;
1068
+ const SERVER_TIMING_ENABLED = () => true;
1069
+
1070
+ const resolveServerTiming = (serverTiming) => {
1071
+ if (typeof serverTiming === "function") {
1072
+ return {
1073
+ serverTimingEnabled: serverTiming,
1074
+ serverTimingMinDuration: 0,
1075
+ };
1076
+ }
1077
+ if (serverTiming && typeof serverTiming === "object") {
1078
+ const { enabled = true, minDuration = 0 } = serverTiming;
1079
+ return {
1080
+ serverTimingEnabled:
1081
+ typeof enabled === "function"
1082
+ ? enabled
1083
+ : enabled
1084
+ ? SERVER_TIMING_ENABLED
1085
+ : SERVER_TIMING_DISABLED,
1086
+ serverTimingMinDuration: minDuration,
1087
+ };
1088
+ }
1089
+ return {
1090
+ serverTimingEnabled: serverTiming
1091
+ ? SERVER_TIMING_ENABLED
1092
+ : SERVER_TIMING_DISABLED,
1093
+ serverTimingMinDuration: 0,
1094
+ };
1095
+ };