@naturalcycles/js-lib 14.254.0 → 14.256.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.
@@ -161,11 +161,15 @@ export declare function _intersection<T>(a1: T[], a2: T[] | Set<T>): T[];
161
161
  */
162
162
  export declare function _intersectsWith<T>(a1: T[], a2: T[] | Set<T>): boolean;
163
163
  /**
164
+ * Returns array1 minus array2.
165
+ *
164
166
  * @example
165
167
  * _difference([2, 1], [2, 3])
166
168
  * // [1]
169
+ *
170
+ * Passing second array as Set is more performant (it'll skip turning the array into Set in-place).
167
171
  */
168
- export declare function _difference<T>(source: T[], ...diffs: T[][]): T[];
172
+ export declare function _difference<T>(a1: T[], a2: T[] | Set<T>): T[];
169
173
  /**
170
174
  * Returns the sum of items, or 0 for empty array.
171
175
  */
@@ -318,16 +318,17 @@ function _intersectsWith(a1, a2) {
318
318
  return a1.some(v => a2set.has(v));
319
319
  }
320
320
  /**
321
+ * Returns array1 minus array2.
322
+ *
321
323
  * @example
322
324
  * _difference([2, 1], [2, 3])
323
325
  * // [1]
326
+ *
327
+ * Passing second array as Set is more performant (it'll skip turning the array into Set in-place).
324
328
  */
