@naturalcycles/js-lib 15.86.0 → 15.86.2
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/error/error.util.js +33 -8
- package/dist/ua/ua.d.ts +71 -1
- package/dist/ua/ua.js +119 -20
- package/package.json +3 -3
- package/src/error/error.util.ts +34 -10
- package/src/ua/ua.ts +136 -24
package/dist/error/error.util.js
CHANGED
|
@@ -174,24 +174,49 @@ export function _errorSnippet(err, opt = {}) {
|
|
|
174
174
|
.join(': ');
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
|
+
// These duck-typing checks read properties of an arbitrary object, which may invoke its
|
|
178
|
+
// property getters, which may throw (e.g a stateful getter, a Proxy trap).
|
|
179
|
+
// A type guard should never throw - an object whose properties cannot even be read safely
|
|
180
|
+
// is, by definition, not a well-behaved Error-like - hence try/catch returning false.
|
|
181
|
+
// try/catch is free in V8 unless it actually catches, so there's no performance penalty.
|
|
177
182
|
export function _isBackendErrorResponseObject(o) {
|
|
178
|
-
|
|
183
|
+
try {
|
|
184
|
+
return _isErrorObject(o?.error);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
179
189
|
}
|
|
180
190
|
export function _isHttpRequestErrorObject(o) {
|
|
181
|
-
|
|
191
|
+
try {
|
|
192
|
+
return !!o && o.name === 'HttpRequestError' && typeof o.data?.requestUrl === 'string';
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
182
197
|
}
|
|
183
198
|
/**
|
|
184
199
|
* Note: any instance of AppError is also automatically an ErrorObject
|
|
185
200
|
*/
|
|
186
201
|
export function _isErrorObject(o) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
202
|
+
try {
|
|
203
|
+
return (!!o &&
|
|
204
|
+
typeof o === 'object' &&
|
|
205
|
+
typeof o.name === 'string' &&
|
|
206
|
+
typeof o.message === 'string' &&
|
|
207
|
+
typeof o.data === 'object');
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
192
212
|
}
|
|
193
213
|
export function _isErrorLike(o) {
|
|
194
|
-
|
|
214
|
+
try {
|
|
215
|
+
return (!!o && typeof o === 'object' && typeof o.name === 'string' && typeof o.message === 'string');
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
195
220
|
}
|
|
196
221
|
/**
|
|
197
222
|
* Convenience function to safely add properties to Error's `data` object
|
package/dist/ua/ua.d.ts
CHANGED
|
@@ -1,8 +1,47 @@
|
|
|
1
1
|
/// <reference lib="dom" preserve="true" />
|
|
2
2
|
export type UADeviceType = 'console' | 'mobile' | 'tablet' | 'smarttv' | 'wearable' | 'embedded';
|
|
3
|
+
/**
|
|
4
|
+
* Popular OS names as produced by the parser, for convenient comparisons
|
|
5
|
+
* (`os.name === UAOSName.iOS`) without magic strings.
|
|
6
|
+
*
|
|
7
|
+
* Not exhaustive - the parser emits many more names; compare against a string for the rest.
|
|
8
|
+
*/
|
|
9
|
+
export declare enum UAOSName {
|
|
10
|
+
iOS = "iOS",
|
|
11
|
+
Android = "Android",
|
|
12
|
+
MacOS = "Mac OS",
|
|
13
|
+
Windows = "Windows",
|
|
14
|
+
Linux = "Linux",
|
|
15
|
+
ChromiumOS = "Chromium OS",
|
|
16
|
+
WatchOS = "watchOS"
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Popular browser names as produced by the parser, for convenient comparisons
|
|
20
|
+
* (`browser.name === UABrowserName.Safari`) without magic strings.
|
|
21
|
+
*
|
|
22
|
+
* Not exhaustive - the parser emits many more names; compare against a string for the rest.
|
|
23
|
+
*/
|
|
24
|
+
export declare enum UABrowserName {
|
|
25
|
+
Chrome = "Chrome",
|
|
26
|
+
ChromeWebView = "Chrome WebView",
|
|
27
|
+
ChromeHeadless = "Chrome Headless",
|
|
28
|
+
Safari = "Safari",
|
|
29
|
+
MobileSafari = "Mobile Safari",
|
|
30
|
+
Firefox = "Firefox",
|
|
31
|
+
Edge = "Edge",
|
|
32
|
+
Opera = "Opera",
|
|
33
|
+
SamsungInternet = "Samsung Internet",
|
|
34
|
+
Facebook = "Facebook",
|
|
35
|
+
Instagram = "Instagram",
|
|
36
|
+
/**
|
|
37
|
+
* Bare WebKit UA without a Safari token, typically an iOS in-app webview (e.g. WKWebView).
|
|
38
|
+
*/
|
|
39
|
+
WebKit = "WebKit"
|
|
40
|
+
}
|
|
3
41
|
export interface UABrowser {
|
|
4
42
|
/**
|
|
5
43
|
* E.g Chrome, Edge, Firefox, Safari, Mobile Safari, Opera, IE, UCBrowser, WeChat, ...
|
|
44
|
+
* Popular values are available as `UABrowserName`.
|
|
6
45
|
*/
|
|
7
46
|
name: string | undefined;
|
|
8
47
|
version: string | undefined;
|
|
@@ -39,6 +78,7 @@ export interface UAEngine {
|
|
|
39
78
|
export interface UAOS {
|
|
40
79
|
/**
|
|
41
80
|
* E.g Windows, iOS, Mac OS, Android, Linux, Chromium OS, ...
|
|
81
|
+
* Popular values are available as `UAOSName`.
|
|
42
82
|
*/
|
|
43
83
|
name: string | undefined;
|
|
44
84
|
version: string | undefined;
|
|
@@ -88,6 +128,7 @@ export interface UAParserExtensions {
|
|
|
88
128
|
* (with client-hints-based refinements, e.g. Brave and iPadOS detection).
|
|
89
129
|
*/
|
|
90
130
|
export declare function parseUserAgent(userAgent?: string, extensions?: UAParserExtensions): UAResult;
|
|
131
|
+
export declare function uaParser(userAgent?: string, extensions?: UAParserExtensions): UAParser;
|
|
91
132
|
/**
|
|
92
133
|
* User-Agent parser.
|
|
93
134
|
*
|
|
@@ -97,11 +138,13 @@ export declare function parseUserAgent(userAgent?: string, extensions?: UAParser
|
|
|
97
138
|
*/
|
|
98
139
|
export declare class UAParser {
|
|
99
140
|
constructor(ua?: string, extensions?: UAParserExtensions);
|
|
100
|
-
constructor(extensions: UAParserExtensions);
|
|
101
141
|
private ua;
|
|
102
142
|
private readonly nav;
|
|
103
143
|
private readonly rgxMap;
|
|
104
144
|
private readonly isSelfNav;
|
|
145
|
+
private cachedBrowser?;
|
|
146
|
+
private cachedOs?;
|
|
147
|
+
private cachedDevice?;
|
|
105
148
|
getBrowser(): UABrowser;
|
|
106
149
|
getCPU(): UACpu;
|
|
107
150
|
getDevice(): UADevice;
|
|
@@ -110,4 +153,31 @@ export declare class UAParser {
|
|
|
110
153
|
getResult(): UAResult;
|
|
111
154
|
getUA(): string;
|
|
112
155
|
setUA(ua: string | undefined): this;
|
|
156
|
+
/**
|
|
157
|
+
* True if the OS is iOS.
|
|
158
|
+
* Note: desktop-mode iPads send a Mac-like UA and are not detected as iOS
|
|
159
|
+
* (except when parsing the browser's own UA, where client-hints refinements apply).
|
|
160
|
+
*/
|
|
161
|
+
isIos(): boolean;
|
|
162
|
+
isAndroid(): boolean;
|
|
163
|
+
isMacOs(): boolean;
|
|
164
|
+
isWindows(): boolean;
|
|
165
|
+
/**
|
|
166
|
+
* True for 'Chrome', 'Chrome WebView' and 'Chrome Headless' (not for Chromium).
|
|
167
|
+
*/
|
|
168
|
+
isChrome(): boolean;
|
|
169
|
+
/**
|
|
170
|
+
* True for 'Safari' and 'Mobile Safari' (not for bare-WebKit iOS in-app webviews).
|
|
171
|
+
*/
|
|
172
|
+
isSafari(): boolean;
|
|
173
|
+
isFirefox(): boolean;
|
|
174
|
+
isEdge(): boolean;
|
|
175
|
+
/**
|
|
176
|
+
* True if the device type is 'mobile' (phones; tablets are 'tablet' - see isTablet).
|
|
177
|
+
*/
|
|
178
|
+
isMobile(): boolean;
|
|
179
|
+
isTablet(): boolean;
|
|
180
|
+
private browser;
|
|
181
|
+
private os;
|
|
182
|
+
private device;
|
|
113
183
|
}
|
package/dist/ua/ua.js
CHANGED
|
@@ -10,6 +10,46 @@
|
|
|
10
10
|
// Detects Browser, Engine, OS, CPU and Device type/model from a User-Agent string.
|
|
11
11
|
// Supports browser & Node.js environments.
|
|
12
12
|
/// <reference lib="dom" preserve="true" />
|
|
13
|
+
/**
|
|
14
|
+
* Popular OS names as produced by the parser, for convenient comparisons
|
|
15
|
+
* (`os.name === UAOSName.iOS`) without magic strings.
|
|
16
|
+
*
|
|
17
|
+
* Not exhaustive - the parser emits many more names; compare against a string for the rest.
|
|
18
|
+
*/
|
|
19
|
+
export var UAOSName;
|
|
20
|
+
(function (UAOSName) {
|
|
21
|
+
UAOSName["iOS"] = "iOS";
|
|
22
|
+
UAOSName["Android"] = "Android";
|
|
23
|
+
UAOSName["MacOS"] = "Mac OS";
|
|
24
|
+
UAOSName["Windows"] = "Windows";
|
|
25
|
+
UAOSName["Linux"] = "Linux";
|
|
26
|
+
UAOSName["ChromiumOS"] = "Chromium OS";
|
|
27
|
+
UAOSName["WatchOS"] = "watchOS";
|
|
28
|
+
})(UAOSName || (UAOSName = {}));
|
|
29
|
+
/**
|
|
30
|
+
* Popular browser names as produced by the parser, for convenient comparisons
|
|
31
|
+
* (`browser.name === UABrowserName.Safari`) without magic strings.
|
|
32
|
+
*
|
|
33
|
+
* Not exhaustive - the parser emits many more names; compare against a string for the rest.
|
|
34
|
+
*/
|
|
35
|
+
export var UABrowserName;
|
|
36
|
+
(function (UABrowserName) {
|
|
37
|
+
UABrowserName["Chrome"] = "Chrome";
|
|
38
|
+
UABrowserName["ChromeWebView"] = "Chrome WebView";
|
|
39
|
+
UABrowserName["ChromeHeadless"] = "Chrome Headless";
|
|
40
|
+
UABrowserName["Safari"] = "Safari";
|
|
41
|
+
UABrowserName["MobileSafari"] = "Mobile Safari";
|
|
42
|
+
UABrowserName["Firefox"] = "Firefox";
|
|
43
|
+
UABrowserName["Edge"] = "Edge";
|
|
44
|
+
UABrowserName["Opera"] = "Opera";
|
|
45
|
+
UABrowserName["SamsungInternet"] = "Samsung Internet";
|
|
46
|
+
UABrowserName["Facebook"] = "Facebook";
|
|
47
|
+
UABrowserName["Instagram"] = "Instagram";
|
|
48
|
+
/**
|
|
49
|
+
* Bare WebKit UA without a Safari token, typically an iOS in-app webview (e.g. WKWebView).
|
|
50
|
+
*/
|
|
51
|
+
UABrowserName["WebKit"] = "WebKit";
|
|
52
|
+
})(UABrowserName || (UABrowserName = {}));
|
|
13
53
|
/**
|
|
14
54
|
* Parses the given User-Agent string into browser, engine, os, device and cpu information.
|
|
15
55
|
*
|
|
@@ -19,6 +59,9 @@
|
|
|
19
59
|
export function parseUserAgent(userAgent, extensions) {
|
|
20
60
|
return new UAParser(userAgent, extensions).getResult();
|
|
21
61
|
}
|
|
62
|
+
export function uaParser(userAgent, extensions) {
|
|
63
|
+
return new UAParser(userAgent, extensions);
|
|
64
|
+
}
|
|
22
65
|
/**
|
|
23
66
|
* User-Agent parser.
|
|
24
67
|
*
|
|
@@ -28,10 +71,6 @@ export function parseUserAgent(userAgent, extensions) {
|
|
|
28
71
|
*/
|
|
29
72
|
export class UAParser {
|
|
30
73
|
constructor(ua, extensions) {
|
|
31
|
-
if (typeof ua === 'object' && ua !== null) {
|
|
32
|
-
extensions = ua;
|
|
33
|
-
ua = undefined;
|
|
34
|
-
}
|
|
35
74
|
this.nav = typeof globalThis.window !== 'undefined' ? globalThis.navigator : undefined;
|
|
36
75
|
this.rgxMap = extensions ? extend(regexes, extensions) : regexes;
|
|
37
76
|
const initialUa = ua || this.nav?.userAgent || '';
|
|
@@ -44,6 +83,11 @@ export class UAParser {
|
|
|
44
83
|
nav;
|
|
45
84
|
rgxMap;
|
|
46
85
|
isSelfNav;
|
|
86
|
+
// Lazily cached results shared by the is* helpers (so multiple checks cost one parse),
|
|
87
|
+
// reset by setUA(). The get* methods deliberately stay uncached and return fresh objects.
|
|
88
|
+
cachedBrowser;
|
|
89
|
+
cachedOs;
|
|
90
|
+
cachedDevice;
|
|
47
91
|
getBrowser() {
|
|
48
92
|
const browser = { name: undefined, version: undefined, major: undefined };
|
|
49
93
|
rgxMapper(browser, this.ua, this.rgxMap.browser);
|
|
@@ -105,8 +149,63 @@ export class UAParser {
|
|
|
105
149
|
setUA(ua) {
|
|
106
150
|
ua ||= '';
|
|
107
151
|
this.ua = ua.length > UA_MAX_LENGTH ? ua.replace(/^\s\s*/, '').slice(0, UA_MAX_LENGTH) : ua;
|
|
152
|
+
this.cachedBrowser = this.cachedOs = this.cachedDevice = undefined;
|
|
108
153
|
return this;
|
|
109
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* True if the OS is iOS.
|
|
157
|
+
* Note: desktop-mode iPads send a Mac-like UA and are not detected as iOS
|
|
158
|
+
* (except when parsing the browser's own UA, where client-hints refinements apply).
|
|
159
|
+
*/
|
|
160
|
+
isIos() {
|
|
161
|
+
return this.os().name === UAOSName.iOS;
|
|
162
|
+
}
|
|
163
|
+
isAndroid() {
|
|
164
|
+
return this.os().name === UAOSName.Android;
|
|
165
|
+
}
|
|
166
|
+
isMacOs() {
|
|
167
|
+
return this.os().name === UAOSName.MacOS;
|
|
168
|
+
}
|
|
169
|
+
isWindows() {
|
|
170
|
+
return this.os().name === UAOSName.Windows;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* True for 'Chrome', 'Chrome WebView' and 'Chrome Headless' (not for Chromium).
|
|
174
|
+
*/
|
|
175
|
+
isChrome() {
|
|
176
|
+
return !!this.browser().name?.startsWith(UABrowserName.Chrome);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* True for 'Safari' and 'Mobile Safari' (not for bare-WebKit iOS in-app webviews).
|
|
180
|
+
*/
|
|
181
|
+
isSafari() {
|
|
182
|
+
const { name } = this.browser();
|
|
183
|
+
return name === UABrowserName.Safari || name === UABrowserName.MobileSafari;
|
|
184
|
+
}
|
|
185
|
+
isFirefox() {
|
|
186
|
+
return this.browser().name === UABrowserName.Firefox;
|
|
187
|
+
}
|
|
188
|
+
isEdge() {
|
|
189
|
+
return this.browser().name === UABrowserName.Edge;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* True if the device type is 'mobile' (phones; tablets are 'tablet' - see isTablet).
|
|
193
|
+
*/
|
|
194
|
+
isMobile() {
|
|
195
|
+
return this.device().type === MOBILE;
|
|
196
|
+
}
|
|
197
|
+
isTablet() {
|
|
198
|
+
return this.device().type === TABLET;
|
|
199
|
+
}
|
|
200
|
+
browser() {
|
|
201
|
+
return (this.cachedBrowser ??= this.getBrowser());
|
|
202
|
+
}
|
|
203
|
+
os() {
|
|
204
|
+
return (this.cachedOs ??= this.getOS());
|
|
205
|
+
}
|
|
206
|
+
device() {
|
|
207
|
+
return (this.cachedDevice ??= this.getDevice());
|
|
208
|
+
}
|
|
110
209
|
}
|
|
111
210
|
///////////
|
|
112
211
|
// Helpers
|
|
@@ -230,9 +329,9 @@ const ASUS = 'ASUS';
|
|
|
230
329
|
const BLACKBERRY = 'BlackBerry';
|
|
231
330
|
const BROWSER = 'Browser';
|
|
232
331
|
const SUFFIX_BROWSER = ' Browser';
|
|
233
|
-
const CHROME =
|
|
234
|
-
const EDGE =
|
|
235
|
-
const FIREFOX =
|
|
332
|
+
const CHROME = UABrowserName.Chrome;
|
|
333
|
+
const EDGE = UABrowserName.Edge;
|
|
334
|
+
const FIREFOX = UABrowserName.Firefox;
|
|
236
335
|
const GOOGLE = 'Google';
|
|
237
336
|
const HONOR = 'Honor';
|
|
238
337
|
const HUAWEI = 'Huawei';
|
|
@@ -242,16 +341,16 @@ const MICROSOFT = 'Microsoft';
|
|
|
242
341
|
const MOTOROLA = 'Motorola';
|
|
243
342
|
const NVIDIA = 'Nvidia';
|
|
244
343
|
const ONEPLUS = 'OnePlus';
|
|
245
|
-
const OPERA =
|
|
344
|
+
const OPERA = UABrowserName.Opera;
|
|
246
345
|
const OPPO = 'OPPO';
|
|
247
346
|
const SAMSUNG = 'Samsung';
|
|
248
347
|
const SHARP = 'Sharp';
|
|
249
348
|
const SONY = 'Sony';
|
|
250
349
|
const XIAOMI = 'Xiaomi';
|
|
251
350
|
const ZEBRA = 'Zebra';
|
|
252
|
-
const FACEBOOK =
|
|
253
|
-
const CHROMIUM_OS =
|
|
254
|
-
const MAC_OS =
|
|
351
|
+
const FACEBOOK = UABrowserName.Facebook;
|
|
352
|
+
const CHROMIUM_OS = UAOSName.ChromiumOS;
|
|
353
|
+
const MAC_OS = UAOSName.MacOS;
|
|
255
354
|
///////////////
|
|
256
355
|
// String maps
|
|
257
356
|
//////////////
|
|
@@ -289,11 +388,11 @@ const regexes = {
|
|
|
289
388
|
[
|
|
290
389
|
/\b(?:crmo|crios)\/([\w\.]+)/i, // Chrome for Android/iOS
|
|
291
390
|
],
|
|
292
|
-
[VERSION, [NAME,
|
|
391
|
+
[VERSION, [NAME, CHROME]],
|
|
293
392
|
[
|
|
294
393
|
/edg(?:e|ios|a)?\/([\w\.]+)/i, // Microsoft Edge
|
|
295
394
|
],
|
|
296
|
-
[VERSION, [NAME,
|
|
395
|
+
[VERSION, [NAME, EDGE]],
|
|
297
396
|
[
|
|
298
397
|
// Presto based
|
|
299
398
|
/(opera mini)\/([-\w\.]+)/i, // Opera Mini
|
|
@@ -415,7 +514,7 @@ const regexes = {
|
|
|
415
514
|
[
|
|
416
515
|
/samsungbrowser\/([\w\.]+)/i, // Samsung Internet
|
|
417
516
|
],
|
|
418
|
-
[VERSION, [NAME,
|
|
517
|
+
[VERSION, [NAME, UABrowserName.SamsungInternet]],
|
|
419
518
|
[
|
|
420
519
|
/metasr[\/ ]?([\d\.]+)/i, // Sogou Explorer
|
|
421
520
|
],
|
|
@@ -468,11 +567,11 @@ const regexes = {
|
|
|
468
567
|
[
|
|
469
568
|
/headlesschrome(?:\/([\w\.]+)| )/i, // Chrome Headless
|
|
470
569
|
],
|
|
471
|
-
[VERSION, [NAME,
|
|
570
|
+
[VERSION, [NAME, UABrowserName.ChromeHeadless]],
|
|
472
571
|
[
|
|
473
572
|
/ wv\).+(chrome)\/([\w\.]+)/i, // Chrome WebView
|
|
474
573
|
],
|
|
475
|
-
[[NAME,
|
|
574
|
+
[[NAME, UABrowserName.ChromeWebView], VERSION],
|
|
476
575
|
[
|
|
477
576
|
/droid.+ version\/([\w\.]+)\b.+(?:mobile safari|safari)/i, // Android Browser
|
|
478
577
|
],
|
|
@@ -484,7 +583,7 @@ const regexes = {
|
|
|
484
583
|
[
|
|
485
584
|
/version\/([\w\.\,]+) .*mobile\/\w+ (safari)/i, // Mobile Safari
|
|
486
585
|
],
|
|
487
|
-
[VERSION, [NAME,
|
|
586
|
+
[VERSION, [NAME, UABrowserName.MobileSafari]],
|
|
488
587
|
[
|
|
489
588
|
/version\/([\w(\.|\,)]+) .*(mobile ?safari|safari)/i, // Safari & Safari Mobile
|
|
490
589
|
],
|
|
@@ -1198,7 +1297,7 @@ const regexes = {
|
|
|
1198
1297
|
],
|
|
1199
1298
|
[
|
|
1200
1299
|
[VERSION, strMapper, windowsVersionMap],
|
|
1201
|
-
[NAME,
|
|
1300
|
+
[NAME, UAOSName.Windows],
|
|
1202
1301
|
],
|
|
1203
1302
|
[
|
|
1204
1303
|
// iOS/macOS
|
|
@@ -1208,7 +1307,7 @@ const regexes = {
|
|
|
1208
1307
|
],
|
|
1209
1308
|
[
|
|
1210
1309
|
[VERSION, /_/g, '.'],
|
|
1211
|
-
[NAME,
|
|
1310
|
+
[NAME, UAOSName.iOS],
|
|
1212
1311
|
],
|
|
1213
1312
|
[
|
|
1214
1313
|
/(mac os x) ?([\w\. ]*)/i,
|
|
@@ -1252,7 +1351,7 @@ const regexes = {
|
|
|
1252
1351
|
[
|
|
1253
1352
|
/watch(?: ?os[,\/]|\d,\d\/)([\d\.]+)/i, // watchOS
|
|
1254
1353
|
],
|
|
1255
|
-
[VERSION, [NAME,
|
|
1354
|
+
[VERSION, [NAME, UAOSName.WatchOS]],
|
|
1256
1355
|
[
|
|
1257
1356
|
// Google Chromecast
|
|
1258
1357
|
/crkey\/([\d\.]+)/i, // Google Chromecast
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/js-lib",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "15.86.
|
|
4
|
+
"version": "15.86.2",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"tslib": "^2"
|
|
7
7
|
},
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
+
"@naturalcycles/dev-lib": "0.0.0",
|
|
17
18
|
"@types/node": "^26",
|
|
18
19
|
"@types/semver": "^7",
|
|
19
20
|
"typescript": "^7",
|
|
20
|
-
"dayjs": "^1"
|
|
21
|
-
"@naturalcycles/dev-lib": "0.0.0"
|
|
21
|
+
"dayjs": "^1"
|
|
22
22
|
},
|
|
23
23
|
"exports": {
|
|
24
24
|
".": "./dist/index.js",
|
package/src/error/error.util.ts
CHANGED
|
@@ -226,12 +226,26 @@ export function _errorSnippet(err: any, opt: ErrorSnippetOptions = {}): string {
|
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
// These duck-typing checks read properties of an arbitrary object, which may invoke its
|
|
230
|
+
// property getters, which may throw (e.g a stateful getter, a Proxy trap).
|
|
231
|
+
// A type guard should never throw - an object whose properties cannot even be read safely
|
|
232
|
+
// is, by definition, not a well-behaved Error-like - hence try/catch returning false.
|
|
233
|
+
// try/catch is free in V8 unless it actually catches, so there's no performance penalty.
|
|
234
|
+
|
|
229
235
|
export function _isBackendErrorResponseObject(o: any): o is BackendErrorResponseObject {
|
|
230
|
-
|
|
236
|
+
try {
|
|
237
|
+
return _isErrorObject(o?.error)
|
|
238
|
+
} catch {
|
|
239
|
+
return false
|
|
240
|
+
}
|
|
231
241
|
}
|
|
232
242
|
|
|
233
243
|
export function _isHttpRequestErrorObject(o: any): o is ErrorObject<HttpRequestErrorData> {
|
|
234
|
-
|
|
244
|
+
try {
|
|
245
|
+
return !!o && o.name === 'HttpRequestError' && typeof o.data?.requestUrl === 'string'
|
|
246
|
+
} catch {
|
|
247
|
+
return false
|
|
248
|
+
}
|
|
235
249
|
}
|
|
236
250
|
|
|
237
251
|
/**
|
|
@@ -240,17 +254,27 @@ export function _isHttpRequestErrorObject(o: any): o is ErrorObject<HttpRequestE
|
|
|
240
254
|
export function _isErrorObject<DATA_TYPE extends ErrorData = ErrorData>(
|
|
241
255
|
o: any,
|
|
242
256
|
): o is ErrorObject<DATA_TYPE> {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
257
|
+
try {
|
|
258
|
+
return (
|
|
259
|
+
!!o &&
|
|
260
|
+
typeof o === 'object' &&
|
|
261
|
+
typeof o.name === 'string' &&
|
|
262
|
+
typeof o.message === 'string' &&
|
|
263
|
+
typeof o.data === 'object'
|
|
264
|
+
)
|
|
265
|
+
} catch {
|
|
266
|
+
return false
|
|
267
|
+
}
|
|
250
268
|
}
|
|
251
269
|
|
|
252
270
|
export function _isErrorLike(o: any): o is ErrorLike {
|
|
253
|
-
|
|
271
|
+
try {
|
|
272
|
+
return (
|
|
273
|
+
!!o && typeof o === 'object' && typeof o.name === 'string' && typeof o.message === 'string'
|
|
274
|
+
)
|
|
275
|
+
} catch {
|
|
276
|
+
return false
|
|
277
|
+
}
|
|
254
278
|
}
|
|
255
279
|
|
|
256
280
|
/**
|
package/src/ua/ua.ts
CHANGED
|
@@ -16,9 +16,50 @@
|
|
|
16
16
|
|
|
17
17
|
export type UADeviceType = 'console' | 'mobile' | 'tablet' | 'smarttv' | 'wearable' | 'embedded'
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Popular OS names as produced by the parser, for convenient comparisons
|
|
21
|
+
* (`os.name === UAOSName.iOS`) without magic strings.
|
|
22
|
+
*
|
|
23
|
+
* Not exhaustive - the parser emits many more names; compare against a string for the rest.
|
|
24
|
+
*/
|
|
25
|
+
export enum UAOSName {
|
|
26
|
+
iOS = 'iOS',
|
|
27
|
+
Android = 'Android',
|
|
28
|
+
MacOS = 'Mac OS',
|
|
29
|
+
Windows = 'Windows',
|
|
30
|
+
Linux = 'Linux',
|
|
31
|
+
ChromiumOS = 'Chromium OS',
|
|
32
|
+
WatchOS = 'watchOS',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Popular browser names as produced by the parser, for convenient comparisons
|
|
37
|
+
* (`browser.name === UABrowserName.Safari`) without magic strings.
|
|
38
|
+
*
|
|
39
|
+
* Not exhaustive - the parser emits many more names; compare against a string for the rest.
|
|
40
|
+
*/
|
|
41
|
+
export enum UABrowserName {
|
|
42
|
+
Chrome = 'Chrome',
|
|
43
|
+
ChromeWebView = 'Chrome WebView',
|
|
44
|
+
ChromeHeadless = 'Chrome Headless',
|
|
45
|
+
Safari = 'Safari',
|
|
46
|
+
MobileSafari = 'Mobile Safari',
|
|
47
|
+
Firefox = 'Firefox',
|
|
48
|
+
Edge = 'Edge',
|
|
49
|
+
Opera = 'Opera',
|
|
50
|
+
SamsungInternet = 'Samsung Internet',
|
|
51
|
+
Facebook = 'Facebook',
|
|
52
|
+
Instagram = 'Instagram',
|
|
53
|
+
/**
|
|
54
|
+
* Bare WebKit UA without a Safari token, typically an iOS in-app webview (e.g. WKWebView).
|
|
55
|
+
*/
|
|
56
|
+
WebKit = 'WebKit',
|
|
57
|
+
}
|
|
58
|
+
|
|
19
59
|
export interface UABrowser {
|
|
20
60
|
/**
|
|
21
61
|
* E.g Chrome, Edge, Firefox, Safari, Mobile Safari, Opera, IE, UCBrowser, WeChat, ...
|
|
62
|
+
* Popular values are available as `UABrowserName`.
|
|
22
63
|
*/
|
|
23
64
|
name: string | undefined
|
|
24
65
|
version: string | undefined
|
|
@@ -59,6 +100,7 @@ export interface UAEngine {
|
|
|
59
100
|
export interface UAOS {
|
|
60
101
|
/**
|
|
61
102
|
* E.g Windows, iOS, Mac OS, Android, Linux, Chromium OS, ...
|
|
103
|
+
* Popular values are available as `UAOSName`.
|
|
62
104
|
*/
|
|
63
105
|
name: string | undefined
|
|
64
106
|
version: string | undefined
|
|
@@ -125,6 +167,10 @@ export function parseUserAgent(userAgent?: string, extensions?: UAParserExtensio
|
|
|
125
167
|
return new UAParser(userAgent, extensions).getResult()
|
|
126
168
|
}
|
|
127
169
|
|
|
170
|
+
export function uaParser(userAgent?: string, extensions?: UAParserExtensions): UAParser {
|
|
171
|
+
return new UAParser(userAgent, extensions)
|
|
172
|
+
}
|
|
173
|
+
|
|
128
174
|
/**
|
|
129
175
|
* User-Agent parser.
|
|
130
176
|
*
|
|
@@ -133,14 +179,7 @@ export function parseUserAgent(userAgent?: string, extensions?: UAParserExtensio
|
|
|
133
179
|
* and reusing the compiled extensions across multiple `setUA()` calls.
|
|
134
180
|
*/
|
|
135
181
|
export class UAParser {
|
|
136
|
-
constructor(ua?: string, extensions?: UAParserExtensions)
|
|
137
|
-
constructor(extensions: UAParserExtensions)
|
|
138
|
-
constructor(ua?: string | UAParserExtensions, extensions?: UAParserExtensions) {
|
|
139
|
-
if (typeof ua === 'object' && ua !== null) {
|
|
140
|
-
extensions = ua
|
|
141
|
-
ua = undefined
|
|
142
|
-
}
|
|
143
|
-
|
|
182
|
+
constructor(ua?: string, extensions?: UAParserExtensions) {
|
|
144
183
|
this.nav = typeof globalThis.window !== 'undefined' ? globalThis.navigator : undefined
|
|
145
184
|
this.rgxMap = extensions ? extend(regexes, extensions) : regexes
|
|
146
185
|
const initialUa = ua || this.nav?.userAgent || ''
|
|
@@ -154,6 +193,11 @@ export class UAParser {
|
|
|
154
193
|
private readonly nav: UaNavigator | undefined
|
|
155
194
|
private readonly rgxMap: UARegexMaps
|
|
156
195
|
private readonly isSelfNav: boolean
|
|
196
|
+
// Lazily cached results shared by the is* helpers (so multiple checks cost one parse),
|
|
197
|
+
// reset by setUA(). The get* methods deliberately stay uncached and return fresh objects.
|
|
198
|
+
private cachedBrowser?: UABrowser
|
|
199
|
+
private cachedOs?: UAOS
|
|
200
|
+
private cachedDevice?: UADevice
|
|
157
201
|
|
|
158
202
|
getBrowser(): UABrowser {
|
|
159
203
|
const browser: UABrowser = { name: undefined, version: undefined, major: undefined }
|
|
@@ -225,8 +269,76 @@ export class UAParser {
|
|
|
225
269
|
setUA(ua: string | undefined): this {
|
|
226
270
|
ua ||= ''
|
|
227
271
|
this.ua = ua.length > UA_MAX_LENGTH ? ua.replace(/^\s\s*/, '').slice(0, UA_MAX_LENGTH) : ua
|
|
272
|
+
this.cachedBrowser = this.cachedOs = this.cachedDevice = undefined
|
|
228
273
|
return this
|
|
229
274
|
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* True if the OS is iOS.
|
|
278
|
+
* Note: desktop-mode iPads send a Mac-like UA and are not detected as iOS
|
|
279
|
+
* (except when parsing the browser's own UA, where client-hints refinements apply).
|
|
280
|
+
*/
|
|
281
|
+
isIos(): boolean {
|
|
282
|
+
return this.os().name === UAOSName.iOS
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
isAndroid(): boolean {
|
|
286
|
+
return this.os().name === UAOSName.Android
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
isMacOs(): boolean {
|
|
290
|
+
return this.os().name === UAOSName.MacOS
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
isWindows(): boolean {
|
|
294
|
+
return this.os().name === UAOSName.Windows
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* True for 'Chrome', 'Chrome WebView' and 'Chrome Headless' (not for Chromium).
|
|
299
|
+
*/
|
|
300
|
+
isChrome(): boolean {
|
|
301
|
+
return !!this.browser().name?.startsWith(UABrowserName.Chrome)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* True for 'Safari' and 'Mobile Safari' (not for bare-WebKit iOS in-app webviews).
|
|
306
|
+
*/
|
|
307
|
+
isSafari(): boolean {
|
|
308
|
+
const { name } = this.browser()
|
|
309
|
+
return name === UABrowserName.Safari || name === UABrowserName.MobileSafari
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
isFirefox(): boolean {
|
|
313
|
+
return this.browser().name === UABrowserName.Firefox
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
isEdge(): boolean {
|
|
317
|
+
return this.browser().name === UABrowserName.Edge
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* True if the device type is 'mobile' (phones; tablets are 'tablet' - see isTablet).
|
|
322
|
+
*/
|
|
323
|
+
isMobile(): boolean {
|
|
324
|
+
return this.device().type === MOBILE
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
isTablet(): boolean {
|
|
328
|
+
return this.device().type === TABLET
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private browser(): UABrowser {
|
|
332
|
+
return (this.cachedBrowser ??= this.getBrowser())
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private os(): UAOS {
|
|
336
|
+
return (this.cachedOs ??= this.getOS())
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private device(): UADevice {
|
|
340
|
+
return (this.cachedDevice ??= this.getDevice())
|
|
341
|
+
}
|
|
230
342
|
}
|
|
231
343
|
|
|
232
344
|
/**
|
|
@@ -384,9 +496,9 @@ const ASUS = 'ASUS'
|
|
|
384
496
|
const BLACKBERRY = 'BlackBerry'
|
|
385
497
|
const BROWSER = 'Browser'
|
|
386
498
|
const SUFFIX_BROWSER = ' Browser'
|
|
387
|
-
const CHROME =
|
|
388
|
-
const EDGE =
|
|
389
|
-
const FIREFOX =
|
|
499
|
+
const CHROME = UABrowserName.Chrome
|
|
500
|
+
const EDGE = UABrowserName.Edge
|
|
501
|
+
const FIREFOX = UABrowserName.Firefox
|
|
390
502
|
const GOOGLE = 'Google'
|
|
391
503
|
const HONOR = 'Honor'
|
|
392
504
|
const HUAWEI = 'Huawei'
|
|
@@ -396,16 +508,16 @@ const MICROSOFT = 'Microsoft'
|
|
|
396
508
|
const MOTOROLA = 'Motorola'
|
|
397
509
|
const NVIDIA = 'Nvidia'
|
|
398
510
|
const ONEPLUS = 'OnePlus'
|
|
399
|
-
const OPERA =
|
|
511
|
+
const OPERA = UABrowserName.Opera
|
|
400
512
|
const OPPO = 'OPPO'
|
|
401
513
|
const SAMSUNG = 'Samsung'
|
|
402
514
|
const SHARP = 'Sharp'
|
|
403
515
|
const SONY = 'Sony'
|
|
404
516
|
const XIAOMI = 'Xiaomi'
|
|
405
517
|
const ZEBRA = 'Zebra'
|
|
406
|
-
const FACEBOOK =
|
|
407
|
-
const CHROMIUM_OS =
|
|
408
|
-
const MAC_OS =
|
|
518
|
+
const FACEBOOK = UABrowserName.Facebook
|
|
519
|
+
const CHROMIUM_OS = UAOSName.ChromiumOS
|
|
520
|
+
const MAC_OS = UAOSName.MacOS
|
|
409
521
|
|
|
410
522
|
///////////////
|
|
411
523
|
// String maps
|
|
@@ -449,11 +561,11 @@ const regexes: UARegexMaps = {
|
|
|
449
561
|
[
|
|
450
562
|
/\b(?:crmo|crios)\/([\w\.]+)/i, // Chrome for Android/iOS
|
|
451
563
|
],
|
|
452
|
-
[VERSION, [NAME,
|
|
564
|
+
[VERSION, [NAME, CHROME]],
|
|
453
565
|
[
|
|
454
566
|
/edg(?:e|ios|a)?\/([\w\.]+)/i, // Microsoft Edge
|
|
455
567
|
],
|
|
456
|
-
[VERSION, [NAME,
|
|
568
|
+
[VERSION, [NAME, EDGE]],
|
|
457
569
|
[
|
|
458
570
|
// Presto based
|
|
459
571
|
/(opera mini)\/([-\w\.]+)/i, // Opera Mini
|
|
@@ -576,7 +688,7 @@ const regexes: UARegexMaps = {
|
|
|
576
688
|
[
|
|
577
689
|
/samsungbrowser\/([\w\.]+)/i, // Samsung Internet
|
|
578
690
|
],
|
|
579
|
-
[VERSION, [NAME,
|
|
691
|
+
[VERSION, [NAME, UABrowserName.SamsungInternet]],
|
|
580
692
|
[
|
|
581
693
|
/metasr[\/ ]?([\d\.]+)/i, // Sogou Explorer
|
|
582
694
|
],
|
|
@@ -629,11 +741,11 @@ const regexes: UARegexMaps = {
|
|
|
629
741
|
[
|
|
630
742
|
/headlesschrome(?:\/([\w\.]+)| )/i, // Chrome Headless
|
|
631
743
|
],
|
|
632
|
-
[VERSION, [NAME,
|
|
744
|
+
[VERSION, [NAME, UABrowserName.ChromeHeadless]],
|
|
633
745
|
[
|
|
634
746
|
/ wv\).+(chrome)\/([\w\.]+)/i, // Chrome WebView
|
|
635
747
|
],
|
|
636
|
-
[[NAME,
|
|
748
|
+
[[NAME, UABrowserName.ChromeWebView], VERSION],
|
|
637
749
|
[
|
|
638
750
|
/droid.+ version\/([\w\.]+)\b.+(?:mobile safari|safari)/i, // Android Browser
|
|
639
751
|
],
|
|
@@ -645,7 +757,7 @@ const regexes: UARegexMaps = {
|
|
|
645
757
|
[
|
|
646
758
|
/version\/([\w\.\,]+) .*mobile\/\w+ (safari)/i, // Mobile Safari
|
|
647
759
|
],
|
|
648
|
-
[VERSION, [NAME,
|
|
760
|
+
[VERSION, [NAME, UABrowserName.MobileSafari]],
|
|
649
761
|
[
|
|
650
762
|
/version\/([\w(\.|\,)]+) .*(mobile ?safari|safari)/i, // Safari & Safari Mobile
|
|
651
763
|
],
|
|
@@ -1372,7 +1484,7 @@ const regexes: UARegexMaps = {
|
|
|
1372
1484
|
],
|
|
1373
1485
|
[
|
|
1374
1486
|
[VERSION, strMapper, windowsVersionMap],
|
|
1375
|
-
[NAME,
|
|
1487
|
+
[NAME, UAOSName.Windows],
|
|
1376
1488
|
],
|
|
1377
1489
|
[
|
|
1378
1490
|
// iOS/macOS
|
|
@@ -1382,7 +1494,7 @@ const regexes: UARegexMaps = {
|
|
|
1382
1494
|
],
|
|
1383
1495
|
[
|
|
1384
1496
|
[VERSION, /_/g, '.'],
|
|
1385
|
-
[NAME,
|
|
1497
|
+
[NAME, UAOSName.iOS],
|
|
1386
1498
|
],
|
|
1387
1499
|
[
|
|
1388
1500
|
/(mac os x) ?([\w\. ]*)/i,
|
|
@@ -1426,7 +1538,7 @@ const regexes: UARegexMaps = {
|
|
|
1426
1538
|
[
|
|
1427
1539
|
/watch(?: ?os[,\/]|\d,\d\/)([\d\.]+)/i, // watchOS
|
|
1428
1540
|
],
|
|
1429
|
-
[VERSION, [NAME,
|
|
1541
|
+
[VERSION, [NAME, UAOSName.WatchOS]],
|
|
1430
1542
|
[
|
|
1431
1543
|
// Google Chromecast
|
|
1432
1544
|
/crkey\/([\d\.]+)/i, // Google Chromecast
|