@jsenv/server 17.3.1 → 17.4.1

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.
@@ -297,41 +297,41 @@ var hasRequiredUtils;
297
297
  function requireUtils () {
298
298
  if (hasRequiredUtils) return utils;
299
299
  hasRequiredUtils = 1;
300
- (function (exports$1) {
300
+ (function (exports) {
301
301
  /**
302
302
  * Utilities for hex, bytes, CSPRNG.
303
303
  * @module
304
304
  */
305
305
  /*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
306
- Object.defineProperty(exports$1, "__esModule", { value: true });
307
- exports$1.wrapXOFConstructorWithOpts = exports$1.wrapConstructorWithOpts = exports$1.wrapConstructor = exports$1.Hash = exports$1.nextTick = exports$1.swap32IfBE = exports$1.byteSwapIfBE = exports$1.swap8IfBE = exports$1.isLE = void 0;
308
- exports$1.isBytes = isBytes;
309
- exports$1.anumber = anumber;
310
- exports$1.abytes = abytes;
311
- exports$1.ahash = ahash;
312
- exports$1.aexists = aexists;
313
- exports$1.aoutput = aoutput;
314
- exports$1.u8 = u8;
315
- exports$1.u32 = u32;
316
- exports$1.clean = clean;
317
- exports$1.createView = createView;
318
- exports$1.rotr = rotr;
319
- exports$1.rotl = rotl;
320
- exports$1.byteSwap = byteSwap;
321
- exports$1.byteSwap32 = byteSwap32;
322
- exports$1.bytesToHex = bytesToHex;
323
- exports$1.hexToBytes = hexToBytes;
324
- exports$1.asyncLoop = asyncLoop;
325
- exports$1.utf8ToBytes = utf8ToBytes;
326
- exports$1.bytesToUtf8 = bytesToUtf8;
327
- exports$1.toBytes = toBytes;
328
- exports$1.kdfInputToBytes = kdfInputToBytes;
329
- exports$1.concatBytes = concatBytes;
330
- exports$1.checkOpts = checkOpts;
331
- exports$1.createHasher = createHasher;
332
- exports$1.createOptHasher = createOptHasher;
333
- exports$1.createXOFer = createXOFer;
334
- exports$1.randomBytes = randomBytes;
306
+ Object.defineProperty(exports, "__esModule", { value: true });
307
+ exports.wrapXOFConstructorWithOpts = exports.wrapConstructorWithOpts = exports.wrapConstructor = exports.Hash = exports.nextTick = exports.swap32IfBE = exports.byteSwapIfBE = exports.swap8IfBE = exports.isLE = void 0;
308
+ exports.isBytes = isBytes;
309
+ exports.anumber = anumber;
310
+ exports.abytes = abytes;
311
+ exports.ahash = ahash;
312
+ exports.aexists = aexists;
313
+ exports.aoutput = aoutput;
314
+ exports.u8 = u8;
315
+ exports.u32 = u32;
316
+ exports.clean = clean;
317
+ exports.createView = createView;
318
+ exports.rotr = rotr;
319
+ exports.rotl = rotl;
320
+ exports.byteSwap = byteSwap;
321
+ exports.byteSwap32 = byteSwap32;
322
+ exports.bytesToHex = bytesToHex;
323
+ exports.hexToBytes = hexToBytes;
324
+ exports.asyncLoop = asyncLoop;
325
+ exports.utf8ToBytes = utf8ToBytes;
326
+ exports.bytesToUtf8 = bytesToUtf8;
327
+ exports.toBytes = toBytes;
328
+ exports.kdfInputToBytes = kdfInputToBytes;
329
+ exports.concatBytes = concatBytes;
330
+ exports.checkOpts = checkOpts;
331
+ exports.createHasher = createHasher;
332
+ exports.createOptHasher = createOptHasher;
333
+ exports.createXOFer = createXOFer;
334
+ exports.randomBytes = randomBytes;
335
335
  // We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
336
336
  // node.js versions earlier than v19 don't declare it in global scope.
337
337
  // For node.js, package.json#exports field mapping rewrites import
@@ -404,7 +404,7 @@ function requireUtils () {
404
404
  return (word << shift) | ((word >>> (32 - shift)) >>> 0);
405
405
  }
406
406
  /** Is current platform little-endian? Most are. Big-Endian platform: IBM */
407
- exports$1.isLE = (() => new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44)();
407
+ exports.isLE = (() => new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44)();
408
408
  /** The byte swap operation for uint32 */
409
409
  function byteSwap(word) {
410
410
  return (((word << 24) & 0xff000000) |
@@ -413,11 +413,11 @@ function requireUtils () {
413
413
  ((word >>> 24) & 0xff));
414
414
  }
415
415
  /** Conditionally byte swap if on a big-endian platform */
416
- exports$1.swap8IfBE = exports$1.isLE
416
+ exports.swap8IfBE = exports.isLE
417
417
  ? (n) => n
418
418
  : (n) => byteSwap(n);
419
419
  /** @deprecated */
420
- exports$1.byteSwapIfBE = exports$1.swap8IfBE;
420
+ exports.byteSwapIfBE = exports.swap8IfBE;
421
421
  /** In place byte swap for Uint32Array */
422
422
  function byteSwap32(arr) {
423
423
  for (let i = 0; i < arr.length; i++) {
@@ -425,7 +425,7 @@ function requireUtils () {
425
425
  }
426
426
  return arr;
427
427
  }
428
- exports$1.swap32IfBE = exports$1.isLE
428
+ exports.swap32IfBE = exports.isLE
429
429
  ? (u) => u
430
430
  : byteSwap32;
431
431
  // Built-in hex conversion https://caniuse.com/mdn-javascript_builtins_uint8array_fromhex
@@ -493,7 +493,7 @@ function requireUtils () {
493
493
  * next scheduler queue processing step and this is exactly what we need.
494
494
  */
495
495
  const nextTick = async () => { };
496
- exports$1.nextTick = nextTick;
496
+ exports.nextTick = nextTick;
497
497
  /** Returns control to thread each 'tick' ms to avoid blocking. */
498
498
  async function asyncLoop(iters, tick, cb) {
499
499
  let ts = Date.now();
@@ -503,7 +503,7 @@ function requireUtils () {
503
503
  const diff = Date.now() - ts;
504
504
  if (diff >= 0 && diff < tick)
505
505
  continue;
506
- await (0, exports$1.nextTick)();
506
+ await (0, exports.nextTick)();
507
507
  ts += diff;
508
508
  }
509
509
  }
@@ -569,7 +569,7 @@ function requireUtils () {
569
569
  /** For runtime check if class implements interface */
570
570
  class Hash {
571
571
  }
572
- exports$1.Hash = Hash;
572
+ exports.Hash = Hash;
573
573
  /** Wraps hash function, creating an interface on top of it */
574
574
  function createHasher(hashCons) {
575
575
  const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
@@ -595,9 +595,9 @@ function requireUtils () {
595
595
  hashC.create = (opts) => hashCons(opts);
596
596
  return hashC;
597
597
  }
598
- exports$1.wrapConstructor = createHasher;
599
- exports$1.wrapConstructorWithOpts = createOptHasher;
600
- exports$1.wrapXOFConstructorWithOpts = createXOFer;
598
+ exports.wrapConstructor = createHasher;
599
+ exports.wrapConstructorWithOpts = createOptHasher;
600
+ exports.wrapXOFConstructorWithOpts = createXOFer;
601
601
  /** Cryptographically secure PRNG. Uses internal OS-level `crypto.getRandomValues`. */
602
602
  function randomBytes(bytesLength = 32) {
603
603
  if (crypto_1.crypto && typeof crypto_1.crypto.getRandomValues === 'function') {
@@ -2907,6 +2907,30 @@ const trackHttp1ServerPendingRequests = (nodeServer) => {
2907
2907
  * But we don't really need a class so we are just returning a regular object under the hood
2908
2908
  */
2909
2909
 
2910
+ /**
2911
+ * Return this from a route `fetch` to accept a websocket upgrade on that
2912
+ * endpoint.
2913
+ *
2914
+ * @param {(websocket: import("ws").WebSocket) => void | (() => void)} webSocketHandler
2915
+ * Called with the raw `ws` socket once the upgrade completes. Use
2916
+ * `websocket.on("message", …)` / `websocket.send(…)`. If it returns a
2917
+ * function, that function runs as cleanup when the socket closes.
2918
+ * @param {object} [init]
2919
+ * @param {number} [init.status=101]
2920
+ * @param {string} [init.statusText]
2921
+ * @param {object} [init.headers]
2922
+ *
2923
+ * @example
2924
+ * {
2925
+ * endpoint: "GET /chat.websocket",
2926
+ * fetch: () => new WebSocketResponse((websocket) => {
2927
+ * websocket.on("message", (data) => websocket.send(data));
2928
+ * return () => {
2929
+ * // socket closed
2930
+ * };
2931
+ * }),
2932
+ * }
2933
+ */
2910
2934
  class WebSocketResponse {
2911
2935
  constructor(
2912
2936
  webSocketHandler,
@@ -2986,7 +3010,7 @@ const getWebSocketHandler = (responseProperties) => {
2986
3010
  * // Use in server route
2987
3011
  * {
2988
3012
  * endpoint: "GET /events",
2989
- * response: (request) => serverEvents.fetch(request)
3013
+ * fetch: (request) => serverEvents.fetch(request)
2990
3014
  * }
2991
3015
  */
2992
3016
  class ServerEvents {
@@ -3044,7 +3068,7 @@ class ServerEvents {
3044
3068
  * // Use in server route
3045
3069
  * {
3046
3070
  * endpoint: "GET /events",
3047
- * response: (request) => events.fetch(request)
3071
+ * fetch: (request) => events.fetch(request)
3048
3072
  * }
3049
3073
  */
3050
3074
  class LazyServerEvents {
package/dist/ws/ws.js CHANGED
@@ -8,6 +8,7 @@ import require$$0$2 from "node:stream";
8
8
  import require$$7 from "node:url";
9
9
  import require$$0 from "node:zlib";
10
10
  import require$$0$1 from "node:buffer";
11
+ import require$$2 from "node:util";
11
12
 
12
13
  function getDefaultExportFromCjs (x) {
13
14
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
@@ -987,6 +988,10 @@ function requireReceiver () {
987
988
  * extensions
988
989
  * @param {Boolean} [options.isServer=false] Specifies whether to operate in
989
990
  * client or server mode
991
+ * @param {Number} [options.maxBufferedChunks=0] The maximum number of
992
+ * buffered data chunks
993
+ * @param {Number} [options.maxFragments=0] The maximum number of message
994
+ * fragments
990
995
  * @param {Number} [options.maxPayload=0] The maximum allowed message length
991
996
  * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
992
997
  * not to skip UTF-8 validation for text and close messages
@@ -1001,6 +1006,8 @@ function requireReceiver () {
1001
1006
  this._binaryType = options.binaryType || BINARY_TYPES[0];
1002
1007
  this._extensions = options.extensions || {};
1003
1008
  this._isServer = !!options.isServer;
1009
+ this._maxBufferedChunks = options.maxBufferedChunks | 0;
1010
+ this._maxFragments = options.maxFragments | 0;
1004
1011
  this._maxPayload = options.maxPayload | 0;
1005
1012
  this._skipUTF8Validation = !!options.skipUTF8Validation;
1006
1013
  this[kWebSocket] = undefined;
@@ -1018,6 +1025,7 @@ function requireReceiver () {
1018
1025
 
1019
1026
  this._totalPayloadLength = 0;
1020
1027
  this._messageLength = 0;
1028
+ this._numFragments = 0;
1021
1029
  this._fragments = [];
1022
1030
 
1023
1031
  this._errored = false;
@@ -1036,6 +1044,22 @@ function requireReceiver () {
1036
1044
  _write(chunk, encoding, cb) {
1037
1045
  if (this._opcode === 0x08 && this._state == GET_INFO) return cb();
1038
1046
 
1047
+ if (
1048
+ this._maxBufferedChunks > 0 &&
1049
+ this._buffers.length >= this._maxBufferedChunks
1050
+ ) {
1051
+ cb(
1052
+ this.createError(
1053
+ RangeError,
1054
+ 'Too many buffered chunks',
1055
+ false,
1056
+ 1008,
1057
+ 'WS_ERR_TOO_MANY_BUFFERED_PARTS'
1058
+ )
1059
+ );
1060
+ return;
1061
+ }
1062
+
1039
1063
  this._bufferedBytes += chunk.length;
1040
1064
  this._buffers.push(chunk);
1041
1065
  this.startLoop(cb);
@@ -1425,6 +1449,19 @@ function requireReceiver () {
1425
1449
  return;
1426
1450
  }
1427
1451
 
1452
+ if (this._maxFragments > 0 && ++this._numFragments > this._maxFragments) {
1453
+ const error = this.createError(
1454
+ RangeError,
1455
+ 'Too many message fragments',
1456
+ false,
1457
+ 1008,
1458
+ 'WS_ERR_TOO_MANY_BUFFERED_PARTS'
1459
+ );
1460
+
1461
+ cb(error);
1462
+ return;
1463
+ }
1464
+
1428
1465
  if (this._compressed) {
1429
1466
  this._state = INFLATING;
1430
1467
  this.decompress(data, cb);
@@ -1497,6 +1534,7 @@ function requireReceiver () {
1497
1534
  this._totalPayloadLength = 0;
1498
1535
  this._messageLength = 0;
1499
1536
  this._fragmented = 0;
1537
+ this._numFragments = 0;
1500
1538
  this._fragments = [];
1501
1539
 
1502
1540
  if (this._opcode === 2) {
@@ -1665,6 +1703,9 @@ function requireSender () {
1665
1703
 
1666
1704
  const { Duplex } = require$$0$2;
1667
1705
  const { randomFillSync } = require$$1;
1706
+ const {
1707
+ types: { isUint8Array }
1708
+ } = require$$2;
1668
1709
 
1669
1710
  const PerMessageDeflate = requirePermessageDeflate();
1670
1711
  const { EMPTY_BUFFER, kWebSocket, NOOP } = requireConstants();
@@ -1861,8 +1902,10 @@ function requireSender () {
1861
1902
 
1862
1903
  if (typeof data === 'string') {
1863
1904
  buf.write(data, 2);
1864
- } else {
1905
+ } else if (isUint8Array(data)) {
1865
1906
  buf.set(data, 2);
1907
+ } else {
1908
+ throw new TypeError('Second argument must be a string or a Uint8Array');
1866
1909
  }
1867
1910
  }
1868
1911
 
@@ -2983,6 +3026,10 @@ function requireWebsocket () {
2983
3026
  * multiple times in the same tick
2984
3027
  * @param {Function} [options.generateMask] The function used to generate the
2985
3028
  * masking key
3029
+ * @param {Number} [options.maxBufferedChunks=0] The maximum number of
3030
+ * buffered data chunks
3031
+ * @param {Number} [options.maxFragments=0] The maximum number of message
3032
+ * fragments
2986
3033
  * @param {Number} [options.maxPayload=0] The maximum allowed message size
2987
3034
  * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
2988
3035
  * not to skip UTF-8 validation for text and close messages
@@ -2994,6 +3041,8 @@ function requireWebsocket () {
2994
3041
  binaryType: this.binaryType,
2995
3042
  extensions: this._extensions,
2996
3043
  isServer: this._isServer,
3044
+ maxBufferedChunks: options.maxBufferedChunks,
3045
+ maxFragments: options.maxFragments,
2997
3046
  maxPayload: options.maxPayload,
2998
3047
  skipUTF8Validation: options.skipUTF8Validation
2999
3048
  });
@@ -3422,6 +3471,10 @@ function requireWebsocket () {
3422
3471
  * masking key
3423
3472
  * @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the
3424
3473
  * handshake request
3474
+ * @param {Number} [options.maxBufferedChunks=262144] The maximum number of
3475
+ * buffered data chunks
3476
+ * @param {Number} [options.maxFragments=16384] The maximum number of message
3477
+ * fragments
3425
3478
  * @param {Number} [options.maxPayload=104857600] The maximum allowed message
3426
3479
  * size
3427
3480
  * @param {Number} [options.maxRedirects=10] The maximum number of redirects
@@ -3442,6 +3495,8 @@ function requireWebsocket () {
3442
3495
  autoPong: true,
3443
3496
  closeTimeout: CLOSE_TIMEOUT,
3444
3497
  protocolVersion: protocolVersions[1],
3498
+ maxBufferedChunks: 256 * 1024,
3499
+ maxFragments: 16 * 1024,
3445
3500
  maxPayload: 100 * 1024 * 1024,
3446
3501
  skipUTF8Validation: false,
3447
3502
  perMessageDeflate: true,
@@ -3799,6 +3854,8 @@ function requireWebsocket () {
3799
3854
  websocket.setSocket(socket, head, {
3800
3855
  allowSynchronousEvents: opts.allowSynchronousEvents,
3801
3856
  generateMask: opts.generateMask,
3857
+ maxBufferedChunks: opts.maxBufferedChunks,
3858
+ maxFragments: opts.maxFragments,
3802
3859
  maxPayload: opts.maxPayload,
3803
3860
  skipUTF8Validation: opts.skipUTF8Validation
3804
3861
  });
@@ -4480,6 +4537,10 @@ function requireWebsocketServer () {
4480
4537
  * called
4481
4538
  * @param {Function} [options.handleProtocols] A hook to handle protocols
4482
4539
  * @param {String} [options.host] The hostname where to bind the server
4540
+ * @param {Number} [options.maxBufferedChunks=262144] The maximum number of
4541
+ * buffered data chunks
4542
+ * @param {Number} [options.maxFragments=16384] The maximum number of message
4543
+ * fragments
4483
4544
  * @param {Number} [options.maxPayload=104857600] The maximum allowed message
4484
4545
  * size
4485
4546
  * @param {Boolean} [options.noServer=false] Enable no server mode
@@ -4502,6 +4563,8 @@ function requireWebsocketServer () {
4502
4563
  options = {
4503
4564
  allowSynchronousEvents: true,
4504
4565
  autoPong: true,
4566
+ maxBufferedChunks: 256 * 1024,
4567
+ maxFragments: 16 * 1024,
4505
4568
  maxPayload: 100 * 1024 * 1024,
4506
4569
  skipUTF8Validation: false,
4507
4570
  perMessageDeflate: false,
@@ -4861,6 +4924,8 @@ function requireWebsocketServer () {
4861
4924
 
4862
4925
  ws.setSocket(socket, head, {
4863
4926
  allowSynchronousEvents: this.options.allowSynchronousEvents,
4927
+ maxBufferedChunks: this.options.maxBufferedChunks,
4928
+ maxFragments: this.options.maxFragments,
4864
4929
  maxPayload: this.options.maxPayload,
4865
4930
  skipUTF8Validation: this.options.skipUTF8Validation
4866
4931
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/server",
3
- "version": "17.3.1",
3
+ "version": "17.4.1",
4
4
  "type": "module",
5
5
  "description": "A modern Node.js HTTP server with declarative routing, content negotiation, and WebSocket support",
6
6
  "repository": {
@@ -56,9 +56,9 @@
56
56
  "@jsenv/utils": "../../tooling/utils",
57
57
  "eventsource": "4.1.0",
58
58
  "formidable": "3.5.4",
59
- "playwright": "1.58.1",
60
- "undici": "7.24.2",
61
- "ws": "8.20.0"
59
+ "playwright": "1.61.1",
60
+ "undici": "8.8.0",
61
+ "ws": "8.21.1"
62
62
  },
63
63
  "publishConfig": {
64
64
  "access": "public"
@@ -49,7 +49,7 @@ import { WebSocketResponse } from "./web_socket_response.js";
49
49
  * // Use in server route
50
50
  * {
51
51
  * endpoint: "GET /events",
52
- * response: (request) => serverEvents.fetch(request)
52
+ * fetch: (request) => serverEvents.fetch(request)
53
53
  * }
54
54
  */
55
55
  export class ServerEvents {
@@ -107,7 +107,7 @@ export class ServerEvents {
107
107
  * // Use in server route
108
108
  * {
109
109
  * endpoint: "GET /events",
110
- * response: (request) => events.fetch(request)
110
+ * fetch: (request) => events.fetch(request)
111
111
  * }
112
112
  */
113
113
  export class LazyServerEvents {
@@ -14,6 +14,30 @@
14
14
  * But we don't really need a class so we are just returning a regular object under the hood
15
15
  */
16
16
 
17
+ /**
18
+ * Return this from a route `fetch` to accept a websocket upgrade on that
19
+ * endpoint.
20
+ *
21
+ * @param {(websocket: import("ws").WebSocket) => void | (() => void)} webSocketHandler
22
+ * Called with the raw `ws` socket once the upgrade completes. Use
23
+ * `websocket.on("message", …)` / `websocket.send(…)`. If it returns a
24
+ * function, that function runs as cleanup when the socket closes.
25
+ * @param {object} [init]
26
+ * @param {number} [init.status=101]
27
+ * @param {string} [init.statusText]
28
+ * @param {object} [init.headers]
29
+ *
30
+ * @example
31
+ * {
32
+ * endpoint: "GET /chat.websocket",
33
+ * fetch: () => new WebSocketResponse((websocket) => {
34
+ * websocket.on("message", (data) => websocket.send(data));
35
+ * return () => {
36
+ * // socket closed
37
+ * };
38
+ * }),
39
+ * }
40
+ */
17
41
  export class WebSocketResponse {
18
42
  constructor(
19
43
  webSocketHandler,