325
- function _difference(source, ...diffs) {
326
- let a = source;
327
- for (const b of diffs) {
328
- a = a.filter(c => !b.includes(c));
329
- }
330
- return a;
329
+ function _difference(a1, a2) {
330
+ const a2set = a2 instanceof Set ? a2 : new Set(a2);
331
+ return a1.filter(v => !a2set.has(v));
331
332
  }
332
333
  /**
333
334
  * Returns the sum of items, or 0 for empty array.
package/dist/bot.d.ts ADDED
@@ -0,0 +1,60 @@
1
+ export interface BotDetectionServiceCfg {
2
+ /**
3
+ * Defaults to false.
4
+ * If true - the instance will memoize (remember) the results of the detection
5
+ * and won't re-run it.
6
+ */
7
+ memoizeResults?: boolean;
8
+ /**
9
+ * Defaults to false.
10
+ * If set to true: `getBotReason()` would return BotReason.CDP if CDP is detected.
11
+ * Otherwise - `getBotReason()` will not perform the CDP check.
12
+ */
13
+ treatCDPAsBotReason?: boolean;
14
+ }
15
+ /**
16
+ * Service to detect bots and CDP (Chrome DevTools Protocol).
17
+ *
18
+ * @experimental
19
+ */
20
+ export declare class BotDetectionService {
21
+ cfg: BotDetectionServiceCfg;
22
+ constructor(cfg?: BotDetectionServiceCfg);
23
+ private botReason;
24
+ private cdp;
25
+ isBotOrCDP(): boolean;
26
+ isBot(): boolean;
27
+ /**
28
+ * Returns null if it's not a Bot,
29
+ * otherwise a truthy BotReason.
30
+ */
31
+ getBotReason(): BotReason | null;
32
+ private detectBotReason;
33
+ /**
34
+ * CDP stands for Chrome DevTools Protocol.
35
+ * This function tests if the current environment is a CDP environment.
36
+ * If it's true - it's one of:
37
+ *
38
+ * 1. Bot, automated with CDP, e.g Puppeteer, Playwright or such.
39
+ * 2. Developer with Chrome DevTools open.
40
+ *
41
+ * 2 is certainly not a bot, but unfortunately we can't distinguish between the two.
42
+ * That's why this function is not part of `isBot()`, because it can give "false positive" with DevTools.
43
+ *
44
+ * Based on: https://deviceandbrowserinfo.com/learning_zone/articles/detecting-headless-chrome-puppeteer-2024
45
+ */
46
+ isCDP(): boolean;
47
+ private detectCDP;
48
+ }
49
+ export declare enum BotReason {
50
+ NoNavigator = 1,
51
+ NoUserAgent = 2,
52
+ UserAgent = 3,
53
+ WebDriver = 4,
54
+ EmptyLanguages = 6,
55
+ /**
56
+ * This is when CDP is considered to be a reason to be a Bot.
57
+ * By default it's not.
58
+ */
59
+ CDP = 8
60
+ }
package/dist/bot.js ADDED
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ // Relevant material:
3
+ // https://deviceandbrowserinfo.com/learning_zone/articles/detecting-headless-chrome-puppeteer-2024
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.BotReason = exports.BotDetectionService = void 0;
6
+ const env_1 = require("./env");
7
+ /**
8
+ * Service to detect bots and CDP (Chrome DevTools Protocol).
9
+ *
10
+ * @experimental
11
+ */
12
+ class BotDetectionService {
13
+ constructor(cfg = {}) {
14
+ this.cfg = cfg;
15
+ }
16
+ isBotOrCDP() {
17
+ return !!this.getBotReason() || this.isCDP();
18
+ }
19
+ isBot() {
20
+ return !!this.getBotReason();
21
+ }
22
+ /**
23
+ * Returns null if it's not a Bot,
24
+ * otherwise a truthy BotReason.
25
+ */
26
+ getBotReason() {
27
+ if (this.cfg.memoizeResults && this.botReason !== undefined) {
28
+ return this.botReason;
29
+ }
30
+ this.botReason = this.detectBotReason();
31
+ return this.botReason;
32
+ }
33
+ detectBotReason() {
34
+ // SSR - not a bot
35
+ if ((0, env_1.isServerSide)())
36
+ return null;
37
+ const { navigator } = globalThis;
38
+ if (!navigator)
39
+ return BotReason.NoNavigator;
40
+ const { userAgent } = navigator;
41
+ if (!userAgent)
42
+ return BotReason.NoUserAgent;
43
+ if (/bot|headless|electron|phantom|slimer/i.test(userAgent)) {
44
+ return BotReason.UserAgent;
45
+ }
46
+ if (navigator.webdriver) {
47
+ return BotReason.WebDriver;
48
+ }
49
+ // Kirill: commented out, as it's no longer seems reliable,
50
+ // e.g generates false positives with latest Android clients (e.g. Chrome 129)
51
+ // if (navigator.plugins?.length === 0) {
52
+ // return BotReason.ZeroPlugins // Headless Chrome
53
+ // }
54
+ if (navigator.languages === '') {
55
+ return BotReason.EmptyLanguages; // Headless Chrome
56
+ }
57
+ // isChrome is true if the browser is Chrome, Chromium or Opera
58
+ // this is "the chrome test" from https://intoli.com/blog/not-possible-to-block-chrome-headless/
59
+ // this property is for some reason not present by default in headless chrome
60
+ // Kirill: criterium removed due to false positives with Android
61
+ // if (userAgent.includes('Chrome') && !(globalThis as any).chrome) {
62
+ // return BotReason.ChromeWithoutChrome // Headless Chrome
63
+ // }
64
+ if (this.cfg.treatCDPAsBotReason && this.detectCDP()) {
65
+ return BotReason.CDP;
66
+ }
67
+ return null;
68
+ }
69
+ /**
70
+ * CDP stands for Chrome DevTools Protocol.
71
+ * This function tests if the current environment is a CDP environment.
72
+ * If it's true - it's one of:
73
+ *
74
+ * 1. Bot, automated with CDP, e.g Puppeteer, Playwright or such.
75
+ * 2. Developer with Chrome DevTools open.
76
+ *
77
+ * 2 is certainly not a bot, but unfortunately we can't distinguish between the two.
78
+ * That's why this function is not part of `isBot()`, because it can give "false positive" with DevTools.
79
+ *
80
+ * Based on: https://deviceandbrowserinfo.com/learning_zone/articles/detecting-headless-chrome-puppeteer-2024
81
+ */
82
+ isCDP() {
83
+ if (this.cfg.memoizeResults && this.cdp !== undefined) {
84
+ return this.cdp;
85
+ }
86
+ this.cdp = this.detectCDP();
87
+ return this.cdp;
88
+ }
89
+ detectCDP() {
90
+ if ((0, env_1.isServerSide)())
91
+ return false;
92
+ let cdpCheck1 = false;
93
+ try {
94
+ /* eslint-disable */
95
+ // biome-ignore lint/suspicious/useErrorMessage: ok
96
+ const e = new window.Error();
97
+ window.Object.defineProperty(e, 'stack', {
98
+ configurable: false,
99
+ enumerable: false,
100
+ // biome-ignore lint/complexity/useArrowFunction: ok
101
+ get: function () {
102
+ cdpCheck1 = true;
103
+ return '';
104
+ },
105
+ });
106
+ // This is part of the detection and shouldn't be deleted
107
+ window.console.debug(e);
108
+ /* eslint-enable */
109
+ }
110
+ catch { }
111
+ return cdpCheck1;
112
+ }
113
+ }
114
+ exports.BotDetectionService = BotDetectionService;
115
+ var BotReason;
116
+ (function (BotReason) {
117
+ BotReason[BotReason["NoNavigator"] = 1] = "NoNavigator";
118
+ BotReason[BotReason["NoUserAgent"] = 2] = "NoUserAgent";
119
+ BotReason[BotReason["UserAgent"] = 3] = "UserAgent";
120
+ BotReason[BotReason["WebDriver"] = 4] = "WebDriver";
121
+ // ZeroPlugins = 5,
122
+ BotReason[BotReason["EmptyLanguages"] = 6] = "EmptyLanguages";
123
+ // ChromeWithoutChrome = 7,
124
+ /**
125
+ * This is when CDP is considered to be a reason to be a Bot.
126
+ * By default it's not.
127
+ */
128
+ BotReason[BotReason["CDP"] = 8] = "CDP";
129
+ })(BotReason || (exports.BotReason = BotReason = {}));
@@ -29,7 +29,7 @@ function _tryCatch(fn, opt = {}) {
29
29
  }
