@ceeblue/web-utils 7.0.0 → 7.1.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.
package/README.md CHANGED
@@ -14,7 +14,7 @@ Then [import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modu
14
14
  ```javascript
15
15
  import { Util, ILog } from '@ceeblue/web-utils';
16
16
  ```
17
- > 💡 **TIP**
17
+ > [!IMPORTANT]
18
18
  >
19
19
  > If your project uses TypeScript, it is recommended that you set target: "ES6" in your configuration to match our use of ES6 features and ensure that your build will succeed (for those requiring a backward-compatible UMD version, a local build is recommended).
20
20
  > Then define the "moduleResolution" compiler option: "Node" in tsconfig.json helps with import failures by ensuring that TypeScript uses the correct import resolution strategy based on the targeted Node.js version.
@@ -27,13 +27,11 @@ import { Util, ILog } from '@ceeblue/web-utils';
27
27
  > }
28
28
  > ```
29
29
 
30
- > ⚠️ **REMARKS**
30
+ > [!TIP]
31
31
  >
32
32
  > To debug production code without modifying it, the library can use special query parameter of the main page's URL:
33
33
  > - __!cb-override-log-level__ : allows to override the log level for the entire library, see [Log.ts](./src/Log.ts) for details on handling log levels.
34
34
 
35
-
36
-
37
35
  ## Building locally
38
36
 
39
37
  1. [Clone](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) this repository
@@ -58,7 +56,12 @@ This monorepo also contains built-in documentation about the APIs in the library
58
56
  ```
59
57
  npm run build:docs
