@naturalcycles/js-lib 15.85.0 → 15.86.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/src/ua/ua.ts ADDED
@@ -0,0 +1,1585 @@
1
+ // Vendored & modernized from https://github.com/mcollina/my-ua-parser (v2.0.4, commit 0ff5baa),
2
+ // which is an MIT fork of https://github.com/faisalman/ua-parser-js
3
+ // Copyright © 2024 Matteo Collina <hello@matteocollina.com>
4
+ // Copyright © 2012-2025 Faisal Salman <f@faisalman.com>
5
+ // MIT license.
6
+ //
7
+ // Regex data refreshed from ua-parser-js v1.0.41 (`1.0.x` branch, 2025-08-19), which stayed
8
+ // MIT-licensed. Nothing is taken from ua-parser-js v2.x, which is AGPL-3.0-licensed.
9
+ //
10
+ // Detects Browser, Engine, OS, CPU and Device type/model from a User-Agent string.
11
+ // Supports browser & Node.js environments.
12
+
13
+ // oxlint-disable no-useless-escape -- regexes are preserved verbatim from upstream, incl. escapes
14
+
15
+ /// <reference lib="dom" preserve="true" />
16
+
17
+ export type UADeviceType = 'console' | 'mobile' | 'tablet' | 'smarttv' | 'wearable' | 'embedded'
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
+
59
+ export interface UABrowser {
60
+ /**
61
+ * E.g Chrome, Edge, Firefox, Safari, Mobile Safari, Opera, IE, UCBrowser, WeChat, ...
62
+ * Popular values are available as `UABrowserName`.
63
+ */
64
+ name: string | undefined
65
+ version: string | undefined
66
+ /**
67
+ * Major version, e.g. '20' for version '20.0.1090.0'.
68
+ */
69
+ major: string | undefined
70
+ }
71
+
72
+ export interface UACpu {
73
+ /**
74
+ * E.g 68k, amd64, arm, arm64, armhf, avr, ia32, ia64, irix, irix64, mips, mips64,
75
+ * pa-risc, ppc, sparc, sparc64.
76
+ */
77
+ architecture: string | undefined
78
+ }
79
+
80
+ export interface UADevice {
81
+ /**
82
+ * E.g Apple, Samsung, Huawei, Xiaomi, Google, Sony, LG, Microsoft, Amazon, ...
83
+ */
84
+ vendor: string | undefined
85
+ model: string | undefined
86
+ /**
87
+ * Undefined means "desktop-like" (no specific device type detected).
88
+ */
89
+ type: UADeviceType | undefined
90
+ }
91
+
92
+ export interface UAEngine {
93
+ /**
94
+ * E.g Blink, Gecko, WebKit, EdgeHTML, Presto, Trident, ...
95
+ */
96
+ name: string | undefined
97
+ version: string | undefined
98
+ }
99
+
100
+ export interface UAOS {
101
+ /**
102
+ * E.g Windows, iOS, Mac OS, Android, Linux, Chromium OS, ...
103
+ * Popular values are available as `UAOSName`.
104
+ */
105
+ name: string | undefined
106
+ version: string | undefined
107
+ }
108
+
109
+ export interface UAResult {
110
+ ua: string
111
+ browser: UABrowser
112
+ engine: UAEngine
113
+ os: UAOS
114
+ device: UADevice
115
+ cpu: UACpu
116
+ }
117
+
118
+ export type UAMapperFn = (match: string) => string | undefined
119
+ export type UAStrMap = Record<string, string | string[]>
120
+
121
+ /**
122
+ * Describes how a regex capture group is assigned to a result property:
123
+ * - `prop` - assign the raw match
124
+ * - `[prop, value]` - assign a fixed value (ignore the match)
125
+ * - `[prop, fn]` - assign `fn(match)`
126
+ * - `[prop, regex, replacement]` - assign `match.replace(regex, replacement)`
127
+ * - `[prop, fn, map]` - assign `fn(match, map)` (string-map lookup)
128
+ * - `[prop, regex, replacement, fn]` - assign `fn(match.replace(regex, replacement))`
129
+ */
130
+ export type UAAction =
131
+ | string
132
+ | readonly [string, string]
133
+ | readonly [string, UAMapperFn]
134
+ | readonly [string, RegExp, string]
135
+ | readonly [string, (str: string, map: UAStrMap) => string | undefined, UAStrMap]
136
+ | readonly [string, RegExp, string, UAMapperFn]
137
+
138
+ /**
139
+ * Alternating sequence of regex-list and action-list:
140
+ * `[RegExp[], UAAction[], RegExp[], UAAction[], ...]`.
141
+ * The regexes are tried in order; the first match wins and its capture groups
142
+ * are assigned via the following action-list.
143
+ */
144
+ export type UARegexMapEntry = RegExp[] | UAAction[]
145
+
146
+ /**
147
+ * Custom regexes to extend the built-in ones.
148
+ * Extension regexes are tried before the built-in regexes of the same category.
149
+ */
150
+ export interface UAParserExtensions {
151
+ browser?: UARegexMapEntry[]
152
+ cpu?: UARegexMapEntry[]
153
+ device?: UARegexMapEntry[]
154
+ engine?: UARegexMapEntry[]
155
+ os?: UARegexMapEntry[]
156
+ }
157
+
158
+ type UARegexMaps = Required<UAParserExtensions>
159
+
160
+ /**
161
+ * Parses the given User-Agent string into browser, engine, os, device and cpu information.
162
+ *
163
+ * When `ua` is not provided and running in a browser - `navigator.userAgent` is used
164
+ * (with client-hints-based refinements, e.g. Brave and iPadOS detection).
165
+ */
166
+ export function parseUserAgent(userAgent?: string, extensions?: UAParserExtensions): UAResult {
167
+ return new UAParser(userAgent, extensions).getResult()
168
+ }
169
+
170
+ export function uaParser(userAgent?: string, extensions?: UAParserExtensions): UAParser {
171
+ return new UAParser(userAgent, extensions)
172
+ }
173
+
174
+ /**
175
+ * User-Agent parser.
176
+ *
177
+ * Compared to `parseUserAgent()`, the class allows parsing only the needed parts
178
+ * (e.g `getBrowser()` alone is much cheaper than `getResult()`),
179
+ * and reusing the compiled extensions across multiple `setUA()` calls.
180
+ */
181
+ export class UAParser {
182
+ constructor(ua?: string, extensions?: UAParserExtensions) {
183
+ this.nav = typeof globalThis.window !== 'undefined' ? globalThis.navigator : undefined
184
+ this.rgxMap = extensions ? extend(regexes, extensions) : regexes
185
+ const initialUa = ua || this.nav?.userAgent || ''
186
+ // Whether we're parsing the environment's own UA - only then the
187
+ // navigator-based refinements (Brave, client hints, iPadOS) apply
188
+ this.isSelfNav = !!this.nav && this.nav.userAgent === initialUa
189
+ this.setUA(initialUa)
190
+ }
191
+
192
+ private ua = ''
193
+ private readonly nav: UaNavigator | undefined
194
+ private readonly rgxMap: UARegexMaps
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
201
+
202
+ getBrowser(): UABrowser {
203
+ const browser: UABrowser = { name: undefined, version: undefined, major: undefined }
204
+ rgxMapper(browser, this.ua, this.rgxMap.browser)
205
+ browser.major = majorize(browser.version)
206
+ // Brave-specific detection
207
+ if (this.isSelfNav && typeof this.nav?.brave?.isBrave === 'function') {
208
+ browser.name = 'Brave'
209
+ }
210
+ return browser
211
+ }
212
+
213
+ getCPU(): UACpu {
214
+ const cpu: UACpu = { architecture: undefined }
215
+ rgxMapper(cpu, this.ua, this.rgxMap.cpu)
216
+ return cpu
217
+ }
218
+
219
+ getDevice(): UADevice {
220
+ const device: UADevice = { vendor: undefined, model: undefined, type: undefined }
221
+ rgxMapper(device, this.ua, this.rgxMap.device)
222
+ if (this.isSelfNav && !device.type && this.nav?.userAgentData?.mobile) {
223
+ device.type = MOBILE
224
+ }
225
+ // iPadOS-specific detection: identified as Mac, but has some iOS-only properties
226
+ if (
227
+ this.isSelfNav &&
228
+ device.model === 'Macintosh' &&
229
+ typeof this.nav?.standalone !== 'undefined' &&
230
+ this.nav.maxTouchPoints > 2
231
+ ) {
232
+ device.model = 'iPad'
233
+ device.type = TABLET
234
+ }
235
+ return device
236
+ }
237
+
238
+ getEngine(): UAEngine {
239
+ const engine: UAEngine = { name: undefined, version: undefined }
240
+ rgxMapper(engine, this.ua, this.rgxMap.engine)
241
+ return engine
242
+ }
243
+
244
+ getOS(): UAOS {
245
+ const os: UAOS = { name: undefined, version: undefined }
246
+ rgxMapper(os, this.ua, this.rgxMap.os)
247
+ const platform = this.nav?.userAgentData?.platform
248
+ if (this.isSelfNav && !os.name && platform && platform !== 'Unknown') {
249
+ os.name = platform.replace(/chrome os/i, CHROMIUM_OS).replace(/macos/i, MAC_OS) // backward compatibility
250
+ }
251
+ return os
252
+ }
253
+
254
+ getResult(): UAResult {
255
+ return {
256
+ ua: this.getUA(),
257
+ browser: this.getBrowser(),
258
+ engine: this.getEngine(),
259
+ os: this.getOS(),
260
+ device: this.getDevice(),
261
+ cpu: this.getCPU(),
262
+ }
263
+ }
264
+
265
+ getUA(): string {
266
+ return this.ua
267
+ }
268
+
269
+ setUA(ua: string | undefined): this {
270
+ ua ||= ''
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
273
+ return this
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
+ }
342
+ }
343
+
344
+ /**
345
+ * Non-standard/experimental Navigator properties used for UA refinement.
346
+ */
347
+ interface UaNavigator extends Navigator {
348
+ userAgentData?: {
349
+ mobile?: boolean
350
+ platform?: string
351
+ }
352
+ brave?: {
353
+ isBrave?: unknown
354
+ }
355
+ standalone?: boolean
356
+ }
357
+
358
+ ///////////
359
+ // Helpers
360
+ //////////
361
+
362
+ function extend(defaults: UARegexMaps, extensions: UAParserExtensions): UARegexMaps {
363
+ return {
364
+ browser: mergeMapEntries(defaults.browser, extensions.browser),
365
+ cpu: mergeMapEntries(defaults.cpu, extensions.cpu),
366
+ device: mergeMapEntries(defaults.device, extensions.device),
367
+ engine: mergeMapEntries(defaults.engine, extensions.engine),
368
+ os: mergeMapEntries(defaults.os, extensions.os),
369
+ }
370
+ }
371
+
372
+ function mergeMapEntries(
373
+ defaults: UARegexMapEntry[],
374
+ extension?: UARegexMapEntry[],
375
+ ): UARegexMapEntry[] {
376
+ // extension must be a valid alternating [regexes, actions, ...] sequence, otherwise ignored
377
+ return extension?.length && extension.length % 2 === 0 ? extension.concat(defaults) : defaults
378
+ }
379
+
380
+ function rgxMapper(
381
+ target: UABrowser | UACpu | UADevice | UAEngine | UAOS,
382
+ ua: string,
383
+ arrays: UARegexMapEntry[],
384
+ ): void {
385
+ const t = target as unknown as Record<string, string | undefined>
386
+ let matches: RegExpExecArray | null = null
387
+
388
+ // loop through all regexes maps
389
+ for (let i = 0; i < arrays.length && !matches; i += 2) {
390
+ const regexList = arrays[i] as RegExp[] // even sequence (0,2,4,..)
391
+ const actions = arrays[i + 1] as UAAction[] // odd sequence (1,3,5,..)
392
+ let k = 0
393
+
394
+ // try matching uastring with regexes
395
+ for (let j = 0; j < regexList.length && !matches; j++) {
396
+ const regex = regexList[j]
397
+ if (!regex) break
398
+ matches = regex.exec(ua)
399
+
400
+ if (matches) {
401
+ for (const q of actions) {
402
+ const match = matches[++k]
403
+ if (typeof q === 'string') {
404
+ // assign the raw match
405
+ t[q] = match || undefined
406
+ } else if (q.length === 2) {
407
+ if (typeof q[1] === 'function') {
408
+ // assign modified match
409
+ t[q[0]] = match === undefined ? undefined : q[1](match)
410
+ } else {
411
+ // assign given value, ignore regex match
412
+ t[q[0]] = q[1]
413
+ }
414
+ } else if (q.length === 3) {
415
+ if (typeof q[1] === 'function') {
416
+ // call function (usually string mapper)
417
+ // typeof check on q[1] doesn't discriminate the tuple union, hence the cast
418
+ const [prop, fn, map] = q as [
419
+ string,
420
+ (str: string, map: UAStrMap) => string | undefined,
421
+ UAStrMap,
422
+ ]
423
+ t[prop] = match ? fn(match, map) : undefined
424
+ } else {
425
+ // sanitize match using given regex
426
+ const [prop, rgx, replacement] = q as [string, RegExp, string]
427
+ t[prop] = match ? match.replace(rgx, replacement) : undefined
428
+ }
429
+ } else if (q.length === 4) {
430
+ t[q[0]] = match ? q[3](match.replace(q[1], q[2])) : undefined
431
+ }
432
+ }
433
+ }
434
+ }
435
+ }
436
+ }
437
+
438
+ function strMapper(str: string, map: UAStrMap): string | undefined {
439
+ for (const key of Object.keys(map)) {
440
+ const value = map[key]!
441
+ // check if current value is array
442
+ if (Array.isArray(value)) {
443
+ for (const v of value) {
444
+ if (has(v, str)) {
445
+ return key === UNKNOWN ? undefined : key
446
+ }
447
+ }
448
+ } else if (has(value, str)) {
449
+ return key === UNKNOWN ? undefined : key
450
+ }
451
+ }
452
+ // '*' is a catch-all fallback value (always a string in the built-in maps)
453
+ const fallback = map['*']
454
+ return typeof fallback === 'string' ? fallback : str
455
+ }
456
+
457
+ function majorize(version: string | undefined): string | undefined {
458
+ return typeof version === 'string' ? version.replaceAll(/[^\d\.]/g, '').split('.')[0] : undefined
459
+ }
460
+
461
+ function has(str1: string, str2: string): boolean {
462
+ return lowerize(str2).includes(lowerize(str1))
463
+ }
464
+
465
+ function lowerize(str: string): string {
466
+ return str.toLowerCase()
467
+ }
468
+
469
+ function trimStr(str: string): string {
470
+ return str.replace(/^\s\s*/, '')
471
+ }
472
+
473
+ //////////////
474
+ // Constants
475
+ /////////////
476
+
477
+ const UNKNOWN = '?'
478
+ const UA_MAX_LENGTH = 500
479
+
480
+ const MODEL = 'model'
481
+ const NAME = 'name'
482
+ const TYPE = 'type'
483
+ const VENDOR = 'vendor'
484
+ const VERSION = 'version'
485
+ const ARCHITECTURE = 'architecture'
486
+ const CONSOLE = 'console'
487
+ const MOBILE = 'mobile'
488
+ const TABLET = 'tablet'
489
+ const SMARTTV = 'smarttv'
490
+ const WEARABLE = 'wearable'
491
+ const EMBEDDED = 'embedded'
492
+
493
+ const AMAZON = 'Amazon'
494
+ const APPLE = 'Apple'
495
+ const ASUS = 'ASUS'
496
+ const BLACKBERRY = 'BlackBerry'
497
+ const BROWSER = 'Browser'
498
+ const SUFFIX_BROWSER = ' Browser'
499
+ const CHROME = UABrowserName.Chrome
500
+ const EDGE = UABrowserName.Edge
501
+ const FIREFOX = UABrowserName.Firefox
502
+ const GOOGLE = 'Google'
503
+ const HONOR = 'Honor'
504
+ const HUAWEI = 'Huawei'
505
+ const LENOVO = 'Lenovo'
506
+ const LG = 'LG'
507
+ const MICROSOFT = 'Microsoft'
508
+ const MOTOROLA = 'Motorola'
509
+ const NVIDIA = 'Nvidia'
510
+ const ONEPLUS = 'OnePlus'
511
+ const OPERA = UABrowserName.Opera
512
+ const OPPO = 'OPPO'
513
+ const SAMSUNG = 'Samsung'
514
+ const SHARP = 'Sharp'
515
+ const SONY = 'Sony'
516
+ const XIAOMI = 'Xiaomi'
517
+ const ZEBRA = 'Zebra'
518
+ const FACEBOOK = UABrowserName.Facebook
519
+ const CHROMIUM_OS = UAOSName.ChromiumOS
520
+ const MAC_OS = UAOSName.MacOS
521
+
522
+ ///////////////
523
+ // String maps
524
+ //////////////
525
+
526
+ // Safari < 3.0
527
+ const oldSafariMap: UAStrMap = {
528
+ '1.0': '/8',
529
+ '1.2': '/1',
530
+ '1.3': '/3',
531
+ '2.0': '/412',
532
+ '2.0.2': '/416',
533
+ '2.0.3': '/417',
534
+ '2.0.4': '/419',
535
+ '?': '/',
536
+ }
537
+
538
+ const windowsVersionMap: UAStrMap = {
539
+ ME: '4.90',
540
+ 'NT 3.11': 'NT3.51',
541
+ 'NT 4.0': 'NT4.0',
542
+ 2000: 'NT 5.0',
543
+ XP: ['NT 5.1', 'NT 5.2'],
544
+ Vista: 'NT 6.0',
545
+ 7: 'NT 6.1',
546
+ 8: 'NT 6.2',
547
+ 8.1: 'NT 6.3',
548
+ 10: ['NT 6.4', 'NT 10.0'],
549
+ RT: 'ARM',
550
+ }
551
+
552
+ //////////////
553
+ // Regex map
554
+ /////////////
555
+
556
+ // The regexes below are preserved verbatim from upstream ua-parser-js 1.0.41 (MIT) -
557
+ // do not "fix" escapes or simplify them, to keep them diffable against upstream.
558
+
559
+ const regexes: UARegexMaps = {
560
+ browser: [
561
+ [
562
+ /\b(?:crmo|crios)\/([\w\.]+)/i, // Chrome for Android/iOS
563
+ ],
564
+ [VERSION, [NAME, CHROME]],
565
+ [
566
+ /edg(?:e|ios|a)?\/([\w\.]+)/i, // Microsoft Edge
567
+ ],
568
+ [VERSION, [NAME, EDGE]],
569
+ [
570
+ // Presto based
571
+ /(opera mini)\/([-\w\.]+)/i, // Opera Mini
572
+ /(opera [mobiletab]{3,6})\b.+version\/([-\w\.]+)/i, // Opera Mobi/Tablet
573
+ /(opera)(?:.+version\/|[\/ ]+)([\w\.]+)/i, // Opera
574
+ ],
575
+ [NAME, VERSION],
576
+ [
577
+ /opios[\/ ]+([\w\.]+)/i, // Opera mini on iphone >= 8.0
578
+ ],
579
+ [VERSION, [NAME, OPERA + ' Mini']],
580
+ [
581
+ /\bop(?:rg)?x\/([\w\.]+)/i, // Opera GX
582
+ ],
583
+ [VERSION, [NAME, OPERA + ' GX']],
584
+ [
585
+ /\bopr\/([\w\.]+)/i, // Opera Webkit
586
+ ],
587
+ [VERSION, [NAME, OPERA]],
588
+ [
589
+ // Mixed
590
+ /\bb[ai]*d(?:uhd|[ub]*[aekoprswx]{5,6})[\/ ]?([\w\.]+)/i, // Baidu
591
+ ],
592
+ [VERSION, [NAME, 'Baidu']],
593
+ [
594
+ /\b(?:mxbrowser|mxios|myie2)\/?([-\w\.]*)\b/i, // Maxthon
595
+ ],
596
+ [VERSION, [NAME, 'Maxthon']],
597
+ [
598
+ /(kindle)\/([\w\.]+)/i, // Kindle
599
+ /(lunascape|maxthon|netfront|jasmine|blazer|sleipnir)[\/ ]?([\w\.]*)/i,
600
+ // Lunascape/Maxthon/Netfront/Jasmine/Blazer/Sleipnir
601
+ // Trident based
602
+ /(avant|iemobile|slim(?:browser|boat|jet))[\/ ]?([\d\.]*)/i, // Avant/IEMobile/SlimBrowser/SlimBoat/Slimjet
603
+ /(?:ms|\()(ie) ([\w\.]+)/i, // Internet Explorer
604
+
605
+ // Blink/Webkit/KHTML based // Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron/Iridium/PhantomJS/Bowser/QupZilla/Falkon
606
+ /(flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi|iridium|phantomjs|bowser|qupzilla|falkon|rekonq|puffin|brave|whale(?!.+naver)|qqbrowserlite|duckduckgo|klar|helio|(?=comodo_)?dragon)\/([-\w\.]+)/i,
607
+ // Rekonq/Puffin/Brave/Whale/QQBrowserLite/QQ//Vivaldi/DuckDuckGo/Klar/Helio/Dragon
608
+ /(heytap|ovi|115)browser\/([\d\.]+)/i, // HeyTap/Ovi/115
609
+ /(weibo)__([\d\.]+)/i, // Weibo
610
+ ],
611
+ [NAME, VERSION],
612
+ [
613
+ /quark(?:pc)?\/([-\w\.]+)/i, // Quark
614
+ ],
615
+ [VERSION, [NAME, 'Quark']],
616
+ [
617
+ /\bddg\/([\w\.]+)/i, // DuckDuckGo
618
+ ],
619
+ [VERSION, [NAME, 'DuckDuckGo']],
620
+ [
621
+ /(?:\buc? ?browser|(?:juc.+)ucweb)[\/ ]?([\w\.]+)/i, // UCBrowser
622
+ ],
623
+ [VERSION, [NAME, 'UC' + BROWSER]],
624
+ [
625
+ /microm.+\bqbcore\/([\w\.]+)/i, // WeChat Desktop for Windows Built-in Browser
626
+ /\bqbcore\/([\w\.]+).+microm/i,
627
+ /micromessenger\/([\w\.]+)/i, // WeChat
628
+ ],
629
+ [VERSION, [NAME, 'WeChat']],
630
+ [
631
+ /konqueror\/([\w\.]+)/i, // Konqueror
632
+ ],
633
+ [VERSION, [NAME, 'Konqueror']],
634
+ [
635
+ /trident.+rv[: ]([\w\.]{1,9})\b.+like gecko/i, // IE11
636
+ ],
637
+ [VERSION, [NAME, 'IE']],
638
+ [
639
+ /ya(?:search)?browser\/([\w\.]+)/i, // Yandex
640
+ ],
641
+ [VERSION, [NAME, 'Yandex']],
642
+ [
643
+ /slbrowser\/([\w\.]+)/i, // Smart Lenovo Browser
644
+ ],
645
+ [VERSION, [NAME, 'Smart Lenovo ' + BROWSER]],
646
+ [
647
+ /(avast|avg)\/([\w\.]+)/i, // Avast/AVG Secure Browser
648
+ ],
649
+ [[NAME, /(.+)/, '$1 Secure ' + BROWSER], VERSION],
650
+ [
651
+ /\bfocus\/([\w\.]+)/i, // Firefox Focus
652
+ ],
653
+ [VERSION, [NAME, FIREFOX + ' Focus']],
654
+ [
655
+ /\bopt\/([\w\.]+)/i, // Opera Touch
656
+ ],
657
+ [VERSION, [NAME, OPERA + ' Touch']],
658
+ [
659
+ /coc_coc\w+\/([\w\.]+)/i, // Coc Coc Browser
660
+ ],
661
+ [VERSION, [NAME, 'Coc Coc']],
662
+ [
663
+ /dolfin\/([\w\.]+)/i, // Dolphin
664
+ ],
665
+ [VERSION, [NAME, 'Dolphin']],
666
+ [
667
+ /coast\/([\w\.]+)/i, // Opera Coast
668
+ ],
669
+ [VERSION, [NAME, OPERA + ' Coast']],
670
+ [
671
+ /miuibrowser\/([\w\.]+)/i, // MIUI Browser
672
+ ],
673
+ [VERSION, [NAME, 'MIUI' + SUFFIX_BROWSER]],
674
+ [
675
+ /fxios\/([\w\.-]+)/i, // Firefox for iOS
676
+ ],
677
+ [VERSION, [NAME, FIREFOX]],
678
+ [
679
+ /\bqihoobrowser\/?([\w\.]*)/i, // 360
680
+ ],
681
+ [VERSION, [NAME, '360']],
682
+ [
683
+ /\b(qq)\/([\w\.]+)/i, // QQ
684
+ ],
685
+ [[NAME, /(.+)/, '$1Browser'], VERSION],
686
+ [/(oculus|sailfish|huawei|vivo|pico)browser\/([\w\.]+)/i],
687
+ [[NAME, /(.+)/, '$1' + SUFFIX_BROWSER], VERSION], // Oculus/Sailfish/HuaweiBrowser/VivoBrowser/PicoBrowser
688
+ [
689
+ /samsungbrowser\/([\w\.]+)/i, // Samsung Internet
690
+ ],
691
+ [VERSION, [NAME, UABrowserName.SamsungInternet]],
692
+ [
693
+ /metasr[\/ ]?([\d\.]+)/i, // Sogou Explorer
694
+ ],
695
+ [VERSION, [NAME, 'Sogou Explorer']],
696
+ [
697
+ /(sogou)mo\w+\/([\d\.]+)/i, // Sogou Mobile
698
+ ],
699
+ [[NAME, 'Sogou Mobile'], VERSION],
700
+ [
701
+ /(electron)\/([\w\.]+) safari/i, // Electron-based App
702
+ /(tesla)(?: qtcarbrowser|\/(20\d\d\.[-\w\.]+))/i, // Tesla
703
+ /m?(qqbrowser|2345(?=browser|chrome|explorer))\w*[\/ ]?v?([\w\.]+)/i, // QQ/2345
704
+ ],
705
+ [NAME, VERSION],
706
+ [
707
+ /(lbbrowser|rekonq)/i, // LieBao Browser/Rekonq
708
+ /\[(linkedin)app\]/i, // LinkedIn App for iOS & Android
709
+ ],
710
+ [NAME],
711
+ [
712
+ /ome\/([\w\.]+) \w* ?(iron) saf/i, // Iron
713
+ /ome\/([\w\.]+).+qihu (360)[es]e/i, // 360
714
+ ],
715
+ [VERSION, NAME],
716
+ [
717
+ // WebView
718
+ /((?:fban\/fbios|fb_iab\/fb4a)(?!.+fbav)|;fbav\/([\w\.]+);)/i, // Facebook App for iOS & Android
719
+ ],
720
+ [[NAME, FACEBOOK], VERSION],
721
+ [
722
+ /(Klarna)\/([\w\.]+)/i, // Klarna Shopping Browser for iOS & Android
723
+ /(kakao(?:talk|story))[\/ ]([\w\.]+)/i, // Kakao App
724
+ /(naver)\(.*?(\d+\.[\w\.]+).*\)/i, // Naver InApp
725
+ /(daum)apps[\/ ]([\w\.]+)/i, // Daum App
726
+ /safari (line)\/([\w\.]+)/i, // Line App for iOS
727
+ /\b(line)\/([\w\.]+)\/iab/i, // Line App for Android
728
+ /(alipay)client\/([\w\.]+)/i, // Alipay
729
+ /(twitter)(?:and| f.+e\/([\w\.]+))/i, // Twitter
730
+ /(chromium|instagram|snapchat)[\/ ]([-\w\.]+)/i, // Chromium/Instagram/Snapchat
731
+ ],
732
+ [NAME, VERSION],
733
+ [
734
+ /\bgsa\/([\w\.]+) .*safari\//i, // Google Search Appliance on iOS
735
+ ],
736
+ [VERSION, [NAME, 'GSA']],
737
+ [
738
+ /musical_ly(?:.+app_?version\/|_)([\w\.]+)/i, // TikTok
739
+ ],
740
+ [VERSION, [NAME, 'TikTok']],
741
+ [
742
+ /headlesschrome(?:\/([\w\.]+)| )/i, // Chrome Headless
743
+ ],
744
+ [VERSION, [NAME, UABrowserName.ChromeHeadless]],
745
+ [
746
+ / wv\).+(chrome)\/([\w\.]+)/i, // Chrome WebView
747
+ ],
748
+ [[NAME, UABrowserName.ChromeWebView], VERSION],
749
+ [
750
+ /droid.+ version\/([\w\.]+)\b.+(?:mobile safari|safari)/i, // Android Browser
751
+ ],
752
+ [VERSION, [NAME, 'Android ' + BROWSER]],
753
+ [
754
+ /(chrome|omniweb|arora|[tizenoka]{5} ?browser)\/v?([\w\.]+)/i, // Chrome/OmniWeb/Arora/Tizen/Nokia
755
+ ],
756
+ [NAME, VERSION],
757
+ [
758
+ /version\/([\w\.\,]+) .*mobile\/\w+ (safari)/i, // Mobile Safari
759
+ ],
760
+ [VERSION, [NAME, UABrowserName.MobileSafari]],
761
+ [
762
+ /version\/([\w(\.|\,)]+) .*(mobile ?safari|safari)/i, // Safari & Safari Mobile
763
+ ],
764
+ [VERSION, NAME],
765
+ [
766
+ /webkit.+?(mobile ?safari|safari)(\/[\w\.]+)/i, // Safari < 3.0
767
+ ],
768
+ [NAME, [VERSION, strMapper, oldSafariMap]],
769
+ [/(webkit|khtml)\/([\w\.]+)/i],
770
+ [NAME, VERSION],
771
+ [
772
+ // Gecko based
773
+ /(navigator|netscape\d?)\/([-\w\.]+)/i, // Netscape
774
+ ],
775
+ [[NAME, 'Netscape'], VERSION],
776
+ [
777
+ /(wolvic|librewolf)\/([\w\.]+)/i, // Wolvic/LibreWolf
778
+ ],
779
+ [NAME, VERSION],
780
+ [
781
+ /mobile vr; rv:([\w\.]+)\).+firefox/i, // Firefox Reality
782
+ ],
783
+ [VERSION, [NAME, FIREFOX + ' Reality']],
784
+ [
785
+ /ekiohf.+(flow)\/([\w\.]+)/i, // Flow
786
+ /(swiftfox)/i, // Swiftfox
787
+ /(icedragon|iceweasel|camino|chimera|fennec|maemo browser|minimo|conkeror)[\/ ]?([\w\.\+]+)/i,
788
+ // IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror
789
+ /(seamonkey|k-meleon|icecat|iceape|firebird|phoenix|palemoon|basilisk|waterfox)\/([-\w\.]+)$/i,
790
+ // Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix
791
+ /(firefox)\/([\w\.]+)/i, // Other Firefox-based
792
+ /(mozilla)\/([\w\.]+) .+rv\:.+gecko\/\d+/i, // Mozilla
793
+ ],
794
+ [NAME, VERSION],
795
+ [
796
+ // Other
797
+ /(amaya|dillo|doris|icab|ladybird|lynx|mosaic|netsurf|obigo|polaris|w3m|(?:go|ice|up)[\. ]?browser)[-\/ ]?v?([\w\.]+)/i,
798
+ // Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf/Obigo/Mosaic/Go/ICE/UP.Browser/Ladybird
799
+ /\b(links) \(([\w\.]+)/i, // Links
800
+ ],
801
+ [NAME, [VERSION, /_/g, '.']],
802
+ [
803
+ /(cobalt)\/([\w\.]+)/i, // Cobalt
804
+ ],
805
+ [NAME, [VERSION, /master.|lts./, '']],
806
+ ],
807
+
808
+ cpu: [
809
+ [
810
+ /\b((amd|x|x86[-_]?|wow|win)64)\b/i, // AMD64 (x64)
811
+ ],
812
+ [[ARCHITECTURE, 'amd64']],
813
+ [
814
+ /(ia32(?=;))/i, // IA32 (quicktime)
815
+ /\b((i[346]|x)86)(pc)?\b/i, // IA32 (x86)
816
+ ],
817
+ [[ARCHITECTURE, 'ia32']],
818
+ [
819
+ /\b(aarch64|arm(v?[89]e?l?|_?64))\b/i, // ARM64
820
+ ],
821
+ [[ARCHITECTURE, 'arm64']],
822
+ [
823
+ /\b(arm(v[67])?ht?n?[fl]p?)\b/i, // ARMHF
824
+ ],
825
+ [[ARCHITECTURE, 'armhf']],
826
+ [
827
+ // PocketPC mistakenly identified as PowerPC
828
+ /( (ce|mobile); ppc;|\/[\w\.]+arm\b)/i,
829
+ ],
830
+ [[ARCHITECTURE, 'arm']],
831
+ [
832
+ /((ppc|powerpc)(64)?)( mac|;|\))/i, // PowerPC
833
+ ],
834
+ [[ARCHITECTURE, /ower/, '', lowerize]],
835
+ [
836
+ / sun4\w[;\)]/i, // SPARC
837
+ ],
838
+ [[ARCHITECTURE, 'sparc']],
839
+ [
840
+ /\b(avr32|ia64(?=;)|68k(?=\))|\barm(?=v([1-7]|[5-7]1)l?|;|eabi)|(irix|mips|sparc)(64)?\b|pa-risc)/i,
841
+ // IA64, 68K, ARM/64, AVR/32, IRIX/64, MIPS/64, SPARC/64, PA-RISC
842
+ ],
843
+ [[ARCHITECTURE, lowerize]],
844
+ ],
845
+
846
+ device: [
847
+ [
848
+ //////////////////////////
849
+ // MOBILES & TABLETS
850
+ /////////////////////////
851
+
852
+ // Samsung
853
+ /\b(sch-i[89]0\d|shw-m380s|sm-[ptx]\w{2,4}|gt-[pn]\d{2,4}|sgh-t8[56]9|nexus 10)/i,
854
+ ],
855
+ [MODEL, [VENDOR, SAMSUNG], [TYPE, TABLET]],
856
+ [
857
+ /\b((?:s[cgp]h|gt|sm)-(?![lr])\w+|sc[g-]?[\d]+a?|galaxy nexus)/i,
858
+ /samsung[- ]((?!sm-[lr])[-\w]+)/i,
859
+ /sec-(sgh\w+)/i,
860
+ ],
861
+ [MODEL, [VENDOR, SAMSUNG], [TYPE, MOBILE]],
862
+ [
863
+ // Apple
864
+ /(?:\/|\()(ip(?:hone|od)[\w, ]*)(?:\/|;)/i, // iPod/iPhone
865
+ ],
866
+ [MODEL, [VENDOR, APPLE], [TYPE, MOBILE]],
867
+ [
868
+ /\((ipad);[-\w\),; ]+apple/i, // iPad
869
+ /applecoremedia\/[\w\.]+ \((ipad)/i,
870
+ /\b(ipad)\d\d?,\d\d?[;\]].+ios/i,
871
+ ],
872
+ [MODEL, [VENDOR, APPLE], [TYPE, TABLET]],
873
+ [/(macintosh);/i],
874
+ [MODEL, [VENDOR, APPLE]],
875
+ [
876
+ // Sharp
877
+ /\b(sh-?[altvz]?\d\d[a-ekm]?)/i,
878
+ ],
879
+ [MODEL, [VENDOR, SHARP], [TYPE, MOBILE]],
880
+ [
881
+ // Honor
882
+ /\b((?:brt|eln|hey2?|gdi|jdn)-a?[lnw]09|(?:ag[rm]3?|jdn2|kob2)-a?[lw]0[09]hn)(?: bui|\)|;)/i,
883
+ ],
884
+ [MODEL, [VENDOR, HONOR], [TYPE, TABLET]],
885
+ [/honor([-\w ]+)[;\)]/i],
886
+ [MODEL, [VENDOR, HONOR], [TYPE, MOBILE]],
887
+ [
888
+ // Huawei
889
+ /\b((?:ag[rs][2356]?k?|bah[234]?|bg[2o]|bt[kv]|cmr|cpn|db[ry]2?|jdn2|got|kob2?k?|mon|pce|scm|sht?|[tw]gr|vrd)-[ad]?[lw][0125][09]b?|605hw|bg2-u03|(?:gem|fdr|m2|ple|t1)-[7a]0[1-4][lu]|t1-a2[13][lw]|mediapad[\w\. ]*(?= bui|\)))\b(?!.+d\/s)/i,
890
+ ],
891
+ [MODEL, [VENDOR, HUAWEI], [TYPE, TABLET]],
892
+ [
893
+ /(?:huawei)([-\w ]+)[;\)]/i,
894
+ /\b(nexus 6p|\w{2,4}e?-[atu]?[ln][\dx][012359c][adn]?)\b(?!.+d\/s)/i,
895
+ ],
896
+ [MODEL, [VENDOR, HUAWEI], [TYPE, MOBILE]],
897
+ [
898
+ // Xiaomi
899
+ /oid[^\)]+; (2[\dbc]{4}(182|283|rp\w{2})[cgl]|m2105k81a?c)(?: bui|\))/i,
900
+ /\b((?:red)?mi[-_ ]?pad[\w- ]*)(?: bui|\))/i, // Mi Pad tablets
901
+ ],
902
+ [
903
+ [MODEL, /_/g, ' '],
904
+ [VENDOR, XIAOMI],
905
+ [TYPE, TABLET],
906
+ ],
907
+ [
908
+ /\b(poco[\w ]+|m2\d{3}j\d\d[a-z]{2})(?: bui|\))/i, // Xiaomi POCO
909
+ /\b; (\w+) build\/hm\1/i, // Xiaomi Hongmi 'numeric' models
910
+ /\b(hm[-_ ]?note?[_ ]?(?:\d\w)?) bui/i, // Xiaomi Hongmi
911
+ /\b(redmi[\-_ ]?(?:note|k)?[\w_ ]+)(?: bui|\))/i, // Xiaomi Redmi
912
+ /oid[^\)]+; (m?[12][0-389][01]\w{3,6}[c-y])( bui|; wv|\))/i, // Xiaomi Redmi 'numeric' models
913
+ /\b(mi[-_ ]?(?:a\d|one|one[_ ]plus|note lte|max|cc)?[_ ]?(?:\d?\w?)[_ ]?(?:plus|se|lite|pro)?)(?: bui|\))/i, // Xiaomi Mi
914
+ / ([\w ]+) miui\/v?\d/i,
915
+ ],
916
+ [
917
+ [MODEL, /_/g, ' '],
918
+ [VENDOR, XIAOMI],
919
+ [TYPE, MOBILE],
920
+ ],
921
+ [
922
+ // OPPO
923
+ /; (\w+) bui.+ oppo/i,
924
+ /\b(cph[12]\d{3}|p(?:af|c[al]|d\w|e[ar])[mt]\d0|x9007|a101op)\b/i,
925
+ ],
926
+ [MODEL, [VENDOR, OPPO], [TYPE, MOBILE]],
927
+ [/\b(opd2(\d{3}a?))(?: bui|\))/i],
928
+ [MODEL, [VENDOR, strMapper, { OnePlus: ['304', '403', '203'], '*': OPPO }], [TYPE, TABLET]],
929
+ [
930
+ // Vivo
931
+ /vivo (\w+)(?: bui|\))/i,
932
+ /\b(v[12]\d{3}\w?[at])(?: bui|;)/i,
933
+ ],
934
+ [MODEL, [VENDOR, 'Vivo'], [TYPE, MOBILE]],
935
+ [
936
+ // Realme
937
+ /\b(rmx[1-3]\d{3})(?: bui|;|\))/i,
938
+ ],
939
+ [MODEL, [VENDOR, 'Realme'], [TYPE, MOBILE]],
940
+ [
941
+ // Motorola
942
+ /\b(milestone|droid(?:[2-4x]| (?:bionic|x2|pro|razr))?:?( 4g)?)\b[\w ]+build\//i,
943
+ /\bmot(?:orola)?[- ](\w*)/i,
944
+ /((?:moto(?! 360)[\w\(\) ]+|xt\d{3,4}|nexus 6)(?= bui|\)))/i,
945
+ ],
946
+ [MODEL, [VENDOR, MOTOROLA], [TYPE, MOBILE]],
947
+ [/\b(mz60\d|xoom[2 ]{0,2}) build\//i],
948
+ [MODEL, [VENDOR, MOTOROLA], [TYPE, TABLET]],
949
+ [
950
+ // LG
951
+ /((?=lg)?[vl]k\-?\d{3}) bui| 3\.[-\w; ]{10}lg?-([06cv9]{3,4})/i,
952
+ ],
953
+ [MODEL, [VENDOR, LG], [TYPE, TABLET]],
954
+ [
955
+ /(lm(?:-?f100[nv]?|-[\w\.]+)(?= bui|\))|nexus [45])/i,
956
+ /\blg[-e;\/ ]+((?!browser|netcast|android tv|watch)\w+)/i,
957
+ /\blg-?([\d\w]+) bui/i,
958
+ ],
959
+ [MODEL, [VENDOR, LG], [TYPE, MOBILE]],
960
+ [
961
+ // Lenovo
962
+ /(ideatab[-\w ]+|602lv|d-42a|a101lv|a2109a|a3500-hv|s[56]000|pb-6505[my]|tb-?x?\d{3,4}(?:f[cu]|xu|[av])|yt\d?-[jx]?\d+[lfmx])( bui|;|\)|\/)/i,
963
+ /lenovo ?(b[68]0[08]0-?[hf]?|tab(?:[\w- ]+?)|tb[\w-]{6,7})( bui|;|\)|\/)/i,
964
+ ],
965
+ [MODEL, [VENDOR, LENOVO], [TYPE, TABLET]],
966
+ [
967
+ // Nokia
968
+ /(nokia) (t[12][01])/i,
969
+ ],
970
+ [VENDOR, MODEL, [TYPE, TABLET]],
971
+ [/(?:maemo|nokia).*(n900|lumia \d+|rm-\d+)/i, /nokia[-_ ]?(([-\w\. ]*))/i],
972
+ [
973
+ [MODEL, /_/g, ' '],
974
+ [TYPE, MOBILE],
975
+ [VENDOR, 'Nokia'],
976
+ ],
977
+ [
978
+ // Google
979
+ /(pixel (c|tablet))\b/i, // Google Pixel C/Tablet
980
+ ],
981
+ [MODEL, [VENDOR, GOOGLE], [TYPE, TABLET]],
982
+ [
983
+ /droid.+; (pixel[\daxl ]{0,6})(?: bui|\))/i, // Google Pixel
984
+ ],
985
+ [MODEL, [VENDOR, GOOGLE], [TYPE, MOBILE]],
986
+ [
987
+ // Sony
988
+ /droid.+; (a?\d[0-2]{2}so|[c-g]\d{4}|so[-gl]\w+|xq-a\w[4-7][12])(?= bui|\).+chrome\/(?![1-6]{0,1}\d\.))/i,
989
+ ],
990
+ [MODEL, [VENDOR, SONY], [TYPE, MOBILE]],
991
+ [/sony tablet [ps]/i, /\b(?:sony)?sgp\w+(?: bui|\))/i],
992
+ [
993
+ [MODEL, 'Xperia Tablet'],
994
+ [VENDOR, SONY],
995
+ [TYPE, TABLET],
996
+ ],
997
+ [
998
+ // OnePlus
999
+ / (kb2005|in20[12]5|be20[12][59])\b/i,
1000
+ /(?:one)?(?:plus)? (a\d0\d\d)(?: b|\))/i,
1001
+ ],
1002
+ [MODEL, [VENDOR, ONEPLUS], [TYPE, MOBILE]],
1003
+ [
1004
+ // Amazon
1005
+ /(alexa)webm/i,
1006
+ /(kf[a-z]{2}wi|aeo(?!bc)\w\w)( bui|\))/i, // Kindle Fire without Silk / Echo Show
1007
+ /(kf[a-z]+)( bui|\)).+silk\//i, // Kindle Fire HD
1008
+ ],
1009
+ [MODEL, [VENDOR, AMAZON], [TYPE, TABLET]],
1010
+ [
1011
+ /((?:sd|kf)[0349hijorstuw]+)( bui|\)).+silk\//i, // Fire Phone
1012
+ ],
1013
+ [
1014
+ [MODEL, /(.+)/g, 'Fire Phone $1'],
1015
+ [VENDOR, AMAZON],
1016
+ [TYPE, MOBILE],
1017
+ ],
1018
+ [
1019
+ // BlackBerry
1020
+ /(playbook);[-\w\),; ]+(rim)/i, // BlackBerry PlayBook
1021
+ ],
1022
+ [MODEL, VENDOR, [TYPE, TABLET]],
1023
+ [
1024
+ /\b((?:bb[a-f]|st[hv])100-\d)/i,
1025
+ /\(bb10; (\w+)/i, // BlackBerry 10
1026
+ ],
1027
+ [MODEL, [VENDOR, BLACKBERRY], [TYPE, MOBILE]],
1028
+ [
1029
+ // Asus
1030
+ /(?:\b|asus_)(transfo[prime ]{4,10} \w+|eeepc|slider \w+|nexus 7|padfone|p00[cj])/i,
1031
+ ],
1032
+ [MODEL, [VENDOR, ASUS], [TYPE, TABLET]],
1033
+ [/ (z[bes]6[027][012][km][ls]|zenfone \d\w?)\b/i],
1034
+ [MODEL, [VENDOR, ASUS], [TYPE, MOBILE]],
1035
+ [
1036
+ // HTC
1037
+ /(nexus 9)/i, // HTC Nexus 9
1038
+ ],
1039
+ [MODEL, [VENDOR, 'HTC'], [TYPE, TABLET]],
1040
+ [
1041
+ /(htc)[-;_ ]{1,2}([\w ]+(?=\)| bui)|\w+)/i, // HTC
1042
+
1043
+ // ZTE
1044
+ /(zte)[- ]([\w ]+?)(?: bui|\/|\))/i,
1045
+ /(alcatel|geeksphone|nexian|panasonic(?!(?:;|\.))|sony(?!-bra))[-_ ]?([-\w]*)/i, // Alcatel/GeeksPhone/Nexian/Panasonic/Sony
1046
+ ],
1047
+ [VENDOR, [MODEL, /_/g, ' '], [TYPE, MOBILE]],
1048
+ [
1049
+ // TCL
1050
+ /droid [\w\.]+; ((?:8[14]9[16]|9(?:0(?:48|60|8[01])|1(?:3[27]|66)|2(?:6[69]|9[56])|466))[gqswx])\w*(\)| bui)/i,
1051
+ ],
1052
+ [MODEL, [VENDOR, 'TCL'], [TYPE, TABLET]],
1053
+ [
1054
+ // itel
1055
+ /(itel) ((\w+))/i,
1056
+ ],
1057
+ [[VENDOR, lowerize], MODEL, [TYPE, strMapper, { tablet: ['p10001l', 'w7001'], '*': 'mobile' }]],
1058
+ [
1059
+ // Acer
1060
+ /droid.+; ([ab][1-7]-?[0178a]\d\d?)/i,
1061
+ ],
1062
+ [MODEL, [VENDOR, 'Acer'], [TYPE, TABLET]],
1063
+ [
1064
+ // Meizu
1065
+ /droid.+; (m[1-5] note) bui/i,
1066
+ /\bmz-([-\w]{2,})/i,
1067
+ ],
1068
+ [MODEL, [VENDOR, 'Meizu'], [TYPE, MOBILE]],
1069
+ [
1070
+ // Ulefone
1071
+ /; ((?:power )?armor(?:[\w ]{0,8}))(?: bui|\))/i,
1072
+ ],
1073
+ [MODEL, [VENDOR, 'Ulefone'], [TYPE, MOBILE]],
1074
+ [
1075
+ // Energizer
1076
+ /; (energy ?\w+)(?: bui|\))/i,
1077
+ /; energizer ([\w ]+)(?: bui|\))/i,
1078
+ ],
1079
+ [MODEL, [VENDOR, 'Energizer'], [TYPE, MOBILE]],
1080
+ [
1081
+ // Cat
1082
+ /; cat (b35);/i,
1083
+ /; (b15q?|s22 flip|s48c|s62 pro)(?: bui|\))/i,
1084
+ ],
1085
+ [MODEL, [VENDOR, 'Cat'], [TYPE, MOBILE]],
1086
+ [
1087
+ // Smartfren
1088
+ /((?:new )?andromax[\w- ]+)(?: bui|\))/i,
1089
+ ],
1090
+ [MODEL, [VENDOR, 'Smartfren'], [TYPE, MOBILE]],
1091
+ [
1092
+ // Nothing
1093
+ /droid.+; (a(?:015|06[35]|142p?))/i,
1094
+ ],
1095
+ [MODEL, [VENDOR, 'Nothing'], [TYPE, MOBILE]],
1096
+ [
1097
+ // Archos
1098
+ /; (x67 5g|tikeasy \w+|ac[1789]\d\w+)( b|\))/i,
1099
+ /archos ?(5|gamepad2?|([\w ]*[t1789]|hello) ?\d+[\w ]*)( b|\))/i,
1100
+ ],
1101
+ [MODEL, [VENDOR, 'Archos'], [TYPE, TABLET]],
1102
+ [/archos ([\w ]+)( b|\))/i, /; (ac[3-6]\d\w{2,8})( b|\))/i],
1103
+ [MODEL, [VENDOR, 'Archos'], [TYPE, MOBILE]],
1104
+ [
1105
+ // MIXED
1106
+ /(imo) (tab \w+)/i, // IMO
1107
+ /(infinix) (x1101b?)/i, // Infinix XPad
1108
+ ],
1109
+ [VENDOR, MODEL, [TYPE, TABLET]],
1110
+ [
1111
+ /(blackberry|benq|palm(?=\-)|sonyericsson|acer|asus(?! zenw)|dell|jolla|meizu|motorola|polytron|infinix|tecno|micromax|advan)[-_ ]?([-\w]*)/i,
1112
+ // BlackBerry/BenQ/Palm/Sony-Ericsson/Acer/Asus/Dell/Meizu/Motorola/Polytron/Infinix/Tecno/Micromax/Advan
1113
+ /; (hmd|imo) ([\w ]+?)(?: bui|\))/i, // HMD/IMO
1114
+ /(hp) ([\w ]+\w)/i, // HP iPAQ
1115
+ /(microsoft); (lumia[\w ]+)/i, // Microsoft Lumia
1116
+ /(lenovo)[-_ ]?([-\w ]+?)(?: bui|\)|\/)/i, // Lenovo
1117
+ /(oppo) ?([\w ]+) bui/i, // OPPO
1118
+ ],
1119
+ [VENDOR, MODEL, [TYPE, MOBILE]],
1120
+ [
1121
+ /(kobo)\s(ereader|touch)/i, // Kobo
1122
+ /(hp).+(touchpad(?!.+tablet)|tablet)/i, // HP TouchPad
1123
+ /(kindle)\/([\w\.]+)/i, // Kindle
1124
+ /(nook)[\w ]+build\/(\w+)/i, // Nook
1125
+ /(dell) (strea[kpr\d ]*[\dko])/i, // Dell Streak
1126
+ /(le[- ]+pan)[- ]+(\w{1,9}) bui/i, // Le Pan Tablets
1127
+ /(trinity)[- ]*(t\d{3}) bui/i, // Trinity Tablets
1128
+ /(gigaset)[- ]+(q\w{1,9}) bui/i, // Gigaset Tablets
1129
+ /(vodafone) ([\w ]+)(?:\)| bui)/i, // Vodafone
1130
+ ],
1131
+ [VENDOR, MODEL, [TYPE, TABLET]],
1132
+ [
1133
+ /(surface duo)/i, // Surface Duo
1134
+ ],
1135
+ [MODEL, [VENDOR, MICROSOFT], [TYPE, TABLET]],
1136
+ [
1137
+ /droid [\d\.]+; (fp\du?)(?: b|\))/i, // Fairphone
1138
+ ],
1139
+ [MODEL, [VENDOR, 'Fairphone'], [TYPE, MOBILE]],
1140
+ [
1141
+ /(u304aa)/i, // AT&T
1142
+ ],
1143
+ [MODEL, [VENDOR, 'AT&T'], [TYPE, MOBILE]],
1144
+ [
1145
+ /\bsie-(\w*)/i, // Siemens
1146
+ ],
1147
+ [MODEL, [VENDOR, 'Siemens'], [TYPE, MOBILE]],
1148
+ [
1149
+ /\b(rct\w+) b/i, // RCA Tablets
1150
+ ],
1151
+ [MODEL, [VENDOR, 'RCA'], [TYPE, TABLET]],
1152
+ [
1153
+ /\b(venue[\d ]{2,7}) b/i, // Dell Venue Tablets
1154
+ ],
1155
+ [MODEL, [VENDOR, 'Dell'], [TYPE, TABLET]],
1156
+ [
1157
+ /\b(q(?:mv|ta)\w+) b/i, // Verizon Tablet
1158
+ ],
1159
+ [MODEL, [VENDOR, 'Verizon'], [TYPE, TABLET]],
1160
+ [
1161
+ /\b(?:barnes[& ]+noble |bn[rt])([\w\+ ]*) b/i, // Barnes & Noble Tablet
1162
+ ],
1163
+ [MODEL, [VENDOR, 'Barnes & Noble'], [TYPE, TABLET]],
1164
+ [/\b(tm\d{3}\w+) b/i],
1165
+ [MODEL, [VENDOR, 'NuVision'], [TYPE, TABLET]],
1166
+ [
1167
+ /\b(k88) b/i, // ZTE K Series Tablet
1168
+ ],
1169
+ [MODEL, [VENDOR, 'ZTE'], [TYPE, TABLET]],
1170
+ [
1171
+ /\b(nx\d{3}j) b/i, // ZTE Nubia
1172
+ ],
1173
+ [MODEL, [VENDOR, 'ZTE'], [TYPE, MOBILE]],
1174
+ [
1175
+ /\b(gen\d{3}) b.+49h/i, // Swiss GEN Mobile
1176
+ ],
1177
+ [MODEL, [VENDOR, 'Swiss'], [TYPE, MOBILE]],
1178
+ [
1179
+ /\b(zur\d{3}) b/i, // Swiss ZUR Tablet
1180
+ ],
1181
+ [MODEL, [VENDOR, 'Swiss'], [TYPE, TABLET]],
1182
+ [
1183
+ /\b((zeki)?tb.*\b) b/i, // Zeki Tablets
1184
+ ],
1185
+ [MODEL, [VENDOR, 'Zeki'], [TYPE, TABLET]],
1186
+ [
1187
+ /\b([yr]\d{2}) b/i,
1188
+ /\b(dragon[- ]+touch |dt)(\w{5}) b/i, // Dragon Touch Tablet
1189
+ ],
1190
+ [[VENDOR, 'Dragon Touch'], MODEL, [TYPE, TABLET]],
1191
+ [
1192
+ /\b(ns-?\w{0,9}) b/i, // Insignia Tablets
1193
+ ],
1194
+ [MODEL, [VENDOR, 'Insignia'], [TYPE, TABLET]],
1195
+ [
1196
+ /\b((nxa|next)-?\w{0,9}) b/i, // NextBook Tablets
1197
+ ],
1198
+ [MODEL, [VENDOR, 'NextBook'], [TYPE, TABLET]],
1199
+ [
1200
+ /\b(xtreme\_)?(v(1[045]|2[015]|[3469]0|7[05])) b/i, // Voice Xtreme Phones
1201
+ ],
1202
+ [[VENDOR, 'Voice'], MODEL, [TYPE, MOBILE]],
1203
+ [
1204
+ /\b(lvtel\-)?(v1[12]) b/i, // LvTel Phones
1205
+ ],
1206
+ [[VENDOR, 'LvTel'], MODEL, [TYPE, MOBILE]],
1207
+ [
1208
+ /\b(ph-1) /i, // Essential PH-1
1209
+ ],
1210
+ [MODEL, [VENDOR, 'Essential'], [TYPE, MOBILE]],
1211
+ [
1212
+ /\b(v(100md|700na|7011|917g).*\b) b/i, // Envizen Tablets
1213
+ ],
1214
+ [MODEL, [VENDOR, 'Envizen'], [TYPE, TABLET]],
1215
+ [
1216
+ /\b(trio[-\w\. ]+) b/i, // MachSpeed Tablets
1217
+ ],
1218
+ [MODEL, [VENDOR, 'MachSpeed'], [TYPE, TABLET]],
1219
+ [
1220
+ /\btu_(1491) b/i, // Rotor Tablets
1221
+ ],
1222
+ [MODEL, [VENDOR, 'Rotor'], [TYPE, TABLET]],
1223
+ [
1224
+ /((?:tegranote|shield t(?!.+d tv))[\w- ]*?)(?: b|\))/i, // Nvidia Tablets
1225
+ ],
1226
+ [MODEL, [VENDOR, NVIDIA], [TYPE, TABLET]],
1227
+ [
1228
+ /(sprint) (\w+)/i, // Sprint Phones
1229
+ ],
1230
+ [VENDOR, MODEL, [TYPE, MOBILE]],
1231
+ [
1232
+ /(kin\.[onetw]{3})/i, // Microsoft Kin
1233
+ ],
1234
+ [
1235
+ [MODEL, /\./g, ' '],
1236
+ [VENDOR, MICROSOFT],
1237
+ [TYPE, MOBILE],
1238
+ ],
1239
+ [
1240
+ /droid.+; (cc6666?|et5[16]|mc[239][23]x?|vc8[03]x?)\)/i, // Zebra
1241
+ ],
1242
+ [MODEL, [VENDOR, ZEBRA], [TYPE, TABLET]],
1243
+ [/droid.+; (ec30|ps20|tc[2-8]\d[kx])\)/i],
1244
+ [MODEL, [VENDOR, ZEBRA], [TYPE, MOBILE]],
1245
+ [
1246
+ ///////////////////
1247
+ // SMARTTVS
1248
+ ///////////////////
1249
+
1250
+ /smart-tv.+(samsung)/i, // Samsung
1251
+ ],
1252
+ [VENDOR, [TYPE, SMARTTV]],
1253
+ [/hbbtv.+maple;(\d+)/i],
1254
+ [
1255
+ [MODEL, /^/, 'SmartTV'],
1256
+ [VENDOR, SAMSUNG],
1257
+ [TYPE, SMARTTV],
1258
+ ],
1259
+ [
1260
+ /(nux; netcast.+smarttv|lg (netcast\.tv-201\d|android tv))/i, // LG SmartTV
1261
+ ],
1262
+ [
1263
+ [VENDOR, LG],
1264
+ [TYPE, SMARTTV],
1265
+ ],
1266
+ [
1267
+ /(apple) ?tv/i, // Apple TV
1268
+ ],
1269
+ [VENDOR, [MODEL, APPLE + ' TV'], [TYPE, SMARTTV]],
1270
+ [
1271
+ /crkey/i, // Google Chromecast
1272
+ ],
1273
+ [
1274
+ [MODEL, CHROME + 'cast'],
1275
+ [VENDOR, GOOGLE],
1276
+ [TYPE, SMARTTV],
1277
+ ],
1278
+ [
1279
+ /droid.+aft(\w+)( bui|\))/i, // Fire TV
1280
+ ],
1281
+ [MODEL, [VENDOR, AMAZON], [TYPE, SMARTTV]],
1282
+ [
1283
+ /(shield \w+ tv)/i, // Nvidia Shield TV
1284
+ ],
1285
+ [MODEL, [VENDOR, NVIDIA], [TYPE, SMARTTV]],
1286
+ [
1287
+ /\(dtv[\);].+(aquos)/i,
1288
+ /(aquos-tv[\w ]+)\)/i, // Sharp
1289
+ ],
1290
+ [MODEL, [VENDOR, SHARP], [TYPE, SMARTTV]],
1291
+ [
1292
+ /(bravia[\w ]+)( bui|\))/i, // Sony
1293
+ ],
1294
+ [MODEL, [VENDOR, SONY], [TYPE, SMARTTV]],
1295
+ [
1296
+ /(mi(tv|box)-?\w+) bui/i, // Xiaomi
1297
+ ],
1298
+ [MODEL, [VENDOR, XIAOMI], [TYPE, SMARTTV]],
1299
+ [
1300
+ /Hbbtv.*(technisat) (.*);/i, // TechniSAT
1301
+ ],
1302
+ [VENDOR, MODEL, [TYPE, SMARTTV]],
1303
+ [
1304
+ /\b(roku)[\dx]*[\)\/]((?:dvp-)?[\d\.]*)/i, // Roku
1305
+ /hbbtv\/\d+\.\d+\.\d+ +\([\w\+ ]*; *([\w\d][^;]*);([^;]*)/i, // HbbTV devices
1306
+ ],
1307
+ [
1308
+ [VENDOR, trimStr],
1309
+ [MODEL, trimStr],
1310
+ [TYPE, SMARTTV],
1311
+ ],
1312
+ [
1313
+ // SmartTV from Unidentified Vendors
1314
+ /droid.+; ([\w- ]+) (?:android tv|smart[- ]?tv)/i,
1315
+ ],
1316
+ [MODEL, [TYPE, SMARTTV]],
1317
+ [/\b(android tv|smart[- ]?tv|opera tv|tv; rv:)\b/i],
1318
+ [[TYPE, SMARTTV]],
1319
+ [
1320
+ ///////////////////
1321
+ // CONSOLES
1322
+ ///////////////////
1323
+
1324
+ /(ouya)/i, // Ouya
1325
+ /(nintendo) ([wids3utch]+)/i, // Nintendo
1326
+ ],
1327
+ [VENDOR, MODEL, [TYPE, CONSOLE]],
1328
+ [
1329
+ /droid.+; (shield)( bui|\))/i, // Nvidia Portable
1330
+ ],
1331
+ [MODEL, [VENDOR, NVIDIA], [TYPE, CONSOLE]],
1332
+ [
1333
+ /(playstation \w+)/i, // Playstation
1334
+ ],
1335
+ [MODEL, [VENDOR, SONY], [TYPE, CONSOLE]],
1336
+ [
1337
+ /\b(xbox(?: one)?(?!; xbox))[\); ]/i, // Microsoft Xbox
1338
+ ],
1339
+ [MODEL, [VENDOR, MICROSOFT], [TYPE, CONSOLE]],
1340
+ [
1341
+ ///////////////////
1342
+ // WEARABLES
1343
+ ///////////////////
1344
+
1345
+ /\b(sm-[lr]\d\d[0156][fnuw]?s?|gear live)\b/i, // Samsung Galaxy Watch
1346
+ ],
1347
+ [MODEL, [VENDOR, SAMSUNG], [TYPE, WEARABLE]],
1348
+ [
1349
+ /((pebble))app/i, // Pebble
1350
+ /(asus|google|lg|oppo) ((pixel |zen)?watch[\w ]*)( bui|\))/i, // Asus ZenWatch / LG Watch / Pixel Watch
1351
+ ],
1352
+ [VENDOR, MODEL, [TYPE, WEARABLE]],
1353
+ [
1354
+ /(ow(?:19|20)?we?[1-3]{1,3})/i, // Oppo Watch
1355
+ ],
1356
+ [MODEL, [VENDOR, OPPO], [TYPE, WEARABLE]],
1357
+ [
1358
+ /(watch)(?: ?os[,\/]|\d,\d\/)[\d\.]+/i, // Apple Watch
1359
+ ],
1360
+ [MODEL, [VENDOR, APPLE], [TYPE, WEARABLE]],
1361
+ [
1362
+ /(opwwe\d{3})/i, // OnePlus Watch
1363
+ ],
1364
+ [MODEL, [VENDOR, ONEPLUS], [TYPE, WEARABLE]],
1365
+ [
1366
+ /(moto 360)/i, // Motorola 360
1367
+ ],
1368
+ [MODEL, [VENDOR, MOTOROLA], [TYPE, WEARABLE]],
1369
+ [
1370
+ /(smartwatch 3)/i, // Sony SmartWatch
1371
+ ],
1372
+ [MODEL, [VENDOR, SONY], [TYPE, WEARABLE]],
1373
+ [
1374
+ /(g watch r)/i, // LG G Watch R
1375
+ ],
1376
+ [MODEL, [VENDOR, LG], [TYPE, WEARABLE]],
1377
+ [/droid.+; (wt63?0{2,3})\)/i],
1378
+ [MODEL, [VENDOR, ZEBRA], [TYPE, WEARABLE]],
1379
+ [
1380
+ ///////////////////
1381
+ // XR
1382
+ ///////////////////
1383
+
1384
+ /droid.+; (glass) \d/i, // Google Glass
1385
+ ],
1386
+ [MODEL, [VENDOR, GOOGLE], [TYPE, WEARABLE]],
1387
+ [
1388
+ /(pico) (4|neo3(?: link|pro)?)/i, // Pico
1389
+ ],
1390
+ [VENDOR, MODEL, [TYPE, WEARABLE]],
1391
+ [
1392
+ /; (quest( \d| pro)?)/i, // Oculus Quest
1393
+ ],
1394
+ [MODEL, [VENDOR, FACEBOOK], [TYPE, WEARABLE]],
1395
+ [
1396
+ ///////////////////
1397
+ // EMBEDDED
1398
+ ///////////////////
1399
+
1400
+ /(tesla)(?: qtcarbrowser|\/[-\w\.]+)/i, // Tesla
1401
+ ],
1402
+ [VENDOR, [TYPE, EMBEDDED]],
1403
+ [
1404
+ /(aeobc)\b/i, // Echo Dot
1405
+ ],
1406
+ [MODEL, [VENDOR, AMAZON], [TYPE, EMBEDDED]],
1407
+ [
1408
+ /(homepod).+mac os/i, // Apple HomePod
1409
+ ],
1410
+ [MODEL, [VENDOR, APPLE], [TYPE, EMBEDDED]],
1411
+ [/windows iot/i],
1412
+ [[TYPE, EMBEDDED]],
1413
+ [
1414
+ ////////////////////
1415
+ // MIXED (GENERIC)
1416
+ ///////////////////
1417
+
1418
+ /droid .+?; ([^;]+?)(?: bui|; wv\)|\) applew).+? mobile safari/i, // Android Phones from Unidentified Vendors
1419
+ ],
1420
+ [MODEL, [TYPE, MOBILE]],
1421
+ [
1422
+ /droid .+?; ([^;]+?)(?: bui|\) applew).+?(?! mobile) safari/i, // Android Tablets from Unidentified Vendors
1423
+ ],
1424
+ [MODEL, [TYPE, TABLET]],
1425
+ [
1426
+ /\b((tablet|tab)[;\/]|focus\/\d(?!.+mobile))/i, // Unidentifiable Tablet
1427
+ ],
1428
+ [[TYPE, TABLET]],
1429
+ [
1430
+ /(phone|mobile(?:[;\/]| [ \w\/\.]*safari)|pda(?=.+windows ce))/i, // Unidentifiable Mobile
1431
+ ],
1432
+ [[TYPE, MOBILE]],
1433
+ [
1434
+ /droid .+?; ([\w\. -]+)( bui|\))/i, // Generic Android Device
1435
+ ],
1436
+ [MODEL, [VENDOR, 'Generic']],
1437
+ ],
1438
+
1439
+ engine: [
1440
+ [
1441
+ /windows.+ edge\/([\w\.]+)/i, // EdgeHTML
1442
+ ],
1443
+ [VERSION, [NAME, EDGE + 'HTML']],
1444
+ [
1445
+ /(arkweb)\/([\w\.]+)/i, // ArkWeb
1446
+ ],
1447
+ [NAME, VERSION],
1448
+ [
1449
+ /webkit\/537\.36.+chrome\/(?!27)([\w\.]+)/i, // Blink
1450
+ ],
1451
+ [VERSION, [NAME, 'Blink']],
1452
+ [
1453
+ /(presto)\/([\w\.]+)/i, // Presto
1454
+ /(webkit|trident|netfront|netsurf|amaya|lynx|w3m|goanna|servo)\/([\w\.]+)/i, // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m/Goanna/Servo
1455
+ /ekioh(flow)\/([\w\.]+)/i, // Flow
1456
+ /(khtml|tasman|links)[\/ ]\(?([\w\.]+)/i, // KHTML/Tasman/Links
1457
+ /(icab)[\/ ]([23]\.[\d\.]+)/i, // iCab
1458
+
1459
+ /\b(libweb)/i, // LibWeb
1460
+ ],
1461
+ [NAME, VERSION],
1462
+ [/ladybird\//i],
1463
+ [[NAME, 'LibWeb']],
1464
+ [
1465
+ /rv\:([\w\.]{1,9})\b.+(gecko)/i, // Gecko
1466
+ ],
1467
+ [VERSION, NAME],
1468
+ ],
1469
+
1470
+ os: [
1471
+ [
1472
+ // Windows
1473
+ /microsoft (windows) (vista|xp)/i, // Windows (iTunes)
1474
+ ],
1475
+ [NAME, VERSION],
1476
+ [
1477
+ /(windows (?:phone(?: os)?|mobile|iot))[\/ ]?([\d\.\w ]*)/i, // Windows Phone
1478
+ ],
1479
+ [NAME, [VERSION, strMapper, windowsVersionMap]],
1480
+ [
1481
+ /windows nt 6\.2; (arm)/i, // Windows RT
1482
+ /windows[\/ ]([ntce\d\. ]+\w)(?!.+xbox)/i,
1483
+ /(?:win(?=3|9|n)|win 9x )([nt\d\.]+)/i,
1484
+ ],
1485
+ [
1486
+ [VERSION, strMapper, windowsVersionMap],
1487
+ [NAME, UAOSName.Windows],
1488
+ ],
1489
+ [
1490
+ // iOS/macOS
1491
+ /[adehimnop]{4,7}\b(?:.*os ([\w]+) like mac|; opera)/i, // iOS
1492
+ /(?:ios;fbsv\/|iphone.+ios[\/ ])([\d\.]+)/i,
1493
+ /cfnetwork\/.+darwin/i,
1494
+ ],
1495
+ [
1496
+ [VERSION, /_/g, '.'],
1497
+ [NAME, UAOSName.iOS],
1498
+ ],
1499
+ [
1500
+ /(mac os x) ?([\w\. ]*)/i,
1501
+ /(macintosh|mac_powerpc\b)(?!.+haiku)/i, // Mac OS
1502
+ ],
1503
+ [
1504
+ [NAME, MAC_OS],
1505
+ [VERSION, /_/g, '.'],
1506
+ ],
1507
+ [
1508
+ // Mobile OSes
1509
+ /droid ([\w\.]+)\b.+(android[- ]x86|harmonyos)/i, // Android-x86/HarmonyOS
1510
+ ],
1511
+ [VERSION, NAME],
1512
+ [
1513
+ /(ubuntu) ([\w\.]+) like android/i, // Ubuntu Touch
1514
+ ],
1515
+ [[NAME, /(.+)/, '$1 Touch'], VERSION],
1516
+ [
1517
+ // Android/Blackberry/WebOS/QNX/Bada/RIM/KaiOS/Maemo/MeeGo/S40/Sailfish OS/OpenHarmony/Tizen
1518
+ /(android|bada|blackberry|kaios|maemo|meego|openharmony|qnx|rim tablet os|sailfish|series40|symbian|tizen|webos)\w*[-\/; ]?([\d\.]*)/i,
1519
+ ],
1520
+ [NAME, VERSION],
1521
+ [
1522
+ /\(bb(10);/i, // BlackBerry 10
1523
+ ],
1524
+ [VERSION, [NAME, BLACKBERRY]],
1525
+ [
1526
+ /(?:symbian ?os|symbos|s60(?=;)|series ?60)[-\/ ]?([\w\.]*)/i, // Symbian
1527
+ ],
1528
+ [VERSION, [NAME, 'Symbian']],
1529
+ [
1530
+ /mozilla\/[\d\.]+ \((?:mobile|tablet|tv|mobile; [\w ]+); rv:.+ gecko\/([\w\.]+)/i, // Firefox OS
1531
+ ],
1532
+ [VERSION, [NAME, FIREFOX + ' OS']],
1533
+ [
1534
+ /web0s;.+rt(tv)/i,
1535
+ /\b(?:hp)?wos(?:browser)?\/([\w\.]+)/i, // WebOS
1536
+ ],
1537
+ [VERSION, [NAME, 'webOS']],
1538
+ [
1539
+ /watch(?: ?os[,\/]|\d,\d\/)([\d\.]+)/i, // watchOS
1540
+ ],
1541
+ [VERSION, [NAME, UAOSName.WatchOS]],
1542
+ [
1543
+ // Google Chromecast
1544
+ /crkey\/([\d\.]+)/i, // Google Chromecast
1545
+ ],
1546
+ [VERSION, [NAME, CHROME + 'cast']],
1547
+ [
1548
+ /(cros) [\w]+(?:\)| ([\w\.]+)\b)/i, // Chromium OS
1549
+ ],
1550
+ [[NAME, CHROMIUM_OS], VERSION],
1551
+ [
1552
+ // Smart TVs
1553
+ /panasonic;(viera)/i, // Panasonic Viera
1554
+ /(netrange)mmh/i, // Netrange
1555
+ /(nettv)\/(\d+\.[\w\.]+)/i, // NetTV
1556
+
1557
+ // Console
1558
+ /(nintendo|playstation) ([wids345portablevuch]+)/i, // Nintendo/Playstation
1559
+ /(xbox); +xbox ([^\);]+)/i, // Microsoft Xbox (360, One, X, S, Series X, Series S)
1560
+
1561
+ // Other
1562
+ /\b(joli|palm)\b ?(?:os)?\/?([\w\.]*)/i, // Joli/Palm
1563
+ /(mint)[\/\(\) ]?(\w*)/i, // Mint
1564
+ /(mageia|vectorlinux)[; ]/i, // Mageia/VectorLinux
1565
+ /([kxln]?ubuntu|debian|suse|opensuse|gentoo|arch(?= linux)|slackware|fedora|mandriva|centos|pclinuxos|red ?hat|zenwalk|linpus|raspbian|plan 9|minix|risc os|contiki|deepin|manjaro|elementary os|sabayon|linspire)(?: gnu\/linux)?(?: enterprise)?(?:[- ]linux)?(?:-gnu)?[-\/ ]?(?!chrom|package)([-\w\.]*)/i,
1566
+ // Ubuntu/Debian/SUSE/Gentoo/Arch/Slackware/Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus/Raspbian/Plan9/Minix/RISCOS/Contiki/Deepin/Manjaro/elementary/Sabayon/Linspire
1567
+ /(hurd|linux)(?: arm\w*| x86\w*| ?)([\w\.]*)/i, // Hurd/Linux
1568
+ /(gnu) ?([\w\.]*)/i, // GNU
1569
+ /\b([-frentopcghs]{0,5}bsd|dragonfly)[\/ ]?(?!amd|[ix346]{1,2}86)([\w\.]*)/i, // FreeBSD/NetBSD/OpenBSD/PC-BSD/GhostBSD/DragonFly
1570
+ /(haiku) (\w+)/i, // Haiku
1571
+ ],
1572
+ [NAME, VERSION],
1573
+ [
1574
+ /(sunos) ?([\w\.\d]*)/i, // Solaris
1575
+ ],
1576
+ [[NAME, 'Solaris'], VERSION],
1577
+ [
1578
+ /((?:open)?solaris)[-\/ ]?([\w\.]*)/i, // Solaris
1579
+ /(aix) ((\d)(?=\.|\)| )[\w\.])*/i, // AIX
1580
+ /\b(beos|os\/2|amigaos|morphos|openvms|fuchsia|hp-ux|serenityos)/i, // BeOS/OS2/AmigaOS/MorphOS/OpenVMS/Fuchsia/HP-UX/SerenityOS
1581
+ /(unix) ?([\w\.]*)/i, // UNIX
1582
+ ],
1583
+ [NAME, VERSION],
1584
+ ],
1585
+ }