@jsenv/server 16.1.2 → 16.2.0

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.
@@ -1563,6 +1563,10 @@ const createRequestLogger = (nodeRequest, write) => {
1563
1563
  write(type, value);
1564
1564
  };
1565
1565
  const writeLogs = (loggerToWrite, depth) => {
1566
+ const logArray = loggerToWrite.logArray;
1567
+ if (logArray.length === 0) {
1568
+ return;
1569
+ }
1566
1570
  let someLogIsError = false;
1567
1571
  let someLogIsWarn = false;
1568
1572
  for (const log of loggerToWrite.logArray) {
@@ -1573,12 +1577,9 @@ const createRequestLogger = (nodeRequest, write) => {
1573
1577
  someLogIsWarn = true;
1574
1578
  }
1575
1579
  }
1576
- const firstLog = loggerToWrite.logArray.shift();
1577
- const lastLog = loggerToWrite.logArray.pop();
1578
- const middleLogs = loggerToWrite.logArray;
1579
- if (!firstLog) {
1580
- debugger;
1581
- }
1580
+ const firstLog = logArray.shift();
1581
+ const lastLog = logArray.pop();
1582
+ const middleLogs = logArray;
1582
1583
  writeLog(firstLog, {
1583
1584
  someLogIsError,
1584
1585
  someLogIsWarn,
@@ -3877,6 +3878,8 @@ const createPattern = (
3877
3878
  namedGroupDelimiter,
3878
3879
  prepareStringToGenerate = (stringToBuild) => stringToBuild,
3879
3880
  finalizeGeneratedString = (generatedString) => generatedString,
3881
+ // encode = encodeURIComponent,
3882
+ // decode = decodeURIComponent,
3880
3883
  } = {},
3881
3884
  ) => {
3882
3885
  if (pattern === "*") {
@@ -4148,7 +4151,7 @@ const createResourcePattern = (pattern) => {
4148
4151
  return {
4149
4152
  match: (resource) => {
4150
4153
  const [pathname, search, hash] = resourceToParts(resource);
4151
- let result = pathnamePattern.match(pathname);
4154
+ let result = pathnamePattern.match(decodeURIComponent(pathname));
4152
4155
  if (!result) {
4153
4156
  return null;
4154
4157
  }
@@ -5523,6 +5526,18 @@ const createServiceController = (services) => {
5523
5526
  }
5524
5527
  }
5525
5528
  };
5529
+ const callAsyncHooks = async (hookName, info, context, callback) => {
5530
+ const hookSet = hookSetMap.get(hookName);
5531
+ if (!hookSet) {
5532
+ return;
5533
+ }
5534
+ for (const hook of hookSet) {
5535
+ const returnValue = await callAsyncHook(hook, info, context);
5536
+ if (returnValue && callback) {
5537
+ await callback(returnValue, hook.plugin);
5538
+ }
5539
+ }
5540
+ };
5526
5541
  const callHooksUntil = (
5527
5542
  hookName,
5528
5543
  info,
@@ -5573,6 +5588,7 @@ const createServiceController = (services) => {
5573
5588
 
5574
5589
  callHooks,
5575
5590
  callHooksUntil,
5591
+ callAsyncHooks,
5576
5592
  callAsyncHooksUntil,
5577
5593
 
5578
5594
  getCurrentService: () => currentService,
@@ -6273,7 +6289,15 @@ const jsenvServiceOpenFile = () => {
6273
6289
  body: "Missing file in url",
6274
6290
  };
6275
6291
  }
6276
- const fileUrl = new URL(file);
6292
+ let fileUrl;
6293
+ try {
6294
+ fileUrl = new URL(file);
6295
+ } catch {
6296
+ return {
6297
+ status: 400,
6298
+ body: `"${file}" is not a file url`,
6299
+ };
6300
+ }
6277
6301
  const filePath = urlToFileSystemPath(fileUrl);
6278
6302
  const require = createRequire(import.meta.url);
6279
6303
  const launch = require("launch-editor");
@@ -6965,7 +6989,7 @@ const startServer = async ({
6965
6989
  );
6966
6990
  },
6967
6991
  };
6968
- serviceController.callHooks(
6992
+ await serviceController.callAsyncHooks(
6969
6993
  "augmentRouteFetchSecondArg",
6970
6994
  request,
6971
6995
  fetchSecondArg,
package/dist/ws/ws.js CHANGED
@@ -743,6 +743,14 @@ function requirePermessageDeflate () {
743
743
  this[kError].code = 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH';
744
744
  this[kError][kStatusCode] = 1009;
745
745
  this.removeListener('data', inflateOnData);
746
+
747
+ //
748
+ // The choice to employ `zlib.reset()` over `zlib.close()` is dictated by the
749
+ // fact that in Node.js versions prior to 13.10.0, the callback for
750
+ // `zlib.flush()` is not called if `zlib.close()` is used. Utilizing
751
+ // `zlib.reset()` ensures that either the callback is invoked or an error is
752
+ // emitted.
753
+ //
746
754
  this.reset();
747
755
  }
748
756
 
@@ -758,6 +766,12 @@ function requirePermessageDeflate () {
758
766
  // closed when an error is emitted.
759
767
  //
760
768
  this[kPerMessageDeflate]._inflate = null;
769
+
770
+ if (this[kError]) {
771
+ this[kCallback](this[kError]);
772
+ return;
773
+ }
774
+
761
775
  err[kStatusCode] = 1007;
762
776
  this[kCallback](err);
763
777
  }
@@ -3475,7 +3489,7 @@ function requireWebsocket () {
3475
3489
  if (parsedUrl.protocol !== 'ws:' && !isSecure && !isIpcUrl) {
3476
3490
  invalidUrlMessage =
3477
3491
  'The URL\'s protocol must be one of "ws:", "wss:", ' +
3478
- '"http:", "https", or "ws+unix:"';
3492
+ '"http:", "https:", or "ws+unix:"';
3479
3493
  } else if (isIpcUrl && !parsedUrl.pathname) {
3480
3494
  invalidUrlMessage = "The URL's pathname is empty";
3481
3495
  } else if (parsedUrl.hash) {
package/package.json CHANGED
@@ -1,23 +1,20 @@
1
1
  {
2
2
  "name": "@jsenv/server",
3
- "version": "16.1.2",
3
+ "version": "16.2.0",
4
4
  "description": "A modern Node.js HTTP server with declarative routing, content negotiation, and WebSocket support",
5
- "license": "MIT",
6
- "author": {
7
- "name": "dmail",
8
- "email": "dmaillard06@gmail.com"
9
- },
10
5
  "repository": {
11
6
  "type": "git",
12
7
  "url": "https://github.com/jsenv/core",
13
- "directory": "packages/independent/backend/server"
8
+ "directory": "packages/backend/server"
14
9
  },
15
- "engines": {
16
- "node": ">=22.13.1"
17
- },
18
- "publishConfig": {
19
- "access": "public"
10
+ "license": "MIT",
11
+ "author": {
12
+ "name": "dmail",
13
+ "email": "dmaillard06@gmail.com"
20
14
  },
15
+ "sideEffects": [
16
+ "./dist/jsenv_server.js"
17
+ ],
21
18
  "type": "module",
22
19
  "exports": {
23
20
  ".": {
@@ -33,22 +30,37 @@
33
30
  "/dist/",
34
31
  "/src/"
35
32
  ],
36
- "sideEffects": [
37
- "./dist/jsenv_server_node_modules.js"
38
- ],
39
33
  "scripts": {
40
34
  "build": "node --experimental-import-meta-resolve ./scripts/build.mjs",
41
35
  "prepublishOnly": "npm run build"
42
36
  },
43
37
  "devDependencies": {
44
- "launch-editor": "2.10.0",
45
- "@jsenv/abort": "file:../../shared/abort",
46
- "@jsenv/humanize": "file:../../tooling/humanize",
47
- "@jsenv/url-meta": "file:../../tooling/url_meta",
48
- "@jsenv/utils": "file:../../tooling/utils",
49
- "ws": "8.18.1",
50
- "formidable": "3.5.2",
38
+ "@jsenv/abort": "../../tooling/abort",
39
+ "@jsenv/assert": "../../tooling/assert",
40
+ "@jsenv/core": "../../../",
41
+ "@jsenv/fetch": "../../tooling/fetch",
42
+ "@jsenv/filesystem": "../../tooling/filesystem",
43
+ "@jsenv/https-local": "../../tooling/https-local",
44
+ "@jsenv/humanize": "../../tooling/humanize",
45
+ "@jsenv/plugin-commonjs": "../../related/plugin-commonjs",
46
+ "@jsenv/router": "../../frontend/router",
47
+ "@jsenv/server": "./",
48
+ "@jsenv/snapshot": "../../tooling/snapshot",
49
+ "@jsenv/url-meta": "../../tooling/url_meta",
50
+ "@jsenv/url-pattern": "../../tooling/url-pattern",
51
+ "@jsenv/urls": "../../tooling/urls",
52
+ "@jsenv/utils": "../../tooling/utils",
51
53
  "eventsource": "3.0.6",
52
- "undici": "7.8.0"
54
+ "formidable": "3.5.4",
55
+ "launch-editor": "2.10.0",
56
+ "playwright": "1.52.0",
57
+ "undici": "7.8.0",
58
+ "ws": "8.18.2"
59
+ },
60
+ "engines": {
61
+ "node": ">=22.13.1"
62
+ },
63
+ "publishConfig": {
64
+ "access": "public"
53
65
  }
54
66
  }
@@ -0,0 +1,42 @@
1
+ // https://bun.sh/docs/api/cookie
2
+
3
+ export const COOKIE = {
4
+ from: (name, value, options = {}) => {
5
+ return { name, value, options };
6
+ },
7
+ parse: (cookieString) => {
8
+ const parts = cookieString.split("; ");
9
+ const [nameValue, ...attributes] = parts;
10
+ const [name, value] = nameValue.split("=");
11
+ const options = {};
12
+ for (const attr of attributes) {
13
+ const [key, val] = attr.split("=");
14
+ if (key === "expires") {
15
+ options.expires = new Date(val);
16
+ continue;
17
+ }
18
+
19
+ if (key === "path") {
20
+ options.path = val;
21
+ continue;
22
+ }
23
+ if (key === "domain") {
24
+ options.domain = val;
25
+ continue;
26
+ }
27
+ if (key === "secure") {
28
+ options.secure = true;
29
+ continue;
30
+ }
31
+ if (key === "httponly") {
32
+ options.httpOnly = true;
33
+ continue;
34
+ }
35
+ }
36
+
37
+ return { name, value, options };
38
+ },
39
+ stringify: ({ name, value, options }) => {
40
+ return `${name}=${value}${options.expires ? `; Expires=${options.expires.toUTCString()}` : ""}${options.path ? `; Path=${options.path}` : ""}${options.domain ? `; Domain=${options.domain}` : ""}${options.secure ? "; Secure" : ""}${options.httpOnly ? "; HttpOnly" : ""}`;
41
+ },
42
+ };
@@ -0,0 +1,18 @@
1
+ import { COOKIE } from "./cookie.js";
2
+
3
+ export const createCookieMap = (setCookieString) => {
4
+ const map = new Map();
5
+
6
+ if (setCookieString) {
7
+ const cookies = setCookieString.split(", ");
8
+ for (const cookie of cookies) {
9
+ const parsedCookie = COOKIE.parse(cookie);
10
+ map.set(parsedCookie.name, parsedCookie);
11
+ }
12
+ }
13
+
14
+ return {
15
+ map,
16
+ toSetCookieHeaders: () => {},
17
+ };
18
+ };
@@ -290,6 +290,10 @@ const createRequestLogger = (nodeRequest, write) => {
290
290
  write(type, value);
291
291
  };
292
292
  const writeLogs = (loggerToWrite, depth) => {
293
+ const logArray = loggerToWrite.logArray;
294
+ if (logArray.length === 0) {
295
+ return;
296
+ }
293
297
  let someLogIsError = false;
294
298
  let someLogIsWarn = false;
295
299
  for (const log of loggerToWrite.logArray) {
@@ -300,12 +304,9 @@ const createRequestLogger = (nodeRequest, write) => {
300
304
  someLogIsWarn = true;
301
305
  }
302
306
  }
303
- const firstLog = loggerToWrite.logArray.shift();
304
- const lastLog = loggerToWrite.logArray.pop();
305
- const middleLogs = loggerToWrite.logArray;
306
- if (!firstLog) {
307
- debugger;
308
- }
307
+ const firstLog = logArray.shift();
308
+ const lastLog = logArray.pop();
309
+ const middleLogs = logArray;
309
310
  writeLog(firstLog, {
310
311
  someLogIsError,
311
312
  someLogIsWarn,
@@ -1,8 +1,10 @@
1
1
  import { parseFunction } from "@jsenv/assert/src/utils/function_parser.js";
2
2
  import { lookupPackageDirectory } from "@jsenv/filesystem";
3
- import { createHeadersPattern } from "@jsenv/router/src/shared/headers_pattern.js";
4
- import { PATTERN } from "@jsenv/router/src/shared/pattern.js";
5
- import { createResourcePattern } from "@jsenv/router/src/shared/resource_pattern.js";
3
+ import {
4
+ createHeadersPattern,
5
+ createResourcePattern,
6
+ PATTERN,
7
+ } from "@jsenv/url-pattern";
6
8
  import { resourceToExtension, urlToRelativeUrl } from "@jsenv/urls";
7
9
  import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
8
10
  import { readFileSync } from "node:fs";
@@ -89,6 +89,18 @@ export const createServiceController = (services) => {
89
89
  }
90
90
  }
91
91
  };
92
+ const callAsyncHooks = async (hookName, info, context, callback) => {
93
+ const hookSet = hookSetMap.get(hookName);
94
+ if (!hookSet) {
95
+ return;
96
+ }
97
+ for (const hook of hookSet) {
98
+ const returnValue = await callAsyncHook(hook, info, context);
99
+ if (returnValue && callback) {
100
+ await callback(returnValue, hook.plugin);
101
+ }
102
+ }
103
+ };
92
104
  const callHooksUntil = (
93
105
  hookName,
94
106
  info,
@@ -139,6 +151,7 @@ export const createServiceController = (services) => {
139
151
 
140
152
  callHooks,
141
153
  callHooksUntil,
154
+ callAsyncHooks,
142
155
  callAsyncHooksUntil,
143
156
 
144
157
  getCurrentService: () => currentService,
@@ -1,33 +1,34 @@
1
1
  const css = /* css */ `
2
- dialog {
3
- opacity: 0;
4
- transition: all 0.3s allow-discrete;
5
- }
6
- dialog::backdrop {
7
- background-image: linear-gradient(
8
- 45deg,
9
- magenta,
10
- rebeccapurple,
11
- dodgerblue,
12
- green
13
- );
14
- opacity: 0;
15
- transition: all 0.3s allow-discrete;
16
- }
17
- dialog:open {
18
- opacity: 1;
19
- }
20
- dialog:open::backdrop {
21
- opacity: 0.75;
22
- }`;
2
+ dialog {
3
+ opacity: 0;
4
+ transition: all 0.3s allow-discrete;
5
+ }
6
+ dialog::backdrop {
7
+ background-image: linear-gradient(
8
+ 45deg,
9
+ magenta,
10
+ rebeccapurple,
11
+ dodgerblue,
12
+ green
13
+ );
14
+ opacity: 0;
15
+ transition: all 0.3s allow-discrete;
16
+ }
17
+ dialog:open {
18
+ opacity: 1;
19
+ }
20
+ dialog:open::backdrop {
21
+ opacity: 0.75;
22
+ }
23
+ `;
23
24
 
24
25
  const html = /* html */ `<style>
25
26
  ${css}
26
- </style>
27
- <dialog>
27
+ </style>
28
+ <dialog>
28
29
  <p>Connection with server is lost</p>
29
30
  <button autofocus name="reconnect">Retry</button>
30
- </dialog>`;
31
+ </dialog>`;
31
32
 
32
33
  class JsenvAutoreloadOnServerRestart extends HTMLElement {
33
34
  constructor({ url } = {}) {
@@ -17,7 +17,15 @@ export const jsenvServiceOpenFile = () => {
17
17
  body: "Missing file in url",
18
18
  };
19
19
  }
20
- const fileUrl = new URL(file);
20
+ let fileUrl;
21
+ try {
22
+ fileUrl = new URL(file);
23
+ } catch {
24
+ return {
25
+ status: 400,
26
+ body: `"${file}" is not a file url`,
27
+ };
28
+ }
21
29
  const filePath = urlToFileSystemPath(fileUrl);
22
30
  const require = createRequire(import.meta.url);
23
31
  const launch = require("launch-editor");
@@ -47,19 +47,38 @@ button:hover {
47
47
  `;
48
48
 
49
49
  const html = /* html */ `<style>
50
- ${css}
51
- </style>
52
- <div class="clipboard_container">
53
- <div id="copied_notif" aria-hidden="true">Copied !</div>
54
- <button id="copy_button">
55
- <svg id="copy_icon" aria-hidden="true" viewBox="0 0 16 16" width="16" height="16">
56
- <path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
57
- </svg>
58
- <svg id="copied_icon" aria-hidden="true" viewBox="0 0 16 16" width="16" height="16">
59
- <path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
60
- </svg>
61
- </button>
62
- </div>`;
50
+ ${css}
51
+ </style>
52
+ <div class="clipboard_container">
53
+ <div id="copied_notif" aria-hidden="true">Copied !</div>
54
+ <button id="copy_button">
55
+ <svg
56
+ id="copy_icon"
57
+ aria-hidden="true"
58
+ viewBox="0 0 16 16"
59
+ width="16"
60
+ height="16"
61
+ >
62
+ <path
63
+ d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"
64
+ ></path>
65
+ <path
66
+ d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"
67
+ ></path>
68
+ </svg>
69
+ <svg
70
+ id="copied_icon"
71
+ aria-hidden="true"
72
+ viewBox="0 0 16 16"
73
+ width="16"
74
+ height="16"
75
+ >
76
+ <path
77
+ d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"
78
+ ></path>
79
+ </svg>
80
+ </button>
81
+ </div>`;
63
82
  class ClipboardCopy extends HTMLElement {
64
83
  constructor() {
65
84
  super();
@@ -130,14 +149,17 @@ ${css}
130
149
  `;
131
150
 
132
151
  const html = /* html */ `<style>
133
- ${css}
134
- </style>
135
- <div id="code_block">
136
- <pre id="pre"><code></code></pre>
137
- <div id="clipboard_copy_container" style="position: absolute; right: 0; top: 0">
138
- <clipboard-copy></clipboard-copy>
139
- </div>
140
- </div>
152
+ ${css}
153
+ </style>
154
+ <div id="code_block">
155
+ <pre id="pre"><code></code></pre>
156
+ <div
157
+ id="clipboard_copy_container"
158
+ style="position: absolute; right: 0; top: 0"
159
+ >
160
+ <clipboard-copy></clipboard-copy>
161
+ </div>
162
+ </div>
141
163
  `;
142
164
 
143
165
  let loadPromise;