60
58
  ```
61
- You can access the documentation by opening the index.html file in the docs folder with your browser (`./docs/index.html`).
59
+
60
+ Once generated, open the `index.html` file located in the `docs` folder (`./docs/index.html`) with your browser.
61
+
62
+ > [!NOTE]
63
+ >
64
+ > An online, continuously maintained version of the latest released documentation is available at https://ceebluetv.github.io/web-utils/
62
65
 
63
66
  ## Contribution
64
67
 
@@ -467,7 +467,7 @@ declare class EventEmitter extends Loggable {
467
467
  * Event subscription only one time, once time fired it's automatically unsubscribe
468
468
  * @param name Name of event without the `on` prefix (ex: `log` to `onLog` event declared)
469
469
  * @param event Subscriber Function
470
- * @param options.abortSignal Optional `AbortSignal` to stop this or multiple subscriptions in same time
470
+ * @param options.signal Optional `AbortSignal` to stop this or multiple subscriptions in same time
471
471
  */
472
472
  once(name: EventKeys<this>, event: Function, options?: {
473
473
  signal?: AbortSignal;
@@ -904,6 +904,8 @@ declare function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Re
904
904
  * Fetch help method adding an explicit error property when Response is NOK, with the more accurate textual error inside
905
905
  * Also measure the rtt of fetching and returns it in the property Response.rtt (guaranteed to be ≥ 1),
906
906
  * supports subtracting server processing time using either the Response-Delay or CMSD-rd header when available
907
+ *
908
+ * WIP => replace the current implementation to use Resource Timing API
907
909
  */
908
910
  declare function fetchWithRTT(input: RequestInfo | URL, init?: RequestInit): Promise<Response & {
909
911
  rtt: number;
@@ -948,8 +950,20 @@ declare function trimStart(value: string, chars?: string): string;
948
950
  * @returns string trimmed
949
951
  */
950
952
  declare function trimEnd(value: string, chars?: string): string;
953
+ /**
954
+ * Wraps an object with a Proxy that makes property access case-insensitive.
955
+ *
956
+ * Property lookup (e.g. `obj.Foo` or `obj.foo`) will resolve to the same underlying key, regardless of casing.
957
+ * Only affects string-based property access (not symbols).
958
+ *
959
+ * @template T
960
+ * @param {T} obj - The original object.
961
+ * @returns {T} A proxied object with case-insensitive property access.
962
+ */
963
+ declare function caseInsensitive<T extends Record<string, any>>(obj: any): T;
951
964
 
952
965
  declare const Util_EMPTY_FUNCTION: typeof EMPTY_FUNCTION;
966
+ declare const Util_caseInsensitive: typeof caseInsensitive;
953
967
  declare const Util_equal: typeof equal;
954
968
  declare const Util_fetch: typeof fetch;
955
969
  declare const Util_fetchWithRTT: typeof fetchWithRTT;
@@ -969,7 +983,7 @@ declare const Util_trimEnd: typeof trimEnd;
969
983
  declare const Util_trimStart: typeof trimStart;
970
984
  declare const Util_unixTime: typeof unixTime;
971
985
  declare namespace Util {
972
- export { Util_EMPTY_FUNCTION as EMPTY_FUNCTION, Util_equal as equal, Util_fetch as fetch, Util_fetchWithRTT as fetchWithRTT, Util_getBaseFile as getBaseFile, Util_getExtension as getExtension, Util_getFile as getFile, Util_iterableEntries as iterableEntries, Util_objectFrom as objectFrom, Util_options as options, Util_safePromise as safePromise, Util_sleep as sleep, Util_stringify as stringify, Util_time as time, Util_toBin as toBin, Util_trim as trim, Util_trimEnd as trimEnd, Util_trimStart as trimStart, Util_unixTime as unixTime };
986
+ export { Util_EMPTY_FUNCTION as EMPTY_FUNCTION, Util_caseInsensitive as caseInsensitive, Util_equal as equal, Util_fetch as fetch, Util_fetchWithRTT as fetchWithRTT, Util_getBaseFile as getBaseFile, Util_getExtension as getExtension, Util_getFile as getFile, Util_iterableEntries as iterableEntries, Util_objectFrom as objectFrom, Util_options as options, Util_safePromise as safePromise, Util_sleep as sleep, Util_stringify as stringify, Util_time as time, Util_toBin as toBin, Util_trim as trim, Util_trimEnd as trimEnd, Util_trimStart as trimStart, Util_unixTime as unixTime };
973
987
  }
974
988
 
975
989
  /**
@@ -1137,24 +1151,22 @@ declare class WebSocketReliable extends EventEmitter {
1137
1151
  * @param {HTMLCanvasElement} canvas
1138
1152
  * @param {CanvasRenderingContext2D} context
1139
1153
  * @param {Date} now current date, new Date() by default
1140
- * @param {Number} blocksPerRow number of blocks in the line, 32 by default
1141
1154
  * @param {Number} tolerance percentage of tolerance for the black and white threshold, 0.2 by default
1142
1155
  * @returns {Number} The latency in millisecond between 'now' and the decoded timestamp, 0 if the timestamp cannot be decoded
1143
1156
  */
1144
- declare function getLatency(sourceEl: HTMLVideoElement, canvas: HTMLCanvasElement, context: CanvasRenderingContext2D, now?: Date, blocksPerRow?: number, tolerance?: number): number;
1157
+ declare function getLatency(sourceEl: HTMLVideoElement, canvas: HTMLCanvasElement, context: CanvasRenderingContext2D, now?: Date, tolerance?: number): number;
1145
1158
  /**
1146
1159
  * Decode a previously encoded timestamp from a canvas
1147
1160
  *
1148
1161
  * @param {CanvasRenderingContext2D} context
1149
1162
  * @param {Number} lineWidth width of the line in pixels
1150
- * @param {Number} blocksPerRow number of blocks in the line, 32 by default
1151
1163
  * @param {Number} tolerance percentage of tolerance for the black and white threshold, 0.2 by default
1152
1164
  * @returns {Date|null} The Date object representing the timestamp or null if the timestamp cannot be decoded
1153
1165
  */
1154
- declare function decodeTimestamp(context: CanvasRenderingContext2D, lineWidth: number, blocksPerRow?: number, tolerance?: number): Date | undefined;
1166
+ declare function decodeTimestamp(context: CanvasRenderingContext2D, lineWidth: number, tolerance?: number): Date | null;
1155
1167
  /**
1156
- * Encode the current timestamp into a line composed of blocks of black and white pixels
1157
- * written on the top of the canvas.
1168
+ * Encode the given date (excluding year and month) into a line composed of blocks
1169
+ * of black and white pixels written on the top of the canvas.
1158
1170
  *
1159
1171
  * @param {CanvasRenderingContext2D} context
1160
1172
  * @param {Number} lineWidth width of the line in pixels
package/dist/web-utils.js CHANGED
@@ -476,12 +476,14 @@ function fetch(input, init) {
476
476
  * Fetch help method adding an explicit error property when Response is NOK, with the more accurate textual error inside
477
477
  * Also measure the rtt of fetching and returns it in the property Response.rtt (guaranteed to be ≥ 1),
478
478
  * supports subtracting server processing time using either the Response-Delay or CMSD-rd header when available
479
+ *
480
+ * WIP => replace the current implementation to use Resource Timing API
479
481
  */
480
482
  function fetchWithRTT(input, init) {
481
483
  return __awaiter(this, void 0, void 0, function* () {
482
- // a first HEAD request to try to ensure a connection
483
- yield fetch(input, Object.assign(Object.assign({}, init), { method: 'HEAD' }));
484
- // the true request
484
+ // A first OPTIONS request to establish a connection (keep-alive)
485
+ yield fetch(input, Object.assign(Object.assign({}, init), { method: 'OPTIONS' }));
486
+ // Actual RTT measurement
485
487
  const startTime = time();
486
488
  const response = (yield fetch(input, init));
487
489
  response.rtt = time() - startTime;
@@ -584,7 +586,30 @@ function trimEnd(value, chars = ' ') {
584
586
  --i;
585
587
  }
586
588
  return value.substring(0, i);
587
- }var Util=/*#__PURE__*/Object.freeze({__proto__:null,EMPTY_FUNCTION:EMPTY_FUNCTION,equal:equal,fetch:fetch,fetchWithRTT:fetchWithRTT,getBaseFile:getBaseFile,getExtension:getExtension,getFile:getFile,iterableEntries:iterableEntries,objectFrom:objectFrom,options:options,safePromise:safePromise,sleep:sleep,stringify:stringify,time:time,toBin:toBin,trim:trim,trimEnd:trimEnd,trimStart:trimStart,unixTime:unixTime});/**
589
+ }
590
+ /**
591
+ * Wraps an object with a Proxy that makes property access case-insensitive.
592
+ *
593
+ * Property lookup (e.g. `obj.Foo` or `obj.foo`) will resolve to the same underlying key, regardless of casing.
594
+ * Only affects string-based property access (not symbols).
595
+ *
596
+ * @template T
597
+ * @param {T} obj - The original object.
598
+ * @returns {T} A proxied object with case-insensitive property access.
599
+ */
600
+ function caseInsensitive(obj) {
601
+ return new Proxy(obj, {
602
+ get(target, prop, receiver) {
603
+ if (typeof prop === 'string') {
604
+ const key = Object.keys(target).find(k => k.toLowerCase() === prop.toLowerCase());
605
+ if (key !== undefined) {
606
+ return Reflect.get(target, key, receiver);
607
+ }
608
+ }
609
+ return Reflect.get(target, prop, receiver);
610
+ }
611
+ });
612
+ }var Util=/*#__PURE__*/Object.freeze({__proto__:null,EMPTY_FUNCTION:EMPTY_FUNCTION,caseInsensitive:caseInsensitive,equal:equal,fetch:fetch,fetchWithRTT:fetchWithRTT,getBaseFile:getBaseFile,getExtension:getExtension,getFile:getFile,iterableEntries:iterableEntries,objectFrom:objectFrom,options:options,safePromise:safePromise,sleep:sleep,stringify:stringify,time:time,toBin:toBin,trim:trim,trimEnd:trimEnd,trimStart:trimStart,unixTime:unixTime});/**
588
613
  * Copyright 2024 Ceeblue B.V.
589
614
  * This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
590
615
  * See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
@@ -1447,7 +1472,7 @@ class EventEmitter extends Loggable {
1447
1472
  * Event subscription only one time, once time fired it's automatically unsubscribe
1448
1473
  * @param name Name of event without the `on` prefix (ex: `log` to `onLog` event declared)
1449
1474
  * @param event Subscriber Function
1450
- * @param options.abortSignal Optional `AbortSignal` to stop this or multiple subscriptions in same time
1475
+ * @param options.signal Optional `AbortSignal` to stop this or multiple subscriptions in same time
1451
1476
  */
1452
1477
  once(name, event, options) {
1453
1478
  var _a;
@@ -2195,6 +2220,8 @@ class WebSocketReliable extends EventEmitter {
2195
2220
  * This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
2196
2221
  * See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
2197
2222
  */
2223
+ // Fixed blocks to 32 because timestamp is encoded on 32 bits
2224
+ const _blocksPerRow = 32;
2198
2225
  /**
2199
2226
  * Decode the timestamp from a video element and compute the latency between the timestamp and the current date.
2200
2227
  *
@@ -2202,19 +2229,18 @@ class WebSocketReliable extends EventEmitter {
2202
2229
  * @param {HTMLCanvasElement} canvas
2203
2230
  * @param {CanvasRenderingContext2D} context
2204
2231
  * @param {Date} now current date, new Date() by default
2205
- * @param {Number} blocksPerRow number of blocks in the line, 32 by default
2206
2232
  * @param {Number} tolerance percentage of tolerance for the black and white threshold, 0.2 by default
2207
2233
  * @returns {Number} The latency in millisecond between 'now' and the decoded timestamp, 0 if the timestamp cannot be decoded
2208
2234
  */
2209
- function getLatency(sourceEl, canvas, context, now = new Date(), blocksPerRow = 32, tolerance = 0.2) {
2235
+ function getLatency(sourceEl, canvas, context, now = new Date(), tolerance = 0.2) {
2210
2236
  canvas.width = sourceEl.videoWidth;
2211
- canvas.height = Math.floor(canvas.width / blocksPerRow);
2237
+ canvas.height = Math.floor(canvas.width / _blocksPerRow);
2212
2238
  if (!canvas.width || !canvas.height) {
2213
2239
  // No pixel to parse!
2214
2240
  return 0;
2215
2241
  }
2216
2242
  context.drawImage(sourceEl, 0, 0, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
2217
- const timestamp = decodeTimestamp(context, canvas.width, blocksPerRow, tolerance);
2243
+ const timestamp = decodeTimestamp(context, canvas.width, tolerance);
2218
2244
  return timestamp == null ? 0 : now.getTime() - timestamp.getTime();
2219
2245
  }
2220
2246
  /**
@@ -2222,20 +2248,25 @@ function getLatency(sourceEl, canvas, context, now = new Date(), blocksPerRow =
2222
2248
  *
2223
2249
  * @param {CanvasRenderingContext2D} context
2224
2250
  * @param {Number} lineWidth width of the line in pixels
2225
- * @param {Number} blocksPerRow number of blocks in the line, 32 by default
2226
2251
  * @param {Number} tolerance percentage of tolerance for the black and white threshold, 0.2 by default
2227
2252
  * @returns {Date|null} The Date object representing the timestamp or null if the timestamp cannot be decoded
2228
2253
  */
2229
- function decodeTimestamp(context, lineWidth, blocksPerRow = 32, tolerance = 0.2) {
2230
- const blockSize = lineWidth / blocksPerRow;
2254
+ function decodeTimestamp(context, lineWidth, tolerance = 0.2) {
2255
+ // Integer block width and ignore the right-hand remainder.
2256
+ const blockSize = Math.floor(lineWidth / _blocksPerRow);
2231
2257
  let binaryTime = '';
2232
- let i = blockSize / 2;
2233
- const data = context.getImageData(0, Math.round(i), lineWidth, 1).data;
2258
+ if (blockSize < 1) {
2259
+ return null;
2260
+ }
2261
+ const effectiveWidth = blockSize * _blocksPerRow;
2262
+ const midBlock = Math.floor(blockSize / 2);
2263
+ const data = context.getImageData(0, midBlock, effectiveWidth, 1).data;
2234
2264
  const pixels = new Uint32Array(data.buffer);
2235
2265
  const blackThreshold = 0xff * tolerance;
2236
2266
  const whiteThreshold = 0xff * (1 - tolerance);
2237
- while (i < pixels.length) {
2238
- const pixel = pixels[Math.round(i)] & 0xffffff;
2267
+ // Sample the center pixel of each integer-sized block that the encoder painted.
2268
+ for (let i = 0; i < effectiveWidth; i += blockSize) {
2269
+ const pixel = pixels[i + midBlock] & 0xffffff;
2239
2270
  // Extract luminance from RGB
2240
2271
  const Y = 0.299 * ((pixel >> 16) & 0xff) + 0.587 * ((pixel >> 8) & 0xff) + 0.114 * (pixel & 0xff);
2241
2272
  if (Y < blackThreshold) {
@@ -2247,21 +2278,24 @@ function decodeTimestamp(context, lineWidth, blocksPerRow = 32, tolerance = 0.2)
2247
2278
  binaryTime += '0';
2248
2279
  }
2249
2280
  else {
2250
- return;
2281
+ return null;
2251
2282
  }
2252
- i += blockSize;
2253
2283
  }
2254
2284
  const day = parseInt(binaryTime.slice(0, 5), 2);
2255
2285
  const hour = parseInt(binaryTime.slice(5, 10), 2);
2256
2286
  const minute = parseInt(binaryTime.slice(10, 16), 2);
2257
2287
  const second = parseInt(binaryTime.slice(16, 22), 2);
2258
2288
  const millisecond = parseInt(binaryTime.slice(22, 32), 2);
2289
+ // Basic sanity checks (match encoder ranges)
2290
+ if (day < 1 || day > 31 || hour > 23 || minute > 59 || second > 59 || millisecond > 999) {
2291
+ return null;
2292
+ }
2259
2293
  const now = new Date();
2260
2294
  return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), day, hour, minute, second, millisecond));
2261
2295
  }
2262
2296
  /**
2263
- * Encode the current timestamp into a line composed of blocks of black and white pixels
2264
- * written on the top of the canvas.
2297
+ * Encode the given date (excluding year and month) into a line composed of blocks
2298
+ * of black and white pixels written on the top of the canvas.
2265
2299
  *
2266
2300
  * @param {CanvasRenderingContext2D} context
2267
2301
  * @param {Number} lineWidth width of the line in pixels
@@ -2275,11 +2309,11 @@ function encodeTimestamp(context, lineWidth, blocksPerRow = 32, now = new Date()
2275
2309
  const minute = now.getUTCMinutes();
2276
2310
  const second = now.getUTCSeconds();
2277
2311
  const millisecond = now.getUTCMilliseconds();
2278
- const binaryDay = day.toString(2).padStart(5, '0');
2279
- const binaryHour = hour.toString(2).padStart(5, '0');
2280
- const binaryMinute = minute.toString(2).padStart(6, '0');
2281
- const binarySecond = second.toString(2).padStart(6, '0');
2282
- const binaryMillisecond = millisecond.toString(2).padStart(10, '0');
2312
+ const binaryDay = day.toString(2).padStart(5, '0'); // 31 possible days/32
2313
+ const binaryHour = hour.toString(2).padStart(5, '0'); // 24 possible hours/32
2314
+ const binaryMinute = minute.toString(2).padStart(6, '0'); // 60 possible minutes/64
2315
+ const binarySecond = second.toString(2).padStart(6, '0'); // 60 possible seconds/64
2316
+ const binaryMillisecond = millisecond.toString(2).padStart(10, '0'); // 1000 possible milliseconds/1024
2283
2317
  const binaryTime = binaryDay + binaryHour + binaryMinute + binarySecond + binaryMillisecond;
2284
2318
  for (let i = 0; i < binaryTime.length; i++) {
2285
2319
  const x = (i % blocksPerRow) * blockSize;
@@ -2546,4 +2580,4 @@ class UIMetrics {
2546
2580
  * This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
2547
2581
  * See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
2548
2582
  */
2549
- const VERSION = '7.0.0';export{BinaryReader,BinaryWriter,BitReader,ByteRate,Connect,EpochTime,EventEmitter,FixMap,Log,LogLevel,Loggable,NetAddress,Numbers,Queue,SDP,UIMetrics,Util,VERSION,WebSocketReliable,log};//# sourceMappingURL=web-utils.js.map
2583
+ const VERSION = '7.1.0';export{BinaryReader,BinaryWriter,BitReader,ByteRate,Connect,EpochTime,EventEmitter,FixMap,Log,LogLevel,Loggable,NetAddress,Numbers,Queue,SDP,UIMetrics,Util,VERSION,WebSocketReliable,log};//# sourceMappingURL=web-utils.js.map