@sv443-network/userutils 8.2.0 → 8.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @sv443-network/userutils
2
2
 
3
+ ## 8.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 390110e: Throw an error when calling `interceptEvent()` on `window` or `unsafeWindow` on FireMonkey instead of crashing the entire page
8
+
9
+ ## 8.3.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 1ecd63c: Added support for the `signal` property in `fetchAdvanced()`
14
+
3
15
  ## 8.2.0
4
16
 
5
17
  ### Minor Changes
package/README.md CHANGED
@@ -622,6 +622,7 @@ If no predicate is specified, all events will be discarded.
622
622
  Calling this function will set the `Error.stackTraceLimit` to 100 (if it's not already higher) to ensure the stack trace is preserved.
623
623
 
624
624
  ⚠️ This function should be called as soon as possible (I recommend using `@run-at document-start`), as it will only intercept events that are *attached* after this function is called.
625
+ ⚠️ Due to this function modifying the `addEventListener` prototype, it might break execution of the page's main script if the userscript is running in an isolated context (like it does in FireMonkey). In that case, calling this function will throw an error.
625
626
 
626
627
  <details><summary><b>Example - click to view</b></summary>
627
628
 
@@ -655,7 +656,8 @@ If no predicate is specified, all events will be discarded.
655
656
  This is essentially the same as [`interceptEvent()`](#interceptevent), but automatically uses the `unsafeWindow` (or falls back to regular `window`).
656
657
 
657
658
  ⚠️ This function should be called as soon as possible (I recommend using `@run-at document-start`), as it will only intercept events that are *attached* after this function is called.
658
- ⚠️ In order for all events to be interceptable, the directive `@grant unsafeWindow` should be set.
659
+ ⚠️ In order to have the best chance at intercepting events, the directive `@grant unsafeWindow` should be set.
660
+ ⚠️ Due to this function modifying the `addEventListener` prototype, it might break execution of the page's main script if the userscript is running in an isolated context (like it does in FireMonkey). In that case, calling this function will throw an error.
659
661
 
660
662
  <details><summary><b>Example - click to view</b></summary>
661
663
 
@@ -1769,30 +1771,39 @@ Usage:
1769
1771
  ```ts
1770
1772
  fetchAdvanced(input: string | Request | URL, options?: {
1771
1773
  timeout?: number,
1772
- // any other options from fetch() except for signal
1774
+ // any other options from fetch()
1773
1775
  }): Promise<Response>
1774
1776
  ```
1775
1777
 
1776
1778
  A drop-in replacement for the native `fetch()` function that adds options like a timeout property.
1777
1779
  The timeout will default to 10 seconds if left undefined. Set it to a negative number to disable the timeout.
1778
- Note that the `signal` option will be overwritten if passed.
1780
+ Pass an [AbortController's signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to the `signal` property to be able to abort the request before it finishes or the timeout kicks in.
1779
1781
 
1780
1782
  <details><summary><b>Example - click to view</b></summary>
1781
1783
 
1782
1784
  ```ts
1783
1785
  import { fetchAdvanced } from "@sv443-network/userutils";
1784
1786
 
1787
+ const { signal, abort } = new AbortController();
1788
+
1785
1789
  fetchAdvanced("https://jokeapi.dev/joke/Any?safe-mode", {
1790
+ // times out after 5 seconds:
1786
1791
  timeout: 5000,
1787
- // also accepts any other fetch options like headers:
1792
+ // also accepts any other fetch options like headers and signal:
1788
1793
  headers: {
1789
1794
  "Accept": "text/plain",
1790
1795
  },
1796
+ // makes the request abortable:
1797
+ signal,
1791
1798
  }).then(async (response) => {
1792
1799
  console.log("Fetch data:", await response.text());
1793
1800
  }).catch((err) => {
1794
1801
  console.error("Fetch error:", err);
1795
1802
  });
1803
+
1804
+ document.querySelector("button#cancel")?.addEventListener("click", () => {
1805
+ abort();
1806
+ });
1796
1807
  ```
1797
1808
  </details>
1798
1809
 
@@ -8,7 +8,7 @@
8
8
  // ==UserLibrary==
9
9
  // @name UserUtils
10
10
  // @description Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, create persistent & synchronous data stores, modify the DOM more easily and more
11
- // @version 8.2.0
11
+ // @version 8.3.1
12
12
  // @license MIT
13
13
  // @copyright Sv443 (https://github.com/Sv443)
14
14
 
@@ -247,13 +247,16 @@ var UserUtils = (function (exports) {
247
247
  }
248
248
  }
249
249
  function interceptEvent(eventObject, eventName, predicate = () => true) {
250
+ var _a;
251
+ if ((eventObject === window || eventObject === getUnsafeWindow()) && ((_a = GM == null ? void 0 : GM.info) == null ? void 0 : _a.scriptHandler) && GM.info.scriptHandler === "FireMonkey")
252
+ throw new Error("Intercepting window events is not supported on FireMonkey due to the isolated context the userscript runs in.");
250
253
  Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
251
254
  if (isNaN(Error.stackTraceLimit))
252
255
  Error.stackTraceLimit = 100;
253
256
  (function(original) {
254
257
  eventObject.__proto__.addEventListener = function(...args) {
255
- var _a, _b;
256
- const origListener = typeof args[1] === "function" ? args[1] : (_b = (_a = args[1]) == null ? void 0 : _a.handleEvent) != null ? _b : () => void 0;
258
+ var _a2, _b;
259
+ const origListener = typeof args[1] === "function" ? args[1] : (_b = (_a2 = args[1]) == null ? void 0 : _a2.handleEvent) != null ? _b : () => void 0;
257
260
  args[1] = function(...a) {
258
261
  if (args[0] === eventName && predicate(Array.isArray(a) ? a[0] : a))
259
262
  return;
@@ -1271,27 +1274,29 @@ Has: ${checksum}`);
1271
1274
  });
1272
1275
  }
1273
1276
  function debounce(func, timeout = 300, edge = "falling") {
1274
- let timer;
1277
+ let id;
1275
1278
  return function(...args) {
1276
1279
  if (edge === "rising") {
1277
- if (!timer) {
1280
+ if (!id) {
1278
1281
  func.apply(this, args);
1279
- timer = setTimeout(() => timer = void 0, timeout);
1282
+ id = setTimeout(() => id = void 0, timeout);
1280
1283
  }
1281
1284
  } else {
1282
- clearTimeout(timer);
1283
- timer = setTimeout(() => func.apply(this, args), timeout);
1285
+ clearTimeout(id);
1286
+ id = setTimeout(() => func.apply(this, args), timeout);
1284
1287
  }
1285
1288
  };
1286
1289
  }
1287
1290
  function fetchAdvanced(_0) {
1288
1291
  return __async(this, arguments, function* (input, options = {}) {
1292
+ var _a;
1289
1293
  const { timeout = 1e4 } = options;
1294
+ const { signal, abort } = new AbortController();
1295
+ (_a = options.signal) == null ? void 0 : _a.addEventListener("abort", abort);
1290
1296
  let signalOpts = {}, id = void 0;
1291
1297
  if (timeout >= 0) {
1292
- const controller = new AbortController();
1293
- id = setTimeout(() => controller.abort(), timeout);
1294
- signalOpts = { signal: controller.signal };
1298
+ id = setTimeout(() => abort(), timeout);
1299
+ signalOpts = { signal };
1295
1300
  }
1296
1301
  try {
1297
1302
  const res = yield fetch(input, __spreadValues(__spreadValues({}, options), signalOpts));
package/dist/index.js CHANGED
@@ -227,13 +227,16 @@ function openInNewTab(href, background) {
227
227
  }
228
228
  }
229
229
  function interceptEvent(eventObject, eventName, predicate = () => true) {
230
+ var _a;
231
+ if ((eventObject === window || eventObject === getUnsafeWindow()) && ((_a = GM == null ? void 0 : GM.info) == null ? void 0 : _a.scriptHandler) && GM.info.scriptHandler === "FireMonkey")
232
+ throw new Error("Intercepting window events is not supported on FireMonkey due to the isolated context the userscript runs in.");
230
233
  Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
231
234
  if (isNaN(Error.stackTraceLimit))
232
235
  Error.stackTraceLimit = 100;
233
236
  (function(original) {
234
237
  eventObject.__proto__.addEventListener = function(...args) {
235
- var _a, _b;
236
- const origListener = typeof args[1] === "function" ? args[1] : (_b = (_a = args[1]) == null ? void 0 : _a.handleEvent) != null ? _b : () => void 0;
238
+ var _a2, _b;
239
+ const origListener = typeof args[1] === "function" ? args[1] : (_b = (_a2 = args[1]) == null ? void 0 : _a2.handleEvent) != null ? _b : () => void 0;
237
240
  args[1] = function(...a) {
238
241
  if (args[0] === eventName && predicate(Array.isArray(a) ? a[0] : a))
239
242
  return;
@@ -1231,27 +1234,29 @@ function pauseFor(time) {
1231
1234
  });
1232
1235
  }
1233
1236
  function debounce(func, timeout = 300, edge = "falling") {
1234
- let timer;
1237
+ let id;
1235
1238
  return function(...args) {
1236
1239
  if (edge === "rising") {
1237
- if (!timer) {
1240
+ if (!id) {
1238
1241
  func.apply(this, args);
1239
- timer = setTimeout(() => timer = void 0, timeout);
1242
+ id = setTimeout(() => id = void 0, timeout);
1240
1243
  }
1241
1244
  } else {
1242
- clearTimeout(timer);
1243
- timer = setTimeout(() => func.apply(this, args), timeout);
1245
+ clearTimeout(id);
1246
+ id = setTimeout(() => func.apply(this, args), timeout);
1244
1247
  }
1245
1248
  };
1246
1249
  }
1247
1250
  function fetchAdvanced(_0) {
1248
1251
  return __async(this, arguments, function* (input, options = {}) {
1252
+ var _a;
1249
1253
  const { timeout = 1e4 } = options;
1254
+ const { signal, abort } = new AbortController();
1255
+ (_a = options.signal) == null ? void 0 : _a.addEventListener("abort", abort);
1250
1256
  let signalOpts = {}, id = void 0;
1251
1257
  if (timeout >= 0) {
1252
- const controller = new AbortController();
1253
- id = setTimeout(() => controller.abort(), timeout);
1254
- signalOpts = { signal: controller.signal };
1258
+ id = setTimeout(() => abort(), timeout);
1259
+ signalOpts = { signal };
1255
1260
  }
1256
1261
  try {
1257
1262
  const res = yield fetch(input, __spreadValues(__spreadValues({}, options), signalOpts));
@@ -1,10 +1,11 @@
1
1
  /// <reference types="greasemonkey" />
2
+ import type { Prettify } from "./types.js";
2
3
  /** Function that takes the data in the old format and returns the data in the new format. Also supports an asynchronous migration. */
3
4
  type MigrationFunc = (oldData: any) => any | Promise<any>;
4
5
  /** Dictionary of format version numbers and the function that migrates to them from the previous whole integer */
5
6
  export type DataMigrationsDict = Record<number, MigrationFunc>;
6
7
  /** Options for the DataStore instance */
7
- export type DataStoreOptions<TData> = {
8
+ export type DataStoreOptions<TData> = Prettify<{
8
9
  /**
9
10
  * A unique internal ID for this data store.
10
11
  * To avoid conflicts with other scripts, it is recommended to use a prefix that is unique to your script.
@@ -66,7 +67,7 @@ export type DataStoreOptions<TData> = {
66
67
  } | {
67
68
  encodeData?: never;
68
69
  decodeData?: never;
69
- });
70
+ })>;
70
71
  /**
71
72
  * Manages a hybrid synchronous & asynchronous persistent JSON database that is cached in memory and persistently saved across sessions using [GM storage](https://wiki.greasespot.net/GM.setValue) (default), [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) or [sessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage).
72
73
  * Supports migrating data from older format versions to newer ones and populating the cache with default data if no persistent data is found.
@@ -1,5 +1,6 @@
1
+ import type { Prettify } from "./types.js";
1
2
  /** Options for the `onSelector()` method of {@linkcode SelectorObserver} */
2
- export type SelectorListenerOptions<TElem extends Element = HTMLElement> = SelectorOptionsOne<TElem> | SelectorOptionsAll<TElem>;
3
+ export type SelectorListenerOptions<TElem extends Element = HTMLElement> = Prettify<SelectorOptionsOne<TElem> | SelectorOptionsAll<TElem>>;
3
4
  type SelectorOptionsOne<TElem extends Element> = SelectorOptionsCommon & {
4
5
  /** Whether to use `querySelectorAll()` instead - default is false */
5
6
  all?: false;
@@ -36,7 +37,7 @@ export type SelectorObserverOptions = {
36
37
  /** If set to a number, the checks will be run on interval instead of on mutation events - in that case all MutationObserverInit props will be ignored */
37
38
  checkInterval?: number;
38
39
  };
39
- export type SelectorObserverConstructorOptions = MutationObserverInit & SelectorObserverOptions;
40
+ export type SelectorObserverConstructorOptions = Prettify<SelectorObserverOptions & MutationObserverInit>;
40
41
  /** Observes the children of the given element for changes */
41
42
  export declare class SelectorObserver {
42
43
  private enabled;
@@ -1,4 +1,4 @@
1
- import type { Stringifiable } from "./types.js";
1
+ import type { Prettify, Stringifiable } from "./types.js";
2
2
  /**
3
3
  * Automatically appends an `s` to the passed {@linkcode word}, if {@linkcode num} is not equal to 1
4
4
  * @param word A word in singular form, to auto-convert to plural
@@ -24,9 +24,9 @@ export declare function pauseFor(time: number): Promise<void>;
24
24
  export declare function debounce<TFunc extends (...args: TArgs[]) => void, // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
25
  TArgs = any>(func: TFunc, timeout?: number, edge?: "rising" | "falling"): (...args: TArgs[]) => void;
26
26
  /** Options for the `fetchAdvanced()` function */
27
- export type FetchAdvancedOpts = Omit<RequestInit & Partial<{
27
+ export type FetchAdvancedOpts = Prettify<Partial<{
28
28
  /** Timeout in milliseconds after which the fetch call will be canceled with an AbortController signal */
29
29
  timeout: number;
30
- }>, "signal">;
30
+ }> & RequestInit>;
31
31
  /** Calls the fetch API with special options like a timeout */
32
32
  export declare function fetchAdvanced(input: RequestInfo | URL, options?: FetchAdvancedOpts): Promise<Response>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sv443-network/userutils",
3
3
  "libName": "UserUtils",
4
- "version": "8.2.0",
4
+ "version": "8.3.1",
5
5
  "description": "Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, create persistent & synchronous data stores, modify the DOM more easily and more",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
@@ -26,7 +26,8 @@
26
26
  "dev-all": "npm run build-all -- --watch",
27
27
  "update-jsr-version": "npm run node-ts -- ./tools/update-jsr-version.mts",
28
28
  "publish-package": "changeset publish",
29
- "publish-package-jsr": "npm run update-jsr-version && npx jsr publish",
29
+ "publish-package-jsr": "npm run update-jsr-version && npx jsr publish --allow-dirty",
30
+ "change": "changeset",
30
31
  "node-ts": "node --no-warnings=ExperimentalWarning --enable-source-maps --loader ts-node/esm",
31
32
  "test-serve": "npm run node-ts -- ./test/TestPage/server.mts",
32
33
  "test-dev": "cd test/TestScript && npm run dev",