30
30
  if (onError) {
31
31
  try {
32
- return await onError((0, index_1._anyToError)(err)); // eslint-disable-line @typescript-eslint/return-await
32
+ return await onError((0, index_1._anyToError)(err));
33
33
  }
34
34
  catch { }
35
35
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './abort';
2
2
  export * from './array/array.util';
3
3
  export * from './array/range';
4
+ export * from './bot';
4
5
  export * from './datetime/dateInterval';
5
6
  export * from './datetime/localDate';
6
7
  export * from './datetime/localTime';
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ const tslib_1 = require("tslib");
5
5
  tslib_1.__exportStar(require("./abort"), exports);
6
6
  tslib_1.__exportStar(require("./array/array.util"), exports);
7
7
  tslib_1.__exportStar(require("./array/range"), exports);
8
+ tslib_1.__exportStar(require("./bot"), exports);
8
9
  tslib_1.__exportStar(require("./datetime/dateInterval"), exports);
9
10
  tslib_1.__exportStar(require("./datetime/localDate"), exports);
10
11
  tslib_1.__exportStar(require("./datetime/localTime"), exports);
package/dist/types.d.ts CHANGED
@@ -1,4 +1,18 @@
1
1
  import type { Promisable } from './typeFest';
2
+ declare const __brand: unique symbol;
3
+ interface Brand<B> {
4
+ [__brand]: B;
5
+ }
6
+ /**
7
+ * Helper to create "Branded" types.
8
+ *
9
+ * Example:
10
+ * export type MyId = Branded<string, 'MyId'>
11
+ *
12
+ * MyId can be assigned to a string,
13
+ * but string cannot be assigned to MyId without casting it (`as MyId`).
14
+ */
15
+ export type Branded<T, B> = T & Brand<B>;
2
16
  /**
3
17
  * Map from String to String (or <T>).
4
18
  *
@@ -292,3 +306,4 @@ export interface CommonClient extends AsyncDisposable {
292
306
  disconnect: () => Promise<void>;
293
307
  ping: () => Promise<void>;
294
308
  }
309
+ export {};
@@ -277,16 +277,17 @@ export function _intersectsWith(a1, a2) {
277
277
  return a1.some(v => a2set.has(v));
278
278
  }
279
279
  /**
280
+ * Returns array1 minus array2.
281
+ *
280
282
  * @example
281
283
  * _difference([2, 1], [2, 3])
282
284
  * // [1]
285
+ *
286
+ * Passing second array as Set is more performant (it'll skip turning the array into Set in-place).
283
287
  */
284
- export function _difference(source, ...diffs) {
285
- let a = source;
286
- for (const b of diffs) {
287
- a = a.filter(c => !b.includes(c));
288
- }
289
- return a;
288
+ export function _difference(a1, a2) {
289
+ const a2set = a2 instanceof Set ? a2 : new Set(a2);
290
+ return a1.filter(v => !a2set.has(v));
290
291
  }
291
292
  /**
292
293
  * Returns the sum of items, or 0 for empty array.
@@ -0,0 +1,125 @@
1
+ // Relevant material:
2
+ // https://deviceandbrowserinfo.com/learning_zone/articles/detecting-headless-chrome-puppeteer-2024
3
+ import { isServerSide } from './env';
4
+ /**
5
+ * Service to detect bots and CDP (Chrome DevTools Protocol).
6
+ *
7
+ * @experimental
8
+ */
9
+ export class BotDetectionService {
10
+ constructor(cfg = {}) {
11
+ this.cfg = cfg;
12
+ }
13
+ isBotOrCDP() {
14
+ return !!this.getBotReason() || this.isCDP();
15
+ }
16
+ isBot() {
17
+ return !!this.getBotReason();
18
+ }
19
+ /**
20
+ * Returns null if it's not a Bot,
21
+ * otherwise a truthy BotReason.
22
+ */
23
+ getBotReason() {
24
+ if (this.cfg.memoizeResults && this.botReason !== undefined) {
25
+ return this.botReason;
26
+ }
27
+ this.botReason = this.detectBotReason();
28
+ return this.botReason;
29
+ }
30
+ detectBotReason() {
31
+ // SSR - not a bot
32
+ if (isServerSide())
33
+ return null;
34
+ const { navigator } = globalThis;
35
+ if (!navigator)
36
+ return BotReason.NoNavigator;
37
+ const { userAgent } = navigator;
38
+ if (!userAgent)
39
+ return BotReason.NoUserAgent;
40
+ if (/bot|headless|electron|phantom|slimer/i.test(userAgent)) {
41
+ return BotReason.UserAgent;
42
+ }
43
+ if (navigator.webdriver) {
44
+ return BotReason.WebDriver;
45
+ }
46
+ // Kirill: commented out, as it's no longer seems reliable,
47
+ // e.g generates false positives with latest Android clients (e.g. Chrome 129)
48
+ // if (navigator.plugins?.length === 0) {
49
+ // return BotReason.ZeroPlugins // Headless Chrome
50
+ // }
51
+ if (navigator.languages === '') {
52
+ return BotReason.EmptyLanguages; // Headless Chrome
53
+ }
54
+ // isChrome is true if the browser is Chrome, Chromium or Opera
55
+ // this is "the chrome test" from https://intoli.com/blog/not-possible-to-block-chrome-headless/
56
+ // this property is for some reason not present by default in headless chrome
57
+ // Kirill: criterium removed due to false positives with Android
58
+ // if (userAgent.includes('Chrome') && !(globalThis as any).chrome) {
59
+ // return BotReason.ChromeWithoutChrome // Headless Chrome
60
+ // }
61
+ if (this.cfg.treatCDPAsBotReason && this.detectCDP()) {
62
+ return BotReason.CDP;
63
+ }
64
+ return null;
65
+ }
66
+ /**
67
+ * CDP stands for Chrome DevTools Protocol.
68
+ * This function tests if the current environment is a CDP environment.
69
+ * If it's true - it's one of:
70
+ *
71
+ * 1. Bot, automated with CDP, e.g Puppeteer, Playwright or such.
72
+ * 2. Developer with Chrome DevTools open.
73
+ *
74
+ * 2 is certainly not a bot, but unfortunately we can't distinguish between the two.
75
+ * That's why this function is not part of `isBot()`, because it can give "false positive" with DevTools.
76
+ *
77
+ * Based on: https://deviceandbrowserinfo.com/learning_zone/articles/detecting-headless-chrome-puppeteer-2024
78
+ */
79
+ isCDP() {
80
+ if (this.cfg.memoizeResults && this.cdp !== undefined) {
81
+ return this.cdp;
82
+ }
83
+ this.cdp = this.detectCDP();
84
+ return this.cdp;
85
+ }
86
+ detectCDP() {
87
+ if (isServerSide())
88
+ return false;
89
+ let cdpCheck1 = false;
90
+ try {
91
+ /* eslint-disable */
92
+ // biome-ignore lint/suspicious/useErrorMessage: ok
93
+ const e = new window.Error();
94
+ window.Object.defineProperty(e, 'stack', {
95
+ configurable: false,
96
+ enumerable: false,
97
+ // biome-ignore lint/complexity/useArrowFunction: ok
98
+ get: function () {
99
+ cdpCheck1 = true;
100
+ return '';
101
+ },
102
+ });
103
+ // This is part of the detection and shouldn't be deleted
104
+ window.console.debug(e);
105
+ /* eslint-enable */
106
+ }
107
+ catch { }
108
+ return cdpCheck1;
109
+ }
110
+ }
111
+ export var BotReason;
112
+ (function (BotReason) {
113
+ BotReason[BotReason["NoNavigator"] = 1] = "NoNavigator";
114
+ BotReason[BotReason["NoUserAgent"] = 2] = "NoUserAgent";
115
+ BotReason[BotReason["UserAgent"] = 3] = "UserAgent";
116
+ BotReason[BotReason["WebDriver"] = 4] = "WebDriver";
117
+ // ZeroPlugins = 5,
118
+ BotReason[BotReason["EmptyLanguages"] = 6] = "EmptyLanguages";
119
+ // ChromeWithoutChrome = 7,
120
+ /**
121
+ * This is when CDP is considered to be a reason to be a Bot.
122
+ * By default it's not.
123
+ */
124
+ BotReason[BotReason["CDP"] = 8] = "CDP";
125
+ })(BotReason || (BotReason = {}));
@@ -25,7 +25,7 @@ export function _tryCatch(fn, opt = {}) {
25
25
  }
26
26
  if (onError) {
27
27
  try {
28
- return await onError(_anyToError(err)); // eslint-disable-line @typescript-eslint/return-await
28
+ return await onError(_anyToError(err));
29
29
  }
30
30
  catch { }
31
31
  }
package/dist-esm/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './abort';
2
2
  export * from './array/array.util';
3
3
  export * from './array/range';
4
+ export * from './bot';
4
5
  export * from './datetime/dateInterval';
5
6
  export * from './datetime/localDate';
6
7
  export * from './datetime/localTime';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.254.0",
3
+ "version": "14.256.0",
4
4
  "scripts": {
5
5
  "prepare": "husky",
6
6
  "build": "dev-lib build-esm-cjs",
@@ -331,16 +331,17 @@ export function _intersectsWith<T>(a1: T[], a2: T[] | Set<T>): boolean {
331
331
  }
332
332
 
333
333
  /**
334
+ * Returns array1 minus array2.
335
+ *
334
336
  * @example
335
337
  * _difference([2, 1], [2, 3])
336
338
  * // [1]
339
+ *
340
+ * Passing second array as Set is more performant (it'll skip turning the array into Set in-place).
337
341
  */
338
- export function _difference<T>(source: T[], ...diffs: T[][]): T[] {
339
- let a = source
340
- for (const b of diffs) {
341
- a = a.filter(c => !b.includes(c))
342
- }
343
- return a
342
+ export function _difference<T>(a1: T[], a2: T[] | Set<T>): T[] {
343
+ const a2set = a2 instanceof Set ? a2 : new Set(a2)
344
+ return a1.filter(v => !a2set.has(v))
344
345
  }
345
346
 
346
347
  /**
package/src/bot.ts ADDED
@@ -0,0 +1,155 @@
1
+ // Relevant material:
2
+ // https://deviceandbrowserinfo.com/learning_zone/articles/detecting-headless-chrome-puppeteer-2024
3
+
4
+ import { isServerSide } from './env'
5
+
6
+ export interface BotDetectionServiceCfg {
7
+ /**
8
+ * Defaults to false.
9
+ * If true - the instance will memoize (remember) the results of the detection
10
+ * and won't re-run it.
11
+ */
12
+ memoizeResults?: boolean
13
+
14
+ /**
15
+ * Defaults to false.
16
+ * If set to true: `getBotReason()` would return BotReason.CDP if CDP is detected.
17
+ * Otherwise - `getBotReason()` will not perform the CDP check.
18
+ */
19
+ treatCDPAsBotReason?: boolean
20
+ }
21
+
22
+ /**
23
+ * Service to detect bots and CDP (Chrome DevTools Protocol).
24
+ *
25
+ * @experimental
26
+ */
27
+ export class BotDetectionService {
28
+ constructor(public cfg: BotDetectionServiceCfg = {}) {}
29
+
30
+ // memoized results
31
+ private botReason: BotReason | null | undefined
32
+ private cdp: boolean | undefined
33
+
34
+ isBotOrCDP(): boolean {
35
+ return !!this.getBotReason() || this.isCDP()
36
+ }
37
+
38
+ isBot(): boolean {
39
+ return !!this.getBotReason()
40
+ }
41
+
42
+ /**
43
+ * Returns null if it's not a Bot,
44
+ * otherwise a truthy BotReason.
45
+ */
46
+ getBotReason(): BotReason | null {
47
+ if (this.cfg.memoizeResults && this.botReason !== undefined) {
48
+ return this.botReason
49
+ }
50
+
51
+ this.botReason = this.detectBotReason()
52
+ return this.botReason
53
+ }
54
+
55
+ private detectBotReason(): BotReason | null {
56
+ // SSR - not a bot
57
+ if (isServerSide()) return null
58
+ const { navigator } = globalThis
59
+ if (!navigator) return BotReason.NoNavigator
60
+ const { userAgent } = navigator
61
+ if (!userAgent) return BotReason.NoUserAgent
62
+
63
+ if (/bot|headless|electron|phantom|slimer/i.test(userAgent)) {
64
+ return BotReason.UserAgent
65
+ }
66
+
67
+ if (navigator.webdriver) {
68
+ return BotReason.WebDriver
69
+ }
70
+
71
+ // Kirill: commented out, as it's no longer seems reliable,
72
+ // e.g generates false positives with latest Android clients (e.g. Chrome 129)
73
+ // if (navigator.plugins?.length === 0) {
74
+ // return BotReason.ZeroPlugins // Headless Chrome
75
+ // }
76
+
77
+ if ((navigator.languages as any) === '') {
78
+ return BotReason.EmptyLanguages // Headless Chrome
79
+ }
80
+
81
+ // isChrome is true if the browser is Chrome, Chromium or Opera
82
+ // this is "the chrome test" from https://intoli.com/blog/not-possible-to-block-chrome-headless/
83
+ // this property is for some reason not present by default in headless chrome
84
+ // Kirill: criterium removed due to false positives with Android
85
+ // if (userAgent.includes('Chrome') && !(globalThis as any).chrome) {
86
+ // return BotReason.ChromeWithoutChrome // Headless Chrome
87
+ // }
88
+
89
+ if (this.cfg.treatCDPAsBotReason && this.detectCDP()) {
90
+ return BotReason.CDP
91
+ }
92
+
93
+ return null
94
+ }
95
+
96
+ /**
97
+ * CDP stands for Chrome DevTools Protocol.
98
+ * This function tests if the current environment is a CDP environment.
99
+ * If it's true - it's one of:
100
+ *
101
+ * 1. Bot, automated with CDP, e.g Puppeteer, Playwright or such.
102
+ * 2. Developer with Chrome DevTools open.
103
+ *
104
+ * 2 is certainly not a bot, but unfortunately we can't distinguish between the two.
105
+ * That's why this function is not part of `isBot()`, because it can give "false positive" with DevTools.
106
+ *
107
+ * Based on: https://deviceandbrowserinfo.com/learning_zone/articles/detecting-headless-chrome-puppeteer-2024
108
+ */
109
+ isCDP(): boolean {
110
+ if (this.cfg.memoizeResults && this.cdp !== undefined) {
111
+ return this.cdp
112
+ }
113
+
114
+ this.cdp = this.detectCDP()
115
+ return this.cdp
116
+ }
117
+
118
+ private detectCDP(): boolean {
119
+ if (isServerSide()) return false
120
+ let cdpCheck1 = false
121
+ try {
122
+ /* eslint-disable */
123
+ // biome-ignore lint/suspicious/useErrorMessage: ok
124
+ const e = new window.Error()
125
+ window.Object.defineProperty(e, 'stack', {
126
+ configurable: false,
127
+ enumerable: false,
128
+ // biome-ignore lint/complexity/useArrowFunction: ok
129
+ get: function () {
130
+ cdpCheck1 = true
131
+ return ''
132
+ },
133
+ })
134
+ // This is part of the detection and shouldn't be deleted
135
+ window.console.debug(e)
136
+ /* eslint-enable */
137
+ } catch {}
138
+ return cdpCheck1
139
+ }
140
+ }
141
+
142
+ export enum BotReason {
143
+ NoNavigator = 1,
144
+ NoUserAgent = 2,
145
+ UserAgent = 3,
146
+ WebDriver = 4,
147
+ // ZeroPlugins = 5,
148
+ EmptyLanguages = 6,
149
+ // ChromeWithoutChrome = 7,
150
+ /**
151
+ * This is when CDP is considered to be a reason to be a Bot.
152
+ * By default it's not.
153
+ */
154
+ CDP = 8,
155
+ }
@@ -56,7 +56,7 @@ export function _tryCatch<T extends AnyFunction>(fn: T, opt: TryCatchOptions = {
56
56
 
57
57
  if (onError) {
58
58
  try {
59
- return await onError(_anyToError(err)) // eslint-disable-line @typescript-eslint/return-await
59
+ return await onError(_anyToError(err))
60
60
  } catch {}
61
61
  }
62
62
  // returns undefined, but doesn't rethrow
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './abort'
2
2
  export * from './array/array.util'
3
3
  export * from './array/range'
4
+ export * from './bot'
4
5
  export * from './datetime/dateInterval'
5
6
  export * from './datetime/localDate'
6
7
  export * from './datetime/localTime'
package/src/types.ts CHANGED
@@ -1,5 +1,22 @@
1
1
  import type { Promisable } from './typeFest'
2
2
 
3
+ declare const __brand: unique symbol
4
+
5
+ interface Brand<B> {
6
+ [__brand]: B
7
+ }
8
+
9
+ /**
10
+ * Helper to create "Branded" types.
11
+ *
12
+ * Example:
13
+ * export type MyId = Branded<string, 'MyId'>
14
+ *
15
+ * MyId can be assigned to a string,
16
+ * but string cannot be assigned to MyId without casting it (`as MyId`).
17
+ */
18
+ export type Branded<T, B> = T & Brand<B>
19
+
3
20
  /**
4
21
  * Map from String to String (or <T>).
5
22
  *