@sv443-network/userutils 9.0.4 → 9.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.
package/dist/index.cjs CHANGED
@@ -99,13 +99,21 @@ function randRange(...args) {
99
99
  } else
100
100
  return Math.floor(Math.random() * (max - min + 1)) + min;
101
101
  }
102
- function digitCount(num) {
102
+ function digitCount(num, withDecimals = true) {
103
103
  num = Number(!["string", "number"].includes(typeof num) ? String(num) : num);
104
104
  if (typeof num === "number" && isNaN(num))
105
105
  return NaN;
106
- return num === 0 ? 1 : Math.floor(
107
- Math.log10(Math.abs(Number(num))) + 1
108
- );
106
+ const [intPart, decPart] = num.toString().split(".");
107
+ const intDigits = intPart === "0" ? 1 : Math.floor(Math.log10(Math.abs(Number(intPart))) + 1);
108
+ const decDigits = withDecimals && decPart ? decPart.length : 0;
109
+ return intDigits + decDigits;
110
+ }
111
+ function roundFixed(num, fractionDigits) {
112
+ const scale = 10 ** fractionDigits;
113
+ return Math.round(num * scale) / scale;
114
+ }
115
+ function bitSetHas(bitSet, checkVal) {
116
+ return (bitSet & checkVal) === checkVal;
109
117
  }
110
118
 
111
119
  // lib/array.ts
@@ -130,7 +138,7 @@ function randomizeArray(array) {
130
138
  if (array.length === 0)
131
139
  return retArray;
132
140
  for (let i = retArray.length - 1; i > 0; i--) {
133
- const j = Math.floor(randRange(0, 1e4) / 1e4 * (i + 1));
141
+ const j = Math.floor(Math.random() * (i + 1));
134
142
  [retArray[i], retArray[j]] = [retArray[j], retArray[i]];
135
143
  }
136
144
  return retArray;
@@ -173,10 +181,10 @@ function darkenColor(color, percent, upperCase = false) {
173
181
  else if (color.startsWith("rgb")) {
174
182
  const rgbValues = (_a = color.match(/\d+(\.\d+)?/g)) == null ? undefined : _a.map(Number);
175
183
  if (!rgbValues)
176
- throw new Error("Invalid RGB/RGBA color format");
184
+ throw new TypeError("Invalid RGB/RGBA color format");
177
185
  [r, g, b, a] = rgbValues;
178
186
  } else
179
- throw new Error("Unsupported color format");
187
+ throw new TypeError("Unsupported color format");
180
188
  [r, g, b] = darkenRgb(r, g, b, percent);
181
189
  if (isHexCol)
182
190
  return rgbToHex(r, g, b, a, color.startsWith("#"), upperCase);
@@ -185,10 +193,39 @@ function darkenColor(color, percent, upperCase = false) {
185
193
  else if (color.startsWith("rgb"))
186
194
  return `rgb(${r}, ${g}, ${b})`;
187
195
  else
188
- throw new Error("Unsupported color format");
196
+ throw new TypeError("Unsupported color format");
189
197
  }
190
198
 
199
+ // lib/errors.ts
200
+ var UUError = class extends Error {
201
+ constructor(message, options) {
202
+ super(message, options);
203
+ __publicField(this, "date");
204
+ this.date = /* @__PURE__ */ new Date();
205
+ }
206
+ };
207
+ var ChecksumMismatchError = class extends UUError {
208
+ constructor(message, options) {
209
+ super(message, options);
210
+ this.name = "ChecksumMismatchError";
211
+ }
212
+ };
213
+ var MigrationError = class extends UUError {
214
+ constructor(message, options) {
215
+ super(message, options);
216
+ this.name = "MigrationError";
217
+ }
218
+ };
219
+ var PlatformError = class extends UUError {
220
+ constructor(message, options) {
221
+ super(message, options);
222
+ this.name = "PlatformError";
223
+ }
224
+ };
225
+
191
226
  // lib/dom.ts
227
+ var domReady = false;
228
+ document.addEventListener("DOMContentLoaded", () => domReady = true);
192
229
  function getUnsafeWindow() {
193
230
  try {
194
231
  return unsafeWindow;
@@ -244,8 +281,8 @@ function openInNewTab(href, background, additionalProps) {
244
281
  }
245
282
  function interceptEvent(eventObject, eventName, predicate = () => true) {
246
283
  var _a;
247
- if ((eventObject === window || eventObject === getUnsafeWindow()) && ((_a = GM == null ? undefined : GM.info) == null ? undefined : _a.scriptHandler) && GM.info.scriptHandler === "FireMonkey")
248
- throw new Error("Intercepting window events is not supported on FireMonkey due to the isolated context the userscript runs in.");
284
+ if (((_a = GM == null ? undefined : GM.info) == null ? undefined : _a.scriptHandler) && GM.info.scriptHandler === "FireMonkey" && (eventObject === window || eventObject === getUnsafeWindow()))
285
+ throw new PlatformError("Intercepting window events is not supported on FireMonkey due to the isolated context the userscript runs in.");
249
286
  Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
250
287
  if (isNaN(Error.stackTraceLimit))
251
288
  Error.stackTraceLimit = 100;
@@ -327,6 +364,36 @@ function setInnerHtmlUnsafe(element, html) {
327
364
  element.innerHTML = (_c = (_b = ttPolicy == null ? undefined : ttPolicy.createHTML) == null ? undefined : _b.call(ttPolicy, html)) != null ? _c : html;
328
365
  return element;
329
366
  }
367
+ function probeElementStyle(probeStyle, element, hideOffscreen = true, parentElement = document.body) {
368
+ const el = element ? typeof element === "function" ? element() : element : document.createElement("span");
369
+ if (hideOffscreen) {
370
+ el.style.position = "absolute";
371
+ el.style.left = "-9999px";
372
+ el.style.top = "-9999px";
373
+ el.style.zIndex = "-9999";
374
+ }
375
+ el.classList.add("_uu_probe_element");
376
+ parentElement.appendChild(el);
377
+ const style = window.getComputedStyle(el);
378
+ const result = probeStyle(style, el);
379
+ setTimeout(() => el.remove(), 1);
380
+ return result;
381
+ }
382
+ function isDomLoaded() {
383
+ return domReady;
384
+ }
385
+ function onDomLoad(cb) {
386
+ return new Promise((res) => {
387
+ if (domReady) {
388
+ cb == null ? undefined : cb();
389
+ res();
390
+ } else
391
+ document.addEventListener("DOMContentLoaded", () => {
392
+ cb == null ? undefined : cb();
393
+ res();
394
+ });
395
+ });
396
+ }
330
397
 
331
398
  // lib/crypto.ts
332
399
  function compress(input, compressionFormat, outputType = "string") {
@@ -374,6 +441,8 @@ function computeHash(input, algorithm = "SHA-256") {
374
441
  });
375
442
  }
376
443
  function randomId(length = 16, radix = 16, enhancedEntropy = false, randomCase = true) {
444
+ if (radix < 2 || radix > 36)
445
+ throw new RangeError("The radix argument must be between 2 and 36");
377
446
  let arr = [];
378
447
  const caseArr = randomCase ? [0, 1] : [0];
379
448
  if (enhancedEntropy) {
@@ -547,8 +616,7 @@ var DataStore = class {
547
616
  lastFmtVer = oldFmtVer = ver;
548
617
  } catch (err) {
549
618
  if (!resetOnError)
550
- throw new Error(`Error while running migration function for format version '${fmtVer}'`);
551
- console.error(`Error while running migration function for format version '${fmtVer}' - resetting to the default value.`, err);
619
+ throw new MigrationError(`Error while running migration function for format version '${fmtVer}'`, { cause: err });
552
620
  yield this.saveDefaultData();
553
621
  return this.getData();
554
622
  }
@@ -609,7 +677,6 @@ var DataStore = class {
609
677
  return JSON.parse(decRes != null ? decRes : data);
610
678
  });
611
679
  }
612
- //#region misc
613
680
  /** Copies a JSON-compatible object and loses all its internal references in the process */
614
681
  deepCopy(obj) {
615
682
  return JSON.parse(JSON.stringify(obj));
@@ -661,7 +728,7 @@ var DataStore = class {
661
728
  };
662
729
 
663
730
  // lib/DataStoreSerializer.ts
664
- var DataStoreSerializer = class {
731
+ var DataStoreSerializer = class _DataStoreSerializer {
665
732
  constructor(stores, options = {}) {
666
733
  __publicField(this, "stores");
667
734
  __publicField(this, "options");
@@ -679,27 +746,25 @@ var DataStoreSerializer = class {
679
746
  return computeHash(input, "SHA-256");
680
747
  });
681
748
  }
682
- /** Serializes a DataStore instance */
683
- serializeStore(storeInst) {
684
- return __async(this, null, function* () {
685
- const data = storeInst.encodingEnabled() ? yield storeInst.encodeData(JSON.stringify(storeInst.getData())) : JSON.stringify(storeInst.getData());
686
- const checksum = this.options.addChecksum ? yield this.calcChecksum(data) : undefined;
687
- return {
688
- id: storeInst.id,
689
- data,
690
- formatVersion: storeInst.formatVersion,
691
- encoded: storeInst.encodingEnabled(),
692
- checksum
693
- };
694
- });
695
- }
696
- /** Serializes the data stores into a string */
697
- serialize() {
749
+ /**
750
+ * Serializes the data stores into a string.
751
+ * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
752
+ * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
753
+ */
754
+ serialize(useEncoding = true, stringified = true) {
698
755
  return __async(this, null, function* () {
699
756
  const serData = [];
700
- for (const store of this.stores)
701
- serData.push(yield this.serializeStore(store));
702
- return JSON.stringify(serData);
757
+ for (const storeInst of this.stores) {
758
+ const data = useEncoding && storeInst.encodingEnabled() ? yield storeInst.encodeData(JSON.stringify(storeInst.getData())) : JSON.stringify(storeInst.getData());
759
+ serData.push({
760
+ id: storeInst.id,
761
+ data,
762
+ formatVersion: storeInst.formatVersion,
763
+ encoded: useEncoding && storeInst.encodingEnabled(),
764
+ checksum: this.options.addChecksum ? yield this.calcChecksum(data) : undefined
765
+ });
766
+ }
767
+ return stringified ? JSON.stringify(serData) : serData;
703
768
  });
704
769
  }
705
770
  /**
@@ -708,7 +773,9 @@ var DataStoreSerializer = class {
708
773
  */
709
774
  deserialize(serializedData) {
710
775
  return __async(this, null, function* () {
711
- const deserStores = JSON.parse(serializedData);
776
+ const deserStores = typeof serializedData === "string" ? JSON.parse(serializedData) : serializedData;
777
+ if (!Array.isArray(deserStores) || !deserStores.every(_DataStoreSerializer.isSerializedDataStore))
778
+ throw new TypeError("Invalid serialized data format! Expected an array of SerializedDataStore objects.");
712
779
  for (const storeData of deserStores) {
713
780
  const storeInst = this.stores.find((s) => s.id === storeData.id);
714
781
  if (!storeInst)
@@ -716,7 +783,7 @@ var DataStoreSerializer = class {
716
783
  if (this.options.ensureIntegrity && typeof storeData.checksum === "string") {
717
784
  const checksum = yield this.calcChecksum(storeData.data);
718
785
  if (checksum !== storeData.checksum)
719
- throw new Error(`Checksum mismatch for DataStore with ID "${storeData.id}"!
786
+ throw new ChecksumMismatchError(`Checksum mismatch for DataStore with ID "${storeData.id}"!
720
787
  Expected: ${storeData.checksum}
721
788
  Has: ${checksum}`);
722
789
  }
@@ -760,6 +827,10 @@ Has: ${checksum}`);
760
827
  return Promise.allSettled(this.stores.map((store) => store.deleteData()));
761
828
  });
762
829
  }
830
+ /** Checks if a given value is a SerializedDataStore object */
831
+ static isSerializedDataStore(obj) {
832
+ return typeof obj === "object" && obj !== null && "id" in obj && "data" in obj && "formatVersion" in obj && "encoded" in obj;
833
+ }
763
834
  };
764
835
  var NanoEmitter = class {
765
836
  constructor(options = {}) {
@@ -899,7 +970,7 @@ var Debouncer = class extends NanoEmitter {
899
970
  }, this.timeout);
900
971
  break;
901
972
  default:
902
- throw new Error(`Invalid debouncer type: ${this.type}`);
973
+ throw new TypeError(`Invalid debouncer type: ${this.type}`);
903
974
  }
904
975
  }
905
976
  };
@@ -1331,10 +1402,23 @@ var Dialog = class _Dialog extends NanoEmitter {
1331
1402
  };
1332
1403
 
1333
1404
  // lib/misc.ts
1334
- function autoPlural(word, num) {
1335
- if (Array.isArray(num) || num instanceof NodeList)
1336
- num = num.length;
1337
- return `${word}${num === 1 ? "" : "s"}`;
1405
+ function autoPlural(term, num, pluralType = "auto") {
1406
+ let n = num;
1407
+ if (typeof n !== "number")
1408
+ n = getListLength(n, false);
1409
+ if (!["-s", "-ies"].includes(pluralType))
1410
+ pluralType = "auto";
1411
+ if (isNaN(n))
1412
+ n = 2;
1413
+ const pType = pluralType === "auto" ? String(term).endsWith("y") ? "-ies" : "-s" : pluralType;
1414
+ switch (pType) {
1415
+ case "-s":
1416
+ return `${term}${n === 1 ? "" : "s"}`;
1417
+ case "-ies":
1418
+ return `${String(term).slice(0, -1)}${n === 1 ? "y" : "ies"}`;
1419
+ default:
1420
+ return String(term);
1421
+ }
1338
1422
  }
1339
1423
  function insertValues(input, ...values) {
1340
1424
  return input.replace(/%\d/gm, (match) => {
@@ -1343,29 +1427,33 @@ function insertValues(input, ...values) {
1343
1427
  return (_b = (_a = values[argIndex]) != null ? _a : match) == null ? undefined : _b.toString();
1344
1428
  });
1345
1429
  }
1346
- function pauseFor(time) {
1347
- return new Promise((res) => {
1348
- setTimeout(() => res(), time);
1430
+ function pauseFor(time, signal, rejectOnAbort = false) {
1431
+ return new Promise((res, rej) => {
1432
+ const timeout = setTimeout(() => res(), time);
1433
+ signal == null ? undefined : signal.addEventListener("abort", () => {
1434
+ clearTimeout(timeout);
1435
+ rejectOnAbort ? rej(new Error("The pause was aborted")) : res();
1436
+ });
1349
1437
  });
1350
1438
  }
1351
1439
  function fetchAdvanced(_0) {
1352
1440
  return __async(this, arguments, function* (input, options = {}) {
1353
- var _a;
1354
1441
  const { timeout = 1e4 } = options;
1355
- const { signal, abort } = new AbortController();
1356
- (_a = options.signal) == null ? undefined : _a.addEventListener("abort", abort);
1357
- let signalOpts = {}, id = undefined;
1442
+ const ctl = new AbortController();
1443
+ const _a = options, { signal } = _a, restOpts = __objRest(_a, ["signal"]);
1444
+ signal == null ? undefined : signal.addEventListener("abort", () => ctl.abort());
1445
+ let sigOpts = {}, id = undefined;
1358
1446
  if (timeout >= 0) {
1359
- id = setTimeout(() => abort(), timeout);
1360
- signalOpts = { signal };
1447
+ id = setTimeout(() => ctl.abort(), timeout);
1448
+ sigOpts = { signal: ctl.signal };
1361
1449
  }
1362
1450
  try {
1363
- const res = yield fetch(input, __spreadValues(__spreadValues({}, options), signalOpts));
1364
- id && clearTimeout(id);
1451
+ const res = yield fetch(input, __spreadValues(__spreadValues({}, restOpts), sigOpts));
1452
+ typeof id !== "undefined" && clearTimeout(id);
1365
1453
  return res;
1366
1454
  } catch (err) {
1367
- id && clearTimeout(id);
1368
- throw err;
1455
+ typeof id !== "undefined" && clearTimeout(id);
1456
+ throw new Error("Error while calling fetch", { cause: err });
1369
1457
  }
1370
1458
  });
1371
1459
  }
@@ -1381,10 +1469,11 @@ function consumeStringGen(strGen) {
1381
1469
  );
1382
1470
  });
1383
1471
  }
1472
+ function getListLength(obj, zeroOnInvalid = true) {
1473
+ return "length" in obj ? obj.length : "size" in obj ? obj.size : "count" in obj ? obj.count : zeroOnInvalid ? 0 : NaN;
1474
+ }
1384
1475
 
1385
1476
  // lib/SelectorObserver.ts
1386
- var domLoaded = false;
1387
- document.addEventListener("DOMContentLoaded", () => domLoaded = true);
1388
1477
  var SelectorObserver = class {
1389
1478
  constructor(baseElement, options = {}) {
1390
1479
  __publicField(this, "enabled", false);
@@ -1425,7 +1514,7 @@ var SelectorObserver = class {
1425
1514
  }
1426
1515
  /** Call to check all selectors in the {@linkcode listenerMap} using {@linkcode checkSelector()} */
1427
1516
  checkAllSelectors() {
1428
- if (!this.enabled || !domLoaded)
1517
+ if (!this.enabled || !isDomLoaded())
1429
1518
  return;
1430
1519
  for (const [selector, listeners] of this.listenerMap.entries())
1431
1520
  this.checkSelector(selector, listeners);
@@ -1569,9 +1658,9 @@ function translate(language, key, ...trArgs) {
1569
1658
  if (typeof language !== "string" || language.length === 0 || typeof trObj !== "object" || trObj === null)
1570
1659
  return fallbackLang ? translate(fallbackLang, key, ...trArgs) : key;
1571
1660
  const transformTrVal = (trKey, trValue) => {
1572
- const tfs = valTransforms.filter(({ regex }) => new RegExp(regex).test(trValue));
1661
+ const tfs = valTransforms.filter(({ regex }) => new RegExp(regex).test(String(trValue)));
1573
1662
  if (tfs.length === 0)
1574
- return trValue;
1663
+ return String(trValue);
1575
1664
  let retStr = String(trValue);
1576
1665
  for (const tf of tfs) {
1577
1666
  const re = new RegExp(tf.regex);
@@ -1720,15 +1809,20 @@ var tr = {
1720
1809
  }
1721
1810
  };
1722
1811
 
1812
+ exports.ChecksumMismatchError = ChecksumMismatchError;
1723
1813
  exports.DataStore = DataStore;
1724
1814
  exports.DataStoreSerializer = DataStoreSerializer;
1725
1815
  exports.Debouncer = Debouncer;
1726
1816
  exports.Dialog = Dialog;
1817
+ exports.MigrationError = MigrationError;
1727
1818
  exports.NanoEmitter = NanoEmitter;
1819
+ exports.PlatformError = PlatformError;
1728
1820
  exports.SelectorObserver = SelectorObserver;
1821
+ exports.UUError = UUError;
1729
1822
  exports.addGlobalStyle = addGlobalStyle;
1730
1823
  exports.addParent = addParent;
1731
1824
  exports.autoPlural = autoPlural;
1825
+ exports.bitSetHas = bitSetHas;
1732
1826
  exports.clamp = clamp;
1733
1827
  exports.compress = compress;
1734
1828
  exports.computeHash = computeHash;
@@ -1741,26 +1835,31 @@ exports.defaultDialogCss = defaultDialogCss;
1741
1835
  exports.defaultStrings = defaultStrings;
1742
1836
  exports.digitCount = digitCount;
1743
1837
  exports.fetchAdvanced = fetchAdvanced;
1838
+ exports.getListLength = getListLength;
1744
1839
  exports.getSiblingsFrame = getSiblingsFrame;
1745
1840
  exports.getUnsafeWindow = getUnsafeWindow;
1746
1841
  exports.hexToRgb = hexToRgb;
1747
1842
  exports.insertValues = insertValues;
1748
1843
  exports.interceptEvent = interceptEvent;
1749
1844
  exports.interceptWindowEvent = interceptWindowEvent;
1845
+ exports.isDomLoaded = isDomLoaded;
1750
1846
  exports.isScrollable = isScrollable;
1751
1847
  exports.lightenColor = lightenColor;
1752
1848
  exports.mapRange = mapRange;
1753
1849
  exports.observeElementProp = observeElementProp;
1850
+ exports.onDomLoad = onDomLoad;
1754
1851
  exports.openDialogs = openDialogs;
1755
1852
  exports.openInNewTab = openInNewTab;
1756
1853
  exports.pauseFor = pauseFor;
1757
1854
  exports.preloadImages = preloadImages;
1855
+ exports.probeElementStyle = probeElementStyle;
1758
1856
  exports.randRange = randRange;
1759
1857
  exports.randomId = randomId;
1760
1858
  exports.randomItem = randomItem;
1761
1859
  exports.randomItemIndex = randomItemIndex;
1762
1860
  exports.randomizeArray = randomizeArray;
1763
1861
  exports.rgbToHex = rgbToHex;
1862
+ exports.roundFixed = roundFixed;
1764
1863
  exports.setInnerHtmlUnsafe = setInnerHtmlUnsafe;
1765
1864
  exports.takeRandomItem = takeRandomItem;
1766
1865
  exports.tr = tr;