From 9bbbb518eb6d0671df694e6a43298b45e1bbd169 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Thu, 22 Feb 2024 21:17:21 +0000 Subject: [PATCH 01/43] wat --- package.json | 4 +- pnpm-lock.yaml | 18 ++++++++- src/__tests__/utils/event-utils.test.ts | 11 +++++ src/utils/event-utils.ts | 53 +++++++++++++------------ 4 files changed, 59 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index f2cc1ba9c..3e7fa9fa5 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,11 @@ ], "dependencies": { "fflate": "^0.4.8", - "preact": "^10.19.3" + "preact": "^10.19.3", + "ua-parser-js": "https://github.com/faisalman/ua-parser-js#8087a1b4f0e25f1663ca3ddc2e06371d36642173" }, "devDependencies": { + "@types/ua-parser-js": "^0.7.39", "@babel/core": "7.18.9", "@babel/plugin-syntax-decorators": "^7.23.3", "@babel/plugin-transform-react-jsx": "^7.23.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a4229887..c3b2e99b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -10,12 +10,18 @@ patchedDependencies: path: patches/rrweb@2.0.0-alpha.11.patch dependencies: + '@types/ua-parser-js': + specifier: ^0.7.39 + version: 0.7.39 fflate: specifier: ^0.4.8 version: 0.4.8 preact: specifier: ^10.19.3 version: 10.19.3 + ua-parser-js: + specifier: https://github.com/faisalman/ua-parser-js#8087a1b4f0e25f1663ca3ddc2e06371d36642173 + version: github.com/faisalman/ua-parser-js/8087a1b4f0e25f1663ca3ddc2e06371d36642173 devDependencies: '@babel/core': @@ -2937,6 +2943,10 @@ packages: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} dev: true + /@types/ua-parser-js@0.7.39: + resolution: {integrity: sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==} + dev: false + /@types/uuid@9.0.1: resolution: {integrity: sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==} dev: true @@ -10812,3 +10822,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + github.com/faisalman/ua-parser-js/8087a1b4f0e25f1663ca3ddc2e06371d36642173: + resolution: {tarball: https://codeload.github.com/faisalman/ua-parser-js/tar.gz/8087a1b4f0e25f1663ca3ddc2e06371d36642173} + name: ua-parser-js + version: 1.0.37 + dev: false diff --git a/src/__tests__/utils/event-utils.test.ts b/src/__tests__/utils/event-utils.test.ts index 4dd7027e5..758cb89d7 100644 --- a/src/__tests__/utils/event-utils.test.ts +++ b/src/__tests__/utils/event-utils.test.ts @@ -1,5 +1,7 @@ import { _info } from '../../utils/event-utils' import * as globals from '../../utils/globals' +import uaParserDeviceTestCases from 'ua-parser-js/test/device-test.json' +import { _isUndefined } from '../../utils/type-utils' describe(`event-utils`, () => { describe('properties', () => { @@ -213,5 +215,14 @@ describe(`event-utils`, () => { test.each(browserTestcases)('browser %s', ({ userAgent, vendor, expectedBrowser }) => { expect(_info.browser(userAgent, vendor, '')).toBe(expectedBrowser) }) + + test.each(uaParserDeviceTestCases)('us parser test cases $ua', (testCase) => { + const actual = _info.deviceType(testCase['ua']).toLowerCase() + const expected = + _isUndefined(testCase['expect']['type']) || testCase['expect']['type'] === 'undefined' + ? 'desktop' + : testCase['expect']['type'] + expect(actual).toBe(expected) + }) }) }) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 717cdc66a..021054d42 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -4,6 +4,7 @@ import { Properties } from '../types' import Config from '../config' import { _each, _extend, _includes, _strip_empty_properties, _timestamp } from './index' import { document, window, location, userAgent, assignableWindow } from './globals' +import UAParser from 'ua-parser-js' /** * Safari detection turns out to be complicted. For e.g. https://stackoverflow.com/a/29696509 @@ -222,34 +223,36 @@ export const _info = { }, device: function (user_agent: string): string { - if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) { - return 'Windows Phone' - } else if (/iPad/.test(user_agent)) { - return 'iPad' - } else if (/iPod/.test(user_agent)) { - return 'iPod Touch' - } else if (/iPhone/.test(user_agent)) { - return 'iPhone' - } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { - return 'BlackBerry' - } else if (/Android/.test(user_agent) && !/Mobile/.test(user_agent)) { - return 'Android Tablet' - } else if (/Android/.test(user_agent)) { - return 'Android' - } else { - return '' - } + return new UAParser(user_agent).getOS().name || '' + // if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) { + // return 'Windows Phone' + // } else if (/iPad/.test(user_agent)) { + // return 'iPad' + // } else if (/iPod/.test(user_agent)) { + // return 'iPod Touch' + // } else if (/iPhone/.test(user_agent)) { + // return 'iPhone' + // } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { + // return 'BlackBerry' + // } else if (/Android/.test(user_agent) && !/Mobile/.test(user_agent)) { + // return 'Android Tablet' + // } else if (/Android/.test(user_agent)) { + // return 'Android' + // } else { + // return '' + // } }, deviceType: function (user_agent: string): string { - const device = this.device(user_agent) - if (device === 'iPad' || device === 'Android Tablet') { - return 'Tablet' - } else if (device) { - return 'Mobile' - } else { - return 'Desktop' - } + return new UAParser(user_agent).getDevice().type || 'Desktop' + // const device = this.device(user_agent) + // if (device === 'iPad' || device === 'Android Tablet') { + // return 'Tablet' + // } else if (device) { + // return 'Mobile' + // } else { + // return 'Desktop' + // } }, referrer: function (): string { From f3517c4264b35be618afdd7bdc64808919c07ffa Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Thu, 22 Feb 2024 21:19:14 +0000 Subject: [PATCH 02/43] wat --- pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c3b2e99b2..772a59de5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,9 +10,6 @@ patchedDependencies: path: patches/rrweb@2.0.0-alpha.11.patch dependencies: - '@types/ua-parser-js': - specifier: ^0.7.39 - version: 0.7.39 fflate: specifier: ^0.4.8 version: 0.4.8 @@ -75,6 +72,9 @@ devDependencies: '@types/sinon': specifier: ^17.0.1 version: 17.0.1 + '@types/ua-parser-js': + specifier: ^0.7.39 + version: 0.7.39 '@types/uuid': specifier: ^9.0.1 version: 9.0.1 @@ -2945,7 +2945,7 @@ packages: /@types/ua-parser-js@0.7.39: resolution: {integrity: sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==} - dev: false + dev: true /@types/uuid@9.0.1: resolution: {integrity: sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==} From c450722e32ab098c3b3b301500d4304ae01bde44 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Thu, 22 Feb 2024 21:21:56 +0000 Subject: [PATCH 03/43] wat --- src/utils/event-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 021054d42..a69f06d61 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -4,7 +4,7 @@ import { Properties } from '../types' import Config from '../config' import { _each, _extend, _includes, _strip_empty_properties, _timestamp } from './index' import { document, window, location, userAgent, assignableWindow } from './globals' -import UAParser from 'ua-parser-js' +import { UAParser } from 'ua-parser-js' /** * Safari detection turns out to be complicted. For e.g. https://stackoverflow.com/a/29696509 From 406deb4e17d7ec0ffe121e390905db6cc19d8636 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Thu, 22 Feb 2024 21:28:00 +0000 Subject: [PATCH 04/43] Revert "wat" This reverts commit c450722e32ab098c3b3b301500d4304ae01bde44. --- src/utils/event-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index a69f06d61..021054d42 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -4,7 +4,7 @@ import { Properties } from '../types' import Config from '../config' import { _each, _extend, _includes, _strip_empty_properties, _timestamp } from './index' import { document, window, location, userAgent, assignableWindow } from './globals' -import { UAParser } from 'ua-parser-js' +import UAParser from 'ua-parser-js' /** * Safari detection turns out to be complicted. For e.g. https://stackoverflow.com/a/29696509 From 54a5e28b55b7a0359ea9033f47e0d86a10ec2b40 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Thu, 22 Feb 2024 21:28:27 +0000 Subject: [PATCH 05/43] Revert "wat" This reverts commit f3517c4264b35be618afdd7bdc64808919c07ffa. --- pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 772a59de5..c3b2e99b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,6 +10,9 @@ patchedDependencies: path: patches/rrweb@2.0.0-alpha.11.patch dependencies: + '@types/ua-parser-js': + specifier: ^0.7.39 + version: 0.7.39 fflate: specifier: ^0.4.8 version: 0.4.8 @@ -72,9 +75,6 @@ devDependencies: '@types/sinon': specifier: ^17.0.1 version: 17.0.1 - '@types/ua-parser-js': - specifier: ^0.7.39 - version: 0.7.39 '@types/uuid': specifier: ^9.0.1 version: 9.0.1 @@ -2945,7 +2945,7 @@ packages: /@types/ua-parser-js@0.7.39: resolution: {integrity: sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==} - dev: true + dev: false /@types/uuid@9.0.1: resolution: {integrity: sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==} From a9c2a7564194e66f4f926279efea529b1211223b Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Thu, 22 Feb 2024 21:28:53 +0000 Subject: [PATCH 06/43] Revert "wat" This reverts commit 9bbbb518eb6d0671df694e6a43298b45e1bbd169. --- package.json | 4 +- pnpm-lock.yaml | 18 +-------- src/__tests__/utils/event-utils.test.ts | 11 ----- src/utils/event-utils.ts | 53 ++++++++++++------------- 4 files changed, 27 insertions(+), 59 deletions(-) diff --git a/package.json b/package.json index 3e7fa9fa5..f2cc1ba9c 100644 --- a/package.json +++ b/package.json @@ -33,11 +33,9 @@ ], "dependencies": { "fflate": "^0.4.8", - "preact": "^10.19.3", - "ua-parser-js": "https://github.com/faisalman/ua-parser-js#8087a1b4f0e25f1663ca3ddc2e06371d36642173" + "preact": "^10.19.3" }, "devDependencies": { - "@types/ua-parser-js": "^0.7.39", "@babel/core": "7.18.9", "@babel/plugin-syntax-decorators": "^7.23.3", "@babel/plugin-transform-react-jsx": "^7.23.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c3b2e99b2..2a4229887 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true @@ -10,18 +10,12 @@ patchedDependencies: path: patches/rrweb@2.0.0-alpha.11.patch dependencies: - '@types/ua-parser-js': - specifier: ^0.7.39 - version: 0.7.39 fflate: specifier: ^0.4.8 version: 0.4.8 preact: specifier: ^10.19.3 version: 10.19.3 - ua-parser-js: - specifier: https://github.com/faisalman/ua-parser-js#8087a1b4f0e25f1663ca3ddc2e06371d36642173 - version: github.com/faisalman/ua-parser-js/8087a1b4f0e25f1663ca3ddc2e06371d36642173 devDependencies: '@babel/core': @@ -2943,10 +2937,6 @@ packages: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} dev: true - /@types/ua-parser-js@0.7.39: - resolution: {integrity: sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==} - dev: false - /@types/uuid@9.0.1: resolution: {integrity: sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==} dev: true @@ -10822,9 +10812,3 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true - - github.com/faisalman/ua-parser-js/8087a1b4f0e25f1663ca3ddc2e06371d36642173: - resolution: {tarball: https://codeload.github.com/faisalman/ua-parser-js/tar.gz/8087a1b4f0e25f1663ca3ddc2e06371d36642173} - name: ua-parser-js - version: 1.0.37 - dev: false diff --git a/src/__tests__/utils/event-utils.test.ts b/src/__tests__/utils/event-utils.test.ts index 758cb89d7..4dd7027e5 100644 --- a/src/__tests__/utils/event-utils.test.ts +++ b/src/__tests__/utils/event-utils.test.ts @@ -1,7 +1,5 @@ import { _info } from '../../utils/event-utils' import * as globals from '../../utils/globals' -import uaParserDeviceTestCases from 'ua-parser-js/test/device-test.json' -import { _isUndefined } from '../../utils/type-utils' describe(`event-utils`, () => { describe('properties', () => { @@ -215,14 +213,5 @@ describe(`event-utils`, () => { test.each(browserTestcases)('browser %s', ({ userAgent, vendor, expectedBrowser }) => { expect(_info.browser(userAgent, vendor, '')).toBe(expectedBrowser) }) - - test.each(uaParserDeviceTestCases)('us parser test cases $ua', (testCase) => { - const actual = _info.deviceType(testCase['ua']).toLowerCase() - const expected = - _isUndefined(testCase['expect']['type']) || testCase['expect']['type'] === 'undefined' - ? 'desktop' - : testCase['expect']['type'] - expect(actual).toBe(expected) - }) }) }) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 021054d42..717cdc66a 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -4,7 +4,6 @@ import { Properties } from '../types' import Config from '../config' import { _each, _extend, _includes, _strip_empty_properties, _timestamp } from './index' import { document, window, location, userAgent, assignableWindow } from './globals' -import UAParser from 'ua-parser-js' /** * Safari detection turns out to be complicted. For e.g. https://stackoverflow.com/a/29696509 @@ -223,36 +222,34 @@ export const _info = { }, device: function (user_agent: string): string { - return new UAParser(user_agent).getOS().name || '' - // if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) { - // return 'Windows Phone' - // } else if (/iPad/.test(user_agent)) { - // return 'iPad' - // } else if (/iPod/.test(user_agent)) { - // return 'iPod Touch' - // } else if (/iPhone/.test(user_agent)) { - // return 'iPhone' - // } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { - // return 'BlackBerry' - // } else if (/Android/.test(user_agent) && !/Mobile/.test(user_agent)) { - // return 'Android Tablet' - // } else if (/Android/.test(user_agent)) { - // return 'Android' - // } else { - // return '' - // } + if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) { + return 'Windows Phone' + } else if (/iPad/.test(user_agent)) { + return 'iPad' + } else if (/iPod/.test(user_agent)) { + return 'iPod Touch' + } else if (/iPhone/.test(user_agent)) { + return 'iPhone' + } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { + return 'BlackBerry' + } else if (/Android/.test(user_agent) && !/Mobile/.test(user_agent)) { + return 'Android Tablet' + } else if (/Android/.test(user_agent)) { + return 'Android' + } else { + return '' + } }, deviceType: function (user_agent: string): string { - return new UAParser(user_agent).getDevice().type || 'Desktop' - // const device = this.device(user_agent) - // if (device === 'iPad' || device === 'Android Tablet') { - // return 'Tablet' - // } else if (device) { - // return 'Mobile' - // } else { - // return 'Desktop' - // } + const device = this.device(user_agent) + if (device === 'iPad' || device === 'Android Tablet') { + return 'Tablet' + } else if (device) { + return 'Mobile' + } else { + return 'Desktop' + } }, referrer: function (): string { From 70669d79c479cbfd7a677f0fbcf3f0ec14c88bd3 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Thu, 22 Feb 2024 21:38:42 +0000 Subject: [PATCH 07/43] use the test suite --- package.json | 1 + pnpm-lock.yaml | 11 ++++++++++- src/__tests__/utils/event-utils.test.ts | 11 +++++++++++ src/utils/event-utils.ts | 13 ++++++++++++- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f2cc1ba9c..b5de51f99 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "testcafe-browser-provider-browserstack": "1.14.0", "tslib": "^2.5.0", "typescript": "^4.9.5", + "ua-parser-js": "https://github.com/faisalman/ua-parser-js#8087a1b4f0e25f1663ca3ddc2e06371d36642173", "yargs": "^17.7.2" }, "lint-staged": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a4229887..6604c9424 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -192,6 +192,9 @@ devDependencies: typescript: specifier: ^4.9.5 version: 4.9.5 + ua-parser-js: + specifier: https://github.com/faisalman/ua-parser-js#8087a1b4f0e25f1663ca3ddc2e06371d36642173 + version: github.com/faisalman/ua-parser-js/8087a1b4f0e25f1663ca3ddc2e06371d36642173 yargs: specifier: ^17.7.2 version: 17.7.2 @@ -10812,3 +10815,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + github.com/faisalman/ua-parser-js/8087a1b4f0e25f1663ca3ddc2e06371d36642173: + resolution: {tarball: https://codeload.github.com/faisalman/ua-parser-js/tar.gz/8087a1b4f0e25f1663ca3ddc2e06371d36642173} + name: ua-parser-js + version: 1.0.37 + dev: true diff --git a/src/__tests__/utils/event-utils.test.ts b/src/__tests__/utils/event-utils.test.ts index 4dd7027e5..758cb89d7 100644 --- a/src/__tests__/utils/event-utils.test.ts +++ b/src/__tests__/utils/event-utils.test.ts @@ -1,5 +1,7 @@ import { _info } from '../../utils/event-utils' import * as globals from '../../utils/globals' +import uaParserDeviceTestCases from 'ua-parser-js/test/device-test.json' +import { _isUndefined } from '../../utils/type-utils' describe(`event-utils`, () => { describe('properties', () => { @@ -213,5 +215,14 @@ describe(`event-utils`, () => { test.each(browserTestcases)('browser %s', ({ userAgent, vendor, expectedBrowser }) => { expect(_info.browser(userAgent, vendor, '')).toBe(expectedBrowser) }) + + test.each(uaParserDeviceTestCases)('us parser test cases $ua', (testCase) => { + const actual = _info.deviceType(testCase['ua']).toLowerCase() + const expected = + _isUndefined(testCase['expect']['type']) || testCase['expect']['type'] === 'undefined' + ? 'desktop' + : testCase['expect']['type'] + expect(actual).toBe(expected) + }) }) }) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 717cdc66a..7be5473cd 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -230,9 +230,18 @@ export const _info = { return 'iPod Touch' } else if (/iPhone/.test(user_agent)) { return 'iPhone' + } else if (/(watch)(?: ?os[,/]|\d,\d\/)[\d.]+/i.test(user_agent)) { + return 'Apple Watch' } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { return 'BlackBerry' - } else if (/Android/.test(user_agent) && !/Mobile/.test(user_agent)) { + } else if ( + (/Android/.test(user_agent) && !/Mobile/.test(user_agent)) || + (/Android/.test(user_agent) && /Nexus 9/.test(user_agent)) || + (/Android/.test(user_agent) && /(HUAWEISHT|BTV)/i.test(user_agent)) + ) { + if (/(huaweimed-al00|tah-)/i.test(user_agent)) { + return 'Android' + } return 'Android Tablet' } else if (/Android/.test(user_agent)) { return 'Android' @@ -245,6 +254,8 @@ export const _info = { const device = this.device(user_agent) if (device === 'iPad' || device === 'Android Tablet') { return 'Tablet' + } else if (device === 'Apple Watch') { + return 'Wearable' } else if (device) { return 'Mobile' } else { From 1abd869229f87be7ff6bd6924a7328670a03f409 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Thu, 22 Feb 2024 21:39:49 +0000 Subject: [PATCH 08/43] split smarttv and wearable tests out --- src/__tests__/utils/event-utils.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/__tests__/utils/event-utils.test.ts b/src/__tests__/utils/event-utils.test.ts index 758cb89d7..6dae0f754 100644 --- a/src/__tests__/utils/event-utils.test.ts +++ b/src/__tests__/utils/event-utils.test.ts @@ -217,6 +217,14 @@ describe(`event-utils`, () => { }) test.each(uaParserDeviceTestCases)('us parser test cases $ua', (testCase) => { + if (testCase['expect']['type'] === 'smarttv') { + // we'll test that separately + return + } + if (testCase['expect']['type'] === 'wearable') { + // we'll test that separately + return + } const actual = _info.deviceType(testCase['ua']).toLowerCase() const expected = _isUndefined(testCase['expect']['type']) || testCase['expect']['type'] === 'undefined' From 03c45f8fa6ee93f681e7b9eade8d0aa7034053d4 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Thu, 22 Feb 2024 22:20:43 +0000 Subject: [PATCH 09/43] more --- package.json | 1 - src/__tests__/utils/device.test.json | 3841 +++++++++++++++++++++++ src/__tests__/utils/event-utils.test.ts | 11 +- src/utils/event-utils.ts | 43 +- 4 files changed, 3880 insertions(+), 16 deletions(-) create mode 100644 src/__tests__/utils/device.test.json diff --git a/package.json b/package.json index b5de51f99..f2cc1ba9c 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,6 @@ "testcafe-browser-provider-browserstack": "1.14.0", "tslib": "^2.5.0", "typescript": "^4.9.5", - "ua-parser-js": "https://github.com/faisalman/ua-parser-js#8087a1b4f0e25f1663ca3ddc2e06371d36642173", "yargs": "^17.7.2" }, "lint-staged": { diff --git a/src/__tests__/utils/device.test.json b/src/__tests__/utils/device.test.json new file mode 100644 index 000000000..43b5d4028 --- /dev/null +++ b/src/__tests__/utils/device.test.json @@ -0,0 +1,3841 @@ +[ + { + "desc": "K", + "ua": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "undefined", + "model": "K", + "type": "mobile" + } + }, + { + "desc": "ASUS Nexus 7", + "ua": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 7 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.99 Safari/537.36", + "expect": { + "vendor": "ASUS", + "model": "Nexus 7", + "type": "tablet" + } + }, + { + "desc": "ASUS Padfone", + "ua": "Mozilla/5.0 (Linux; Android 4.1.1; PadFone 2 Build/JRO03L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.117 Safari/537.36", + "expect": { + "vendor": "ASUS", + "model": "PadFone", + "type": "tablet" + } + }, + { + "desc": "ASUS ZenPad 10", + "ua": "Mozilla/5.0 (Linux; Android 6.0; P00C Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Safari/537.36", + "expect": { + "vendor": "ASUS", + "model": "P00C", + "type": "tablet" + } + }, + { + "desc": "ASUS ZenPad Z8s", + "ua": "Mozilla/5.0 (Linux; Android 7.0; ASUS_P00J) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Safari/537.36\n", + "expect": { + "vendor": "ASUS", + "model": "P00J", + "type": "tablet" + } + }, + { + "desc": "ASUS ROG", + "ua": "Mozilla/5.0 (Linux; Android 8.1; ZS600KL Build/OPM1.171019.026) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.126 Mobile Safari/537.36", + "expect": { + "vendor": "ASUS", + "model": "ZS600KL", + "type": "mobile" + } + }, + { + "desc": "ASUS ROG II", + "ua": "Mozilla/5.0 (Linux; Android 9; ASUS_I001DA Build/PKQ1.190414.001; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.136 Mobile Safari/537.36", + "expect": { + "vendor": "ASUS", + "model": "I001DA", + "type": "mobile" + } + }, + { + "desc": "ASUS Zenfone 2", + "ua": "Mozilla/5.0 (Linux; Android 5.0; ASUS ZenFone 2 Build/LRX22C) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/37.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "ASUS", + "model": "ZenFone 2", + "type": "mobile" + } + }, + { + "desc": "ASUS Zenfone 3 Deluxe", + "ua": "Mozilla/5.0 (Linux; Android 6.0; ASUS_Z016D Build/MXB48T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.132 Mobile Safari/537.36", + "expect": { + "vendor": "ASUS", + "model": "Z016D", + "type": "mobile" + } + }, + { + "desc": "ASUS Zenfone 5", + "ua": "Mozilla/5.0 (Linux; Android 8.0; ZE620KL Build/OPR1.170623.032) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.158 Mobile Safari/537.36", + "expect": { + "vendor": "ASUS", + "model": "ZE620KL", + "type": "mobile" + } + }, + { + "desc": "ASUS Zenfone 7", + "ua": "Mozilla/5.0 (Linux; Android 10; ASUS_I002D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.81 Mobile Safari/537.36", + "expect": { + "vendor": "ASUS", + "model": "I002D", + "type": "mobile" + } + }, + { + "desc": "ASUS Zenfone 7 Pro", + "ua": "Mozilla/5.0 (Linux; Android 10; ZS671KS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Mobile Safari/537.36", + "expect": { + "vendor": "ASUS", + "model": "ZS671KS", + "type": "mobile" + } + }, + { + "desc": "ASUS Zenfone Max Pro", + "ua": "Mozilla/5.0 (Linux; Android 9; ZB602KL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.116 Mobile Safari/537.36", + "expect": { + "vendor": "ASUS", + "model": "ZB602KL", + "type": "mobile" + } + }, + { + "desc": "ASUS Zenfone Max Pro (M1)", + "ua": "Mozilla/5.0 (Linux; Android 8.1; ASUS_X00TD Build/OPM1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36", + "expect": { + "vendor": "ASUS", + "model": "X00TD", + "type": "mobile" + } + }, + { + "desc": "ASUS Zenfone Max M2", + "ua": "Mozilla/5.0 (Linux; Android 8.1; ASUS_X01AD) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.99 Mobile Safari/537.36", + "expect": { + "vendor": "ASUS", + "model": "X01AD", + "type": "mobile" + } + }, + { + "desc": "ASUS Zenfone Max Pro M2", + "ua": "Mozilla/5.0 (Linux; Android 8.1; ASUS_X01BDA) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.99 Mobile Safari/537.36", + "expect": { + "vendor": "ASUS", + "model": "X01BDA", + "type": "mobile" + } + }, + { + "desc": "ASUS Zenfone Go", + "ua": "Mozilla/5.0 (Linux; Android 6.0; ASUS_X009DA Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36", + "expect": { + "vendor": "ASUS", + "model": "X009DA", + "type": "mobile" + } + }, + { + "desc": "Acer Iconia A1-810", + "ua": "Mozilla/5.0 (Linux; Android 4.2.2; A1-810 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Safari/537.36", + "expect": { + "vendor": "Acer", + "model": "A1-810", + "type": "tablet" + } + }, + { + "desc": "BlackBerry Priv", + "ua": "User-Agent: Mozilla/5.0 (Linux; Android 5.1.1; STV100-1 Build/LMY47V; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/46.0.2490.76 Mobile Safari/537.36", + "expect": { + "vendor": "BlackBerry", + "model": "STV100-1", + "type": "mobile" + } + }, + { + "desc": "BlackBerry Keyone", + "ua": "Mozilla/5.0 (Linux; Android 8.1.0; BBB100-1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.111 Mobile Safari/537.36", + "expect": { + "vendor": "BlackBerry", + "model": "BBB100-1", + "type": "mobile" + } + }, + { + "desc": "BlackBerry Key2", + "ua": "Mozilla/5.0 (Linux; Android 8.1.0; BBF100-1 Build/OPM1.171019.026) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.91 Mobile Safari/537.36", + "expect": { + "vendor": "BlackBerry", + "model": "BBF100-1", + "type": "mobile" + } + }, + { + "desc": "BlackBerry Key2 LE", + "ua": "User-Agent: Mozilla/5.0 (Linux; Android 8.1.0; BBE100-1 Build/OPM1.171019.026) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497 Mobile Safari/537.36", + "expect": { + "vendor": "BlackBerry", + "model": "BBE100-1", + "type": "mobile" + } + }, + { + "desc": "Blackview 4900Pro", + "ua": "Mozilla/5.0 (Linux; Android 12; BV4900Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "undefined", + "model": "BV4900Pro", + "type": "mobile" + } + }, + { + "desc": "Desktop (IE11 with Tablet string)", + "ua": "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729; Tablet PC 2.0; GWX:MANAGED; rv:11.0) like Gecko", + "expect": { + "vendor": "undefined", + "model": "undefined", + "type": "undefined" + } + }, + { + "desc": "Mobile (DuckDuckGo mobile browser)", + "ua": "Mozilla/5.0 (Linux; Android 8.1.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/92.0.4515.131 Mobile DuckDuckGo/5 Safari/537.36", + "expect": { + "vendor": "undefined", + "model": "undefined", + "type": "mobile" + } + }, + { + "desc": "Essential PH-1", + "ua": "Mozilla/5.0 (Linux; Android 9; PH-1 Build/PPR1.180905.036) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.86 Mobile Safari/537.36", + "expect": { + "vendor": "Essential", + "model": "PH-1", + "type": "mobile" + } + }, + { + "desc": "Fairphone 1U", + "ua": "Mozilla/5.0 (Linux; U; Android 4.2.2; FP1U Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + "expect": { + "vendor": "Fairphone", + "model": "FP1U", + "type": "mobile" + } + }, + { + "desc": "Fairphone 2", + "ua": "Mozilla/5.0 (Linux; Android 7.1.2; FP2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.136 Mobile Safari/537.36", + "expect": { + "vendor": "Fairphone", + "model": "FP2", + "type": "mobile" + } + }, + { + "desc": "Fairphone 3", + "ua": "Mozilla/5.0 (Linux; Android 9; FP3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.93 Mobile Safari/537.36", + "expect": { + "vendor": "Fairphone", + "model": "FP3", + "type": "mobile" + } + }, + { + "desc": "HTC Desire 820", + "ua": "Mozilla/5.0 (Linux; Android 6.0.1; HTC Desire 820 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36", + "expect": { + "vendor": "HTC", + "model": "Desire 820", + "type": "mobile" + } + }, + { + "desc": "HTC Evo Shift 4G", + "ua": "Mozilla/5.0 (Linux; U; Android 2.3.4; en-us; Sprint APA7373KT Build/GRJ22) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0", + "expect": { + "vendor": "Sprint", + "model": "APA7373KT", + "type": "mobile" + } + }, + { + "desc": "HTC Nexus 9", + "ua": "Mozilla/5.0 (Linux; Android 5.0; Nexus 9 Build/LRX21R) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Mobile Crosswalk/7.36.154.13 Safari/537.36", + "expect": { + "vendor": "HTC", + "model": "Nexus 9", + "type": "tablet" + } + }, + { + "desc": "Huawei Honor", + "ua": "Mozilla/5.0 (Linux; U; Android 2.3; xx-xx; U8860 Build/HuaweiU8860) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", + "expect": { + "vendor": "Huawei", + "model": "U8860", + "type": "mobile" + } + }, + { + "desc": "Huawei Honor 20 Pro", + "ua": "Mozilla/5.0 (Linux; Android 10; YAL-L41) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.127 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "YAL-L41", + "type": "mobile" + } + }, + { + "desc": "Huawei Honor 20 Pro", + "ua": "Mozilla/5.0 (Linux; Android 10; YAL-AL10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.127 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "YAL-AL10", + "type": "mobile" + } + }, + { + "desc": "Huawei Nexus 6P", + "ua": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 6P Build/MTC19V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.81 Mobile Safari/537", + "expect": { + "vendor": "Huawei", + "model": "Nexus 6P", + "type": "mobile" + } + }, + { + "desc": "Huawei P10", + "ua": "Mozilla/5.0 (Linux; Android 7.0; VTR-L09 Build/HUAWEIVTR-L09; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/56.0.2924.87 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "VTR-L09", + "type": "mobile" + } + }, + { + "desc": "Huawei Y3II", + "ua": "Mozilla/5.0 (Linux; U; Android 5.1; xx-xx; HUAWEI LUA-L03 Build/HUAWEILUA-L03) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "LUA-L03", + "type": "mobile" + } + }, + { + "desc": "HUAWEI MediaPad M3 Lite 10", + "ua": "Mozilla/5.0 (Linux; Android 7.0; BAH-L09) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "BAH-L09", + "type": "tablet" + } + }, + { + "desc": "HUAWEI MediaPad M5 Lite", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; BAH2-W19 Build/HUAWEIBAH2-W19; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "BAH2-W19", + "type": "tablet" + } + }, + { + "desc": "HUAWEI MediaPad M5", + "ua": "Mozilla/5.0 (Linux; Android 9; SHT-AL09 Build/HUAWEISHT-AL09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.90 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "SHT-AL09", + "type": "tablet" + } + }, + { + "desc": "HUAWEI MediaPad T5", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; AGS2-L09 Build/HUAWEIAGS2-L09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/84.0.4147.125 Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "AGS2-L09", + "type": "tablet" + } + }, + { + "desc": "HUAWEI MediaPad T10", + "ua": "Mozilla/5.0 (Linux; Android 10; AGR-W09 Build/HUAWEIAGR-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "AGR-W09", + "type": "tablet" + } + }, + { + "desc": "HUAWEI MediaPad T10s", + "ua": "Mozilla/5.0 (Linux; Android 10; AGS3-W09 Build/HUAWEIAGS3-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "AGS3-W09", + "type": "tablet" + } + }, + { + "desc": "Huawei MatePad T 10", + "ua": "Mozilla/5.0 (Linux; Android 10; AGR-L09; HMSCore 5.0.4.301) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 HuaweiBrowser/11.0.3.304 Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "AGR-L09", + "type": "tablet" + } + }, + { + "desc": "Huawei M3", + "ua": "Mozilla/5.0 (Linux; Android 7.0; BTV-W09) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.116 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "BTV-W09", + "type": "tablet" + } + }, + { + "desc": "Huawei Mate 10 Pro", + "ua": "Mozilla/5.0 (Linux; Android 8.0; BLA-L29 Build/HUAWEIBLA-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3236.6 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "BLA-L29", + "type": "mobile" + } + }, + { + "desc": "Huawei Mate X", + "ua": "Mozilla/5.0 (Linux; Android 9; TAH-AN00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.111 Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "TAH-AN00", + "type": "mobile" + } + }, + { + "desc": "Huawei Mate X2", + "ua": "Mozilla/5.0 (Linux; Android 10; TET-AN00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.96 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "TET-AN00", + "type": "mobile" + } + }, + { + "desc": "Huawei Mate 20 X", + "ua": "Mozilla/5.0 (Linux; Android 9; EVR-L29 Build/HUAWEIEVR-L29; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "EVR-L29", + "type": "mobile" + } + }, + { + "desc": "Huawei Mate 20 Pro", + "ua": "Mozilla/5.0 (Linux; Android 9; LYA-L09) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.90 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "LYA-L09", + "type": "mobile" + } + }, + { + "desc": "Huawei Mate 20 Pro", + "ua": "Mozilla/5.0 (Linux; Android 9; LYA-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.90 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "LYA-AL00", + "type": "mobile" + } + }, + { + "desc": "Huawei Mate 20 Pro", + "ua": "Mozilla/5.0 (Linux; Android 9; LYA-AL10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.90 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "LYA-AL10", + "type": "mobile" + } + }, + { + "desc": "Huawei Mate 20 Pro", + "ua": "Mozilla/5.0 (Linux; Android 9; LYA-L0C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.90 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "LYA-L0C", + "type": "mobile" + } + }, + { + "desc": "Huawei Mate 20 Pro", + "ua": "Mozilla/5.0 (Linux; Android 9; LYA-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.90 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "LYA-L29", + "type": "mobile" + } + }, + { + "desc": "Huawei Mate 20 Pro", + "ua": "Mozilla/5.0 (Linux; Android 9; LYA-TL00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.90 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "LYA-TL00", + "type": "mobile" + } + }, + { + "desc": "Huawei Mate 50 Pro", + "ua": "Mozilla/5.0 (Linux; Android 12; DCO-LX9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "DCO-LX9", + "type": "mobile" + } + }, + { + "desc": "Huawei P20 Lite", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.143 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "ANE-LX1", + "type": "mobile" + } + }, + { + "desc": "Huawei P20", + "ua": "Mozilla/5.0 (Linux; Android 8.1.0; EML-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "EML-L29", + "type": "mobile" + } + }, + { + "desc": "Huawei P20 Pro", + "ua": "Mozilla/5.0 (Linux; Android 9; CLT-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.90 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "CLT-L29", + "type": "mobile" + } + }, + { + "desc": "Huawei P30", + "ua": "Mozilla/5.0 (Linux; Android 9; ELE-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "ELE-L29", + "type": "mobile" + } + }, + { + "desc": "Huawei P30 Pro", + "ua": "Mozilla/5.0 (Linux; Android 9; VOG-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.143 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "VOG-L29", + "type": "mobile" + } + }, + { + "desc": "Huawei P40", + "ua": "Mozilla/5.0 (Linux; Android 10; ANA-AN00 Build/HUAWEIANA-AN00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/76.0.3809.89 Mobile Safari/537.36 T7/11.26 SP-engine/2.22.0 baiduboxapp/11.26.0.10 (Baidu; P1 10) NABar/1.0", + "expect": { + "vendor": "Huawei", + "model": "ANA-AN00", + "type": "mobile" + } + }, + { + "desc": "Huawei P40 Pro", + "ua": "Mozilla/5.0 (Linux; Android 10; ELS-AN00 Build/HUAWEIELS-AN00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Mobile Safari/537.36 mailapp/6.0.0", + "expect": { + "vendor": "Huawei", + "model": "ELS-AN00", + "type": "mobile" + } + }, + { + "desc": "Huawei 30 Pro+", + "ua": "Mozilla/5.0 (Linux; Android 10; EBG-AN10 Build/HUAWEIEBG-AN10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.86 Mobile Safari/537.36 EdgA/42.0.0.2741", + "expect": { + "vendor": "Huawei", + "model": "EBG-AN10", + "type": "mobile" + } + }, + { + "desc": "Huawei 30S", + "ua": "Mozilla/5.0 (Linux; Android 10; CDY-AN90 Build/HUAWEICDY-AN90; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Mobile Safari/537.36 mailapp/5.8.0", + "expect": { + "vendor": "Huawei", + "model": "CDY-AN90", + "type": "mobile" + } + }, + { + "desc": "Huawei Nova 5T", + "ua": "Mozilla/5.0 (Linux; Android 10; YAL-L21) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "YAL-L21", + "type": "mobile" + } + }, + { + "desc": "Huawei Nova 5T", + "ua": "Mozilla/5.0 (Linux; Android 10; YAL-L61) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "YAL-L61", + "type": "mobile" + } + }, + { + "desc": "Huawei Nova 5T", + "ua": "Mozilla/5.0 (Linux; Android 10; YAL-L71) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "YAL-L71", + "type": "mobile" + } + }, + { + "desc": "Huawei Nova 5T", + "ua": "Mozilla/5.0 (Linux; Android 10; YAL-L61D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "YAL-L61D", + "type": "mobile" + } + }, + { + "desc": "Huawei Nova 5T", + "ua": "Mozilla/5.0 (Linux; Android 10; YALE-L61A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "YALE-L61A", + "type": "mobile" + } + }, + { + "desc": "Huawei Nova 5T", + "ua": "Mozilla/5.0 (Linux; Android 10; YALE-L61D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "YALE-L61D", + "type": "mobile" + } + }, + { + "desc": "Huawei Nova 5T", + "ua": "Mozilla/5.0 (Linux; Android 10; YALE-L71A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "YALE-L71A", + "type": "mobile" + } + }, + { + "desc": "Huawei Enjoy10e", + "ua": "Dalvik/2.1.0 (Linux; U; Android 10; MED-AL00 Build/HUAWEIMED-AL00)", + "expect": { + "vendor": "Huawei", + "model": "MED-AL00", + "type": "mobile" + } + }, + { + "desc": "Huawei Honor 6A", + "ua": "Mozilla/5.0 (Linux; Android 7.0; DLI-L22 Build/HONORDLI-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/79.0.3945.116 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/252.0.0.22.355;]", + "expect": { + "vendor": "Huawei", + "model": "DLI-L22", + "type": "mobile" + } + }, + { + "desc": "Huawei Honor 7", + "ua": "Mozilla/5.0 (Linux; Android 6.0; PLK-L01 Build/HONORPLK-L01; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/79.0.3945.116 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "PLK-L01", + "type": "mobile" + } + }, + { + "desc": "Huawei 10 Lite", + "ua": "Mozilla/5.0 (Linux; Android 9; HRY-LX1 Build/HONORHRY-LX1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.91 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "HRY-LX1", + "type": "mobile" + } + }, + { + "desc": "Huawei Y7 2018", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; LDN-L01) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.62 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "LDN-L01", + "type": "mobile" + } + }, + { + "desc": "Huawei Honor 8X", + "ua": "Mozilla/5.0 (Linux; Android 9; JSN-L21) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "JSN-L21", + "type": "mobile" + } + }, + { + "desc": "Huawei Y6 2019", + "ua": "Mozilla/5.0 (Linux; Android 9; MRD-LX1N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "MRD-LX1N", + "type": "mobile" + } + }, + { + "desc": "Huawei Y9 2019", + "ua": "Mozilla/5.0 (Linux; Android 9; JKM-LX2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "JKM-LX2", + "type": "mobile" + } + }, + { + "desc": "Huawei Y5", + "ua": "Mozilla/5.0 (Linux; Android 9; AMN-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.116 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "AMN-LX3", + "type": "mobile" + } + }, + { + "desc": "Huawei Y7p", + "ua": "Mozilla/5.0 (Linux; Android 9; ART-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "ART-L29", + "type": "mobile" + } + }, + { + "desc": "Huawei Mate 20 Lite", + "ua": "Mozilla/5.0 (Linux; Android 8.1.0; SNE-LX1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.116 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "SNE-LX1", + "type": "mobile" + } + }, + { + "desc": "Huawei P10 Lite", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX1A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "WAS-LX1A", + "type": "mobile" + } + }, + { + "desc": "Huawei Y5 Lite 2018", + "ua": "Mozilla/5.0 (Linux; Android 8.1.0; DRA-LX5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "DRA-LX5", + "type": "mobile" + } + }, + { + "desc": "Huawei Honor 8C", + "ua": "Mozilla/5.0 (Linux; Android 8.1.0; BKK-LX2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.136 Mobile Safari/537.36", + "expect": { + "vendor": "Huawei", + "model": "BKK-LX2", + "type": "mobile" + } + }, + { + "desc": "Infinix Hot 7 Pro", + "ua": "Mozilla/5.0 (Linux; Android 9; Infinix X625C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Infinix", + "model": "X625C", + "type": "mobile" + } + }, + { + "desc": "Infinix Hot 10T", + "ua": "Mozilla/5.0 (Linux; Android 11; Infinix X689C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Infinix", + "model": "X689C", + "type": "mobile" + } + }, + { + "desc": "Infinix Hot 11s", + "ua": "Mozilla/5.0 (Linux; Android 11; Infinix X6812 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/111.0.5563.116 Mobile Safari/537.36", + "expect": { + "vendor": "Infinix", + "model": "X6812", + "type": "mobile" + } + }, + { + "desc": "Infinix Smart 5", + "ua": "Mozilla/5.0 (Linux; Android 10; Infinix X657C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Infinix", + "model": "X657C", + "type": "mobile" + } + }, + { + "desc": "Infinix Zero 5G", + "ua": "Mozilla/5.0 (Linux; Android 12; Infinix X6815B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Infinix", + "model": "X6815B", + "type": "mobile" + } + }, + { + "desc": "Apple Desktop", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15", + "expect": { + "vendor": "Apple", + "model": "Macintosh", + "type": "undefined" + } + }, + { + "desc": "Apple Watch", + "ua": "atc/1.0 watchOS/7.3.3 model/Watch4,2 hwp/t8006 build/18S830 (6; dt:191)", + "expect": { + "vendor": "Apple", + "model": "watch", + "type": "wearable" + } + }, + { + "desc": "iPad using UCBrowser", + "ua": "Mozilla/5.0 (iPad; U; CPU OS 11_2 like Mac OS X; zh-CN; iPad5,3) AppleWebKit/534.46 (KHTML, like Gecko) UCBrowser/3.0.1.776 U3/ Mobile/10A403 Safari/7543.48.3", + "expect": { + "vendor": "Apple", + "model": "iPad", + "type": "tablet" + } + }, + { + "desc": "iPad Air", + "ua": "Mozilla/5.0 (iPad; CPU OS 12_4_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 [FBAN/FBIOS;FBDV/iPad4,1;FBMD/iPad;FBSN/iOS;FBSV/12.4.5;FBSS/2;FBID/tablet;FBLC/en_US;FBOP/5;FBCR/]", + "expect": { + "vendor": "Apple", + "model": "iPad", + "type": "tablet" + } + }, + { + "desc": "iPad using Facebook Browser", + "ua": "Mozilla/5.0 (iPad; CPU OS 14_4_2 like Mac OS X) WebKit/8610 (KHTML, like Gecko) Mobile/18D70 [FBAN/FBIOS;FBDV/iPad7,11;FBMD/iPad;FBSN/iOS;FBSV/14.4.2;FBSS/2;FBID/tablet;FBLC/en_US;FBOP/5]", + "expect": { + "vendor": "Apple", + "model": "iPad", + "type": "tablet" + } + }, + { + "desc": "iPod", + "ua": "Mozilla/5.0 (iPod touch; CPU iPhone OS 7_0_4 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11B554a Safari/9537.53", + "expect": { + "vendor": "Apple", + "model": "iPod touch", + "type": "mobile" + } + }, + { + "desc": "JVC LT-43V55LFA Smart TV", + "ua": "Mozilla/5.0 (Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36 OPR/40.0.2207.0 OMI/4.9.0.237.DOM3-OPT.245 Model/Vestel-MB211 VSTVB MB200 HbbTV/1.2.1 (; JVC; MB211; 3.19.4.2; _TV_NT72563_2017 SmartTvA/3.0.0", + "expect": { + "vendor": "JVC", + "model": "MB211", + "type": "smarttv" + } + }, + { + "desc": "JVC LT-43V65LUA Smart TV", + "ua": "Mozilla/5.0 (Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36 OPR/40.0.2207.0 OMI/4.9.0.237.DOM3-OPT.245 Model/Vestel-MB130 VSTVB MB100 HbbTV/1.2.1 (; JVC; MB130; 5.7.20.0; _TV_G10_2017;) SmartTvA/3.0.0", + "expect": { + "vendor": "JVC", + "model": "MB130", + "type": "smarttv" + } + }, + { + "desc": "Kobo eReader", + "ua": "Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Kobo eReader Safari/538.1", + "expect": { + "vendor": "Kobo", + "model": "eReader", + "type": "tablet" + } + }, + { + "desc": "Kobo Touch", + "ua": "Mozilla/5.0 (Linux; U; Android 2.0; en-us;) AppleWebKit/538.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/538.1 (Kobo Touch 0377/4.20.14622)", + "expect": { + "vendor": "Kobo", + "model": "Touch", + "type": "tablet" + } + }, + { + "desc": "Lenovo Tab 2", + "ua": "Mozilla/5.0 (Linux; Android 5.0.1; Lenovo TAB 2 A7-30HC Build/LRX21M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.157 Safari/537.36", + "expect": { + "vendor": "Lenovo", + "model": "TAB 2 A7", + "type": "tablet" + } + }, + { + "desc": "Lenovo Phone", + "ua": "Mozilla/5.0 (Linux; Android 6.0; Lenovo PB2-650M Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.105 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/311.0.0.44.117;]", + "expect": { + "vendor": "Lenovo", + "model": "PB2-650M", + "type": "mobile" + } + }, + { + "desc": "Lenovo Tab 3 Pro", + "ua": "Mozilla/5.0 (Linux; Android 6.0.1; Lenovo YT3-X90F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.99 Safari/537.36", + "expect": { + "vendor": "Lenovo", + "model": "YT3-X90F", + "type": "tablet" + } + }, + { + "desc": "Lenovo Tab 4", + "ua": "Mozilla/5.0 (Linux; Android 7.1.1; Lenovo TB-X304F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.99 Safari/537.36", + "expect": { + "vendor": "Lenovo", + "model": "TB-X304F", + "type": "tablet" + } + }, + { + "desc": "Lenovo Tab 4", + "ua": "Mozilla/5.0 (Linux; Android 4.4.2; Lenovo TAB 2 A7-30HC) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36", + "expect": { + "vendor": "Lenovo", + "model": "TAB 2 A7", + "type": "tablet" + } + }, + { + "desc": "Lenovo Tab 7 Essential", + "ua": "Mozilla/5.0 (Linux; Android 7.0; Lenovo TB-7304X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36", + "expect": { + "vendor": "Lenovo", + "model": "TB-7304X", + "type": "tablet" + } + }, + { + "desc": "Lenovo Tab M10", + "ua": "Mozilla/5.0 (Linux; arm_64; Android 9; Lenovo TB-X606F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.127 YaBrowser/20.9.4.99.01 Safari/537.36", + "expect": { + "vendor": "Lenovo", + "model": "TB-X606F", + "type": "tablet" + } + }, + { + "desc": "LG V40 ThinQ", + "ua": "Mozilla/5.0 (Linux; Android 9; LM-V405) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36", + "expect": { + "vendor": "LG", + "model": "LM-V405", + "type": "mobile" + } + }, + { + "desc": "LG K30", + "ua": "Mozilla/5.0 (Linux; Android 8.1.0; LM-X410.F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.116 Mobile Safari/537.36", + "expect": { + "vendor": "LG", + "model": "LM-X410.F", + "type": "mobile" + } + }, + { + "desc": "LG K30", + "ua": "Mozilla/5.0 (Linux; Android 9; LM-X410.FGN) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.93 Mobile Safari/537.36", + "expect": { + "vendor": "LG", + "model": "LM-X410.FGN", + "type": "mobile" + } + }, + { + "desc": "LG K40", + "ua": "Mozilla/5.0 (Linux; Android 10; LM-X420) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.5563.57 Mobile Safari/537.36", + "expect": { + "vendor": "LG", + "model": "LM-X420", + "type": "mobile" + } + }, + { + "desc": "LG Stylo 4", + "ua": "Mozilla/5.0 (Linux; Android 10; LM-Q710(FGN)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.5563.57 Mobile Safari/537.36", + "expect": { + "vendor": "undefined", + "model": "LM-Q710(FGN)", + "type": "mobile" + } + }, + { + "desc": "LG Stylo 5", + "ua": "Mozilla/5.0 (Linux; Android 9; LM-Q720) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.96 Mobile Safari/537.36", + "expect": { + "vendor": "LG", + "model": "LM-Q720", + "type": "mobile" + } + }, + { + "desc": "LG G7 ThinQ", + "ua": "Mozilla/5.0 (Linux; Android 9; LM-G710VM Build/PKQ1.181105.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/79.0.3945.136 Mobile Safari/537.36", + "expect": { + "vendor": "LG", + "model": "LM-G710VM", + "type": "mobile" + } + }, + { + "desc": "LG K20", + "ua": "Mozilla/5.0 (Android 13; Mobile; LG-M255; rv:111.0) Gecko/111.0 Firefox/111.0", + "expect": { + "vendor": "LG", + "model": "M255", + "type": "mobile" + } + }, + { + "desc": "LG K500", + "ua": "Mozilla/5.0 (Linux; Android 6.0.1; LG-K500 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "expect": { + "vendor": "LG", + "model": "K500", + "type": "mobile" + } + }, + { + "desc": "LG Nexus 4", + "ua": "Mozilla/5.0 (Linux; Android 4.2.1; Nexus 4 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19", + "expect": { + "vendor": "LG", + "model": "Nexus 4", + "type": "mobile" + } + }, + { + "desc": "LG Nexus 4", + "ua": "Mozilla/5.0 (Linux; U; Android 4.3; en-us; Google Nexus 4 - 4.3 - API 18 - 768x1280 Build/JLS36G) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + "expect": { + "vendor": "LG", + "model": "Nexus 4", + "type": "mobile" + } + }, + { + "desc": "LG Nexus 5", + "ua": "Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19", + "expect": { + "vendor": "LG", + "model": "Nexus 5", + "type": "mobile" + } + }, + { + "desc": "LG Wing", + "ua": "Mozilla/5.0 (Linux; Android 10; LM-F100N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.101 Mobile Safari/537.36", + "expect": { + "vendor": "LG", + "model": "LM-F100N", + "type": "mobile" + } + }, + { + "desc": "LG Smart TV", + "ua": "Mozilla/5.0 (DirectFB; U; Linux mips; en) AppleWebKit/528.5+ (KHTML, like Gecko, Safari/528.5+) LG Browser (; LG NetCast.TV-2011)", + "expect": { + "vendor": "LG", + "model": "undefined", + "type": "smarttv" + } + }, + { + "desc": "LG Smart TV", + "ua": "Mozilla/5.0 (Linux; NetCast; U) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/53.0.2785 34 Safari/537.31 SmartTV/8.5", + "expect": { + "vendor": "LG", + "model": "undefined", + "type": "smarttv" + } + }, + { + "desc": "LG Android TV", + "ua": "Mozilla/5.0 (Linux; U; Android 4.2.2; zh-cn; LG Android TV Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30", + "expect": { + "vendor": "LG", + "model": "undefined", + "type": "smarttv" + } + }, + { + "desc": "Loewe Smart TV", + "ua": "Mozilla/5.0 (Linux; U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36 OPR/46.0.2207.0 LOEWE-SL410/5.2.0.0 HbbTV/1.4.1 (; LOEWE; SL410; LOH/5.2.0.0;;) FVC/3.0 (LOEWE; SL410;) CE-HTML/1.0 Config (L:deu,CC:DEU) NETRANGEMMH", + "expect": { + "vendor": "LOEWE", + "model": "SL410", + "type": "smarttv" + } + }, + { + "desc": "Meizu M5 Note", + "ua": "Mozilla/5.0 (Linux; Android 6.0; M5 Note Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.49 Mobile MQQBrowser/6.2 TBS/043024 Safari/537.36 MicroMessenger/6.5.7.1040 NetType/WIFI Language/zh_CN", + "expect": { + "vendor": "Meizu", + "model": "M5 Note", + "type": "mobile" + } + }, + { + "desc": "Microsoft Lumia 950", + "ua": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586", + "expect": { + "vendor": "Microsoft", + "model": "Lumia 950", + "type": "mobile" + } + }, + { + "desc": "Microsoft Surface Duo", + "ua": "Dalvik/2.1.0 (Linux; U; Android 10; Surface Duo Build/2020.1014.61)", + "expect": { + "vendor": "Microsoft", + "model": "Surface Duo", + "type": "tablet" + } + }, + { + "desc": "Motorola Moto X", + "ua": "Mozilla/5.0 (Linux; Android 4.4.4; XT1097 Build/KXE21.187-38) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.109 Mobile Safari/537.36", + "expect": { + "vendor": "Motorola", + "model": "XT1097", + "type": "mobile" + } + }, + { + "desc": "Motorola Moto Z3 Play", + "ua": "Mozilla/5.0 (Linux; Android 9; Moto Z3 Play) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Motorola", + "model": "Moto Z3 Play", + "type": "mobile" + } + }, + { + "desc": "Meizu M3S", + "ua": "Mozilla/5.0 (X11; Linux; Android 5.1; MZ-M3s Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrom/45.0.2454.94 Mobile Safari/537.36", + "expect": { + "vendor": "Meizu", + "model": "M3s", + "type": "mobile" + } + }, + { + "desc": "Microsoft Lumia 950", + "ua": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586", + "expect": { + "vendor": "Microsoft", + "model": "Lumia 950", + "type": "mobile" + } + }, + { + "desc": "Motorola Nexus 6", + "ua": "Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.20 Mobile Safari/537.36", + "expect": { + "vendor": "Motorola", + "model": "Nexus 6", + "type": "mobile" + } + }, + { + "desc": "Motorola Droid RAZR 4G", + "ua": "Mozilla/5.0 (Linux; U; Android 2.3; xx-xx; DROID RAZR 4G Build/6.5.1-73_DHD-11_M1-29) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", + "expect": { + "vendor": "Motorola", + "model": "DROID RAZR 4G", + "type": "mobile" + } + }, + { + "desc": "Motorola RAZR 2019", + "ua": "Mozilla/5.0 (Linux; Android 9; motorola razr) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/11.1 Chrome/75.0.3770.143 Mobile Safari/537.36", + "expect": { + "vendor": "Motorola", + "model": "razr", + "type": "mobile" + } + }, + { + "desc": "iPhone", + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53", + "expect": { + "vendor": "Apple", + "model": "iPhone", + "type": "mobile" + } + }, + { + "desc": "iPhone SE", + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 [FBAN/FBIOS;FBDV/iPhone8,4;FBMD/iPhone;FBSN/iOS;FBSV/13.3.1;FBSS/2;FBID/phone;FBLC/en_US;FBOP/5;FBCR/]", + "expect": { + "vendor": "Apple", + "model": "iPhone", + "type": "mobile" + } + }, + { + "desc": "iPhone SE using Facebook App", + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 [FBAN/FBIOS;FBDV/iPhone8,4;FBMD/iPhone;FBSN/iOS;FBSV/13.3.1;FBSS/2;FBID/phone;FBLC/en_US;FBOP/5;FBCR/]", + "expect": { + "vendor": "Apple", + "model": "iPhone", + "type": "mobile" + } + }, + { + "desc": "iPhone 11 Pro Max", + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 [FBAN/FBIOS;FBDV/iPhone12,5;FBMD/iPhone;FBSN/iOS;FBSV/13.3.1;FBSS/3;FBID/phone;FBLC/en_US;FBOP/5;FBCR/]", + "expect": { + "vendor": "Apple", + "model": "iPhone", + "type": "mobile" + } + }, + { + "desc": "iPhone XS", + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 [FBAN/FBIOS;FBDV/iPhone11,2;FBMD/iPhone;FBSN/iOS;FBSV/13.3.1;FBSS/3;FBID/phone;FBLC/en_US;FBOP/5;FBCR/]", + "expect": { + "vendor": "Apple", + "model": "iPhone", + "type": "mobile" + } + }, + { + "desc": "iPod touch", + "ua": "Mozilla/5.0 (iPod touch; CPU iPhone OS 7_0_2 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A501 Safari/9537.53", + "expect": { + "vendor": "Apple", + "model": "iPod touch", + "type": "mobile" + } + }, + { + "desc": "Moto X", + "ua": "Mozilla/5.0 (Linux; U; Android 4.2; xx-xx; XT1058 Build/13.9.0Q2.X-70-GHOST-ATT_LE-2) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + "expect": { + "vendor": "Motorola", + "model": "XT1058", + "type": "mobile" + } + }, + { + "desc": "Motorola Moto g(6) Play", + "ua": "Mozilla/5.0 (Linux; Android 9; moto g(6) play) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.136 Mobile Safari/537.36", + "expect": { + "vendor": "Motorola", + "model": "moto g(6) play", + "type": "mobile" + } + }, + { + "desc": "Motorola Moto g(7) Supra", + "ua": "Mozilla/5.0 (Linux; Android 9; moto g(7) supra Build/PCOS29.114-134-2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/73.0.3683.90 Mobile Safari/537.36", + "expect": { + "vendor": "Motorola", + "model": "moto g(7) supra", + "type": "mobile" + } + }, + { + "desc": "Motorola Moto E", + "ua": "Mozilla/5.0 (Linux; Android 7.1.1; Moto E (4) Build/NDQS26.69-64-11-7; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/56.0.2924.87 Mobile Safari/537.36", + "expect": { + "vendor": "Motorola", + "model": "Moto E (4)", + "type": "mobile" + } + }, + { + "desc": "Nokia3xx", + "ua": "Nokia303/14.87 CLDC-1.1", + "expect": { + "vendor": "Nokia", + "model": "303", + "type": "mobile" + } + }, + { + "desc": "Nokia 3.2", + "ua": "Mozilla/5.0 (Linux; Android 10; Nokia 3.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36", + "expect": { + "vendor": "Nokia", + "model": "3.2", + "type": "mobile" + } + }, + { + "desc": "Nokia 7", + "ua": "Mozilla/5.0 (Linux; Android 11; Nokia 7.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Nokia", + "model": "7.2", + "type": "mobile" + } + }, + { + "desc": "Nokia N9", + "ua": "Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13", + "expect": { + "vendor": "Nokia", + "model": "N9", + "type": "mobile" + } + }, + { + "desc": "Nokia 2720 Flip", + "ua": "Mozilla/5.0 (Mobile; Nokia_2720_Flip; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5.2", + "expect": { + "vendor": "Nokia", + "model": "2720 Flip", + "type": "mobile" + } + }, + { + "desc": "Oculus Quest", + "ua": "Mozilla/5.0 (Linux; Android 10; Quest) AppleWebKit/537.36 (KHTML, like Gecko) OculusBrowser/15.0.0.0.22.280317669 SamsungBrowser/4.0 Chrome/89.0.4389.90 VR Safari/537.36", + "expect": { + "vendor": "Facebook", + "model": "Quest", + "type": "wearable" + } + }, + { + "desc": "Oculus Quest 2", + "ua": "Mozilla/5.0 (Linux; Android 10; Quest 2) AppleWebKit/537.36 (KHTML, like Gecko) OculusBrowser/15.0.0.0.22.280317669 SamsungBrowser/4.0 Chrome/89.0.4389.90 VR Safari/537.36", + "expect": { + "vendor": "Facebook", + "model": "Quest 2", + "type": "wearable" + } + }, + { + "desc": "Oculus Quest Pro", + "ua": "Mozilla/5.0 (X11; Linux x86_64; Quest Pro) AppleWebKit/537.36 (KHTML, like Gecko) OculusBrowser/24.4.0.22.60.426469926 SamsungBrowser/4.0 Chrome/106.0.5249.181 VR Safari/537.36", + "expect": { + "vendor": "Facebook", + "model": "Quest Pro", + "type": "wearable" + } + }, + { + "desc": "OnePlus One", + "ua": "Mozilla/5.0 (Linux; Android 4.4.4; A0001 Build/KTU84Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.59 Mobile Safari/537.36", + "expect": { + "vendor": "OnePlus", + "model": "A0001", + "type": "mobile" + } + }, + { + "desc": "OnePlus One", + "ua": "Mozilla/5.0 (Linux; Android 4.4.2; OnePlus One A0001 Build/KVT49L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.117 Mobile Safari/537.36", + "expect": { + "vendor": "OnePlus", + "model": "A0001", + "type": "mobile" + } + }, + { + "desc": "OnePlus 2", + "ua": "Mozilla/5.0 (Linux; Android 6.0.1; ONE A2003) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.93 Mobile Safari/537.36", + "expect": { + "vendor": "OnePlus", + "model": "A2003", + "type": "mobile" + } + }, + { + "desc": "OnePlus 3", + "ua": "Mozilla/5.0 (Linux; Android 7.1.1; ONEPLUS A3000 Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36", + "expect": { + "vendor": "OnePlus", + "model": "A3000", + "type": "mobile" + } + }, + { + "desc": "OnePlus 6", + "ua": "Mozilla/5.0 (Linux; Android 9; ONEPLUS A6003) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.89 Mobile Safari/537.36", + "expect": { + "vendor": "OnePlus", + "model": "A6003", + "type": "mobile" + } + }, + { + "desc": "OnePlus 6T", + "ua": "Mozilla/5.0 (Linux; Android 9; ONEPLUS A6010) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.96 Mobile Safari/537.36", + "expect": { + "vendor": "OnePlus", + "model": "A6010", + "type": "mobile" + } + }, + { + "desc": "OnePlus 7T Pro", + "ua": "Mozilla/5.0 (Linux; Android 10; HD1913) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.5563.57 Mobile Safari/537.36 EdgA/110.0.1587.66", + "expect": { + "vendor": "undefined", + "model": "HD1913", + "type": "mobile" + } + }, + { + "desc": "OnePlus 8T", + "ua": "Mozilla/5.0 (Linux; Android 11; KB2005) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.127 Mobile Safari/537.36", + "expect": { + "vendor": "OnePlus", + "model": "KB2005", + "type": "mobile" + } + }, + { + "desc": "OnePlus 8 Pro", + "ua": "Mozilla/5.0 (Linux; Android 10; IN2025) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.119 Mobile Safari/537.36", + "expect": { + "vendor": "OnePlus", + "model": "IN2025", + "type": "mobile" + } + }, + { + "desc": "OnePlus 10RT", + "ua": "Mozilla/5.0 (Linux; Android 13; CPH2413) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "OPPO", + "model": "CPH2413", + "type": "mobile" + } + }, + { + "desc": "OnePlus Nord N100", + "ua": "Mozilla/5.0 (Linux; Android 10; BE2015 Build/QKQ1.200719.002; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36", + "expect": { + "vendor": "OnePlus", + "model": "BE2015", + "type": "mobile" + } + }, + { + "desc": "OnePlus Nord N10 5G", + "ua": "Mozilla/5.0 (Linux; Android 10; BE2029) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.185 Mobile Safari/537.36", + "expect": { + "vendor": "OnePlus", + "model": "BE2029", + "type": "mobile" + } + }, + { + "desc": "OPPO Neo", + "ua": "Mozilla/5.0 (Linux; U; Android 4.2.2; zh-cn; R831T Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 OppoBrowser/3.3.2 Mobile Safari/534.30", + "expect": { + "vendor": "OPPO", + "model": "R831T", + "type": "mobile" + } + }, + { + "desc": "OPPO R7s", + "ua": "Mozilla/5.0 (Linux; U; Android 4.4.4; zh-cn; OPPO R7s Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko)Version/4.0 Chrome/37.0.0.0 MQQBrowser/7.1 Mobile Safari/537.36", + "expect": { + "vendor": "OPPO", + "model": "R7s", + "type": "mobile" + } + }, + { + "desc": "OPPO A3s", + "ua": "Mozilla/5.0 (Linux; Android 8.1; CPH1803 Build/OPM1.171019.026; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 Mobile Safari/537.36", + "expect": { + "vendor": "OPPO", + "model": "CPH1803", + "type": "mobile" + } + }, + { + "desc": "OPPO A12", + "ua": "Mozilla/5.0 (Linux; Android 9; CPH2083) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.116 Mobile Safari/537.36", + "expect": { + "vendor": "OPPO", + "model": "CPH2083", + "type": "mobile" + } + }, + { + "desc": "OPPO Reno", + "ua": "Mozilla/5.0 (Linux; Android 9; PCAT00 Build/PKQ1.190101.001; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Mobile Safari/537.36", + "expect": { + "vendor": "OPPO", + "model": "PCAT00", + "type": "mobile" + } + }, + { + "desc": "OPPO Reno3 Pro 5G", + "ua": "Mozilla/5.0 (Linux; Android 10; PCLM50) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.117 Mobile Safari/537.36", + "expect": { + "vendor": "OPPO", + "model": "PCLM50", + "type": "mobile" + } + }, + { + "desc": "OPPO Reno4 SE", + "ua": "Mozilla/5.0 (Linux; U; Android 10; xx-xx; PEAM00 Build/QP1A.190711.020) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36", + "expect": { + "vendor": "OPPO", + "model": "PEAM00", + "type": "mobile" + } + }, + { + "desc": "OPPO Reno4 5G", + "ua": "Mozilla/5.0 (Linux; Android 10; PDPM00 Build/QKQ1.200216.002; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/76.0.3809.89 Mobile Safari/537.36", + "expect": { + "vendor": "OPPO", + "model": "PDPM00", + "type": "mobile" + } + }, + { + "desc": "OPPO Reno4 Pro 5G", + "ua": "Mozilla/5.0 (Linux; U; Android 10; xx-xx; PDNT00 Build/QKQ1.200216.002) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36", + "expect": { + "vendor": "OPPO", + "model": "PDNT00", + "type": "mobile" + } + }, + { + "desc": "OPPO Reno5 A", + "ua": "Mozilla/5.0 (Linux; Android 11; A101OP) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Mobile Safari/537.36", + "expect": { + "vendor": "OPPO", + "model": "A101OP", + "type": "mobile" + } + }, + { + "desc": "OPPO Find X", + "ua": "Mozilla/5.0 (Linux; Android 8.1; PAFM00 Build/OPM1.171019.026) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "expect": { + "vendor": "OPPO", + "model": "PAFM00", + "type": "mobile" + } + }, + { + "desc": "OPPO Find 7a", + "ua": "Mozilla/5.0 (Linux; U; Android 4.3; xx-xx; X9007 Build/JLS36C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + "expect": { + "vendor": "OPPO", + "model": "X9007", + "type": "mobile" + } + }, + { + "desc": "Realme C1", + "ua": "Mozilla/5.0 (Linux; Android 8.1; RMX1811 Build/OPM1.171019.026) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.126 Mobile Safari/537.36", + "expect": { + "vendor": "Realme", + "model": "RMX1811", + "type": "mobile" + } + }, + { + "desc": "Realme C2", + "ua": "Mozilla/5.0 (Linux; Android 9; RMX1941) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Mobile Safari/537.36", + "expect": { + "vendor": "Realme", + "model": "RMX1941", + "type": "mobile" + } + }, + { + "desc": "Realme Narzo 20", + "ua": "Mozilla/5.0 (Linux; U; Android 10; xx-xx; RMX2193 Build/QP1A.190711.020) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36", + "expect": { + "vendor": "Realme", + "model": "RMX2193", + "type": "mobile" + } + }, + { + "desc": "Realme 2 Pro", + "ua": "Mozilla/5.0 (Linux; Android 9; RMX1801) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36", + "expect": { + "vendor": "Realme", + "model": "RMX1801", + "type": "mobile" + } + }, + { + "desc": "Realme 3 Pro", + "ua": "Mozilla/5.0 (Linux; Android 11; RMX1851) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Realme", + "model": "RMX1851", + "type": "mobile" + } + }, + { + "desc": "Realme 8", + "ua": "Mozilla/5.0 (Linux; Android 12; RMX3085) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Realme", + "model": "RMX3085", + "type": "mobile" + } + }, + { + "desc": "Realme 9 Pro", + "ua": "Mozilla/5.0 (Linux; Android 13; RMX3471) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Realme", + "model": "RMX3471", + "type": "mobile" + } + }, + { + "desc": "Realme GT Master", + "ua": "Mozilla/5.0 (Linux; Android 13; RMX3363) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Realme", + "model": "RMX3363", + "type": "mobile" + } + }, + { + "desc": "Panasonic T31", + "ua": "Mozilla/5.0 (Linux; Android 4.2.2; Panasonic T31 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.170 Mobile Safari/537.36 ", + "expect": { + "vendor": "Panasonic", + "model": "T31", + "type": "mobile" + } + }, + { + "desc": "Panasonic TX-32CSW514 SmartTV", + "ua": "HbbTV/1.2.1 (;Panasonic;VIERA 2015;3.014;a001-003 4000-0000;)", + "expect": { + "vendor": "Panasonic", + "model": "VIERA 2015", + "type": "smarttv" + } + }, + { + "desc": "Panasonic TX-40FXW724 SmartTV", + "ua": "HbbTV/1.4.1 (+DRM;Panasonic;SmartTV2018mid;3.024;4301-0003 0002-0000;SmartTV2018;)", + "expect": { + "vendor": "Panasonic", + "model": "SmartTV2018mid", + "type": "smarttv" + } + }, + { + "desc": "Panasonic TX-43HXW904 SmartTV", + "ua": "HbbTV/1.5.1 (+DRM;Panasonic;SmartTV2020mid;3.326;4301-0003 0008-0000;com.panasonic.SmartTV2020mid;)", + "expect": { + "vendor": "Panasonic", + "model": "SmartTV2020mid", + "type": "smarttv" + } + }, + { + "desc": "Panasonic DMR-HST130 SAT receiver", + "ua": "HbbTV/1.1.1 (+PVR;Panasonic;DIGA WebKit M8658;3.420;;)", + "expect": { + "vendor": "Panasonic", + "model": "DIGA WebKit M8658", + "type": "smarttv" + } + }, + { + "desc": "Philips SmartTV", + "ua": "Opera/9.80 HbbTV/1.1.1 (; Philips; ; ; ; ) NETTV/4.0.2; en) Version/11.60", + "expect": { + "vendor": "Philips", + "model": "", + "type": "smarttv" + } + }, + { + "desc": "Philips 32PFL6606K/02 SmartTV (2011)", + "ua": "Opera/9.80 (Linux mips ; U; HbbTV/1.1.1 (; Philips; ; ; ; ) CE-HTML/1.0 NETTV/3.1.0; en) Presto/2.6.33 Version/10.70", + "expect": { + "vendor": "Philips", + "model": "", + "type": "smarttv" + } + }, + { + "desc": "Philips 32PFL6606K/02 SmartTV (2013)", + "ua": "Opera/9.80 (Linux mips ; U; HbbTV/1.1.1 (; Philips; ; ; ; ) CE-HTML/1.0 NETTV/3.1.0; en) Presto/2.6.33 Version/10.70", + "expect": { + "vendor": "Philips", + "model": "", + "type": "smarttv" + } + }, + { + "desc": "Philips 32PHS5301/12 SmartTV (2016)", + "ua": "Mozilla/5.0 (Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.152 Safari/537.36 OPR/29.0.1803.0 OMI/4.5.23.37.MOT2.13 HbbTV/1.2.1 (;Philips;32PHS5301/12;;_TV_MT5800;) Firmware/TPM161E_012.002.045.001 en", + "expect": { + "vendor": "Philips", + "model": "32PHS5301/12", + "type": "smarttv" + } + }, + { + "desc": "Roku", + "ua": "Mozilla/5.0 (Roku) AppleWebKit/537.36 (KHTML, like Gecko) Web/1.1 Safari/537.36", + "expect": { + "vendor": "Roku", + "model": "", + "type": "smarttv" + } + }, + { + "desc": "Roku", + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36 Roku/DVP-8.10 (468.10E04145A)", + "expect": { + "vendor": "Roku", + "model": "DVP-8.10", + "type": "smarttv" + } + }, + { + "desc": "Roku", + "ua": "Roku4640X/DVP-7.70 (297.70E04154A)", + "expect": { + "vendor": "Roku", + "model": "DVP-7.70", + "type": "smarttv" + } + }, + { + "desc": "Xiaomi TV", + "ua": "Mozilla/5.0 (Linux; Android 10; MiTV-MOOQ0 Build/QTG3.200305.006; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/94.0.4606.61 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "MiTV-MOOQ0", + "type": "smarttv" + } + }, + { + "desc": "Kindle Fire HD", + "ua": "Mozilla/5.0 (Linux; U; Android 4.0.3; en-us; KFTT Build/IML74K) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.4 Mobile Safari/535.19 Silk-Accelerated=true", + "expect": { + "vendor": "Amazon", + "model": "KFTT", + "type": "tablet" + } + }, + { + "desc": "Kindle Fire HD", + "ua": "Mozilla/5.0 (Linux; U; Android 4.0.3; en-us; KFTT) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.4 Mobile Safari/535.19 Silk-Accelerated=true", + "expect": { + "vendor": "Amazon", + "model": "KFTT", + "type": "tablet" + } + }, + { + "desc": "Echo Show 5", + "ua": "Mozilla/5.0 (Linux; Android 5.1; AEORK Build/LVY48F; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Mobile Safari/537.36", + "expect": { + "vendor": "Amazon", + "model": "AEORK", + "type": "tablet" + } + }, + { + "desc": "Echo Show 8", + "ua": "Mozilla/5.0 (Linux; Android 7.1; AEOCH) AppleWebKit/537.36 (KHTML, like Gecko) Silk/77.2.21 like Chrome/77.0.3865.92 Mobile Safari/537.36", + "expect": { + "vendor": "Amazon", + "model": "AEOCH", + "type": "tablet" + } + }, + { + "desc": "Echo Dot", + "ua": "Dalvik/2.1.0 (Linux; U; Android 5.1.1; AEOBC Build/LVY48F)", + "expect": { + "vendor": "Amazon", + "model": "AEOBC", + "type": "embedded" + } + }, + { + "desc": "Samsung Galaxy A21s", + "ua": "Mozilla/5.0 (Linux; Android 10; SAMSUNG SM-A217F) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/11.0 Chrome/75.0.3770.143 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-A217F", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy A31", + "ua": "Mozilla/5.0 (Linux; Android 10; SM-A315G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-A315G", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy A50", + "ua": "Mozilla/5.0 (Linux; Android 9; SM-A505F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.105 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-A505F", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy A50s", + "ua": "Mozilla/5.0 (Linux; Android 11; SM-A507FN) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-A507FN", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy A52s", + "ua": "Mozilla/5.0 (Linux; Android 13; SM-A528B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-A528B", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy A80", + "ua": "Mozilla/5.0 (Linux; Android 9; SM-A805F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.112 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-A805F", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy Fold", + "ua": "Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-F900U Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.2 Chrome/67.0.3396.87 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-F900U", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy Z Flip", + "ua": "Mozilla/5.0 (Linux; Android 10; SM-F700N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.136 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-F700N", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy Z Fold2", + "ua": "Mozilla/5.0 (Linux; Android 10; SM-F916B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-F916B", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy S10E", + "ua": "Mozilla/5.0 (Linux; Android 9; SM-G970F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-G970F", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy S20 5G", + "ua": "Mozilla/5.0 (Linux; Android 10; SCG01) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.127 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SCG01", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy Note 10+", + "ua": "Mozilla/5.0 (Linux; Android 9; SM-N976V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.89 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-N976V", + "type": "mobile" + } + }, + { + "desc": "Samsung SM-C5000", + "ua": "Mozilla/5.0 (Linux; Android 6.0.1; SM-C5000 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36 wkbrowser 4.1.35 3065", + "expect": { + "vendor": "Samsung", + "model": "SM-C5000", + "type": "mobile" + } + }, + { + "desc": "Samsung C8", + "ua": "Mozilla/5.0 (Linux; Android 7.1.1; SM-C7108) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-C7108", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy Note 8", + "ua": "Mozilla/5.0 (Linux; Android 4.2.2; GT-N5100 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.141 Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "GT-N5100", + "type": "tablet" + } + }, + { + "desc": "Samsung SM-T231", + "ua": "Mozilla/5.0 (Linux; Android 4.4.2; SM-T231 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.135 Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-T231", + "type": "tablet" + } + }, + { + "desc": "Samsung Galaxy Tab 6 Lite", + "ua": "Mozilla/5.0 (Linux; Android 10; SAMSUNG SM-P610) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/12.0 Chrome/79.0.3945.136 Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-P610", + "type": "tablet" + } + }, + { + "desc": "Samsung Galaxy Tab A 9.7", + "ua": "Mozilla/5.0 (Linux; Android 7.1.1; SM-P550 Build/NMF26X; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.90 Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-P550", + "type": "tablet" + } + }, + { + "desc": "Samsung Galaxy Tab A 10.1", + "ua": " Mozilla/5.0 (Linux; Android 10; SAMSUNG SM-T515) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/13.0 Chrome/83.0.4103.106 Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-T515", + "type": "tablet" + } + }, + { + "desc": "Samsung Galaxy Tab S7", + "ua": "Mozilla/5.0 (Linux; Android 10; SM-T870) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-T870", + "type": "tablet" + } + }, + { + "desc": "Samsung Galaxy Tab S8", + "ua": "Mozilla/5.0 (Linux; Android 12; SM-X706B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-X706B", + "type": "tablet" + } + }, + { + "desc": "Samsung Galaxy Tab S", + "ua": "Mozilla/5.0 (Linux; Android 4.4.2; SM-T700 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.135 Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-T700", + "type": "tablet" + } + }, + { + "desc": "Samsung Galaxy Tab Pro 10.1", + "ua": "Mozilla/5.0 (Linux; Android 4.4.2; SM-T520 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.135 Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-T520", + "type": "tablet" + } + }, + { + "desc": "Samsung Note 10.1", + "ua": "Mozilla/5.0 (Linux; Android 5.1.1; SM-P605) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-P605", + "type": "tablet" + } + }, + { + "desc": "Samsung SmartTV2011", + "ua": "HbbTV/1.1.1 (;;;;;) Maple;2011", + "expect": { + "vendor": "Samsung", + "model": "SmartTV2011", + "type": "smarttv" + } + }, + { + "desc": "Samsung SmartTV2012", + "ua": "HbbTV/1.1.1 (;Samsung;SmartTV2012;;;) WebKit", + "expect": { + "vendor": "Samsung", + "model": "SmartTV2012", + "type": "smarttv" + } + }, + { + "desc": "Samsung SmartTV2014", + "ua": "HbbTV/1.1.1 (;Samsung;SmartTV2014;T-NT14UDEUC-1060.4;;) WebKit", + "expect": { + "vendor": "Samsung", + "model": "SmartTV2014", + "type": "smarttv" + } + }, + { + "desc": "Samsung SmartTV", + "ua": "Mozilla/5.0 (SMART-TV; X11; Linux armv7l) AppleWebkit/537.42 (KHTML, like Gecko) Safari/537.42", + "expect": { + "vendor": "undefined", + "model": "undefined", + "type": "smarttv" + } + }, + { + "desc": "Samsung SmartTV", + "ua": "Mozilla/5.0 (SMART-TV; Linux; Tizen 2.3) AppleWebkit/538.1 (KHTML, like Gecko) SamsungBrowser/1.0 TV Safari/538.1", + "expect": { + "vendor": "Samsung", + "model": "undefined", + "type": "smarttv" + } + }, + { + "desc": "Samsung SmartTV HBBTV", + "ua": "HbbTV/1.5.1 (+DRM;Samsung;SmartTV2021:UAU7000;T-KSU2EDEUC-1506.0;KantSU2e;urn:samsungtv:familyname:21_KANTSU2E_UHD_BASIC:2021;) Tizen/6.0 (+TVPLUS+SmartHubLink) Chrome/76 LaTivu_1.0.1_2021 RVID/17", + "expect": { + "vendor": "Samsung", + "model": "SmartTV2021:UAU7000", + "type": "smarttv" + } + }, + { + "desc": "Sharp AQUOS-TVX19B", + "ua": "Mozilla/5.0 (Linux; Android 9; AQUOS-TVX19B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Sharp", + "model": "AQUOS-TVX19B", + "type": "smarttv" + } + }, + { + "desc": "Sharp Aquos B10", + "ua": "Mozilla/5.0 (Linux; Android 7.0; SH-A01) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Mobile Safari/537.36", + "expect": { + "vendor": "Sharp", + "model": "SH-A01", + "type": "mobile" + } + }, + { + "desc": "Sharp Aquos L2", + "ua": "Mozilla/5.0 (Linux; Android 7.0; SH-L02 Build/S4045) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36", + "expect": { + "vendor": "Sharp", + "model": "SH-L02", + "type": "mobile" + } + }, + { + "desc": "Sharp Aquos L2", + "ua": "Mozilla/5.0 (Linux; Android 7.0; SH-L02) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Sharp", + "model": "SH-L02", + "type": "mobile" + } + }, + { + "desc": "Sharp Aquos R2", + "ua": "Mozilla/5.0 (Linux; Android 8.0; SHV42) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36", + "expect": { + "vendor": "Sharp", + "model": "SHV42", + "type": "mobile" + } + }, + { + "desc": "SONY Xperia 1 III", + "ua": "Mozilla/5.0 (Linux; Android 11; A101SO) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Mobile Safari/537.36", + "expect": { + "vendor": "Sony", + "model": "A101SO", + "type": "mobile" + } + }, + { + "desc": "Sony G8141 (Xperia XZ1)", + "ua": "Mozilla/5.0 (Linux; Android 9; SO-01K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Sony", + "model": "SO-01K", + "type": "mobile" + } + }, + { + "desc": "Sony G8141 (Xperia XZ Premium)", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; G8141) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", + "expect": { + "vendor": "Sony", + "model": "G8141", + "type": "mobile" + } + }, + { + "desc": "Sony C5303 (Xperia SP)", + "ua": "Mozilla/5.0 (Linux; Android 4.3; C5303 Build/12.1.A.1.205) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.93 Mobile Safari/537.36", + "expect": { + "vendor": "Sony", + "model": "C5303", + "type": "mobile" + } + }, + { + "desc": "Sony SO-02F (Xperia Z1 F)", + "ua": "Mozilla/5.0 (Linux; Android 4.2.2; SO-02F Build/14.1.H.2.119) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.114 Mobile Safari/537.36", + "expect": { + "vendor": "Sony", + "model": "SO-02F", + "type": "mobile" + } + }, + { + "desc": "Sony D6653 (Xperia Z3)", + "ua": "Mozilla/5.0 (Linux; Android 4.4; D6653 Build/23.0.A.0.376) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.141 Mobile Safari/537.36", + "expect": { + "vendor": "Sony", + "model": "D6653", + "type": "mobile" + } + }, + { + "desc": "Sony Xperia SOL25 (ZL2)", + "ua": "Mozilla/5.0 (Linux; U; Android 4.4; SOL25 Build/17.1.1.C.1.64) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + "expect": { + "vendor": "Sony", + "model": "SOL25", + "type": "mobile" + } + }, + { + "desc": "Sony Xperia SP", + "ua": "Mozilla/5.0 (Linux; Android 4.3; C5302 Build/12.1.A.1.201) AppleWebkit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.114 Mobile Safari/537.36", + "expect": { + "vendor": "Sony", + "model": "C5302", + "type": "mobile" + } + }, + { + "desc": "Sony Xperia L4", + "ua": "Mozilla/5.0 (Linux; Android 9; XQ-AD51) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.83 Mobile Safari/537.36", + "expect": { + "vendor": "Sony", + "model": "XQ-AD51", + "type": "mobile" + } + }, + { + "desc": "Sony Xperia 1ii", + "ua": "Mozilla/5.0 (Linux; Android 10; XQ-AT51) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Mobile Safari/537.36", + "expect": { + "vendor": "Sony", + "model": "XQ-AT51", + "type": "mobile" + } + }, + { + "desc": "Sony Xperia 1ii", + "ua": "Mozilla/5.0 (Linux; Android 10; SOG01) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.127 Mobile Safari/537.36", + "expect": { + "vendor": "Sony", + "model": "SOG01", + "type": "mobile" + } + }, + { + "desc": "Sony Xperia 10ii", + "ua": "Mozilla/5.0 (Linux; Android 10; XQ-AU52) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36", + "expect": { + "vendor": "Sony", + "model": "XQ-AU52", + "type": "mobile" + } + }, + { + "desc": "Sony Xperia Pro", + "ua": "Mozilla/5.0 (Linux; Android 10; XQ-AQ52) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.185 Mobile Safari/537.36", + "expect": { + "vendor": "Sony", + "model": "XQ-AQ52", + "type": "mobile" + } + }, + { + "desc": "Sony SGP521 (Xperia Z2 Tablet)", + "ua": "Mozilla/5.0 (Linux; Android 4.4; SGP521 Build/17.1.A.0.432) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.99 Safari/537.36", + "expect": { + "vendor": "Sony", + "model": "Xperia Tablet", + "type": "tablet" + } + }, + { + "desc": "Sony Xperia Z2 Tablet", + "ua": "Mozilla/5.0 (Linux; Android 5.1.1; SGP561) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.99 Safari/537.36", + "expect": { + "vendor": "Sony", + "model": "Xperia Tablet", + "type": "tablet" + } + }, + { + "desc": "Sony Tablet S", + "ua": "Mozilla/5.0 (Linux; U; Android 3.1; Sony Tablet S Build/THMAS10000) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13", + "expect": { + "vendor": "Sony", + "model": "Xperia Tablet", + "type": "tablet" + } + }, + { + "desc": "Sony Tablet Z LTE", + "ua": "Mozilla/5.0 (Linux; U; Android 4.1; SonySGP321 Build/10.2.C.0.143) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30", + "expect": { + "vendor": "Sony", + "model": "Xperia Tablet", + "type": "tablet" + } + }, + { + "desc": "Sony BRAVIA 4K GB ATV3", + "ua": "Mozilla/5.0 (Linux; Andr0id 9; BRAVIA 4K GB ATV3 Build/PTT1.190515.001.S38) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36 OPR/46.0.2207.0 OMI/4.13.0.180.DIA5.104 Model/Sony-BRAVIA-4K-GB-ATV3", + "expect": { + "vendor": "Sony", + "model": "BRAVIA 4K GB ATV3", + "type": "smarttv" + } + }, + { + "desc": "Sony BRAVIA 4K GB ATV3", + "ua": "Mozilla/5.0 (Linux; Android 9; BRAVIA 4K GB ATV3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Sony", + "model": "BRAVIA 4K GB ATV3", + "type": "smarttv" + } + }, + { + "desc": "Sony Bravia 4k UR2", + "ua": "Mozilla/5.0 (Linux: Andr0id 9: BRAVIA 4K UR2 Build/PTT1.190515.001.S104) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36 OPR/46.0.2207.0 OMI/4.13.5.431.DIA5HBBTV.250 Model/Sony-BRAVIA-4K-UR2", + "expect": { + "vendor": "Sony", + "model": "BRAVIA 4K UR2", + "type": "smarttv" + } + }, + { + "desc": "Tecno KC8", + "ua": "Mozilla/5.0 (Linux; Android 10; TECNO KC8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "TECNO", + "model": "KC8", + "type": "mobile" + } + }, + { + "desc": "Tesla", + "ua": "Mozilla/5.0 (X11; GNU/Linux) AppleWebKit/601.1 (KHTML, like Gecko) Tesla QtCarBrowser Safari/601.1", + "expect": { + "vendor": "Tesla", + "model": "undefined", + "type": "embedded" + } + }, + { + "desc": "Tesla", + "ua": "Mozilla/5.0 (X11; GNU/Linux) AppleWebKit/537.36 (KHTML, like Gecko) Chromium/79.0.3945.130 Chrome/79.0.3945.130 Safari/537.36 Tesla/2020.16.2.1-e99c70fff409", + "expect": { + "vendor": "Tesla", + "model": "undefined", + "type": "embedded" + } + }, + { + "desc": "TechniSAT Digit ISIO S SAT receiver", + "ua": "Opera/9.80 (Linux sh4; U; HbbTV/1.1.1 (;;;;;); CE-HTML; TechniSat Digit ISIO S; de) Presto/2.9.167 Version/11.50", + "expect": { + "vendor": "TechniSat", + "model": "Digit ISIO S", + "type": "smarttv" + } + }, + { + "desc": "TechniSAT MultyVision SmartTV", + "ua": "Opera/9.80 (Linux i686; U; HbbTV/1.1.1 (;;;;;); CE-HTML; TechniSat MultyVision ISIO; de) Presto/2.9.167 Version/11.50", + "expect": { + "vendor": "TechniSat", + "model": "MultyVision ISIO", + "type": "smarttv" + } + }, + { + "desc": "Ulefone Armor", + "ua": "Mozilla/5.0 (Linux; Android 6.0; Armor Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.107 Mobile Safari/537.36", + "expect": { + "vendor": "Ulefone", + "model": "Armor", + "type": "mobile" + } + }, + { + "desc": "Ulefone Armor", + "ua": "Mozilla/5.0 (Linux; arm_64; Android 6.0; Armor) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 YaBrowser/20.4.2.101.00 SA/1 Mobile Safari/537.36", + "expect": { + "vendor": "Ulefone", + "model": "Armor", + "type": "mobile" + } + }, + { + "desc": "Ulefone Armor 8 Pro", + "ua": "Mozilla/5.0 (Linux; Android 11; Armor 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.192 Mobile Safari/537.36 OPR/74.1.3922.71199", + "expect": { + "vendor": "Ulefone", + "model": "Armor 8 Pro", + "type": "mobile" + } + }, + { + "desc": "Ulefone Armor 12 5G", + "ua": "Mozilla/5.0 (Linux; Android 11; Armor 12 5G Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/115.0.5790.166 Mobile Safari/537.36", + "expect": { + "vendor": "Ulefone", + "model": "Armor 12 5G", + "type": "mobile" + } + }, + { + "desc": "Ulefone Armor 20WT", + "ua": "Mozilla/5.0 (Linux; Android 12; Armor 20WT) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/22.0 Chrome/111.0.5563.116 Mobile Safari/537.36", + "expect": { + "vendor": "Ulefone", + "model": "Armor 20WT", + "type": "mobile" + } + }, + { + "desc": "Ulefone Armor Pad", + "ua": "Mozilla/5.0 (Linux; Android 12; Armor Pad Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/116.0.0.0 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/431.0.0.30.108;]", + "expect": { + "vendor": "Ulefone", + "model": "Armor Pad", + "type": "mobile" + } + }, + { + "desc": "Ulefone Armor X5 Pro", + "ua": "Mozilla/5.0 (Linux; Android 10; Armor X5 Pro Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/116.0.0.0 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/430.0.0.23.113;]", + "expect": { + "vendor": "Ulefone", + "model": "Armor X5 Pro", + "type": "mobile" + } + }, + { + "desc": "Ulefone Power Armor 14 Pro", + "ua": "Mozilla/5.0 (Linux; Android 12; Power Armor14 Pro Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/115.0.5790.138 Mobile Safari/537.36", + "expect": { + "vendor": "Ulefone", + "model": "Power Armor14 Pro", + "type": "mobile" + } + }, + { + "desc": "Ulefone Power Armor 18T", + "ua": "Mozilla/5.0 (Linux; Android 12; Power Armor 18T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Ulefone", + "model": "Power Armor 18T", + "type": "mobile" + } + }, + { + "desc": "Ulefone Power Armor 19T", + "ua": "Mozilla/5.0 (Linux; Android 12; Power Armor 19T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.192 Mobile Safari/537.36 OPR/74.3.3922.71982", + "expect": { + "vendor": "Ulefone", + "model": "Power Armor 19T", + "type": "mobile" + } + }, + { + "desc": "Xiaomi 2201117TG", + "ua": "Mozilla/5.0 (Linux; Android 11; 2201117TG) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.98 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "2201117TG", + "type": "mobile" + } + }, + { + "desc": "Xiaomi M2004J19C", + "ua": "Mozilla/5.0 (Linux; Android 11; M2004J19C Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.77 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "M2004J19C", + "type": "mobile" + } + }, + { + "desc": "Xiaomi M2006C3MNG", + "ua": "Mozilla/5.0 (Linux; Android 11; M2006C3MNG) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.85 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "M2006C3MNG", + "type": "mobile" + } + }, + { + "desc": "Xiaomi 21061119DG", + "ua": "Mozilla/5.0 (Linux; arm_64; Android 11; 21061119DG) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 YaBrowser/23.3.7.24.00 SA/3 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "21061119DG", + "type": "mobile" + } + }, + { + "desc": "Xiaomi 2013023", + "ua": "Mozilla/5.0 (Linux; U; Android 4.2.2; en-US; 2013023 Build/HM2013023) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 UCBrowser/10.0.1.512 U3/0.8.0 Mobile Safari/533.1", + "expect": { + "vendor": "Xiaomi", + "model": "2013023", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Hongmi Note 1W", + "ua": "Mozilla/5.0 (Linux; U; Android 4.2.2; zh-CN; HM NOTE 1W Build/JDQ39) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 UCBrowser/9.7.9.439 U3/0.8.0 Mobile Safari/533.1", + "expect": { + "vendor": "Xiaomi", + "model": "HM NOTE 1W", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Mi 3C", + "ua": "Mozilla/5.0 (Linux; U; Android 4.3; zh-CN; MI 3C Build/JLS36C) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 UCBrowser/9.7.9.439 U3/0.8.0 Mobile Safari/533.1", + "expect": { + "vendor": "Xiaomi", + "model": "MI 3C", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Mi 5", + "ua": "Mozilla/5.0 (Linux; Android 7.0; MI 5 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "MI 5", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Mi 6", + "ua": "Mozilla/5.0 (Linux; Android 7.1; MI 6 Build/NMF26X; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "MI 6", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Mi 5s Plus", + "ua": "Mozilla/5.0 (Linux; U; Android 6.0.1; zh-cn; MI 5s Plus Build/MXB48T) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.146 Mobile Safari/537.36 XiaoMi/MiuiBrowser/8.7.1", + "expect": { + "vendor": "Xiaomi", + "model": "MI 5s Plus", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Mi A1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; Mi A1 Build/OPR1.170623.026) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "Mi A1", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Mi Note", + "ua": "Mozilla/5.0 (Linux; Android 4.4.4; MI NOTE LTE Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "MI NOTE LTE", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Mi One Plus", + "ua": "Mozilla/5.0 (Linux; U; Android 4.0.4; en-us; MI-ONE Plus Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + "expect": { + "vendor": "Xiaomi", + "model": "MI-ONE Plus", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Mi Max 3", + "ua": "Mozilla/5.0 (Linux; Android 9; MI MAX 3 Build/PKQ1.181007.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/79.0.3945.116 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "MI MAX 3", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Mi A1", + "ua": "Mozilla/5.0 (Linux; Android 9; Mi A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.101 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "Mi A1", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Mi A2 Lite", + "ua": "Mozilla/5.0 (Linux; Android 9; Mi A2 Lite) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.62 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "Mi A2 Lite", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Mi 9 SE", + "ua": "Mozilla/5.0 (Linux; Android 9; Mi 9 SE) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "Mi 9 SE", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Mi A2", + "ua": "Mozilla/5.0 (Linux; Android 9; Mi A2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "Mi A2", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Mi CC9", + "ua": "Mozilla/5.0 (Linux; U; Android 11; zh-cn; MI CC 9 Build/RKQ1.200826.002) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.116 Mobile Safari/537.36 XiaoMi/MiuiBrowser/15.5.18", + "expect": { + "vendor": "Xiaomi", + "model": "MI CC 9", + "type": "mobile" + } + }, + { + "desc": "Xiaomi MI PAD 2", + "ua": "Mozilla/5.0 (Linux; Android 5.1; MI PAD 2 Build/LMY47I; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.107 Safari/537.36 [FB_IAB/FB4A;FBAV/137.0.0.24.91;]", + "expect": { + "vendor": "Xiaomi", + "model": "MI PAD 2", + "type": "tablet" + } + }, + { + "desc": "Xiaomi MI PAD 4 PLUS", + "ua": "Mozilla/5.0 (Linux; Android 8.1; MI PAD 4 PLUS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "MI PAD 4 PLUS", + "type": "tablet" + } + }, + { + "desc": "Xiaomi POCO X2", + "ua": "Mozilla/5.0 (Linux; Android 10; POCO X2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.136 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "POCO X2", + "type": "mobile" + } + }, + { + "desc": "Xiaomi POCO X3 Pro", + "ua": "Mozilla/5.0 (Linux; Android 11; M2102J20SI) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "M2102J20SI", + "type": "mobile" + } + }, + { + "desc": "Xiaomi POCO X3 Pro", + "ua": "Mozilla/5.0 (Linux; Android 12; M2102J20SG) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "M2102J20SG", + "type": "mobile" + } + }, + { + "desc": "Xiaomi POCO X3 NFC", + "ua": "Mozilla/5.0 (Linux; Android 12; M2007J20CG) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "M2007J20CG", + "type": "mobile" + } + }, + { + "desc": "Xiaomi POCO M2 Pro", + "ua": "Mozilla/5.0 (Linux; arm_64; Android 11; POCO M2 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 YaBrowser/22.11.7.42.00 SA/3 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "POCO M2 Pro", + "type": "mobile" + } + }, + { + "desc": "Xiaomi POCO M3", + "ua": "Mozilla/5.0 (Linux; Android 10; M2010J19CI) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "M2010J19CI", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Redmi 4A", + "ua": "Mozilla/5.0 (Linux; Android 6.0; Redmi 4A Build/MMB29M; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/56.0.2924.87 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "Redmi 4A", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Redmi 10C", + "ua": "Mozilla/5.0 (Linux; Android 12; 220333QAG) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "220333QAG", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Redmi K30 5G", + "ua": "Mozilla/5.0 (Linux; Android 10; Redmi K30 5G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.96 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "Redmi K30 5G", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Redmi K30 Pro", + "ua": "Mozilla/5.0 (Linux; Android 10; Redmi K30 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "Redmi K30 Pro", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Redmi Note 3", + "ua": "Mozilla/5.0 (Linux; Android 6.0.1; Redmi Note 3 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.116 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "Redmi Note 3", + "type": "mobile" + } + }, + { + "desc": "Xiaomi Redmi Note 9 Pro Max", + "ua": "Mozilla/5.0 (Linux; Android 10; Redmi Note 9 Pro Max) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.99 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "Redmi Note 9 Pro Max", + "type": "mobile" + } + }, + { + "desc": "XiaoMi Redmi Note 9S", + "ua": "Mozilla/5.0 (Linux; Android 10; Redmi Note 9S) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "Redmi Note 9S", + "type": "mobile" + } + }, + { + "desc": "XiaoMi Redmi Note 10 5G", + "ua": "Mozilla/5.0 (Linux; Android 12; M2103K19C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.88 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "M2103K19C", + "type": "mobile" + } + }, + { + "desc": "XiaoMi Redmi Note 10 Pro", + "ua": "Mozilla/5.0 (Linux; Android 13; M2101K6P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "M2101K6P", + "type": "mobile" + } + }, + { + "desc": "XiaoMi Redmi Note 10 Pro", + "ua": "Mozilla/5.0 (Linux; Android 12; M2101K6G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "M2101K6G", + "type": "mobile" + } + }, + { + "desc": "XiaoMi Redmi Note 8", + "ua": "Mozilla/5.0 (Linux; Android 10; Redmi Note 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Xiaomi", + "model": "Redmi Note 8", + "type": "mobile" + } + }, + { + "desc": "XiaoMi Redmi Note 12 Turbo", + "ua": "Mozilla/5.0 (Linux; Android 13; 23049RAD8C; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.141 Mobile Safari/537.36 VivoBrowser/16.7.1.1", + "expect": { + "vendor": "Xiaomi", + "model": "23049RAD8C", + "type": "mobile" + } + }, + { + "desc": "ZTE Blade A6", + "ua": "Mozilla/5.0 (Linux; Android 7.1.1; ZTE BLADE A0620 Build/NMF26F; ru-ru) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.136 Mobile Safari/537.36 Puffin/9.2.0.50586AP", + "expect": { + "vendor": "ZTE", + "model": "BLADE A0620", + "type": "mobile" + } + }, + { + "desc": "PlayStation 4", + "ua": "Mozilla/5.0 (PlayStation 4 3.00) AppleWebKit/537.73 (KHTML, like Gecko)", + "expect": { + "vendor": "Sony", + "model": "PlayStation 4", + "type": "console" + } + }, + { + "desc": "PlayStation 5", + "ua": "Mozilla/5.0 (Playstation; Playstation 5/1.05) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15", + "expect": { + "vendor": "Sony", + "model": "Playstation 5", + "type": "console" + } + }, + { + "desc": "PlayStation Vita", + "ua": "Mozilla/5.0 (PlayStation Vita 3.52) AppleWebKit/537.73 (KHTML, like Gecko) Silk/3.2", + "expect": { + "vendor": "Sony", + "model": "PlayStation Vita", + "type": "console" + } + }, + { + "desc": "Nintendo Switch", + "ua": "Mozilla/5.0 (Nintendo Switch; WifiWebAuthApplet) AppleWebKit/606.4 (KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20393", + "expect": { + "vendor": "Nintendo", + "model": "Switch", + "type": "console" + } + }, + { + "desc": "Nintendo WiiU", + "ua": "Mozilla/5.0 (Nintendo WiiU) AppleWebKit/536.30 (KHTML, like Gecko) NX/3.0.4.2.9 NintendoBrowser/4.2.0.11146.EU", + "expect": { + "vendor": "Nintendo", + "model": "WiiU", + "type": "console" + } + }, + { + "desc": "Nintendo Wii", + "ua": "Opera/9.10 (Nintendo Wii; U; ; 1621; en)", + "expect": { + "vendor": "Nintendo", + "model": "Wii", + "type": "console" + } + }, + { + "desc": "Nintendo 3DS", + "ua": "Mozilla/5.0 (Nintendo 3DS; U; ; en) Version/1.7610.EU", + "expect": { + "vendor": "Nintendo", + "model": "3DS", + "type": "console" + } + }, + { + "desc": "Nintendo 3DS", + "ua": "Mozilla/5.0 (New Nintendo 3DS like iPhone) AppleWebKit/536.30 (KHTML, like Gecko) NX/3.0.0.5.15 Mobile NintendoBrowser/1.3.10126.EU", + "expect": { + "vendor": "Nintendo", + "model": "3DS", + "type": "console" + } + }, + { + "desc": "Galaxy Nexus", + "ua": "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19", + "expect": { + "vendor": "Samsung", + "model": "Galaxy Nexus", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy C9 Pro", + "ua": "Mozilla/5.0 (Linux; Android 6.0; SAMSUNG SM-C900F Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/4.2 Chrome/44.0.2403.133 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-C900F", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy S5", + "ua": "Mozilla/5.0 (Linux; Android 5.0; SM-G900F Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.78 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-G900F", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy J7 Prime", + "ua": "Mozilla/5.0 (Linux; Android 8.1.0; SM-G610F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-G610F", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy S6", + "ua": "Mozilla/5.0 (Linux; Android 4.4.2; SM-G920I Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.135 Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-G920I", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy S6 Edge", + "ua": "Mozilla/5.0 (Linux; Android 4.4.2; SM-G925I Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.135 Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-G925I", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy Note 5 Chrome", + "ua": "Mozilla/5.0 (Linux; Android 5.1.1; SM-N920C Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.91 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-N920C", + "type": "mobile" + } + }, + { + "desc": "Samsung Galaxy Note 5 Samsung Browser", + "ua": "Mozilla/5.0 (Linux; Android 5.1.1; SAMSUNG SM-N920C Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/4.0 Chrome/44.0.2403.133 Mobile Safari/537.36", + "expect": { + "vendor": "Samsung", + "model": "SM-N920C", + "type": "mobile" + } + }, + { + "desc": "Google Chromecast", + "ua": "Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.84 Safari/537.36 CrKey/1.22.79313", + "expect": { + "vendor": "Google", + "model": "Chromecast", + "type": "smarttv" + } + }, + { + "desc": "Google Pixel C", + "ua": "Mozilla/5.0 (Linux; Android 7.0; Pixel C Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/52.0.2743.98 Safari/537.36", + "expect": { + "vendor": "Google", + "model": "Pixel C", + "type": "tablet" + } + }, + { + "desc": "Google Pixel C", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.64 Safari/537.36", + "expect": { + "vendor": "Google", + "model": "Pixel C", + "type": "tablet" + } + }, + { + "desc": "Google Pixel", + "ua": "Mozilla/5.0 (Linux; Android 7.1; Pixel Build/NDE63V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.85 Mobile Safari/537.36", + "expect": { + "vendor": "Google", + "model": "Pixel", + "type": "mobile" + } + }, + { + "desc": "Google Pixel XL", + "ua": "Mozilla/5.0 (Linux; Android 7.1; Pixel XL Build/NDE63X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.85 Mobile Safari/537.36", + "expect": { + "vendor": "Google", + "model": "Pixel XL", + "type": "mobile" + } + }, + { + "desc": "Google Pixel XL", + "ua": "Mozilla/5.0 (Linux; Android 9; Pixel XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36", + "expect": { + "vendor": "Google", + "model": "Pixel XL", + "type": "mobile" + } + }, + { + "desc": "Google Pixel 2", + "ua": "Mozilla/5.0 (Linux; Android 8.1.0; Pixel 2 Build/OPM1.171019.013) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Safari/537.36", + "expect": { + "vendor": "Google", + "model": "Pixel 2", + "type": "mobile" + } + }, + { + "desc": "Google Pixel 2 XL", + "ua": "Mozilla/5.0 (Linux; Android 8.1.0; Pixel 2 XL Build/OPM1.171019.013) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Safari/537.36", + "expect": { + "vendor": "Google", + "model": "Pixel 2 XL", + "type": "mobile" + } + }, + { + "desc": "Google Pixel 2 XL", + "ua": "Mozilla/5.0 (Linux; Android 9; Pixel 2 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36", + "expect": { + "vendor": "Google", + "model": "Pixel 2 XL", + "type": "mobile" + } + }, + { + "desc": "Google Pixel 3", + "ua": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PD1A.180720.030) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "expect": { + "vendor": "Google", + "model": "Pixel 3", + "type": "mobile" + } + }, + { + "desc": "Google Pixel 3 XL", + "ua": "Mozilla/5.0 (Linux; Android 9; Pixel 3 XL Build/PD1A.180720.030) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "expect": { + "vendor": "Google", + "model": "Pixel 3 XL", + "type": "mobile" + } + }, + { + "desc": "Google Pixel 3 XL", + "ua": "Mozilla/5.0 (Linux; Android 9; Pixel 3 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36", + "expect": { + "vendor": "Google", + "model": "Pixel 3 XL", + "type": "mobile" + } + }, + { + "desc": "Google Pixel 3a", + "ua": "Mozilla/5.0 (Linux; Android 10; Pixel 3a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Mobile Safari/537.36", + "expect": { + "vendor": "Google", + "model": "Pixel 3a", + "type": "mobile" + } + }, + { + "desc": "Google Pixel 3a XL", + "ua": "Mozilla/5.0 (Linux; Android 10; Pixel 3a XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Mobile Safari/537.36", + "expect": { + "vendor": "Google", + "model": "Pixel 3a XL", + "type": "mobile" + } + }, + { + "desc": "Google Pixel 4", + "ua": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Mobile Safari/537.36", + "expect": { + "vendor": "Google", + "model": "Pixel 4", + "type": "mobile" + } + }, + { + "desc": "Google Pixel 4a", + "ua": "Mozilla/5.0 (Linux; Android 10; Pixel 4a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.83 Mobile Safari/537.36", + "expect": { + "vendor": "Google", + "model": "Pixel 4a", + "type": "mobile" + } + }, + { + "desc": "Google Pixel 4 XL", + "ua": "Mozilla/5.0 (Linux; Android 10; Pixel 4 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Mobile Safari/537.36", + "expect": { + "vendor": "Google", + "model": "Pixel 4 XL", + "type": "mobile" + } + }, + { + "desc": "Google Pixel 5", + "ua": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.120 Mobile Safari/537.36", + "expect": { + "vendor": "Google", + "model": "Pixel 5", + "type": "mobile" + } + }, + { + "desc": "Generic Android Device", + "ua": "Mozilla/5.0 (Linux; U; Android 6.0.1; i980 Build/MRA58K)", + "expect": { + "vendor": "Generic", + "model": "Android 6.0.1" + } + }, + { + "desc": "Android Phone Unidentified Vendor (docomo F-04K)", + "ua": "Mozilla/5.0 (Linux; Android 8.1.0; F-04K Build/V15R060P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36", + "expect": { + "model": "F-04K", + "type": "mobile" + } + }, + { + "desc": "docomo SH-02M", + "ua": "Mozilla/5.0 (Linux; Android 9; SH-02M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36", + "expect": { + "vendor": "Sharp", + "model": "SH-02M", + "type": "mobile" + } + }, + { + "desc": "Android Tablet Unidentified Vendor (docomo F-02K)", + "ua": "Mozilla/5.0 (Linux; Android 8.1.0; F-02K Build/V44R059G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Safari/537.36", + "expect": { + "model": "F-02K", + "type": "tablet" + } + }, + { + "desc": "Android Tablet Unidentified Vendor (docomo d-02K)", + "ua": "Mozilla/5.0 (Linux; Android 9; d-02K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Safari/537.36", + "expect": { + "model": "d-02K", + "type": "tablet" + } + }, + { + "desc": "LG VK Series Tablet", + "ua": "Mozilla/5.0 (Linux; Android 5.0.2; VK700 Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.84 Safari/537.36", + "expect": { + "vendor": "LG", + "model": "VK700", + "type": "tablet" + } + }, + { + "desc": "LG LK Series Tablet", + "ua": "Mozilla/5.0 (Linux; Android 5.0.1; LGLK430 Build/LRX21Y) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/38.0.2125.102 Safari/537.36", + "expect": { + "vendor": "LG", + "model": "LK430", + "type": "tablet" + } + }, + { + "desc": "RCA Voyager III Tablet", + "ua": "Mozilla/5.0 (Linux; Android 6.0.1; RCT6973W43 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "expect": { + "vendor": "RCA", + "model": "RCT6973W43", + "type": "tablet" + } + }, + { + "desc": "RCA Voyager II Tablet", + "ua": "Mozilla/5.0 (Linux; Android 5.0; RCT6773W22B Build/LRX21M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "expect": { + "vendor": "RCA", + "model": "RCT6773W22B", + "type": "tablet" + } + }, + { + "desc": "Verizon Quanta Tablet", + "ua": "Mozilla/5.0 (Linux; Android 4.4.2; QMV7B Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "expect": { + "vendor": "Verizon", + "model": "QMV7B", + "type": "tablet" + } + }, + { + "desc": "Verizon Ellipsis 8 Tablet", + "ua": "Mozilla/5.0 (Linux; Android 5.1.1; QTAQZ3 Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "expect": { + "vendor": "Verizon", + "model": "QTAQZ3", + "type": "tablet" + } + }, + { + "desc": "Verizon Ellipsis 8HD Tablet", + "ua": "Mozilla/5.0 (Linux; Android 6.0.1; QTASUN1 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.81 Safari/537.36", + "expect": { + "vendor": "Verizon", + "model": "QTASUN1", + "type": "tablet" + } + }, + { + "desc": "Dell Venue 8 Tablet", + "ua": "Mozilla/5.0 (Linux; Android 4.4.2; Venue 8 3830 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "expect": { + "vendor": "Dell", + "model": "Venue 8 3830", + "type": "tablet" + } + }, + { + "desc": "Dell Venue 7 Tablet", + "ua": "Mozilla/5.0 (Linux; Android 4.4.2; Venue 7 3730 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "expect": { + "vendor": "Dell", + "model": "Venue 7 3730", + "type": "tablet" + } + }, + { + "desc": "Barnes & Noble Nook HD+ Tablet", + "ua": "Mozilla/5.0 (Linux; U; Android 4.1.2; en-us; Barnes & Noble Nook HD+ Build/JZO54K; CyanogenMod-10) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + "expect": { + "vendor": "Barnes & Noble", + "model": "Nook HD+", + "type": "tablet" + } + }, + { + "desc": "Barnes & Noble V400 Tablet", + "ua": "Mozilla/5.0 (Linux; Android 4.0.4; BNTV400 Build/IMM76L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.111 Safari/537.36", + "expect": { + "vendor": "Barnes & Noble", + "model": "V400", + "type": "tablet" + } + }, + { + "desc": "NuVision TM101A540N Tablet", + "ua": "Mozilla/5.0 (Linux; Android 5.1; TM101A540N Build/LMY47I; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/50.0.2661.86 Safari/537.36", + "expect": { + "vendor": "NuVision", + "model": "TM101A540N", + "type": "tablet" + } + }, + { + "desc": "ZTE-Z431", + "ua": "ZTE-Z431/1.4.0 NetFront/4.2 QTV5.1 Profile/MIDP-2.1 Configuration/CLDC-1.1", + "expect": { + "vendor": "ZTE", + "model": "Z431", + "type": "mobile" + } + }, + { + "desc": "ZTE", + "ua": "Mozilla/5.0 (Linux; U; Android 4.1.2; en-us; ZTE-Z740G Build/JZO54K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + "expect": { + "vendor": "ZTE", + "model": "Z740G", + "type": "mobile" + } + }, + { + "desc": "ZTE K Series Tablet", + "ua": "Mozilla/5.0 (Linux; Android 6.0.1; K88 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "expect": { + "vendor": "ZTE", + "model": "K88", + "type": "tablet" + } + }, + { + "desc": "ZTE Nubia Red Magic 3", + "ua": "Mozilla/5.0 (Linux; Android 9; NX629J Build/PKQ1.190321.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/45016 Mobile Safari/537.36 MMWEBID/4064 MicroMessenger/7.0.10.1580(0x27000A34) Process/tools NetType/WIFI Language/zh_CN ABI/arm64", + "expect": { + "vendor": "ZTE", + "model": "NX629J", + "type": "mobile" + } + }, + { + "desc": "ZTE Blade A5", + "ua": "Mozilla/5.0 (Linux; Android 9; ZTE Blade A5 2019) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.116 Mobile Safari/537.36", + "expect": { + "vendor": "ZTE", + "model": "Blade A5 2019", + "type": "mobile" + } + }, + { + "desc": "ZTE BLADE V0730", + "ua": "Mozilla/5.0 (Linux; Android 6.0; ZTE BLADE V0730) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.116 Mobile Safari/537.36", + "expect": { + "vendor": "ZTE", + "model": "BLADE V0730", + "type": "mobile" + } + }, + { + "desc": "ZTE B2017G", + "ua": "Mozilla/5.0 (Linux; Android 7.1.1; ZTE B2017G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.93 Mobile Safari/537.36", + "expect": { + "vendor": "ZTE", + "model": "B2017G", + "type": "mobile" + } + }, + { + "desc": "Swizz GEN610", + "ua": "Mozilla/5.0 (Linux; Android 4.4.2; GEN610 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.83 Mobile Safari/537.36", + "expect": { + "vendor": "Swiss", + "model": "GEN610", + "type": "mobile" + } + }, + { + "desc": "Swizz ZUR700", + "ua": "Mozilla/5.0 (Linux; Android 4.4.2; ZUR700 Build/KVT49L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Safari/537.36", + "expect": { + "vendor": "Swiss", + "model": "ZUR700", + "type": "tablet" + } + }, + { + "desc": "Zeki TB782b Tablet", + "ua": "Mozilla/5.0 (Linux; U; Android 4.0.4; en-US; TB782B Build/IMM76D) AppleWebKit/534.31 (KHTML, like Gecko) UCBrowser/9.0.2.299 U3/0.8.0 Mobile Safari/534.31", + "expect": { + "vendor": "Zeki", + "model": "TB782B", + "type": "tablet" + } + }, + { + "desc": "Dragon Touch Tablet", + "ua": "Mozilla/5.0 (Linux; Android 4.0.4; DT9138B Build/IMM76D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.72 Mobile Safari/537.36", + "expect": { + "vendor": "Dragon Touch", + "model": "9138B", + "type": "tablet" + } + }, + { + "desc": "Insignia Tablet", + "ua": "Mozilla/5.0 (Linux; U; Android 6.0.1; NS-P08A7100 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/56.0.2924.87 Safari/537.36", + "expect": { + "vendor": "Insignia", + "model": "NS-P08A7100", + "type": "tablet" + } + }, + { + "desc": "Voice Xtreme V75", + "ua": "Mozilla/5.0 (Linux; U; Android 4.2.1; en-us; V75 Build/JOP40D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + "expect": { + "vendor": "Voice", + "model": "V75", + "type": "mobile" + } + }, + { + "desc": "LvTel V11", + "ua": "Mozilla/5.0 (Linux; Android 5.1.1; V11 Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Safari/537.36", + "expect": { + "vendor": "LvTel", + "model": "V11", + "type": "mobile" + } + }, + { + "desc": "Envizen Tablet V100MD", + "ua": "Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; V100MD Build/V100MD.20130816) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30", + "expect": { + "vendor": "Envizen", + "model": "V100MD", + "type": "tablet" + } + }, + { + "desc": "Rotor Tablet", + "ua": "mozilla/5.0 (linux; android 5.0.1; tu_1491 build/lrx22c) applewebkit/537.36 (khtml, like gecko) chrome/43.0.2357.93 safari/537.36", + "expect": { + "vendor": "Rotor", + "model": "1491", + "type": "tablet" + } + }, + { + "desc": "MachSpeed Tablets", + "ua": "Mozilla/5.0 (Linux; Android 4.4.2; Trio 7.85 vQ Build/Trio_7.85_vQ) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Safari/537.36", + "expect": { + "vendor": "MachSpeed", + "model": "Trio 7.85 vQ", + "type": "tablet" + } + }, + { + "desc": "Trinity Tablets", + "ua": "Mozilla/5.0 (Linux; Android 5.0.1; Trinity T101 Build/LRX22C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.83 Safari/537.36", + "expect": { + "vendor": "Trinity", + "model": "T101", + "type": "tablet" + } + }, + { + "desc": "NextBook Next7", + "ua": "Mozilla/5.0 (Linux; U; Android 4.0.4; en-us; Next7P12 Build/IMM76I) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30", + "expect": { + "vendor": "NextBook", + "model": "Next7P12", + "type": "tablet" + } + }, + { + "desc": "NextBook Tablets", + "ua": "Mozilla/5.0 (Linux; Android 5.0; NXA8QC116 Build/LRX21V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "expect": { + "vendor": "NextBook", + "model": "NXA8QC116", + "type": "tablet" + } + }, + { + "desc": "Le Pan Tablets", + "ua": "Mozilla/5.0 (Linux; Android 4.2.2; Le Pan TC802A Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "expect": { + "vendor": "Le Pan", + "model": "TC802A", + "type": "tablet" + } + }, + { + "desc": "Le Pan Tablets", + "ua": "Mozilla/5.0 (Linux; Android 4.2.2; Le Pan TC802A Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "expect": { + "vendor": "Le Pan", + "model": "TC802A", + "type": "tablet" + } + }, + { + "desc": "Amazon Alexa Echo Show", + "ua": "AlexaWebMediaPlayer/1.0.200641.0 (Linux;Android 5.1.1)", + "expect": { + "vendor": "Amazon", + "model": "Alexa", + "type": "tablet" + } + }, + { + "desc": "Amazon Kindle Fire Tablet", + "ua": "Mozilla/5.0 (Linux; U; Android 4.4.3; en-us; KFSAWI Build/KTU84M) AppleWebKit/537.36 (KHTML, like Gecko) Silk/3.66 like Chrome/39.0.2171.93 Safari/537.36", + "expect": { + "vendor": "Amazon", + "model": "KFSAWI", + "type": "tablet" + } + }, + { + "desc": "Amazon Kindle Fire Tablet", + "ua": "Mozilla/5.0 (Linux; U; Android 4.4.3; en-us; KFSAWI) AppleWebKit/537.36 (KHTML, like Gecko) Silk/3.66 like Chrome/39.0.2171.93 Safari/537.36", + "expect": { + "vendor": "Amazon", + "model": "KFSAWI", + "type": "tablet" + } + }, + { + "desc": "Amazon Kindle Fire Tablet", + "ua": "Mozilla/5.0 (Linux; Android 9; KFMAWI Build/PS7312; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Safari/537.36", + "expect": { + "vendor": "Amazon", + "model": "KFMAWI", + "type": "tablet" + } + }, + { + "desc": "Amazon Fire TV", + "ua": "Mozilla/5.0 (Linux; Android 4.2.2; AFTB Build/JDQ39) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.173 Mobile Safari/537.22", + "expect": { + "vendor": "Amazon", + "model": "B", + "type": "smarttv" + } + }, + { + "desc": "Amazon Fire TV", + "ua": "Mozilla/5.0 (Linux; Android 5.1.1; AFTT) AppleWebKit/537.36 (KHTML, like Gecko) Silk/86.3.20 like Chrome/86.0.4240.198 Safari/537.36", + "expect": { + "vendor": "Amazon", + "model": "T", + "type": "smarttv" + } + }, + { + "desc": "Amazon Fire TV", + "ua": "Mozilla/5.0 (Linux; Android 9; AFTKA Build/PS7633.3445N; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/108.0.5359.160 Mobile Safari/537.36", + "expect": { + "vendor": "Amazon", + "model": "KA", + "type": "smarttv" + } + }, + { + "desc": "Android TV", + "ua": "Mozilla/5.0 (Linux; Android 10; 2020/2021 UHD Android TV Build/QTG3.201102.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) version/4.0 Chrome/83.0.4103.101 Mobile Safari/537.36", + "expect": { + "vendor": "undefined", + "model": "undefined", + "type": "smarttv" + } + }, + { + "desc": "Gigaset Tablet", + "ua": "Mozilla/5.0 (Linux; Android 4.2.2; Gigaset QV830 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "expect": { + "vendor": "Gigaset", + "model": "QV830", + "type": "tablet" + } + }, + { + "desc": "Amazon Fire 7", + "ua": "Mozilla/5.0 (Linux; Android 5.1.1; KFAUWI) AppleWebKit/537.36 (KHTML, like Gecko) Silk/80.5.3 like Chrome/80.0.3987.162 Safari/537.36", + "expect": { + "vendor": "Amazon", + "model": "KFAUWI", + "type": "tablet" + } + }, + { + "desc": "AT&T Radiant Core U304AA", + "ua": "Dalvik/2.1.0 (Linux; U; Android 9; U304AA Build/P00610)", + "expect": { + "vendor": "AT&T", + "model": "U304AA", + "type": "mobile" + } + }, + { + "desc": "Vodafone Smart Tab 4G", + "ua": "Mozilla/5.0 (Linux; Android 4.4.4; Vodafone Smart Tab 4G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36", + "expect": { + "vendor": "Vodafone", + "model": "Smart Tab 4G", + "type": "tablet" + } + }, + { + "desc": "Vodafone Smart ultra 6", + "ua": "Mozilla/5.0 (Linux; Android 5.0.2; Vodafone Smart ultra 6 Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.133 Mobile Safari/537.36", + "expect": { + "vendor": "Vodafone", + "model": "Smart ultra 6", + "type": "tablet" + } + }, + { + "desc": "4ife 4K Smart TV Box", + "ua": "Mozilla/5.0 (Linux; Android 4.4.2; 4ife 4K Smart TV Box Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Safari/537.36 Vinebre", + "expect": { + "vendor": "undefined", + "model": "undefined", + "type": "smarttv" + } + }, + { + "desc": "FaceBook Mobile App", + "ua": "[FBAN/FBIOS;FBAV/283.0.0.44.117;FBBV/238386386;FBDV/iPhone12,1;FBMD/iPhone;FBSN/iOS;FBSV/13.6.1;FBSS/2;FBID/phone;FBLC/en_US;FBOP/5;FBRV/240127608]", + "expect": { + "vendor": "Apple", + "model": "iPhone12,1", + "type": "mobile" + } + }, + { + "desc": "Issue #519", + "ua": "ios/iPhone/14.2/SOME_CUSTOM_APP_VERSION", + "expect": { + "vendor": "Apple", + "model": "iPhone", + "type": "mobile" + } + }, + { + "desc": "Issue #454", + "ua": "Mosamzilla/5.0 (Windows; U; Win98; en-US; rv:1.7.5) Gecko/20050603 Netscape/8.0.2", + "expect": { + "vendor": "undefined", + "model": "undefined", + "type": "undefined" + } + }, + { + "desc": "Alcatel", + "ua": "Mozilla/5.0 (Linux; Android 4.4.2; ALCATEL A564C Build/KVT49L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.133 Mobile Safari/537.36", + "expect": { + "vendor": "ALCATEL", + "model": "A564C", + "type": "mobile" + } + }, + { + "desc": "Alcatel Go Flip", + "ua": "Mozilla/5.0 (Mobile; ALCATEL4044T; rv:37.0) Gecko/37.0 Firefox/37.0 KaiOS/1.0", + "expect": { + "vendor": "ALCATEL", + "model": "4044T", + "type": "mobile" + } + }, + { + "desc": "Jolla", + "ua": "Mozilla/5.0 (Maemo; Linux; U; Jolla; Sailfish; Mobile; rv:31.0) Gecko/31.0 Firefox/31.0 SailfishBrowser/1.0", + "expect": { + "vendor": "Jolla", + "model": "undefined", + "type": "mobile" + } + }, + { + "desc": "Xbox One", + "ua": "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; Xbox; Xbox One)", + "expect": { + "vendor": "Microsoft", + "model": "Xbox One", + "type": "console" + } + }, + { + "desc": "Xbox", + "ua": "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; Xbox)", + "expect": { + "vendor": "Microsoft", + "model": "Xbox", + "type": "console" + } + }, + { + "desc": "Nvidia Shield Tablet", + "ua": "Mozilla/5.0 (Linux; Android 5.1.1; SHIELD Tablet Build/LVY48E; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/45.0.2454.19 Safari/537.36", + "expect": { + "vendor": "Nvidia", + "model": "SHIELD Tablet", + "type": "tablet" + } + }, + { + "desc": "Ouya", + "ua": "Mozilla/5.0 (Linux; Android 4.1.2; OUYA Console Build/JZO54L-OUYA) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.84 Safari/537.36", + "expect": { + "vendor": "OUYA", + "model": "undefined", + "type": "console" + } + }, + { + "desc": "Vivo Y52s", + "ua": "Mozilla/5.0 (Linux; Android 10; V2057A Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/76.0.3809.89 Mobile Safari/537.36 T7/12.10 SP-engine/2.28.0 baiduboxapp/12.10.0.10 (Baidu; P1 10) NABar/1.0", + "expect": { + "vendor": "Vivo", + "model": "V2057A", + "type": "mobile" + } + }, + { + "desc": "Vivo X60", + "ua": "Mozilla/5.0 (Linux; Android 11; V2046A; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36 VivoBrowser/8.8.71.0", + "expect": { + "vendor": "Vivo", + "model": "V2046A", + "type": "mobile" + } + }, + { + "desc": "Vivo Y79A", + "ua": "Mozilla/5.0 (Linux; Android 7.1.2; vivo Y79A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36 VivoBrowser/9.0.14.0", + "expect": { + "vendor": "Vivo", + "model": "Y79A", + "type": "mobile" + } + }, + { + "desc": "Vivo Y93", + "ua": "Mozilla/5.0 (Linux; Android 8.1.0; vivo 1814) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Vivo", + "model": "1814", + "type": "mobile" + } + }, + { + "desc": "Vivo Y97", + "ua": "Mozilla/5.0 (Linux; Android 8.1.0; V1813T Build/O11019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36 VivoBrowser/9.0.14.0", + "expect": { + "vendor": "Vivo", + "model": "V1813T", + "type": "mobile" + } + }, + { + "desc": "Vivo iQOO Pro", + "ua": "Mozilla/5.0 (Linux; Android 11; V1916A; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36 VivoBrowser/9.1.10.6", + "expect": { + "vendor": "Vivo", + "model": "V1916A", + "type": "mobile" + } + }, + { + "desc": "Vivo 1906 (Y11)", + "ua": "Mozilla/5.0 (Linux; Android 11; vivo 1906) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", + "expect": { + "vendor": "Vivo", + "model": "1906", + "type": "mobile" + } + }, + { + "desc": "Unknown Mobile using Firefox", + "ua": "Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0", + "expect": { + "vendor": "undefined", + "model": "undefined", + "type": "mobile" + } + }, + { + "desc": "Unknown Tablet using Firefox", + "ua": "Mozilla/5.0 (Android 4.4; Tablet; rv:41.0) Gecko/41.0 Firefox/41.0", + "expect": { + "vendor": "undefined", + "model": "undefined", + "type": "tablet" + } + }, + { + "desc": "Unknown Mobile using Focus for Android", + "ua": "Mozilla/5.0 (Linux; Android 7.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Focus/1.0 Chrome/59.0.3029.83 Mobile Safari/537.36", + "expect": { + "vendor": "undefined", + "model": "undefined", + "type": "mobile" + } + }, + { + "desc": "Unknown Tablet using Focus for Android", + "ua": "Mozilla/5.0 (Linux; Android 7.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Focus/1.0 Chrome/59.0.3029.83 Safari/537.36", + "expect": { + "vendor": "undefined", + "model": "undefined", + "type": "tablet" + } + }, + { + "desc": "Unknown Device using Focus for Android with GeckoView", + "ua": "Mozilla/5.0 (Android 7.0; Mobile; rv:62.0) Gecko/62.0 Firefox/62.0", + "expect": { + "vendor": "undefined", + "model": "undefined", + "type": "mobile" + } + }, + { + "desc": "Unknown Mobile using Firefox OS", + "ua": "Mozilla/5.0 (Mobile; rv:26.0) Gecko/26.0 Firefox/26.0", + "expect": { + "vendor": "undefined", + "model": "undefined", + "type": "mobile" + } + }, + { + "desc": "Unknown Tablet using Firefox OS", + "ua": "Mozilla/5.0 (Tablet; rv:26.0) Gecko/26.0 Firefox/26.0", + "expect": { + "vendor": "undefined", + "model": "undefined", + "type": "tablet" + } + }, + { + "desc": "Unknown TV using Firefox OS", + "ua": "Mozilla/5.0 (TV; rv:44.0) Gecko/44.0 Firefox/44.0", + "expect": { + "vendor": "undefined", + "model": "undefined", + "type": "smarttv" + } + }, + { + "desc": "PDA with Windows CE", + "ua": "Mozilla/4.0 (PDA; Windows CE/1.0.1) NetFront/3.0", + "expect": { + "vendor": "undefined", + "model": "undefined", + "type": "mobile" + } + } +] diff --git a/src/__tests__/utils/event-utils.test.ts b/src/__tests__/utils/event-utils.test.ts index 6dae0f754..1db6763b7 100644 --- a/src/__tests__/utils/event-utils.test.ts +++ b/src/__tests__/utils/event-utils.test.ts @@ -1,6 +1,6 @@ import { _info } from '../../utils/event-utils' import * as globals from '../../utils/globals' -import uaParserDeviceTestCases from 'ua-parser-js/test/device-test.json' +import uaParserDeviceTestCases from './device.test.json' import { _isUndefined } from '../../utils/type-utils' describe(`event-utils`, () => { @@ -216,6 +216,11 @@ describe(`event-utils`, () => { expect(_info.browser(userAgent, vendor, '')).toBe(expectedBrowser) }) + /** + * ua-parser-js v1 has MIT licensed test cases + * at "https://github.com/faisalman/ua-parser-js#8087a1b4f0e25f1663ca3ddc2e06371d36642173" + * they were copied here + */ test.each(uaParserDeviceTestCases)('us parser test cases $ua', (testCase) => { if (testCase['expect']['type'] === 'smarttv') { // we'll test that separately @@ -225,6 +230,10 @@ describe(`event-utils`, () => { // we'll test that separately return } + if (testCase['expect']['type'] === 'embedded') { + // we don't support it + return + } const actual = _info.deviceType(testCase['ua']).toLowerCase() const expected = _isUndefined(testCase['expect']['type']) || testCase['expect']['type'] === 'undefined' diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 7be5473cd..e4cf08fd7 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -5,13 +5,15 @@ import Config from '../config' import { _each, _extend, _includes, _strip_empty_properties, _timestamp } from './index' import { document, window, location, userAgent, assignableWindow } from './globals' +const ANDROID = 'Android' + /** * Safari detection turns out to be complicted. For e.g. https://stackoverflow.com/a/29696509 * We can be slightly loose because some options have been ruled out (e.g. firefox on iOS) * before this check is made */ function isSafari(userAgent: string): boolean { - return _includes(userAgent, 'Safari') && !_includes(userAgent, 'Chrome') && !_includes(userAgent, 'Android') + return _includes(userAgent, 'Safari') && !_includes(userAgent, 'Chrome') && !_includes(userAgent, ANDROID) } export const _info = { @@ -112,7 +114,7 @@ export const _info = { return 'Mobile Safari' } return 'Safari' - } else if (_includes(user_agent, 'Android')) { + } else if (_includes(user_agent, ANDROID)) { return 'Android Mobile' } else if (_includes(user_agent, 'Konqueror') || _includes(user_agent, 'konqueror')) { return 'Konqueror' @@ -200,9 +202,9 @@ export const _info = { const match = /Android (\d+)\.(\d+)\.?(\d+)?/i.exec(user_agent) if (match && match[1]) { const versionParts = [match[1], match[2], match[3] || '0'] - return { os_name: 'Android', os_version: versionParts.join('.') } + return { os_name: ANDROID, os_version: versionParts.join('.') } } - return { os_name: 'Android', os_version: '' } + return { os_name: ANDROID, os_version: '' } } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { return { os_name: 'BlackBerry', os_version: '' } } else if (/Mac/i.test(user_agent)) { @@ -222,7 +224,9 @@ export const _info = { }, device: function (user_agent: string): string { - if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) { + if (/(Nintendo \w+)/i.test(user_agent) || /(playstation \w+)/i.test(user_agent)) { + return 'Nintendo' + } else if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) { return 'Windows Phone' } else if (/iPad/.test(user_agent)) { return 'iPad' @@ -234,17 +238,26 @@ export const _info = { return 'Apple Watch' } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { return 'BlackBerry' + } else if (/(kobo)\s(ereader|touch)/i.test(user_agent)) { + return 'Kobo' + } else if (/Nokia/i.test(user_agent)) { + return 'Nokia' } else if ( - (/Android/.test(user_agent) && !/Mobile/.test(user_agent)) || - (/Android/.test(user_agent) && /Nexus 9/.test(user_agent)) || - (/Android/.test(user_agent) && /(HUAWEISHT|BTV)/i.test(user_agent)) + // Kindle Fire without Silk / Echo Show + /(kf[a-z]{2}wi|aeo[c-r]{2})( bui|\))/i.test(user_agent) || + // Kindle Fire HD + /(kf[a-z]+)( bui|\)).+silk\//i.test(user_agent) ) { - if (/(huaweimed-al00|tah-)/i.test(user_agent)) { - return 'Android' - } - return 'Android Tablet' + return 'Kindle Fire' } else if (/Android/.test(user_agent)) { - return 'Android' + if (!/Mobile/.test(user_agent) || /Nexus 9/.test(user_agent) || /(HUAWEISHT|BTV)/i.test(user_agent)) { + if (/(huaweimed-al00|tah-|APA)/i.test(user_agent)) { + return ANDROID + } + return 'Android Tablet' + } else { + return ANDROID + } } else { return '' } @@ -252,8 +265,10 @@ export const _info = { deviceType: function (user_agent: string): string { const device = this.device(user_agent) - if (device === 'iPad' || device === 'Android Tablet') { + if (device === 'iPad' || device === 'Android Tablet' || device === 'Kobo' || device === 'Kindle Fire') { return 'Tablet' + } else if (device === 'Nintendo') { + return 'Console' } else if (device === 'Apple Watch') { return 'Wearable' } else if (device) { From c2fe3384d9427be7308377e1ed424d4993c7c43c Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Thu, 22 Feb 2024 22:23:05 +0000 Subject: [PATCH 10/43] a little bit of constants for the bundler god --- src/utils/event-utils.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index e4cf08fd7..968e17056 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -6,6 +6,10 @@ import { _each, _extend, _includes, _strip_empty_properties, _timestamp } from ' import { document, window, location, userAgent, assignableWindow } from './globals' const ANDROID = 'Android' +const IPAD = 'iPad' +const SAFARI = 'Safari' +const BLACKBERRY = 'BlackBerry' +const CHROME = 'Chrome' /** * Safari detection turns out to be complicted. For e.g. https://stackoverflow.com/a/29696509 @@ -13,7 +17,7 @@ const ANDROID = 'Android' * before this check is made */ function isSafari(userAgent: string): boolean { - return _includes(userAgent, 'Safari') && !_includes(userAgent, 'Chrome') && !_includes(userAgent, ANDROID) + return _includes(userAgent, SAFARI) && !_includes(userAgent, CHROME) && !_includes(userAgent, ANDROID) } export const _info = { @@ -91,7 +95,7 @@ export const _info = { } return 'Opera' } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { - return 'BlackBerry' + return BLACKBERRY } else if (_includes(user_agent, 'IEMobile') || _includes(user_agent, 'WPDesktop')) { return 'Internet Explorer Mobile' } else if (_includes(user_agent, 'SamsungBrowser/')) { @@ -101,8 +105,8 @@ export const _info = { return 'Microsoft Edge' } else if (_includes(user_agent, 'FBIOS')) { return 'Facebook Mobile' - } else if (_includes(user_agent, 'Chrome')) { - return 'Chrome' + } else if (_includes(user_agent, CHROME)) { + return CHROME } else if (_includes(user_agent, 'CriOS')) { return 'Chrome iOS' } else if (_includes(user_agent, 'UCWEB') || _includes(user_agent, 'UCBrowser')) { @@ -113,7 +117,7 @@ export const _info = { if (_includes(user_agent, 'Mobile')) { return 'Mobile Safari' } - return 'Safari' + return SAFARI } else if (_includes(user_agent, ANDROID)) { return 'Android Mobile' } else if (_includes(user_agent, 'Konqueror') || _includes(user_agent, 'konqueror')) { @@ -206,7 +210,7 @@ export const _info = { } return { os_name: ANDROID, os_version: '' } } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { - return { os_name: 'BlackBerry', os_version: '' } + return { os_name: BLACKBERRY, os_version: '' } } else if (/Mac/i.test(user_agent)) { const match = /Mac OS X (\d+)[_.](\d+)[_.]?(\d+)?/i.exec(user_agent) if (match && match[1]) { @@ -229,7 +233,7 @@ export const _info = { } else if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) { return 'Windows Phone' } else if (/iPad/.test(user_agent)) { - return 'iPad' + return IPAD } else if (/iPod/.test(user_agent)) { return 'iPod Touch' } else if (/iPhone/.test(user_agent)) { @@ -237,7 +241,7 @@ export const _info = { } else if (/(watch)(?: ?os[,/]|\d,\d\/)[\d.]+/i.test(user_agent)) { return 'Apple Watch' } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { - return 'BlackBerry' + return BLACKBERRY } else if (/(kobo)\s(ereader|touch)/i.test(user_agent)) { return 'Kobo' } else if (/Nokia/i.test(user_agent)) { @@ -265,7 +269,7 @@ export const _info = { deviceType: function (user_agent: string): string { const device = this.device(user_agent) - if (device === 'iPad' || device === 'Android Tablet' || device === 'Kobo' || device === 'Kindle Fire') { + if (device === IPAD || device === 'Android Tablet' || device === 'Kobo' || device === 'Kindle Fire') { return 'Tablet' } else if (device === 'Nintendo') { return 'Console' From 5d94dcfeed2143a3fe55005105b4176ec259f2e2 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Thu, 22 Feb 2024 22:34:10 +0000 Subject: [PATCH 11/43] fangling --- pnpm-lock.yaml | 9 --------- src/utils/event-utils.ts | 9 +++++++-- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6604c9424..6935c7afb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -192,9 +192,6 @@ devDependencies: typescript: specifier: ^4.9.5 version: 4.9.5 - ua-parser-js: - specifier: https://github.com/faisalman/ua-parser-js#8087a1b4f0e25f1663ca3ddc2e06371d36642173 - version: github.com/faisalman/ua-parser-js/8087a1b4f0e25f1663ca3ddc2e06371d36642173 yargs: specifier: ^17.7.2 version: 17.7.2 @@ -10815,9 +10812,3 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true - - github.com/faisalman/ua-parser-js/8087a1b4f0e25f1663ca3ddc2e06371d36642173: - resolution: {tarball: https://codeload.github.com/faisalman/ua-parser-js/tar.gz/8087a1b4f0e25f1663ca3ddc2e06371d36642173} - name: ua-parser-js - version: 1.0.37 - dev: true diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 968e17056..bbc170628 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -254,8 +254,13 @@ export const _info = { ) { return 'Kindle Fire' } else if (/Android/.test(user_agent)) { - if (!/Mobile/.test(user_agent) || /Nexus 9/.test(user_agent) || /(HUAWEISHT|BTV)/i.test(user_agent)) { - if (/(huaweimed-al00|tah-|APA)/i.test(user_agent)) { + if ( + !/Mobile/.test(user_agent) || + /Nexus 9/.test(user_agent) || + /(HUAWEISHT|BTV)/i.test(user_agent) || + /nexus 7/i.test(user_agent) + ) { + if (/(pixel[\daxl ]{0,6})/i.test(user_agent) || /(huaweimed-al00|tah-|APA|)/i.test(user_agent)) { return ANDROID } return 'Android Tablet' From db9699438c01c2f5898449abf2695d13e742bb76 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Thu, 22 Feb 2024 22:40:02 +0000 Subject: [PATCH 12/43] fiddling --- src/__tests__/utils/device.test.json | 3 ++- src/utils/event-utils.ts | 7 +------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/__tests__/utils/device.test.json b/src/__tests__/utils/device.test.json index 43b5d4028..998ce62bc 100644 --- a/src/__tests__/utils/device.test.json +++ b/src/__tests__/utils/device.test.json @@ -3136,7 +3136,8 @@ "ua": "Mozilla/5.0 (Linux; U; Android 6.0.1; i980 Build/MRA58K)", "expect": { "vendor": "Generic", - "model": "Android 6.0.1" + "model": "Android 6.0.1", + "type": "mobile" } }, { diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index bbc170628..2cbc9d187 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -254,12 +254,7 @@ export const _info = { ) { return 'Kindle Fire' } else if (/Android/.test(user_agent)) { - if ( - !/Mobile/.test(user_agent) || - /Nexus 9/.test(user_agent) || - /(HUAWEISHT|BTV)/i.test(user_agent) || - /nexus 7/i.test(user_agent) - ) { + if (!/Mobile/.test(user_agent) || /Nexus (9|7)i/.test(user_agent) || /(HUAWEISHT|BTV)/i.test(user_agent)) { if (/(pixel[\daxl ]{0,6})/i.test(user_agent) || /(huaweimed-al00|tah-|APA|)/i.test(user_agent)) { return ANDROID } From 1e584702c3cd0cb68b9db78321ded4259de22947 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Thu, 22 Feb 2024 22:47:09 +0000 Subject: [PATCH 13/43] fiddling --- src/utils/event-utils.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 2cbc9d187..a237a7dba 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -6,10 +6,14 @@ import { _each, _extend, _includes, _strip_empty_properties, _timestamp } from ' import { document, window, location, userAgent, assignableWindow } from './globals' const ANDROID = 'Android' +const ANDROID_TABLET = `${ANDROID} Tablet` const IPAD = 'iPad' +const APPLE_WATCH = 'Apple Watch' const SAFARI = 'Safari' const BLACKBERRY = 'BlackBerry' const CHROME = 'Chrome' +const INTERNET_EXPLORER = 'Internet Explorer' +const INTERNET_EXPLORER_MOBILE = `${INTERNET_EXPLORER} Mobile` /** * Safari detection turns out to be complicted. For e.g. https://stackoverflow.com/a/29696509 @@ -97,7 +101,7 @@ export const _info = { } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { return BLACKBERRY } else if (_includes(user_agent, 'IEMobile') || _includes(user_agent, 'WPDesktop')) { - return 'Internet Explorer Mobile' + return INTERNET_EXPLORER_MOBILE } else if (_includes(user_agent, 'SamsungBrowser/')) { // https://developer.samsung.com/internet/user-agent-string-format return 'Samsung Internet' @@ -125,7 +129,7 @@ export const _info = { } else if (_includes(user_agent, 'Firefox')) { return 'Firefox' } else if (_includes(user_agent, 'MSIE') || _includes(user_agent, 'Trident/')) { - return 'Internet Explorer' + return INTERNET_EXPLORER } else if (_includes(user_agent, 'Gecko')) { return 'Mozilla' } else { @@ -144,7 +148,7 @@ export const _info = { browserVersion: function (userAgent: string, vendor: string | undefined, opera: string): number | null { const browser = _info.browser(userAgent, vendor, opera) const versionRegexes: Record = { - 'Internet Explorer Mobile': [/rv:(\d+(\.\d+)?)/], + INTERNET_EXPLORER_MOBILE: [/rv:(\d+(\.\d+)?)/], 'Microsoft Edge': [/Edge?\/(\d+(\.\d+)?)/], Chrome: [/Chrome\/(\d+(\.\d+)?)/], 'Chrome iOS': [/CriOS\/(\d+(\.\d+)?)/], @@ -159,7 +163,7 @@ export const _info = { BlackBerry: [/BlackBerry (\d+(\.\d+)?)/, /Version\/(\d+(\.\d+)?)/], 'Android Mobile': [/android\s(\d+(\.\d+)?)/], 'Samsung Internet': [/SamsungBrowser\/(\d+(\.\d+)?)/], - 'Internet Explorer': [/(rv:|MSIE )(\d+(\.\d+)?)/], + INTERNET_EXPLORER: [/(rv:|MSIE )(\d+(\.\d+)?)/], Mozilla: [/rv:(\d+(\.\d+)?)/], } const regexes: RegExp[] | undefined = versionRegexes[browser as keyof typeof versionRegexes] @@ -239,7 +243,7 @@ export const _info = { } else if (/iPhone/.test(user_agent)) { return 'iPhone' } else if (/(watch)(?: ?os[,/]|\d,\d\/)[\d.]+/i.test(user_agent)) { - return 'Apple Watch' + return APPLE_WATCH } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { return BLACKBERRY } else if (/(kobo)\s(ereader|touch)/i.test(user_agent)) { @@ -258,7 +262,7 @@ export const _info = { if (/(pixel[\daxl ]{0,6})/i.test(user_agent) || /(huaweimed-al00|tah-|APA|)/i.test(user_agent)) { return ANDROID } - return 'Android Tablet' + return ANDROID_TABLET } else { return ANDROID } @@ -269,11 +273,11 @@ export const _info = { deviceType: function (user_agent: string): string { const device = this.device(user_agent) - if (device === IPAD || device === 'Android Tablet' || device === 'Kobo' || device === 'Kindle Fire') { + if (device === IPAD || device === ANDROID_TABLET || device === 'Kobo' || device === 'Kindle Fire') { return 'Tablet' } else if (device === 'Nintendo') { return 'Console' - } else if (device === 'Apple Watch') { + } else if (device === APPLE_WATCH) { return 'Wearable' } else if (device) { return 'Mobile' From b2f13410b52aa2a31c98e66592b4f39d2915e76a Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Thu, 22 Feb 2024 23:27:47 +0000 Subject: [PATCH 14/43] some more OS detection --- src/__tests__/utils/event-utils.test.ts | 16 +- src/__tests__/utils/os-test.json | 1106 +++++++++++++++++++++++ src/utils/event-utils.ts | 10 +- 3 files changed, 1130 insertions(+), 2 deletions(-) create mode 100644 src/__tests__/utils/os-test.json diff --git a/src/__tests__/utils/event-utils.test.ts b/src/__tests__/utils/event-utils.test.ts index 1db6763b7..aac0266e8 100644 --- a/src/__tests__/utils/event-utils.test.ts +++ b/src/__tests__/utils/event-utils.test.ts @@ -1,6 +1,7 @@ import { _info } from '../../utils/event-utils' import * as globals from '../../utils/globals' import uaParserDeviceTestCases from './device.test.json' +import uaParserOSTestCases from './os-test.json' import { _isUndefined } from '../../utils/type-utils' describe(`event-utils`, () => { @@ -221,7 +222,7 @@ describe(`event-utils`, () => { * at "https://github.com/faisalman/ua-parser-js#8087a1b4f0e25f1663ca3ddc2e06371d36642173" * they were copied here */ - test.each(uaParserDeviceTestCases)('us parser test cases $ua', (testCase) => { + test.each(uaParserDeviceTestCases)('device - $ua', (testCase) => { if (testCase['expect']['type'] === 'smarttv') { // we'll test that separately return @@ -241,5 +242,18 @@ describe(`event-utils`, () => { : testCase['expect']['type'] expect(actual).toBe(expected) }) + + /** + * ua-parser-js v1 has MIT licensed test cases + * at "https://github.com/faisalman/ua-parser-js#8087a1b4f0e25f1663ca3ddc2e06371d36642173" + * they were copied here + * + * we had to edit them a chunk because we don't aim for the same level of detail + */ + test.each(uaParserOSTestCases)('OS - $ua', (testCase) => { + const actual = _info.os(testCase['ua']) + const expected = testCase['expect'] + expect(actual).toStrictEqual(expected) + }) }) }) diff --git a/src/__tests__/utils/os-test.json b/src/__tests__/utils/os-test.json new file mode 100644 index 000000000..1a56a8678 --- /dev/null +++ b/src/__tests__/utils/os-test.json @@ -0,0 +1,1106 @@ +[ + { + "desc": "Windows 95", + "ua": "Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)", + "expect": { + "os_name": "Windows", + "os_version": "95" + } + }, + { + "desc": "Windows 98", + "ua": "Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)", + "expect": { + "os_name": "Windows", + "os_version": "98" + } + }, + { + "desc": "Windows ME", + "ua": "Mozilla/5.0 (Windows; U; Win 9x 4.90) Gecko/20020502 CS 2000 7.0/7.0", + "expect": { + "os_name": "Windows", + "os_version": "ME" + } + }, + { + "desc": "Windows 2000", + "ua": "Mozilla/3.0 (compatible; MSIE 3.0; Windows NT 5.0)", + "expect": { + "os_name": "Windows", + "os_version": "2000" + } + }, + { + "desc": "Windows XP", + "ua": "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 5.2)", + "expect": { + "os_name": "Windows", + "os_version": "XP" + } + }, + { + "desc": "Windows Vista", + "ua": "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.0; fr-FR)", + "expect": { + "os_name": "Windows", + "os_version": "Vista" + } + }, + { + "desc": "Windows 7", + "ua": "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)", + "expect": { + "os_name": "Windows", + "os_version": "7" + } + }, + { + "desc": "Windows 8", + "ua": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; Win64; x64; Trident/6.0; .NET4.0E; .NET4.0C)", + "expect": { + "os_name": "Windows", + "os_version": "8" + } + }, + { + "desc": "Windows 10", + "ua": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0", + "expect": { + "os_name": "Windows", + "os_version": "10" + } + }, + { + "desc": "WeChat Desktop for Windows Built-in Browser", + "ua": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 MicroMessenger/6.5.2.501 NetType/WIFI WindowsWechat QBCore/3.43.901.400 QQBrowser/9.0.2524.400", + "expect": { + "os_name": "Windows", + "os_version": "7" + } + }, + { + "desc": "WeChat Desktop for Windows Built-in Browser major version in 4", + "ua": "mozilla/5.0 (windows nt 6.1; wow64) applewebkit/537.36 (khtml, like gecko) chrome/81.0.4044.138 safari/537.36 nettype/wifi micromessenger/7.0.20.1781(0x6700143b) windowswechat", + "expect": { + "os_name": "Windows", + "os_version": "7" + } + }, + { + "desc": "Windows RT", + "ua": "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; ARM; Trident/6.0)", + "expect": { + "os_name": "Windows", + "os_version": "RT" + } + }, + { + "desc": "Windows CE", + "ua": "Mozilla/4.0 (compatible; MSIE 6.0; Windows CE; IEMobile 7.11)", + "expect": { + "os_name": "Windows", + "os_version": "CE" + } + }, + { + "desc": "Windows Mobile", + "ua": "Mozilla/5.0 (ZTE-E_N72/N72V1.0.0B02;U;Windows Mobile/6.1;Profile/MIDP-2.0 Configuration/CLDC-1.1;320*240;CTC/2.0) IE/6.0 (compatible; MSIE 4.01; Windows CE; PPC)/UC Browser7.7.1.88", + "expect": { + "os_name": "Windows Mobile", + "os_version": "6.1" + } + }, + { + "desc": "Windows Mobile", + "ua": "Opera/9.80 (Windows Mobile; WCE; Opera Mobi/WMD-50433; U; en) Presto/2.4.13 Version/10.00", + "expect": { + "os_name": "Windows Mobile", + "os_version": "" + } + }, + { + "desc": "Windows Phone", + "ua": "Opera/9.80 (Windows Phone; Opera Mini/7.6.8/35.7518; U; ru) Presto/2.8.119 Version/11.10", + "expect": { + "os_name": "Windows Phone", + "os_version": "" + } + }, + { + "desc": "Windows Phone OS", + "ua": "Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0; DELL; Venue Pro)", + "expect": { + "os_name": "Windows Phone OS", + "os_version": "7.0" + } + }, + { + "desc": "Windows Phone 8", + "ua": "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; HTC; Windows Phone 8X by HTC)", + "expect": { + "os_name": "Windows Phone", + "os_version": "8.0" + } + }, + { + "desc": "Windows NT on x86 or aarch64 CPU using Firefox", + "ua": "Mozilla/5.0 (Windows NT x.y; rv:10.0) Gecko/20100101 Firefox/10.0", + "expect": { + "os_name": "Windows", + "os_version": "NT x" + } + }, + { + "desc": "Windows NT on x64 CPU using Firefox", + "ua": "Mozilla/5.0 (Windows NT x.y; Win64; x64; rv:10.0) Gecko/20100101 Firefox/10.0", + "expect": { + "os_name": "Windows", + "os_version": "NT x" + } + }, + { + "desc": "BlackBerry", + "ua": "BlackBerry9300/5.0.0.912 Profile/MIDP-2.1 Configuration/CLDC-1.1 VendorID/378", + "expect": { + "os_name": "BlackBerry", + "os_version": "5.0.0.912" + } + }, + { + "desc": "BlackBerry 10", + "ua": "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.3+ (KHTML, like Gecko) Version/10.0.9.386 Mobile Safari/537.3+", + "expect": { + "os_name": "BlackBerry", + "os_version": "10" + } + }, + { + "desc": "Tizen", + "ua": "Mozilla/5.0 (SMART-TV; Linux; Tizen 2.3) AppleWebkit/538.1 (KHTML, like Gecko) SamsungBrowser/1.0 TV Safari/538.1", + "expect": { + "os_name": "Tizen", + "os_version": "2.3" + } + }, + { + "desc": "Tizen", + "ua": "Mozilla/5.0 (Linux; Tizen 2.3; SAMSUNG SM-Z130H) AppleWebKit/537.3 (KHTML, like Gecko) Version/2.3 Mobile Safari/537.3", + "expect": { + "os_name": "Tizen", + "os_version": "2.3" + } + }, + { + "desc": "Tizen 6.0", + "ua": "HbbTV/1.5.1 (+DRM;Samsung;SmartTV2021:UAU7000;T-KSU2EDEUC-1506.0;KantSU2e;urn:samsungtv:familyname:21_KANTSU2E_UHD_BASIC:2021;) Tizen/6.0 (+TVPLUS+SmartHubLink) Chrome/76 LaTivu_1.0.1_2021 RVID/17", + "expect": { + "os_name": "Tizen", + "os_version": "6.0" + } + }, + { + "desc": "Android", + "ua": "Mozilla/5.0 (Linux; U; Android 2.2.2; en-us; VM670 Build/FRG83G) AppleWebKit/533.1 (KHTML, like Gecko)", + "expect": { + "os_name": "Android", + "os_version": "2.2.2" + } + }, + { + "desc": "HarmonyOS", + "ua": "Mozilla/5.0 (Linux; Android 10; HarmonyOS; YAL-AL10; HMSCore 6.3.0.327; GMSCore 21.48.15) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.105 HuaweiBrowser/12.0.3.310 Mobile Safari/537.36", + "expect": { + "os_name": "HarmonyOS", + "os_version": "10" + } + }, + { + "desc": "Sailfish", + "ua": "Mozilla/5.0 (Linux; U; Sailfish 3.0; Mobile; rv:45.0) Gecko/45.0 Firefox/45.0 SailfishBrowser/1.0", + "expect": { + "os_name": "Sailfish", + "os_version": "3.0" + } + }, + { + "desc": "WebOS", + "ua": "Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.5; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/234.83 Safari/534.6 TouchPad/1.0", + "expect": { + "os_name": "webOS", + "os_version": "3.0.5" + } + }, + { + "desc": "WebOS", + "ua": "Mozilla/5.0 (webOS/1.4.5; U; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Version/1.0 Safari/532.2 Pre/1.0", + "expect": { + "os_name": "webOS", + "os_version": "1.4.5" + } + }, + { + "desc": "WebOS TV 5.x", + "ua": "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36 WebAppManager", + "expect": { + "os_name": "webOS", + "os_version": "TV" + } + }, + { + "desc": "WebOS TV 4.x", + "ua": "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.34 Safari/537.36 WebAppManager", + "expect": { + "os_name": "webOS", + "os_version": "TV" + } + }, + { + "desc": "WebOS TV 3.x", + "ua": "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) QtWebEngine/5.2.1 Chrome/38.0.2125.122 Safari/537.36 WebAppManager", + "expect": { + "os_name": "webOS", + "os_version": "TV" + } + }, + { + "desc": "WebOS TV 2.x", + "ua": "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/538.2 (KHTML, like Gecko) Large Screen WebAppManager Safari/538.2", + "expect": { + "os_name": "webOS", + "os_version": "TV" + } + }, + { + "desc": "WebOS TV 1.x", + "ua": "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.41 (KHTML, like Gecko) Large Screen WebAppManager Safari/537.41", + "expect": { + "os_name": "webOS", + "os_version": "TV" + } + }, + { + "desc": "QNX", + "ua": "Mozilla/5.0 (Photon; U; QNX x86pc; en-US; rv:1.8.1.20) Gecko/20090127 BonEcho/2.0.0.20", + "expect": { + "os_name": "QNX", + "os_version": "x86pc" + } + }, + { + "desc": "Bada", + "ua": "Mozilla/5.0 (SAMSUNG; SAMSUNG-GT-S5253/S5253DDKC1; U; Bada/1.0; en-us) AppleWebKit/533.1 (KHTML, like Gecko) Dolfin/2.0 Mobile WQVGA SMM-MMS/1.2.0 OPN-B", + "expect": { + "os_name": "Bada", + "os_version": "1.0" + } + }, + { + "desc": "RIM Tablet OS", + "ua": "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+", + "expect": { + "os_name": "RIM Tablet OS", + "os_version": "2.1.0" + } + }, + { + "desc": "Nokia N900 Linux mobile, on the Fennec browser", + "ua": "Mozilla/5.0 (Maemo; Linux armv7l; rv:10.0) Gecko/20100101 Firefox/10.0 Fennec/10.0", + "expect": { + "os_name": "Maemo", + "os_version": "" + } + }, + { + "desc": "MeeGo", + "ua": "Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13", + "expect": { + "os_name": "MeeGo", + "os_version": "" + } + }, + { + "desc": "Symbian", + "ua": "Nokia5250/10.0.011 (SymbianOS/9.4; U; Series60/5.0 Mozilla/5.0; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/525 (KHTML, like Gecko) Safari/525 3gpp-gba", + "expect": { + "os_name": "Symbian", + "os_version": "9.4" + } + }, + { + "desc": "Symbian", + "ua": "Mozilla/5.0 (Symbian/3; Series60/5.2 NokiaC7-00/024.001; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/533.4 (KHTML, like Gecko) NokiaBrowser/7.3.1.37 Mobile Safari/533.4 3gpp-gba", + "expect": { + "os_name": "Symbian", + "os_version": "5.2" + } + }, + { + "desc": "Series40", + "ua": "Mozilla/5.0 (Series40; Nokia2055/03.20; Profile/MIDP-2.1 Configuration/CLDC-1.1) Gecko/20100401 S40OviBrowser/2.2.0.0.34", + "expect": { + "os_name": "Series40", + "os_version": "" + } + }, + { + "desc": "Firefox OS", + "ua": "Mozilla/5.0 (Mobile; rv:14.0) Gecko/14.0 Firefox/14.0", + "expect": { + "os_name": "Firefox OS", + "os_version": "14.0" + } + }, + { + "desc": "Firefox OS on Tablet", + "ua": "Mozilla/5.0 (Tablet; rv:26.0) Gecko/26.0 Firefox/26.0", + "expect": { + "os_name": "Firefox OS", + "os_version": "26.0" + } + }, + { + "desc": "Firefox OS on TV", + "ua": "Mozilla/5.0 (TV; rv:44.0) Gecko/44.0 Firefox/44.0", + "expect": { + "os_name": "Firefox OS", + "os_version": "44.0" + } + }, + { + "desc": "Google Chromecast", + "ua": "Mozilla/5.0 (X11; Linux aarch64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.81 Safari/537.36 CrKey/1.42.183786", + "expect": { + "os_name": "Chromecast", + "os_version": "1.42.183786" + } + }, + { + "desc": "Nintendo Switch", + "ua": "Mozilla/5.0 (Nintendo Switch; WifiWebAuthApplet) AppleWebKit/606.4 (KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20393", + "expect": { + "os_name": "Nintendo", + "os_version": "Switch" + } + }, + { + "desc": "PlayStation 4", + "ua": "Mozilla/5.0 (PlayStation 4 3.00) AppleWebKit/537.73 (KHTML, like Gecko)", + "expect": { + "os_name": "PlayStation", + "os_version": "4" + } + }, + { + "desc": "Xbox 360", + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox 360) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36", + "expect": { + "os_name": "Xbox", + "os_version": "360" + } + }, + { + "desc": "Xbox One", + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19041", + "expect": { + "os_name": "Xbox", + "os_version": "One" + } + }, + { + "desc": "Xbox X", + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36 Edge/20.02", + "expect": { + "os_name": "Xbox", + "os_version": "X" + } + }, + { + "desc": "Xbox Series X", + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox Series X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36 Edge/20.02 ", + "expect": { + "os_name": "Xbox", + "os_version": "Series X" + } + }, + { + "desc": "Mint", + "ua": "Opera/9.80 (X11; Linux x86_64; Edition Linux Mint) Presto/2.12.388 Version/12.16", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Mint", + "ua": "Opera/9.64 (X11; Linux i686; U; Linux Mint; nb) Presto/2.1.1", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Mint", + "ua": "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.5) Gecko/2008121622 Linux Mint/6 (Felicia) Firefox/3.0.4", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Ubuntu", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.22+ (KHTML, like Gecko) Chromium/17.0.963.56 Chrome/17.0.963.56 Safari/535.22+ Ubuntu/12.04 (3.4.1-0ubuntu1) Epiphany/3.4.1", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Ubuntu", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/31.0.1650.63 Chrome/31.0.1650.63 Safari/537.36", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Kubuntu", + "ua": "Mozilla/5.0 (compatible; Konqueror/4.4; Linux 2.6.32-22-generic; X11; en_US) KHTML/4.4.3 (like Gecko) Kubuntu", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Debian", + "ua": "Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.7 (like Gecko) (Debian)", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Debian", + "ua": "Mozilla/5.0 (X11; Linux x86_64; Debian GNU/Linux 8.1 (jessie)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.0 Maxthon/1.0.5.3 Safari/537.36", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Debian", + "ua": "ELinks/0.12~pre5-4 (textmode; Debian; Linux 3.2.0-4-amd64 x86_64 192x47-2)", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Debian", + "ua": "w3m/0.5.3+debian-19", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Debian", + "ua": "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.0.3) Gecko/2008092814 (Debian-3.0.1-1)", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Debian", + "ua": "Mozilla/5.0 (compatible; Konqueror/3.5; Linux 2.6.24.4; X11) KHTML/3.5.9 (like Gecko) (Debian package 4:3.5.9.dfsg.1-2+b1)", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "OpenSUSE", + "ua": "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.17) Gecko/20110420 SUSE/3.6.17-0.2.1 Firefox/3.6.17", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Gentoo", + "ua": "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.16) Gecko/20080716 (Gentoo) Galeon/2.0.6", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Gentoo", + "ua": "Xombrero (X11; U; Gentoo Linux amd64; en-US) Webkit/2.8.5", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Gentoo", + "ua": "Xombrero/1.6.4 (Linux amd64; en; Gentoo)", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Gentoo", + "ua": "Links (2.8; Linux 3.17.2-gentoo-x86 i686; GNU C 4.8.2; x)", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Arch", + "ua": "Uzbl (Webkit 1.1.10) (Arch Linux)", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Slackware", + "ua": "Mozilla/5.0 Slackware/13.37 (X11; U; Linux x86_64; en-US) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Fedora", + "ua": "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:40.0) Gecko/20100101 Firefox/40.0", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Fedora", + "ua": "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:2.0) Gecko/20110404 Fedora/16-dev Firefox/4.0", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Fedora", + "ua": "Mozilla/5.0 (X11; U; Linux i686; sk; rv:1.9.0.4) Gecko/2008111217 Fedora/3.0.4-1.fc10 Firefox/3.0.4", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Mandriva", + "ua": "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.22) Gecko/20110907 Mandriva Linux/1.9.2.22-0.1mdv2010.2 (2010.2) Firefox/3.6.22", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Chrome OS", + "ua": "Mozilla/5.0 (X11; CrOS x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.0.0 Safari/537.36", + "expect": { + "os_name": "Chrome OS", + "os_version": "" + } + }, + { + "desc": "Chromium OS", + "ua": "Mozilla/5.0 (X11; CrOS x86_64 10575.58.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", + "expect": { + "os_name": "Chrome OS", + "os_version": "" + } + }, + { + "desc": "Fuchsia", + "ua": "Mozilla/5.0 (X11; Fuchsia x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3557.0 Safari/537.36", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "Solaris", + "ua": "Mozilla/5.0 (X11; U; SunOS sun4u; en-US; rv:1.7) Gecko/20070606", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "FreeBSD", + "ua": "Mozilla/5.0 (X11; U; FreeBSD x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "OpenBSD", + "ua": "Mozilla/5.0 (X11; U; OpenBSD i386; en-US; rv:1.9.1) Gecko/20090702 Firefox/3.5", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "NetBSD", + "ua": "ELinks (0.4.3; NetBSD 3.0.2PATCH sparc64; 141x19)", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "DragonFly", + "ua": "Mozilla/5.0 (X11; U; DragonFly i386; de; rv:1.9.1) Gecko/20090720 Firefox/3.5.1", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "iOS in App", + "ua": "AppName/version CFNetwork/version Darwin/version", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "iOS with Chrome", + "ua": "Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3", + "expect": { + "os_name": "iOS", + "os_version": "5.1.1" + } + }, + { + "desc": "iOS with Opera Mini", + "ua": "Opera/9.80 (iPhone; Opera Mini/7.1.32694/27.1407; U; en) Presto/2.8.119 Version/11.10", + "expect": { + "os_name": "iOS", + "os_version": "" + } + }, + { + "desc": "iOS with FaceBook Mobile App", + "ua": "[FBAN/FBIOS;FBAV/283.0.0.44.117;FBBV/238386386;FBDV/iPhone12,1;FBMD/iPhone;FBSN/iOS;FBSV/13.6.1;FBSS/2;FBID/phone;FBLC/en_US;FBOP/5;FBRV/240127608]", + "expect": { + "os_name": "iOS", + "os_version": "" + } + }, + { + "desc": "iOS with Slack App", + "ua": "com.tinyspeck.chatlyio/23.04.10 (iPhone; iOS 16.4.1; Scale/3.00)", + "expect": { + "os_name": "iOS", + "os_version": "" + } + }, + { + "desc": "watchOS", + "ua": "server-bag [Watch OS,8.4,19S546,Watch3,4]", + "expect": { + "os_name": "watchOS", + "os_version": "8.4" + } + }, + { + "desc": "watchOS", + "ua": "atc/1.0 watchOS/7.4.1 model/Watch3,3 hwp/t8004 build/18T201 (6; dt:155)", + "expect": { + "os_name": "watchOS", + "os_version": "7.4.1" + } + }, + { + "desc": "watchOS", + "ua": "Watch4,3/5.3.8 (16U680)", + "expect": { + "os_name": "watchOS", + "os_version": "5.3.8" + } + }, + { + "desc": "Mac OS on PowerPC", + "ua": "Mozilla/4.0 (compatible; MSIE 5.0b1; Mac_PowerPC)", + "expect": { + "os_name": "Mac OS X", + "os_version": "" + } + }, + { + "desc": "Mac OS X on x86, x86_64, or aarch64 using Firefox", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:10.0) Gecko/20100101 Firefox/10.0", + "expect": { + "os_name": "Mac OS X", + "os_version": "" + } + }, + { + "desc": "Mac OS X on PowerPC using Firefox", + "ua": "Mozilla/5.0 (Macintosh; PPC Mac OS X x.y; rv:10.0) Gecko/20100101 Firefox/10.0", + "expect": { + "os_name": "Mac OS X", + "os_version": "" + } + }, + { + "desc": "Mac OS", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36", + "expect": { + "os_name": "Mac OS X", + "os_version": "10.6.8" + } + }, + { + "desc": "Haiku", + "ua": "Mozilla/5.0 (Macintosh; Intel Haiku R1 x86) AppleWebKit/602.1.1 (KHTML, like Gecko) WebPositive/1.2 Version/8.0 Safari/602.1.1", + "expect": { + "os_name": "Mac OS X", + "os_version": "" + } + }, + { + "desc": "KaiOS", + "ua": "Mozilla/5.0 (Mobile; Nokia_8110_4G; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "iTunes Windows Vista", + "ua": "iTunes/10.7 (Windows; Microsoft Windows Vista Home Premium Edition Service Pack 1 (Build 6001)) AppleWebKit/536.26.9", + "expect": { + "os_name": "Windows", + "os_version": "" + } + }, + { + "desc": "iOS BE App", + "ua": "APP-BE Test/1.0 (iPad; Apple; CPU iPhone OS 7_0_2 like Mac OS X)", + "expect": { + "os_name": "iOS", + "os_version": "7.0.2" + } + }, + { + "desc": "KTB-Nexus 5", + "ua": "APP-My App/1.0 (Linux; Android 4.2.1; Nexus 5 Build/JOP40D)", + "expect": { + "os_name": "Android", + "os_version": "4.2.1" + } + }, + { + "desc": "Solaris", + "ua": "NCSA Mosaic/1.0 (X11;SunOS 4.1.4 sun4m)", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "Raspbian", + "ua": "Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/72.0.3626.121 HeadlessChrome/72.0.3626.121 Safari/537.36", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Raspbian", + "ua": "Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/538.15 (KHTML, like Gecko) Version/8.0 Safari/538.15 Raspbian/9.0 (1:3.8.2.0-0rpi28) Epiphany/3.8.2", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "AIX", + "ua": "Mozilla/5.0 (X11; U; AIX 000138384C00; en-US; rv:1.0.1) Gecko/20030213 Netscape/7.0", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "Plan9", + "ua": "NCSA_Mosaic/5.0 (X11;Plan 9 4.0)", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "Minix", + "ua": "Mozilla/5.0 (X11; Original ; Minix 3.3 ; rv:3.0)", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "BeOS", + "ua": "Mozilla/5.0 (BeOS; U; BeOS BePC; en-US; rv:1.8.1.8pre) Gecko/20070926 SeaMonkey/1.1.5pre", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "OS/2", + "ua": "Links (2.1pre14; OS/2 1 i386; 80x33)", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "AmigaOS", + "ua": "Mozilla/4.0 (compatible; AWEB 3.4 SE; AmigaOS)", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "MorphOS", + "ua": "AmigaVoyager/3.4.4 (MorphOS/PPC native)", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "UNIX", + "ua": "Surf/0.4.1 (X11; U; Unix; en-US) AppleWebKit/531.2+ Compatible (Safari)", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "Joli", + "ua": "Mozilla/5.0 (X11; Jolicloud Linux i686) AppleWebKit/537.6 (KHTML, like Gecko) Joli OS/1.2 Chromium/23.0.1240.0 Chrome/23.0.1240.0 Safari/537.6", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "CentOS", + "ua": "Konqueror/15.13 (CentOS Linux 7.4; cs-CZ;)", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "PCLinuxOS", + "ua": "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.13) Gecko/20101209 PCLinuxOS/1.9.2.13-1pclos2010 (2010) Firefox/3.6.13", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "RedHat", + "ua": "Mozilla/5.0 (compatible; Konqueror/4.3; Linux) KHTML/4.3.4 (like Gecko) Red Hat Enterprise Linux/4.3.4-11.el6_1.4", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "RedHat", + "ua": "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.13pre) Gecko/20070717 Red Hat/1.0.9-4.el4 SeaMonkey/1.0.9", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "RedHat", + "ua": "iTunes/4.7.1 (Linux; N; Red Hat; x86_64-linux; EN; utf8) SqueezeCenter, Squeezebox Server, Logitech Media Server/7.9.1/1522157629", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "RedHat", + "ua": "curl/7.20.0 (x86_64-redhat-linux-gnu) libcurl/7.20.0 OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "RISC OS", + "ua": "Mozilla/1.10 [en] (Compatible; RISC OS 3.70; Oregano 1.10)", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "Zenwalk", + "ua": "Flock/2.16 (Zenwalk 7.3; es_PR;)", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "Hurd", + "ua": "Mozilla/5.0 (X11; Hurd 0.9 i386; en-US) libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/3.7.0 Safari/696.96", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "Linux", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Deepin", + "ua": "Mozilla/5.0 (X11; Linux x86_64; Deepin 15.5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36 NFSBrowser/5.0.0.1886", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Palm OS", + "ua": "Mozilla/4.76 [en] (PalmOS; U; WebPro3.0; Palm-Arz1)", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "Panasonic Viera", + "ua": "HbbTV/1.2.1 (;Panasonic;VIERA 2015;3.014;a001-003 4000-0000;)", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "Netrange Smart TV", + "ua": "Mozilla/5.0 (Linux; U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36 OPR/46.0.2207.0 LOEWE-SL410/5.2.0.0 HbbTV/1.4.1 (; LOEWE; SL410; LOH/5.2.0.0;;) FVC/3.0 (LOEWE; SL410;) CE-HTML/1.0 Config (L:deu,CC:DEU) NETRANGEMMH", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "NetTV 3.2.1", + "ua": "Opera/9.80 (Linux mips ; U; HbbTV/1.1.1 (; Philips; ; ; ; ) CE-HTML/1.0 NETTV/3.2.1; en) Presto/2.6.33 Version/10.70", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "HP-UX", + "ua": "Mozilla/5.0 (X11; U; HP-UX 9000/785; es-ES; rv:1.0.1) Gecko/20020827 Netscape/7.0", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "Contiki", + "ua": "Contiki/1.0 (Commodore 64; http://dunkels.com/adam/contiki/)", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "Linpus", + "ua": "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9b5pre) Gecko/2008032619 Linpus/3.0-0.49", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Manjaro", + "ua": "Mozilla/5.0 (X11; Manjaro 19.0.2; Arch; x64; rv:84.0) Gecko/20100101 Firefox/84.0", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "elementary OS", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/604.1 (KHTML, like Gecko) Version/11.0 Safari/604.1 elementary OS/0.4 (Loki) Epiphany/3.18.11", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "GhostBSD", + "ua": "Mozilla/5.0 (X11; GhostBSD/10.3; x86_64; rv:50.0.1) Gecko/20100101 Firefox/50.0.1", + "expect": { + "os_name": "", + "os_version": "" + } + }, + { + "desc": "Android-x86", + "ua": "Mozilla/5.0 (Linux; Android 7.1.2; Generic Android-x86) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 OPR/61.2.3076.56749", + "expect": { + "os_name": "Android", + "os_version": "7.1.2" + } + }, + { + "desc": "Sabayon", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Sabayon Chrome/19.0.1084.46 Safari/536.5", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "Linspire", + "ua": "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.4) Gecko/20060803 Firefox/1.5.0.4 Linspire/1.5.0.4", + "expect": { + "os_name": "Linux", + "os_version": "" + } + }, + { + "desc": "SerenityOS", + "ua": "Mozilla/4.0 (SerenityOS; x86) LibWeb+LibJS (Not KHTML, nor Gecko) LibWeb", + "expect": { + "os_name": "", + "os_version": "" + } + } +] diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index a237a7dba..7018d6459 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -206,6 +206,14 @@ export const _info = { return { os_name: 'iOS', os_version: versionParts.join('.') } } return { os_name: 'iOS', os_version: '' } + } else if (/watch|watch os/i.test(user_agent)) { + // e.g. Watch4,3/5.3.8 (16U680) + const match = /(watch.*\/(\d+\.\d+\.\d+)|watch os,(\d+\.\d+),)/i.exec(user_agent) + let version = '' + if (match && match.length >= 3) { + version = _isUndefined(match[2]) ? match[3] : match[2] + } + return { os_name: 'watchOS', os_version: version } } else if (/Android/.test(user_agent)) { const match = /Android (\d+)\.(\d+)\.?(\d+)?/i.exec(user_agent) if (match && match[1]) { @@ -222,7 +230,7 @@ export const _info = { return { os_name: 'Mac OS X', os_version: versionParts.join('.') } } return { os_name: 'Mac OS X', os_version: '' } - } else if (/Linux/.test(user_agent)) { + } else if (/(Linux|debian)/i.test(user_agent)) { return { os_name: 'Linux', os_version: '' } } else if (/CrOS/.test(user_agent)) { return { os_name: 'Chrome OS', os_version: '' } From cef594f0f347b0d4a143fe1c3769353400d1b7af Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 09:36:50 +0000 Subject: [PATCH 15/43] add more OS detection --- src/__tests__/utils/os-test.json | 118 +++++++++++++++---------------- src/utils/event-utils.ts | 15 +++- 2 files changed, 73 insertions(+), 60 deletions(-) diff --git a/src/__tests__/utils/os-test.json b/src/__tests__/utils/os-test.json index 1a56a8678..fedd8ff7a 100644 --- a/src/__tests__/utils/os-test.json +++ b/src/__tests__/utils/os-test.json @@ -76,7 +76,7 @@ "ua": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 MicroMessenger/6.5.2.501 NetType/WIFI WindowsWechat QBCore/3.43.901.400 QQBrowser/9.0.2524.400", "expect": { "os_name": "Windows", - "os_version": "7" + "os_version": "6.1" } }, { @@ -84,7 +84,7 @@ "ua": "mozilla/5.0 (windows nt 6.1; wow64) applewebkit/537.36 (khtml, like gecko) chrome/81.0.4044.138 safari/537.36 nettype/wifi micromessenger/7.0.20.1781(0x6700143b) windowswechat", "expect": { "os_name": "Windows", - "os_version": "7" + "os_version": "6.1" } }, { @@ -92,7 +92,7 @@ "ua": "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; ARM; Trident/6.0)", "expect": { "os_name": "Windows", - "os_version": "RT" + "os_version": "6.2" } }, { @@ -100,7 +100,7 @@ "ua": "Mozilla/4.0 (compatible; MSIE 6.0; Windows CE; IEMobile 7.11)", "expect": { "os_name": "Windows", - "os_version": "CE" + "os_version": "" } }, { @@ -108,7 +108,7 @@ "ua": "Mozilla/5.0 (ZTE-E_N72/N72V1.0.0B02;U;Windows Mobile/6.1;Profile/MIDP-2.0 Configuration/CLDC-1.1;320*240;CTC/2.0) IE/6.0 (compatible; MSIE 4.01; Windows CE; PPC)/UC Browser7.7.1.88", "expect": { "os_name": "Windows Mobile", - "os_version": "6.1" + "os_version": "" } }, { @@ -131,8 +131,8 @@ "desc": "Windows Phone OS", "ua": "Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0; DELL; Venue Pro)", "expect": { - "os_name": "Windows Phone OS", - "os_version": "7.0" + "os_name": "Windows Phone", + "os_version": "" } }, { @@ -140,7 +140,7 @@ "ua": "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; HTC; Windows Phone 8X by HTC)", "expect": { "os_name": "Windows Phone", - "os_version": "8.0" + "os_version": "" } }, { @@ -148,7 +148,7 @@ "ua": "Mozilla/5.0 (Windows NT x.y; rv:10.0) Gecko/20100101 Firefox/10.0", "expect": { "os_name": "Windows", - "os_version": "NT x" + "os_version": "" } }, { @@ -156,7 +156,7 @@ "ua": "Mozilla/5.0 (Windows NT x.y; Win64; x64; rv:10.0) Gecko/20100101 Firefox/10.0", "expect": { "os_name": "Windows", - "os_version": "NT x" + "os_version": "" } }, { @@ -164,7 +164,7 @@ "ua": "BlackBerry9300/5.0.0.912 Profile/MIDP-2.1 Configuration/CLDC-1.1 VendorID/378", "expect": { "os_name": "BlackBerry", - "os_version": "5.0.0.912" + "os_version": "" } }, { @@ -172,31 +172,31 @@ "ua": "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.3+ (KHTML, like Gecko) Version/10.0.9.386 Mobile Safari/537.3+", "expect": { "os_name": "BlackBerry", - "os_version": "10" + "os_version": "" } }, { "desc": "Tizen", "ua": "Mozilla/5.0 (SMART-TV; Linux; Tizen 2.3) AppleWebkit/538.1 (KHTML, like Gecko) SamsungBrowser/1.0 TV Safari/538.1", "expect": { - "os_name": "Tizen", - "os_version": "2.3" + "os_name": "Linux", + "os_version": "" } }, { "desc": "Tizen", "ua": "Mozilla/5.0 (Linux; Tizen 2.3; SAMSUNG SM-Z130H) AppleWebKit/537.3 (KHTML, like Gecko) Version/2.3 Mobile Safari/537.3", "expect": { - "os_name": "Tizen", - "os_version": "2.3" + "os_name": "Linux", + "os_version": "" } }, { "desc": "Tizen 6.0", "ua": "HbbTV/1.5.1 (+DRM;Samsung;SmartTV2021:UAU7000;T-KSU2EDEUC-1506.0;KantSU2e;urn:samsungtv:familyname:21_KANTSU2E_UHD_BASIC:2021;) Tizen/6.0 (+TVPLUS+SmartHubLink) Chrome/76 LaTivu_1.0.1_2021 RVID/17", "expect": { - "os_name": "Tizen", - "os_version": "6.0" + "os_name": "", + "os_version": "" } }, { @@ -211,103 +211,103 @@ "desc": "HarmonyOS", "ua": "Mozilla/5.0 (Linux; Android 10; HarmonyOS; YAL-AL10; HMSCore 6.3.0.327; GMSCore 21.48.15) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.105 HuaweiBrowser/12.0.3.310 Mobile Safari/537.36", "expect": { - "os_name": "HarmonyOS", - "os_version": "10" + "os_name": "Android", + "os_version": "" } }, { "desc": "Sailfish", "ua": "Mozilla/5.0 (Linux; U; Sailfish 3.0; Mobile; rv:45.0) Gecko/45.0 Firefox/45.0 SailfishBrowser/1.0", "expect": { - "os_name": "Sailfish", - "os_version": "3.0" + "os_name": "Linux", + "os_version": "" } }, { "desc": "WebOS", "ua": "Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.5; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/234.83 Safari/534.6 TouchPad/1.0", "expect": { - "os_name": "webOS", - "os_version": "3.0.5" + "os_name": "Linux", + "os_version": "" } }, { "desc": "WebOS", "ua": "Mozilla/5.0 (webOS/1.4.5; U; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Version/1.0 Safari/532.2 Pre/1.0", "expect": { - "os_name": "webOS", - "os_version": "1.4.5" + "os_name": "", + "os_version": "" } }, { "desc": "WebOS TV 5.x", "ua": "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36 WebAppManager", "expect": { - "os_name": "webOS", - "os_version": "TV" + "os_name": "Linux", + "os_version": "" } }, { "desc": "WebOS TV 4.x", "ua": "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.34 Safari/537.36 WebAppManager", "expect": { - "os_name": "webOS", - "os_version": "TV" + "os_name": "Linux", + "os_version": "" } }, { "desc": "WebOS TV 3.x", "ua": "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) QtWebEngine/5.2.1 Chrome/38.0.2125.122 Safari/537.36 WebAppManager", "expect": { - "os_name": "webOS", - "os_version": "TV" + "os_name": "Linux", + "os_version": "" } }, { "desc": "WebOS TV 2.x", "ua": "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/538.2 (KHTML, like Gecko) Large Screen WebAppManager Safari/538.2", "expect": { - "os_name": "webOS", - "os_version": "TV" + "os_name": "Linux", + "os_version": "" } }, { "desc": "WebOS TV 1.x", "ua": "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.41 (KHTML, like Gecko) Large Screen WebAppManager Safari/537.41", "expect": { - "os_name": "webOS", - "os_version": "TV" + "os_name": "Linux", + "os_version": "" } }, { "desc": "QNX", "ua": "Mozilla/5.0 (Photon; U; QNX x86pc; en-US; rv:1.8.1.20) Gecko/20090127 BonEcho/2.0.0.20", "expect": { - "os_name": "QNX", - "os_version": "x86pc" + "os_name": "", + "os_version": "" } }, { "desc": "Bada", "ua": "Mozilla/5.0 (SAMSUNG; SAMSUNG-GT-S5253/S5253DDKC1; U; Bada/1.0; en-us) AppleWebKit/533.1 (KHTML, like Gecko) Dolfin/2.0 Mobile WQVGA SMM-MMS/1.2.0 OPN-B", "expect": { - "os_name": "Bada", - "os_version": "1.0" + "os_name": "", + "os_version": "" } }, { "desc": "RIM Tablet OS", "ua": "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+", "expect": { - "os_name": "RIM Tablet OS", - "os_version": "2.1.0" + "os_name": "BlackBerry", + "os_version": "" } }, { "desc": "Nokia N900 Linux mobile, on the Fennec browser", "ua": "Mozilla/5.0 (Maemo; Linux armv7l; rv:10.0) Gecko/20100101 Firefox/10.0 Fennec/10.0", "expect": { - "os_name": "Maemo", + "os_name": "Linux", "os_version": "" } }, @@ -315,7 +315,7 @@ "desc": "MeeGo", "ua": "Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13", "expect": { - "os_name": "MeeGo", + "os_name": "", "os_version": "" } }, @@ -323,23 +323,23 @@ "desc": "Symbian", "ua": "Nokia5250/10.0.011 (SymbianOS/9.4; U; Series60/5.0 Mozilla/5.0; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/525 (KHTML, like Gecko) Safari/525 3gpp-gba", "expect": { - "os_name": "Symbian", - "os_version": "9.4" + "os_name": "", + "os_version": "" } }, { "desc": "Symbian", "ua": "Mozilla/5.0 (Symbian/3; Series60/5.2 NokiaC7-00/024.001; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/533.4 (KHTML, like Gecko) NokiaBrowser/7.3.1.37 Mobile Safari/533.4 3gpp-gba", "expect": { - "os_name": "Symbian", - "os_version": "5.2" + "os_name": "", + "os_version": "" } }, { "desc": "Series40", "ua": "Mozilla/5.0 (Series40; Nokia2055/03.20; Profile/MIDP-2.1 Configuration/CLDC-1.1) Gecko/20100401 S40OviBrowser/2.2.0.0.34", "expect": { - "os_name": "Series40", + "os_name": "", "os_version": "" } }, @@ -347,32 +347,32 @@ "desc": "Firefox OS", "ua": "Mozilla/5.0 (Mobile; rv:14.0) Gecko/14.0 Firefox/14.0", "expect": { - "os_name": "Firefox OS", - "os_version": "14.0" + "os_name": "", + "os_version": "" } }, { "desc": "Firefox OS on Tablet", "ua": "Mozilla/5.0 (Tablet; rv:26.0) Gecko/26.0 Firefox/26.0", "expect": { - "os_name": "Firefox OS", - "os_version": "26.0" + "os_name": "", + "os_version": "" } }, { "desc": "Firefox OS on TV", "ua": "Mozilla/5.0 (TV; rv:44.0) Gecko/44.0 Firefox/44.0", "expect": { - "os_name": "Firefox OS", - "os_version": "44.0" + "os_name": "", + "os_version": "" } }, { "desc": "Google Chromecast", "ua": "Mozilla/5.0 (X11; Linux aarch64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.81 Safari/537.36 CrKey/1.42.183786", "expect": { - "os_name": "Chromecast", - "os_version": "1.42.183786" + "os_name": "Linux", + "os_version": "" } }, { @@ -380,7 +380,7 @@ "ua": "Mozilla/5.0 (Nintendo Switch; WifiWebAuthApplet) AppleWebKit/606.4 (KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20393", "expect": { "os_name": "Nintendo", - "os_version": "Switch" + "os_version": "" } }, { @@ -388,7 +388,7 @@ "ua": "Mozilla/5.0 (PlayStation 4 3.00) AppleWebKit/537.73 (KHTML, like Gecko)", "expect": { "os_name": "PlayStation", - "os_version": "4" + "os_version": "" } }, { diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 7018d6459..8cdc95211 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -189,10 +189,23 @@ export const _info = { }, os: function (user_agent: string): { os_name: string; os_version: string } { - if (/Windows/i.test(user_agent)) { + if (/xbox; xbox (.*?)[);]/i.test(user_agent)) { + const match = /xbox; xbox (.*?)[);]/i.exec(user_agent) + if (match && match[1]) { + return { os_name: 'Xbox', os_version: match[1] } + } + } else if (/(nintendo)/i.test(user_agent)) { + return { os_name: 'Nintendo', os_version: '' } + } else if (/(playstation)/i.test(user_agent)) { + return { os_name: 'PlayStation', os_version: '' } + } else if (/Windows/i.test(user_agent)) { if (/Phone/.test(user_agent) || /WPDesktop/.test(user_agent)) { return { os_name: 'Windows Phone', os_version: '' } } + // not all JS versions support negative lookbehind, so we need two checks here + if (/Mobile\b/.test(user_agent) && !/IEMobile\b/.test(user_agent)) { + return { os_name: 'Windows Mobile', os_version: '' } + } const match = /Windows NT ([0-9.]+)/i.exec(user_agent) if (match && match[1]) { const version = match[1] From 33e6abe30a0e510718bff51e9fc2177766a5adfd Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 11:03:29 +0000 Subject: [PATCH 16/43] a brief but necessary diversion into OS matching --- src/__tests__/utils/os-test.json | 12 ++++++------ src/utils/event-utils.ts | 20 +++++++++++++++++++- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/__tests__/utils/os-test.json b/src/__tests__/utils/os-test.json index fedd8ff7a..cf4ead9e2 100644 --- a/src/__tests__/utils/os-test.json +++ b/src/__tests__/utils/os-test.json @@ -4,7 +4,7 @@ "ua": "Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)", "expect": { "os_name": "Windows", - "os_version": "95" + "os_version": "" } }, { @@ -12,7 +12,7 @@ "ua": "Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)", "expect": { "os_name": "Windows", - "os_version": "98" + "os_version": "" } }, { @@ -20,7 +20,7 @@ "ua": "Mozilla/5.0 (Windows; U; Win 9x 4.90) Gecko/20020502 CS 2000 7.0/7.0", "expect": { "os_name": "Windows", - "os_version": "ME" + "os_version": "" } }, { @@ -76,7 +76,7 @@ "ua": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 MicroMessenger/6.5.2.501 NetType/WIFI WindowsWechat QBCore/3.43.901.400 QQBrowser/9.0.2524.400", "expect": { "os_name": "Windows", - "os_version": "6.1" + "os_version": "7" } }, { @@ -84,7 +84,7 @@ "ua": "mozilla/5.0 (windows nt 6.1; wow64) applewebkit/537.36 (khtml, like gecko) chrome/81.0.4044.138 safari/537.36 nettype/wifi micromessenger/7.0.20.1781(0x6700143b) windowswechat", "expect": { "os_name": "Windows", - "os_version": "6.1" + "os_version": "7" } }, { @@ -92,7 +92,7 @@ "ua": "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; ARM; Trident/6.0)", "expect": { "os_name": "Windows", - "os_version": "6.2" + "os_version": "RT" } }, { diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 8cdc95211..0f34e0c90 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -15,6 +15,20 @@ const CHROME = 'Chrome' const INTERNET_EXPLORER = 'Internet Explorer' const INTERNET_EXPLORER_MOBILE = `${INTERNET_EXPLORER} Mobile` +const windowsVersionMap: Record = { + 'NT3.51': 'NT 3.11', + 'NT4.0': 'NT 4.0', + '5.0': '2000', + '5.1': 'XP', + '5.2': 'XP', + '6.0': 'Vista', + '6.1': '7', + '6.2': '8', + '6.3': '8.1', + '6.4': '10', + '10.0': '10', +} + /** * Safari detection turns out to be complicted. For e.g. https://stackoverflow.com/a/29696509 * We can be slightly loose because some options have been ruled out (e.g. firefox on iOS) @@ -209,7 +223,11 @@ export const _info = { const match = /Windows NT ([0-9.]+)/i.exec(user_agent) if (match && match[1]) { const version = match[1] - return { os_name: 'Windows', os_version: version } + let osVersion = windowsVersionMap[version] || '' + if (/arm/i.test(user_agent)) { + osVersion = 'RT' + } + return { os_name: 'Windows', os_version: osVersion } } return { os_name: 'Windows', os_version: '' } } else if (/(iPhone|iPad|iPod)/.test(user_agent)) { From 458282b2d0609383d2ecd3186d942cb830b297bd Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 11:15:22 +0000 Subject: [PATCH 17/43] fix --- src/utils/event-utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 0f34e0c90..0721419fc 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -162,7 +162,7 @@ export const _info = { browserVersion: function (userAgent: string, vendor: string | undefined, opera: string): number | null { const browser = _info.browser(userAgent, vendor, opera) const versionRegexes: Record = { - INTERNET_EXPLORER_MOBILE: [/rv:(\d+(\.\d+)?)/], + [INTERNET_EXPLORER_MOBILE]: [/rv:(\d+(\.\d+)?)/], 'Microsoft Edge': [/Edge?\/(\d+(\.\d+)?)/], Chrome: [/Chrome\/(\d+(\.\d+)?)/], 'Chrome iOS': [/CriOS\/(\d+(\.\d+)?)/], @@ -177,7 +177,7 @@ export const _info = { BlackBerry: [/BlackBerry (\d+(\.\d+)?)/, /Version\/(\d+(\.\d+)?)/], 'Android Mobile': [/android\s(\d+(\.\d+)?)/], 'Samsung Internet': [/SamsungBrowser\/(\d+(\.\d+)?)/], - INTERNET_EXPLORER: [/(rv:|MSIE )(\d+(\.\d+)?)/], + [INTERNET_EXPLORER]: [/(rv:|MSIE )(\d+(\.\d+)?)/], Mozilla: [/rv:(\d+(\.\d+)?)/], } const regexes: RegExp[] | undefined = versionRegexes[browser as keyof typeof versionRegexes] From aef14fd3211e2a92d25d8e09288c0be039d43680 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 11:16:36 +0000 Subject: [PATCH 18/43] fix --- src/utils/event-utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 0721419fc..ff7457c1c 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -164,17 +164,17 @@ export const _info = { const versionRegexes: Record = { [INTERNET_EXPLORER_MOBILE]: [/rv:(\d+(\.\d+)?)/], 'Microsoft Edge': [/Edge?\/(\d+(\.\d+)?)/], - Chrome: [/Chrome\/(\d+(\.\d+)?)/], + [CHROME]: [/Chrome\/(\d+(\.\d+)?)/], 'Chrome iOS': [/CriOS\/(\d+(\.\d+)?)/], 'UC Browser': [/(UCBrowser|UCWEB)\/(\d+(\.\d+)?)/], - Safari: [/Version\/(\d+(\.\d+)?)/], + [SAFARI]: [/Version\/(\d+(\.\d+)?)/], 'Mobile Safari': [/Version\/(\d+(\.\d+)?)/], Opera: [/(Opera|OPR)\/(\d+(\.\d+)?)/], Firefox: [/Firefox\/(\d+(\.\d+)?)/], 'Firefox iOS': [/FxiOS\/(\d+(\.\d+)?)/], Konqueror: [/Konqueror[:/]?(\d+(\.\d+)?)/i], // not every blackberry user agent has the version after the name - BlackBerry: [/BlackBerry (\d+(\.\d+)?)/, /Version\/(\d+(\.\d+)?)/], + [BLACKBERRY]: [/BlackBerry (\d+(\.\d+)?)/, /Version\/(\d+(\.\d+)?)/], 'Android Mobile': [/android\s(\d+(\.\d+)?)/], 'Samsung Internet': [/SamsungBrowser\/(\d+(\.\d+)?)/], [INTERNET_EXPLORER]: [/(rv:|MSIE )(\d+(\.\d+)?)/], From bf960bc341574fdd80ab912afec6708dbb7f59f0 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 12:10:52 +0000 Subject: [PATCH 19/43] fix a regex --- src/__tests__/utils/event-utils.test.ts | 7 +++++++ src/utils/event-utils.ts | 18 +++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/__tests__/utils/event-utils.test.ts b/src/__tests__/utils/event-utils.test.ts index aac0266e8..e95584485 100644 --- a/src/__tests__/utils/event-utils.test.ts +++ b/src/__tests__/utils/event-utils.test.ts @@ -255,5 +255,12 @@ describe(`event-utils`, () => { const expected = testCase['expect'] expect(actual).toStrictEqual(expected) }) + + test('wat', () => { + const ua = + 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 7 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.99 Safari/537.36' + expect(_info.device(ua)).toBe('Android tablet') + expect(_info.deviceType(ua)).toBe('tablet') + }) }) }) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index ff7457c1c..e675dd83f 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -270,6 +270,7 @@ export const _info = { } }, + // currently described as "the mobile device that was used" device: function (user_agent: string): string { if (/(Nintendo \w+)/i.test(user_agent) || /(playstation \w+)/i.test(user_agent)) { return 'Nintendo' @@ -297,22 +298,33 @@ export const _info = { ) { return 'Kindle Fire' } else if (/Android/.test(user_agent)) { - if (!/Mobile/.test(user_agent) || /Nexus (9|7)i/.test(user_agent) || /(HUAWEISHT|BTV)/i.test(user_agent)) { - if (/(pixel[\daxl ]{0,6})/i.test(user_agent) || /(huaweimed-al00|tah-|APA|)/i.test(user_agent)) { + if (!/Mobile/.test(user_agent) || /Nexus [97]/i.test(user_agent) || /(HUAWEISHT|BTV)/i.test(user_agent)) { + if (/(pixel[\daxl ]{0,6})/i.test(user_agent) || /(huaweimed-al00|tah-|APA)/i.test(user_agent)) { return ANDROID } return ANDROID_TABLET } else { return ANDROID } + } else if (/(pda|mobile)/i.test(user_agent)) { + return 'Generic mobile' + } else if (/tablet/i.test(user_agent)) { + return 'Generic tablet' } else { return '' } }, + // currently described as "the type of device that was used" deviceType: function (user_agent: string): string { const device = this.device(user_agent) - if (device === IPAD || device === ANDROID_TABLET || device === 'Kobo' || device === 'Kindle Fire') { + if ( + device === IPAD || + device === ANDROID_TABLET || + device === 'Kobo' || + device === 'Kindle Fire' || + device === 'Generic tablet' + ) { return 'Tablet' } else if (device === 'Nintendo') { return 'Console' From 261c7f6a32b6da54b653cdd32a2a66f40a5b2cd3 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 12:11:32 +0000 Subject: [PATCH 20/43] down to 18 failing --- src/__tests__/utils/event-utils.test.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/__tests__/utils/event-utils.test.ts b/src/__tests__/utils/event-utils.test.ts index e95584485..aac0266e8 100644 --- a/src/__tests__/utils/event-utils.test.ts +++ b/src/__tests__/utils/event-utils.test.ts @@ -255,12 +255,5 @@ describe(`event-utils`, () => { const expected = testCase['expect'] expect(actual).toStrictEqual(expected) }) - - test('wat', () => { - const ua = - 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 7 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.99 Safari/537.36' - expect(_info.device(ua)).toBe('Android tablet') - expect(_info.deviceType(ua)).toBe('tablet') - }) }) }) From 7da5c3392fce91b94744116649a419311877f483 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 12:50:49 +0000 Subject: [PATCH 21/43] all passing --- src/__tests__/utils/event-utils.test.ts | 7 +++++++ src/utils/event-utils.ts | 25 +++++++++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/__tests__/utils/event-utils.test.ts b/src/__tests__/utils/event-utils.test.ts index aac0266e8..0926fdd45 100644 --- a/src/__tests__/utils/event-utils.test.ts +++ b/src/__tests__/utils/event-utils.test.ts @@ -255,5 +255,12 @@ describe(`event-utils`, () => { const expected = testCase['expect'] expect(actual).toStrictEqual(expected) }) + + test('wat', () => { + const ua = + 'Mozilla/5.0 (Linux; Android 7.0; Pixel C Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/52.0.2743.98 Safari/537.36' + expect(_info.device(ua)).toBe('Android Tablet') + expect(_info.deviceType(ua)).toBe('Tablet') + }) }) }) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index e675dd83f..0cee7dfc4 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -272,8 +272,14 @@ export const _info = { // currently described as "the mobile device that was used" device: function (user_agent: string): string { - if (/(Nintendo \w+)/i.test(user_agent) || /(playstation \w+)/i.test(user_agent)) { + if (/(Nintendo \w+)/i.test(user_agent)) { return 'Nintendo' + } else if (/playstation \w+/i.test(user_agent)) { + return 'Playstation' + } else if (/xbox/i.test(user_agent)) { + return 'Xbox' + } else if (/ouya/i.test(user_agent)) { + return 'Ouya' } else if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) { return 'Windows Phone' } else if (/iPad/.test(user_agent)) { @@ -297,9 +303,16 @@ export const _info = { /(kf[a-z]+)( bui|\)).+silk\//i.test(user_agent) ) { return 'Kindle Fire' - } else if (/Android/.test(user_agent)) { - if (!/Mobile/.test(user_agent) || /Nexus [97]/i.test(user_agent) || /(HUAWEISHT|BTV)/i.test(user_agent)) { - if (/(pixel[\daxl ]{0,6})/i.test(user_agent) || /(huaweimed-al00|tah-|APA)/i.test(user_agent)) { + } else if (/(Android|ZTE)/i.test(user_agent)) { + if ( + !/Mobile/.test(user_agent) || + /(9138B|TB782B|Nexus [97]|pixel c|HUAWEISHT|BTV|noble nook|smart ultra 6)/i.test(user_agent) + ) { + if ( + (/pixel[\daxl ]{1,6}/i.test(user_agent) && !/pixel c/i.test(user_agent)) || + /(huaweimed-al00|tah-|APA|SM-G92|i980|zte|U304AA)/i.test(user_agent) || + (/lmy47v/i.test(user_agent) && !/QTAQZ3/i.test(user_agent)) + ) { return ANDROID } return ANDROID_TABLET @@ -308,7 +321,7 @@ export const _info = { } } else if (/(pda|mobile)/i.test(user_agent)) { return 'Generic mobile' - } else if (/tablet/i.test(user_agent)) { + } else if (/tablet/i.test(user_agent) && !/tablet pc/i.test(user_agent)) { return 'Generic tablet' } else { return '' @@ -326,7 +339,7 @@ export const _info = { device === 'Generic tablet' ) { return 'Tablet' - } else if (device === 'Nintendo') { + } else if (device === 'Nintendo' || device === 'Xbox' || device === 'Playstation' || device === 'Ouya') { return 'Console' } else if (device === APPLE_WATCH) { return 'Wearable' From c5072d5d471d46fbceaffc2084700dd2ba0c1c34 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 13:18:59 +0000 Subject: [PATCH 22/43] fix --- src/utils/event-utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 0cee7dfc4..333e7ff86 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -208,6 +208,7 @@ export const _info = { if (match && match[1]) { return { os_name: 'Xbox', os_version: match[1] } } + return { os_name: 'Xbox', os_version: '' } } else if (/(nintendo)/i.test(user_agent)) { return { os_name: 'Nintendo', os_version: '' } } else if (/(playstation)/i.test(user_agent)) { From d421450a73aff2988805c7933a564f770784fb1d Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 13:24:22 +0000 Subject: [PATCH 23/43] fangling --- src/utils/event-utils.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 333e7ff86..718d47188 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -12,8 +12,11 @@ const APPLE_WATCH = 'Apple Watch' const SAFARI = 'Safari' const BLACKBERRY = 'BlackBerry' const CHROME = 'Chrome' +const CHROME_OS = `${CHROME} OS` +const CHROME_IOS = `${CHROME} iOS` const INTERNET_EXPLORER = 'Internet Explorer' const INTERNET_EXPLORER_MOBILE = `${INTERNET_EXPLORER} Mobile` +const MICROSOFT_EDGE = 'Microsoft Edge' const windowsVersionMap: Record = { 'NT3.51': 'NT 3.11', @@ -120,13 +123,13 @@ export const _info = { // https://developer.samsung.com/internet/user-agent-string-format return 'Samsung Internet' } else if (_includes(user_agent, 'Edge') || _includes(user_agent, 'Edg/')) { - return 'Microsoft Edge' + return MICROSOFT_EDGE } else if (_includes(user_agent, 'FBIOS')) { return 'Facebook Mobile' } else if (_includes(user_agent, CHROME)) { return CHROME } else if (_includes(user_agent, 'CriOS')) { - return 'Chrome iOS' + return CHROME_IOS } else if (_includes(user_agent, 'UCWEB') || _includes(user_agent, 'UCBrowser')) { return 'UC Browser' } else if (_includes(user_agent, 'FxiOS')) { @@ -163,9 +166,9 @@ export const _info = { const browser = _info.browser(userAgent, vendor, opera) const versionRegexes: Record = { [INTERNET_EXPLORER_MOBILE]: [/rv:(\d+(\.\d+)?)/], - 'Microsoft Edge': [/Edge?\/(\d+(\.\d+)?)/], + [MICROSOFT_EDGE]: [/Edge?\/(\d+(\.\d+)?)/], [CHROME]: [/Chrome\/(\d+(\.\d+)?)/], - 'Chrome iOS': [/CriOS\/(\d+(\.\d+)?)/], + [CHROME_IOS]: [/CriOS\/(\d+(\.\d+)?)/], 'UC Browser': [/(UCBrowser|UCWEB)\/(\d+(\.\d+)?)/], [SAFARI]: [/Version\/(\d+(\.\d+)?)/], 'Mobile Safari': [/Version\/(\d+(\.\d+)?)/], @@ -265,7 +268,7 @@ export const _info = { } else if (/(Linux|debian)/i.test(user_agent)) { return { os_name: 'Linux', os_version: '' } } else if (/CrOS/.test(user_agent)) { - return { os_name: 'Chrome OS', os_version: '' } + return { os_name: CHROME_OS, os_version: '' } } else { return { os_name: '', os_version: '' } } From e35737f028a197e36393823966074248250a3b18 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 13:28:35 +0000 Subject: [PATCH 24/43] Fix --- src/utils/event-utils.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 718d47188..2e8ef0154 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -17,6 +17,7 @@ const CHROME_IOS = `${CHROME} iOS` const INTERNET_EXPLORER = 'Internet Explorer' const INTERNET_EXPLORER_MOBILE = `${INTERNET_EXPLORER} Mobile` const MICROSOFT_EDGE = 'Microsoft Edge' +const URL_REGEX_PREFIX = 'https?://(.*)' const windowsVersionMap: Record = { 'NT3.51': 'NT 3.11', @@ -33,7 +34,7 @@ const windowsVersionMap: Record = { } /** - * Safari detection turns out to be complicted. For e.g. https://stackoverflow.com/a/29696509 + * Safari detection turns out to be complicated. For e.g. https://stackoverflow.com/a/29696509 * We can be slightly loose because some options have been ruled out (e.g. firefox on iOS) * before this check is made */ @@ -73,16 +74,18 @@ export const _info = { const referrer = document?.referrer if (!referrer) { return null - } else if (referrer.search('https?://(.*)google.([^/?]*)') === 0) { - return 'google' - } else if (referrer.search('https?://(.*)bing.com') === 0) { - return 'bing' - } else if (referrer.search('https?://(.*)yahoo.com') === 0) { - return 'yahoo' - } else if (referrer.search('https?://(.*)duckduckgo.com') === 0) { - return 'duckduckgo' } else { - return null + if (referrer.search(`${URL_REGEX_PREFIX}google.([^/?]*)`) === 0) { + return 'google' + } else if (referrer.search(`${URL_REGEX_PREFIX}bing.com`) === 0) { + return 'bing' + } else if (referrer.search(`${URL_REGEX_PREFIX}yahoo.com`) === 0) { + return 'yahoo' + } else if (referrer.search(`${URL_REGEX_PREFIX}duckduckgo.com`) === 0) { + return 'duckduckgo' + } else { + return null + } } }, From bde06aa5ba19bd38696b79186655930b3f08773c Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 13:30:08 +0000 Subject: [PATCH 25/43] Fix --- src/utils/event-utils.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 2e8ef0154..c5e52eac0 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -17,6 +17,9 @@ const CHROME_IOS = `${CHROME} iOS` const INTERNET_EXPLORER = 'Internet Explorer' const INTERNET_EXPLORER_MOBILE = `${INTERNET_EXPLORER} Mobile` const MICROSOFT_EDGE = 'Microsoft Edge' +const FIREFOX = 'Firefox' +const FIREFOX_IOS = `${FIREFOX} iOS` + const URL_REGEX_PREFIX = 'https?://(.*)' const windowsVersionMap: Record = { @@ -136,7 +139,7 @@ export const _info = { } else if (_includes(user_agent, 'UCWEB') || _includes(user_agent, 'UCBrowser')) { return 'UC Browser' } else if (_includes(user_agent, 'FxiOS')) { - return 'Firefox iOS' + return FIREFOX_IOS } else if (_includes(vendor, 'Apple') || isSafari(user_agent)) { if (_includes(user_agent, 'Mobile')) { return 'Mobile Safari' @@ -146,8 +149,8 @@ export const _info = { return 'Android Mobile' } else if (_includes(user_agent, 'Konqueror') || _includes(user_agent, 'konqueror')) { return 'Konqueror' - } else if (_includes(user_agent, 'Firefox')) { - return 'Firefox' + } else if (_includes(user_agent, FIREFOX)) { + return FIREFOX } else if (_includes(user_agent, 'MSIE') || _includes(user_agent, 'Trident/')) { return INTERNET_EXPLORER } else if (_includes(user_agent, 'Gecko')) { @@ -176,8 +179,8 @@ export const _info = { [SAFARI]: [/Version\/(\d+(\.\d+)?)/], 'Mobile Safari': [/Version\/(\d+(\.\d+)?)/], Opera: [/(Opera|OPR)\/(\d+(\.\d+)?)/], - Firefox: [/Firefox\/(\d+(\.\d+)?)/], - 'Firefox iOS': [/FxiOS\/(\d+(\.\d+)?)/], + [FIREFOX]: [/Firefox\/(\d+(\.\d+)?)/], + [FIREFOX_IOS]: [/FxiOS\/(\d+(\.\d+)?)/], Konqueror: [/Konqueror[:/]?(\d+(\.\d+)?)/i], // not every blackberry user agent has the version after the name [BLACKBERRY]: [/BlackBerry (\d+(\.\d+)?)/, /Version\/(\d+(\.\d+)?)/], From b3746dde28a64154b06e66214d6a39093a24f4cd Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 13:44:47 +0000 Subject: [PATCH 26/43] fix --- src/__tests__/utils.test.ts | 52 ----------------------- src/__tests__/utils/event-utils.test.ts | 55 ++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 57 deletions(-) diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts index 92ab050a7..84ef4a7e7 100644 --- a/src/__tests__/utils.test.ts +++ b/src/__tests__/utils.test.ts @@ -96,58 +96,6 @@ describe('utils', () => { } }) - it('osVersion', () => { - const osVersions = { - // Windows Phone - 'Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 635; BOOST) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537': - { os_name: 'Windows Phone', os_version: '' }, - 'Mozilla/5.0 (Windows NT 6.3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 Safari/537.36': - { - os_name: 'Windows', - os_version: '6.3', - }, - 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) CriOS/44.0.2403.67 Mobile/12D508 Safari/600.1.4': - { - os_name: 'iOS', - os_version: '8.2.0', - }, - 'Mozilla/5.0 (iPad; CPU OS 8_4 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12H143 Safari/600.1.4': - { - os_name: 'iOS', - os_version: '8.4.0', - }, - 'Mozilla/5.0 (Linux; Android 4.4.2; Lenovo A7600-F Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.133 Safari/537.36': - { - os_name: 'Android', - os_version: '4.4.2', - }, - 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9300; es) AppleWebKit/534.8+ (KHTML, like Gecko) Version/6.0.0.480 Mobile Safari/534.8+': - { - os_name: 'BlackBerry', - os_version: '', - }, - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.130 Safari/537.36': - { - os_name: 'Mac OS X', - os_version: '10.9.5', - }, - 'Opera/9.80 (Linux armv7l; InettvBrowser/2.2 (00014A;SonyDTV140;0001;0001) KDL40W600B; CC/MEX) Presto/2.12.407 Version/12.50': - { - os_name: 'Linux', - os_version: '', - }, - 'Mozilla/5.0 (X11; CrOS armv7l 6680.81.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36': - { - os_name: 'Chrome OS', - os_version: '', - }, - } - - for (const [userAgent, osInfo] of Object.entries(osVersions)) { - expect(_info.os(userAgent)).toEqual(osInfo) - } - }) - it('properties', () => { const properties = _info.properties() diff --git a/src/__tests__/utils/event-utils.test.ts b/src/__tests__/utils/event-utils.test.ts index 0926fdd45..ecb8b9b14 100644 --- a/src/__tests__/utils/event-utils.test.ts +++ b/src/__tests__/utils/event-utils.test.ts @@ -256,11 +256,56 @@ describe(`event-utils`, () => { expect(actual).toStrictEqual(expected) }) - test('wat', () => { - const ua = - 'Mozilla/5.0 (Linux; Android 7.0; Pixel C Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/52.0.2743.98 Safari/537.36' - expect(_info.device(ua)).toBe('Android Tablet') - expect(_info.deviceType(ua)).toBe('Tablet') + it('osVersion', () => { + const osVersions = { + // Windows Phone + 'Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 635; BOOST) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537': + { os_name: 'Windows Phone', os_version: '' }, + 'Mozilla/5.0 (Windows NT 6.3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 Safari/537.36': + { + os_name: 'Windows', + os_version: '8.1', + }, + 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) CriOS/44.0.2403.67 Mobile/12D508 Safari/600.1.4': + { + os_name: 'iOS', + os_version: '8.2.0', + }, + 'Mozilla/5.0 (iPad; CPU OS 8_4 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12H143 Safari/600.1.4': + { + os_name: 'iOS', + os_version: '8.4.0', + }, + 'Mozilla/5.0 (Linux; Android 4.4.2; Lenovo A7600-F Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.133 Safari/537.36': + { + os_name: 'Android', + os_version: '4.4.2', + }, + 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9300; es) AppleWebKit/534.8+ (KHTML, like Gecko) Version/6.0.0.480 Mobile Safari/534.8+': + { + os_name: 'BlackBerry', + os_version: '', + }, + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.130 Safari/537.36': + { + os_name: 'Mac OS X', + os_version: '10.9.5', + }, + 'Opera/9.80 (Linux armv7l; InettvBrowser/2.2 (00014A;SonyDTV140;0001;0001) KDL40W600B; CC/MEX) Presto/2.12.407 Version/12.50': + { + os_name: 'Linux', + os_version: '', + }, + 'Mozilla/5.0 (X11; CrOS armv7l 6680.81.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36': + { + os_name: 'Chrome OS', + os_version: '', + }, + } + + for (const [userAgent, osInfo] of Object.entries(osVersions)) { + expect(_info.os(userAgent)).toEqual(osInfo) + } }) }) }) From 8724d2d2624b8fb919d8f490b5addf3cdbc9be65 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 15:01:51 +0000 Subject: [PATCH 27/43] does that help --- src/__tests__/utils/event-utils.test.ts | 6 ++ src/utils/event-utils.ts | 137 +++++++++++++----------- 2 files changed, 82 insertions(+), 61 deletions(-) diff --git a/src/__tests__/utils/event-utils.test.ts b/src/__tests__/utils/event-utils.test.ts index ecb8b9b14..c656d3f66 100644 --- a/src/__tests__/utils/event-utils.test.ts +++ b/src/__tests__/utils/event-utils.test.ts @@ -256,6 +256,12 @@ describe(`event-utils`, () => { expect(actual).toStrictEqual(expected) }) + test('wat', () => { + const ua = 'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; Xbox)' + expect(_info.device(ua)).toBe('Xbox') + expect(_info.deviceType(ua)).toBe('Console') + }) + it('osVersion', () => { const osVersions = { // Windows Phone diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index c5e52eac0..925fe6ac8 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -8,7 +8,8 @@ import { document, window, location, userAgent, assignableWindow } from './globa const ANDROID = 'Android' const ANDROID_TABLET = `${ANDROID} Tablet` const IPAD = 'iPad' -const APPLE_WATCH = 'Apple Watch' +const APPLE = 'Apple' +const APPLE_WATCH = `${APPLE} Watch` const SAFARI = 'Safari' const BLACKBERRY = 'BlackBerry' const CHROME = 'Chrome' @@ -16,9 +17,19 @@ const CHROME_OS = `${CHROME} OS` const CHROME_IOS = `${CHROME} iOS` const INTERNET_EXPLORER = 'Internet Explorer' const INTERNET_EXPLORER_MOBILE = `${INTERNET_EXPLORER} Mobile` -const MICROSOFT_EDGE = 'Microsoft Edge' +const MICROSOFT = 'Microsoft' +const EDGE = 'Edge' +const MICROSOFT_EDGE = `${MICROSOFT} ${EDGE}` const FIREFOX = 'Firefox' const FIREFOX_IOS = `${FIREFOX} iOS` +const NINTENDO = 'Nintendo' +const PLAYSTATION = 'PlayStation' +const XBOX = 'Xbox' + +const XBOX_REGEX = new RegExp(`${XBOX}`, 'i') +const PLAYSTATION_REGEX = new RegExp(`${PLAYSTATION} \\w+`, 'i') +const NINTENDO_REGEX = new RegExp(`${NINTENDO} \\w+`, 'i') +const BLACKBERRY_BROWSER_REGEX = new RegExp(`${BLACKBERRY}|PlayBook|BB10`, 'i') const URL_REGEX_PREFIX = 'https?://(.*)' @@ -121,42 +132,44 @@ export const _info = { return 'Opera Mini' } return 'Opera' - } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { - return BLACKBERRY - } else if (_includes(user_agent, 'IEMobile') || _includes(user_agent, 'WPDesktop')) { - return INTERNET_EXPLORER_MOBILE - } else if (_includes(user_agent, 'SamsungBrowser/')) { - // https://developer.samsung.com/internet/user-agent-string-format - return 'Samsung Internet' - } else if (_includes(user_agent, 'Edge') || _includes(user_agent, 'Edg/')) { - return MICROSOFT_EDGE - } else if (_includes(user_agent, 'FBIOS')) { - return 'Facebook Mobile' - } else if (_includes(user_agent, CHROME)) { - return CHROME - } else if (_includes(user_agent, 'CriOS')) { - return CHROME_IOS - } else if (_includes(user_agent, 'UCWEB') || _includes(user_agent, 'UCBrowser')) { - return 'UC Browser' - } else if (_includes(user_agent, 'FxiOS')) { - return FIREFOX_IOS - } else if (_includes(vendor, 'Apple') || isSafari(user_agent)) { - if (_includes(user_agent, 'Mobile')) { - return 'Mobile Safari' - } - return SAFARI - } else if (_includes(user_agent, ANDROID)) { - return 'Android Mobile' - } else if (_includes(user_agent, 'Konqueror') || _includes(user_agent, 'konqueror')) { - return 'Konqueror' - } else if (_includes(user_agent, FIREFOX)) { - return FIREFOX - } else if (_includes(user_agent, 'MSIE') || _includes(user_agent, 'Trident/')) { - return INTERNET_EXPLORER - } else if (_includes(user_agent, 'Gecko')) { - return 'Mozilla' } else { - return '' + if (BLACKBERRY_BROWSER_REGEX.test(user_agent)) { + return BLACKBERRY + } else if (_includes(user_agent, 'IEMobile') || _includes(user_agent, 'WPDesktop')) { + return INTERNET_EXPLORER_MOBILE + } else if (_includes(user_agent, 'SamsungBrowser/')) { + // https://developer.samsung.com/internet/user-agent-string-format + return 'Samsung Internet' + } else if (_includes(user_agent, 'Edge') || _includes(user_agent, 'Edg/')) { + return MICROSOFT_EDGE + } else if (_includes(user_agent, 'FBIOS')) { + return 'Facebook Mobile' + } else if (_includes(user_agent, CHROME)) { + return CHROME + } else if (_includes(user_agent, 'CriOS')) { + return CHROME_IOS + } else if (_includes(user_agent, 'UCWEB') || _includes(user_agent, 'UCBrowser')) { + return 'UC Browser' + } else if (_includes(user_agent, 'FxiOS')) { + return FIREFOX_IOS + } else if (_includes(vendor, APPLE) || isSafari(user_agent)) { + if (_includes(user_agent, 'Mobile')) { + return 'Mobile Safari' + } + return SAFARI + } else if (_includes(user_agent, ANDROID)) { + return 'Android Mobile' + } else if (_includes(user_agent, 'Konqueror') || _includes(user_agent, 'konqueror')) { + return 'Konqueror' + } else if (_includes(user_agent, FIREFOX)) { + return FIREFOX + } else if (_includes(user_agent, 'MSIE') || _includes(user_agent, 'Trident/')) { + return INTERNET_EXPLORER + } else if (_includes(user_agent, 'Gecko')) { + return 'Mozilla' + } else { + return '' + } } }, @@ -170,24 +183,26 @@ export const _info = { */ browserVersion: function (userAgent: string, vendor: string | undefined, opera: string): number | null { const browser = _info.browser(userAgent, vendor, opera) + const BROWSER_VERSION_REGEX_SUFFIX = '(\\d+(\\.\\d+)?)' + const DEFAULT_BROWSER_VERSION_REGEX = new RegExp(`Version\\/${BROWSER_VERSION_REGEX_SUFFIX}`) const versionRegexes: Record = { - [INTERNET_EXPLORER_MOBILE]: [/rv:(\d+(\.\d+)?)/], - [MICROSOFT_EDGE]: [/Edge?\/(\d+(\.\d+)?)/], - [CHROME]: [/Chrome\/(\d+(\.\d+)?)/], - [CHROME_IOS]: [/CriOS\/(\d+(\.\d+)?)/], - 'UC Browser': [/(UCBrowser|UCWEB)\/(\d+(\.\d+)?)/], - [SAFARI]: [/Version\/(\d+(\.\d+)?)/], - 'Mobile Safari': [/Version\/(\d+(\.\d+)?)/], - Opera: [/(Opera|OPR)\/(\d+(\.\d+)?)/], - [FIREFOX]: [/Firefox\/(\d+(\.\d+)?)/], - [FIREFOX_IOS]: [/FxiOS\/(\d+(\.\d+)?)/], - Konqueror: [/Konqueror[:/]?(\d+(\.\d+)?)/i], + [INTERNET_EXPLORER_MOBILE]: [new RegExp(`rv:${BROWSER_VERSION_REGEX_SUFFIX}`)], + [MICROSOFT_EDGE]: [new RegExp(`${EDGE}?\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [CHROME]: [new RegExp(`${CHROME}/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [CHROME_IOS]: [new RegExp(`CriOS\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + 'UC Browser': [new RegExp(`(UCBrowser|UCWEB)\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [SAFARI]: [DEFAULT_BROWSER_VERSION_REGEX], + 'Mobile Safari': [DEFAULT_BROWSER_VERSION_REGEX], + Opera: [new RegExp(`(Opera|OPR)\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [FIREFOX]: [new RegExp(`${FIREFOX}\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [FIREFOX_IOS]: [new RegExp(`FxiOS\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + Konqueror: [new RegExp(`Konqueror[:/]?${BROWSER_VERSION_REGEX_SUFFIX}`, 'i')], // not every blackberry user agent has the version after the name - [BLACKBERRY]: [/BlackBerry (\d+(\.\d+)?)/, /Version\/(\d+(\.\d+)?)/], - 'Android Mobile': [/android\s(\d+(\.\d+)?)/], - 'Samsung Internet': [/SamsungBrowser\/(\d+(\.\d+)?)/], - [INTERNET_EXPLORER]: [/(rv:|MSIE )(\d+(\.\d+)?)/], - Mozilla: [/rv:(\d+(\.\d+)?)/], + [BLACKBERRY]: [new RegExp(`${BLACKBERRY} ${BROWSER_VERSION_REGEX_SUFFIX}`), DEFAULT_BROWSER_VERSION_REGEX], + 'Android Mobile': [new RegExp(`android\\s${BROWSER_VERSION_REGEX_SUFFIX}`)], + 'Samsung Internet': [new RegExp(`SamsungBrowser\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [INTERNET_EXPLORER]: [new RegExp(`(rv:|MSIE )${BROWSER_VERSION_REGEX_SUFFIX}`)], + Mozilla: [new RegExp(`rv:${BROWSER_VERSION_REGEX_SUFFIX}`)], } const regexes: RegExp[] | undefined = versionRegexes[browser as keyof typeof versionRegexes] if (_isUndefined(regexes)) { @@ -282,12 +297,12 @@ export const _info = { // currently described as "the mobile device that was used" device: function (user_agent: string): string { - if (/(Nintendo \w+)/i.test(user_agent)) { - return 'Nintendo' - } else if (/playstation \w+/i.test(user_agent)) { - return 'Playstation' - } else if (/xbox/i.test(user_agent)) { - return 'Xbox' + if (NINTENDO_REGEX.test(user_agent)) { + return NINTENDO + } else if (PLAYSTATION_REGEX.test(user_agent)) { + return PLAYSTATION + } else if (XBOX_REGEX.test(user_agent)) { + return XBOX } else if (/ouya/i.test(user_agent)) { return 'Ouya' } else if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) { @@ -300,7 +315,7 @@ export const _info = { return 'iPhone' } else if (/(watch)(?: ?os[,/]|\d,\d\/)[\d.]+/i.test(user_agent)) { return APPLE_WATCH - } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { + } else if (BLACKBERRY_BROWSER_REGEX.test(user_agent)) { return BLACKBERRY } else if (/(kobo)\s(ereader|touch)/i.test(user_agent)) { return 'Kobo' @@ -349,7 +364,7 @@ export const _info = { device === 'Generic tablet' ) { return 'Tablet' - } else if (device === 'Nintendo' || device === 'Xbox' || device === 'Playstation' || device === 'Ouya') { + } else if (device === NINTENDO || device === XBOX || device === PLAYSTATION || device === 'Ouya') { return 'Console' } else if (device === APPLE_WATCH) { return 'Wearable' From b8cffa6ac492f884a18e0c7a94e60763265b6507 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 15:02:17 +0000 Subject: [PATCH 28/43] does that help --- src/utils/event-utils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 925fe6ac8..18003cf94 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -26,6 +26,9 @@ const NINTENDO = 'Nintendo' const PLAYSTATION = 'PlayStation' const XBOX = 'Xbox' +const BROWSER_VERSION_REGEX_SUFFIX = '(\\d+(\\.\\d+)?)' +const DEFAULT_BROWSER_VERSION_REGEX = new RegExp(`Version\\/${BROWSER_VERSION_REGEX_SUFFIX}`) + const XBOX_REGEX = new RegExp(`${XBOX}`, 'i') const PLAYSTATION_REGEX = new RegExp(`${PLAYSTATION} \\w+`, 'i') const NINTENDO_REGEX = new RegExp(`${NINTENDO} \\w+`, 'i') @@ -183,8 +186,6 @@ export const _info = { */ browserVersion: function (userAgent: string, vendor: string | undefined, opera: string): number | null { const browser = _info.browser(userAgent, vendor, opera) - const BROWSER_VERSION_REGEX_SUFFIX = '(\\d+(\\.\\d+)?)' - const DEFAULT_BROWSER_VERSION_REGEX = new RegExp(`Version\\/${BROWSER_VERSION_REGEX_SUFFIX}`) const versionRegexes: Record = { [INTERNET_EXPLORER_MOBILE]: [new RegExp(`rv:${BROWSER_VERSION_REGEX_SUFFIX}`)], [MICROSOFT_EDGE]: [new RegExp(`${EDGE}?\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], From 1e877c1f6ed88e440e701a389c9480e7deea40a4 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 15:48:41 +0000 Subject: [PATCH 29/43] does that help --- src/utils/event-utils.ts | 89 ++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 18003cf94..a3ea54f83 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -3,36 +3,45 @@ import { _isNull, _isUndefined } from './type-utils' import { Properties } from '../types' import Config from '../config' import { _each, _extend, _includes, _strip_empty_properties, _timestamp } from './index' -import { document, window, location, userAgent, assignableWindow } from './globals' +import { assignableWindow, document, location, userAgent, window } from './globals' +const FACEBOOK = 'Facebook' +const MOBILE = 'Mobile' +const IOS = 'iOS' const ANDROID = 'Android' -const ANDROID_TABLET = `${ANDROID} Tablet` +const ANDROID_TABLET = ANDROID + ' Tablet' const IPAD = 'iPad' const APPLE = 'Apple' -const APPLE_WATCH = `${APPLE} Watch` +const APPLE_WATCH = APPLE + ' Watch' const SAFARI = 'Safari' const BLACKBERRY = 'BlackBerry' +const SAMSUNG = 'Samsung' +const SAMSUNG_BROWSER = SAMSUNG + 'Browser' +const SAMSUNG_INTERNET = SAMSUNG + ' Internet' const CHROME = 'Chrome' -const CHROME_OS = `${CHROME} OS` -const CHROME_IOS = `${CHROME} iOS` +const CHROME_OS = CHROME + ' OS' +const CHROME_IOS = CHROME + ' ' + IOS const INTERNET_EXPLORER = 'Internet Explorer' -const INTERNET_EXPLORER_MOBILE = `${INTERNET_EXPLORER} Mobile` -const MICROSOFT = 'Microsoft' +const INTERNET_EXPLORER_MOBILE = INTERNET_EXPLORER + ' ' + MOBILE +const OPERA = 'Opera' +const OPERA_MINI = OPERA + ' Mini' const EDGE = 'Edge' -const MICROSOFT_EDGE = `${MICROSOFT} ${EDGE}` +const MICROSOFT_EDGE = 'Microsoft ' + EDGE const FIREFOX = 'Firefox' -const FIREFOX_IOS = `${FIREFOX} iOS` +const FIREFOX_IOS = FIREFOX + ' ' + IOS const NINTENDO = 'Nintendo' const PLAYSTATION = 'PlayStation' const XBOX = 'Xbox' +const ANDROID_MOBILE = ANDROID + ' ' + MOBILE +const MOBILE_SAFARI = MOBILE + ' ' + SAFARI const BROWSER_VERSION_REGEX_SUFFIX = '(\\d+(\\.\\d+)?)' -const DEFAULT_BROWSER_VERSION_REGEX = new RegExp(`Version\\/${BROWSER_VERSION_REGEX_SUFFIX}`) +const DEFAULT_BROWSER_VERSION_REGEX = new RegExp('Version/' + BROWSER_VERSION_REGEX_SUFFIX) -const XBOX_REGEX = new RegExp(`${XBOX}`, 'i') -const PLAYSTATION_REGEX = new RegExp(`${PLAYSTATION} \\w+`, 'i') -const NINTENDO_REGEX = new RegExp(`${NINTENDO} \\w+`, 'i') -const BLACKBERRY_BROWSER_REGEX = new RegExp(`${BLACKBERRY}|PlayBook|BB10`, 'i') +const XBOX_REGEX = new RegExp(XBOX, 'i') +const PLAYSTATION_REGEX = new RegExp(PLAYSTATION + ' \\w+', 'i') +const NINTENDO_REGEX = new RegExp(NINTENDO + ' \\w+', 'i') +const BLACKBERRY_REGEX = new RegExp(BLACKBERRY + '|PlayBook|BB10', 'i') const URL_REGEX_PREFIX = 'https?://(.*)' @@ -132,21 +141,21 @@ export const _info = { vendor = vendor || '' // vendor is undefined for at least IE9 if (opera || _includes(user_agent, ' OPR/')) { if (_includes(user_agent, 'Mini')) { - return 'Opera Mini' + return OPERA_MINI } - return 'Opera' + return OPERA } else { - if (BLACKBERRY_BROWSER_REGEX.test(user_agent)) { + if (BLACKBERRY_REGEX.test(user_agent)) { return BLACKBERRY - } else if (_includes(user_agent, 'IEMobile') || _includes(user_agent, 'WPDesktop')) { + } else if (_includes(user_agent, 'IE' + MOBILE) || _includes(user_agent, 'WPDesktop')) { return INTERNET_EXPLORER_MOBILE - } else if (_includes(user_agent, 'SamsungBrowser/')) { + } else if (_includes(user_agent, `${SAMSUNG_BROWSER}/`)) { // https://developer.samsung.com/internet/user-agent-string-format - return 'Samsung Internet' - } else if (_includes(user_agent, 'Edge') || _includes(user_agent, 'Edg/')) { + return SAMSUNG_INTERNET + } else if (_includes(user_agent, EDGE) || _includes(user_agent, 'Edg/')) { return MICROSOFT_EDGE } else if (_includes(user_agent, 'FBIOS')) { - return 'Facebook Mobile' + return FACEBOOK + ' ' + MOBILE } else if (_includes(user_agent, CHROME)) { return CHROME } else if (_includes(user_agent, 'CriOS')) { @@ -156,12 +165,12 @@ export const _info = { } else if (_includes(user_agent, 'FxiOS')) { return FIREFOX_IOS } else if (_includes(vendor, APPLE) || isSafari(user_agent)) { - if (_includes(user_agent, 'Mobile')) { - return 'Mobile Safari' + if (_includes(user_agent, MOBILE)) { + return MOBILE_SAFARI } return SAFARI } else if (_includes(user_agent, ANDROID)) { - return 'Android Mobile' + return ANDROID_MOBILE } else if (_includes(user_agent, 'Konqueror') || _includes(user_agent, 'konqueror')) { return 'Konqueror' } else if (_includes(user_agent, FIREFOX)) { @@ -193,15 +202,15 @@ export const _info = { [CHROME_IOS]: [new RegExp(`CriOS\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], 'UC Browser': [new RegExp(`(UCBrowser|UCWEB)\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], [SAFARI]: [DEFAULT_BROWSER_VERSION_REGEX], - 'Mobile Safari': [DEFAULT_BROWSER_VERSION_REGEX], - Opera: [new RegExp(`(Opera|OPR)\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [MOBILE_SAFARI]: [DEFAULT_BROWSER_VERSION_REGEX], + [OPERA]: [new RegExp(`(${OPERA}|OPR)\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], [FIREFOX]: [new RegExp(`${FIREFOX}\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], [FIREFOX_IOS]: [new RegExp(`FxiOS\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], Konqueror: [new RegExp(`Konqueror[:/]?${BROWSER_VERSION_REGEX_SUFFIX}`, 'i')], // not every blackberry user agent has the version after the name [BLACKBERRY]: [new RegExp(`${BLACKBERRY} ${BROWSER_VERSION_REGEX_SUFFIX}`), DEFAULT_BROWSER_VERSION_REGEX], - 'Android Mobile': [new RegExp(`android\\s${BROWSER_VERSION_REGEX_SUFFIX}`)], - 'Samsung Internet': [new RegExp(`SamsungBrowser\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [ANDROID_MOBILE]: [new RegExp(`android\\s${BROWSER_VERSION_REGEX_SUFFIX}`)], + [SAMSUNG_INTERNET]: [new RegExp(`${SAMSUNG_BROWSER}\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], [INTERNET_EXPLORER]: [new RegExp(`(rv:|MSIE )${BROWSER_VERSION_REGEX_SUFFIX}`)], Mozilla: [new RegExp(`rv:${BROWSER_VERSION_REGEX_SUFFIX}`)], } @@ -231,20 +240,20 @@ export const _info = { if (/xbox; xbox (.*?)[);]/i.test(user_agent)) { const match = /xbox; xbox (.*?)[);]/i.exec(user_agent) if (match && match[1]) { - return { os_name: 'Xbox', os_version: match[1] } + return { os_name: XBOX, os_version: match[1] } } - return { os_name: 'Xbox', os_version: '' } - } else if (/(nintendo)/i.test(user_agent)) { - return { os_name: 'Nintendo', os_version: '' } - } else if (/(playstation)/i.test(user_agent)) { - return { os_name: 'PlayStation', os_version: '' } + return { os_name: XBOX, os_version: '' } + } else if (/nintendo/i.test(user_agent)) { + return { os_name: NINTENDO, os_version: '' } + } else if (/playstation/i.test(user_agent)) { + return { os_name: PLAYSTATION, os_version: '' } } else if (/Windows/i.test(user_agent)) { if (/Phone/.test(user_agent) || /WPDesktop/.test(user_agent)) { return { os_name: 'Windows Phone', os_version: '' } } // not all JS versions support negative lookbehind, so we need two checks here if (/Mobile\b/.test(user_agent) && !/IEMobile\b/.test(user_agent)) { - return { os_name: 'Windows Mobile', os_version: '' } + return { os_name: 'Windows ' + MOBILE, os_version: '' } } const match = /Windows NT ([0-9.]+)/i.exec(user_agent) if (match && match[1]) { @@ -260,9 +269,9 @@ export const _info = { const match = /OS (\d+)_(\d+)_?(\d+)?/i.exec(user_agent) if (match && match[1]) { const versionParts = [match[1], match[2], match[3] || '0'] - return { os_name: 'iOS', os_version: versionParts.join('.') } + return { os_name: IOS, os_version: versionParts.join('.') } } - return { os_name: 'iOS', os_version: '' } + return { os_name: IOS, os_version: '' } } else if (/watch|watch os/i.test(user_agent)) { // e.g. Watch4,3/5.3.8 (16U680) const match = /(watch.*\/(\d+\.\d+\.\d+)|watch os,(\d+\.\d+),)/i.exec(user_agent) @@ -278,7 +287,7 @@ export const _info = { return { os_name: ANDROID, os_version: versionParts.join('.') } } return { os_name: ANDROID, os_version: '' } - } else if (/(BlackBerry|PlayBook|BB10)/i.test(user_agent)) { + } else if (BLACKBERRY_REGEX.test(user_agent)) { return { os_name: BLACKBERRY, os_version: '' } } else if (/Mac/i.test(user_agent)) { const match = /Mac OS X (\d+)[_.](\d+)[_.]?(\d+)?/i.exec(user_agent) @@ -316,7 +325,7 @@ export const _info = { return 'iPhone' } else if (/(watch)(?: ?os[,/]|\d,\d\/)[\d.]+/i.test(user_agent)) { return APPLE_WATCH - } else if (BLACKBERRY_BROWSER_REGEX.test(user_agent)) { + } else if (BLACKBERRY_REGEX.test(user_agent)) { return BLACKBERRY } else if (/(kobo)\s(ereader|touch)/i.test(user_agent)) { return 'Kobo' From ae87965a85137b97b2b83dc1c7e9b438026bfd1e Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 16:30:35 +0000 Subject: [PATCH 30/43] does that help --- src/__tests__/utils/event-utils.test.ts | 13 +- src/utils/event-utils.ts | 188 +++++++++++++++--------- 2 files changed, 128 insertions(+), 73 deletions(-) diff --git a/src/__tests__/utils/event-utils.test.ts b/src/__tests__/utils/event-utils.test.ts index c656d3f66..b720c5b5c 100644 --- a/src/__tests__/utils/event-utils.test.ts +++ b/src/__tests__/utils/event-utils.test.ts @@ -251,15 +251,14 @@ describe(`event-utils`, () => { * we had to edit them a chunk because we don't aim for the same level of detail */ test.each(uaParserOSTestCases)('OS - $ua', (testCase) => { - const actual = _info.os(testCase['ua']) + const result = _info.os(testCase['ua']) const expected = testCase['expect'] - expect(actual).toStrictEqual(expected) + expect(result).toStrictEqual([expected.os_name, expected.os_version]) }) test('wat', () => { - const ua = 'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; Xbox)' - expect(_info.device(ua)).toBe('Xbox') - expect(_info.deviceType(ua)).toBe('Console') + const ua = 'Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)' + expect(_info.os(ua)).toStrictEqual(['Windows', '']) }) it('osVersion', () => { @@ -310,7 +309,9 @@ describe(`event-utils`, () => { } for (const [userAgent, osInfo] of Object.entries(osVersions)) { - expect(_info.os(userAgent)).toEqual(osInfo) + const [os_name, os_version] = _info.os(userAgent) + expect(os_name).toBe(osInfo.os_name) + expect(os_version).toBe(osInfo.os_version) } }) }) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index a3ea54f83..c0e4cf0f5 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -236,73 +236,127 @@ export const _info = { ) }, - os: function (user_agent: string): { os_name: string; os_version: string } { - if (/xbox; xbox (.*?)[);]/i.test(user_agent)) { - const match = /xbox; xbox (.*?)[);]/i.exec(user_agent) - if (match && match[1]) { - return { os_name: XBOX, os_version: match[1] } + os: function (user_agent: string): [string, string] { + // to avoid repeating regexes or calling them twice, we have an array of matches + // the first regex that matches uses its matcher function to return the result + const osMatchers: [RegExp, (match: RegExpMatchArray | null) => [string, string]][] = [ + [ + /xbox; xbox (.*?)[);]/i, + (match) => { + return [XBOX, (match && match[1]) || ''] + }, + ], + [ + /nintendo/i, + () => { + return [NINTENDO, ''] + }, + ], + [ + /playstation/i, + () => { + return [PLAYSTATION, ''] + }, + ], + [ + BLACKBERRY_REGEX, + () => { + return [BLACKBERRY, ''] + }, + ], + [ + /Windows/i, + () => { + if (/Phone/.test(user_agent) || /WPDesktop/.test(user_agent)) { + return ['Windows Phone', ''] + } + // not all JS versions support negative lookbehind, so we need two checks here + if (/Mobile\b/.test(user_agent) && !/IEMobile\b/.test(user_agent)) { + return ['Windows ' + MOBILE, ''] + } + const match = /Windows NT ([0-9.]+)/i.exec(user_agent) + if (match && match[1]) { + const version = match[1] + let osVersion = windowsVersionMap[version] || '' + if (/arm/i.test(user_agent)) { + osVersion = 'RT' + } + return ['Windows', osVersion] + } + return ['Windows', ''] + }, + ], + [ + /((iPhone|iPad|iPod).*?OS (\d+)_(\d+)_?(\d+)?|iPhone)/, + (match) => { + if (match && match[3]) { + const versionParts = [match[3], match[4], match[5] || '0'] + return [IOS, versionParts.join('.')] + } + return [IOS, ''] + }, + ], + [ + /(watch.*\/(\d+\.\d+\.\d+)|watch os,(\d+\.\d+),)/i, + (match) => { + // e.g. Watch4,3/5.3.8 (16U680) + let version = '' + if (match && match.length >= 3) { + version = _isUndefined(match[2]) ? match[3] : match[2] + } + return ['watchOS', version] + }, + ], + [ + /(Android (\d+)\.(\d+)\.?(\d+)?|Android)/i, + (match) => { + if (match && match[2]) { + const versionParts = [match[2], match[3], match[4] || '0'] + return [ANDROID, versionParts.join('.')] + } + return [ANDROID, ''] + }, + ], + [ + /Mac OS X (\d+)[_.](\d+)[_.]?(\d+)?/i, + (match) => { + const result: [string, string] = ['Mac OS X', ''] + if (match && match[1]) { + const versionParts = [match[1], match[2], match[3] || '0'] + result[1] = versionParts.join('.') + } + return result + }, + ], + [ + /Mac/i, + () => { + // mop up a few non-standard UAs that should match mac + return ['Mac OS X', ''] + }, + ], + [ + /CrOS/, + () => { + return [CHROME_OS, ''] + }, + ], + [ + /Linux|debian/i, + () => { + return ['Linux', ''] + }, + ], + ] + for (let i = 0; i < osMatchers.length; i++) { + const [rgex, matcherFn] = osMatchers[i] + const match = rgex.exec(user_agent) + const result = match && matcherFn(match) + if (result) { + return result } - return { os_name: XBOX, os_version: '' } - } else if (/nintendo/i.test(user_agent)) { - return { os_name: NINTENDO, os_version: '' } - } else if (/playstation/i.test(user_agent)) { - return { os_name: PLAYSTATION, os_version: '' } - } else if (/Windows/i.test(user_agent)) { - if (/Phone/.test(user_agent) || /WPDesktop/.test(user_agent)) { - return { os_name: 'Windows Phone', os_version: '' } - } - // not all JS versions support negative lookbehind, so we need two checks here - if (/Mobile\b/.test(user_agent) && !/IEMobile\b/.test(user_agent)) { - return { os_name: 'Windows ' + MOBILE, os_version: '' } - } - const match = /Windows NT ([0-9.]+)/i.exec(user_agent) - if (match && match[1]) { - const version = match[1] - let osVersion = windowsVersionMap[version] || '' - if (/arm/i.test(user_agent)) { - osVersion = 'RT' - } - return { os_name: 'Windows', os_version: osVersion } - } - return { os_name: 'Windows', os_version: '' } - } else if (/(iPhone|iPad|iPod)/.test(user_agent)) { - const match = /OS (\d+)_(\d+)_?(\d+)?/i.exec(user_agent) - if (match && match[1]) { - const versionParts = [match[1], match[2], match[3] || '0'] - return { os_name: IOS, os_version: versionParts.join('.') } - } - return { os_name: IOS, os_version: '' } - } else if (/watch|watch os/i.test(user_agent)) { - // e.g. Watch4,3/5.3.8 (16U680) - const match = /(watch.*\/(\d+\.\d+\.\d+)|watch os,(\d+\.\d+),)/i.exec(user_agent) - let version = '' - if (match && match.length >= 3) { - version = _isUndefined(match[2]) ? match[3] : match[2] - } - return { os_name: 'watchOS', os_version: version } - } else if (/Android/.test(user_agent)) { - const match = /Android (\d+)\.(\d+)\.?(\d+)?/i.exec(user_agent) - if (match && match[1]) { - const versionParts = [match[1], match[2], match[3] || '0'] - return { os_name: ANDROID, os_version: versionParts.join('.') } - } - return { os_name: ANDROID, os_version: '' } - } else if (BLACKBERRY_REGEX.test(user_agent)) { - return { os_name: BLACKBERRY, os_version: '' } - } else if (/Mac/i.test(user_agent)) { - const match = /Mac OS X (\d+)[_.](\d+)[_.]?(\d+)?/i.exec(user_agent) - if (match && match[1]) { - const versionParts = [match[1], match[2], match[3] || '0'] - return { os_name: 'Mac OS X', os_version: versionParts.join('.') } - } - return { os_name: 'Mac OS X', os_version: '' } - } else if (/(Linux|debian)/i.test(user_agent)) { - return { os_name: 'Linux', os_version: '' } - } else if (/CrOS/.test(user_agent)) { - return { os_name: CHROME_OS, os_version: '' } - } else { - return { os_name: '', os_version: '' } } + return ['', ''] }, // currently described as "the mobile device that was used" @@ -400,7 +454,7 @@ export const _info = { if (!userAgent) { return {} } - const { os_name, os_version } = _info.os(userAgent) + const [os_name, os_version] = _info.os(userAgent) return _extend( _strip_empty_properties({ $os: os_name, @@ -433,7 +487,7 @@ export const _info = { return {} } - const { os_name, os_version } = _info.os(userAgent) + const [os_name, os_version] = _info.os(userAgent) return _extend( _strip_empty_properties({ $os: os_name, From 1d5ced78bc1866b84160e837a5b4d7fa4af61384 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 16:58:34 +0000 Subject: [PATCH 31/43] split this into its own file so size impact is clearer --- src/utils/event-utils.ts | 355 +-------------------------------- src/utils/user-agent-utils.ts | 365 ++++++++++++++++++++++++++++++++++ 2 files changed, 374 insertions(+), 346 deletions(-) create mode 100644 src/utils/user-agent-utils.ts diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index c0e4cf0f5..561d3f678 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -1,73 +1,13 @@ import { _getQueryParam, convertToURL } from './request-utils' -import { _isNull, _isUndefined } from './type-utils' +import { _isNull } from './type-utils' import { Properties } from '../types' import Config from '../config' -import { _each, _extend, _includes, _strip_empty_properties, _timestamp } from './index' +import { _each, _extend, _strip_empty_properties, _timestamp } from './index' import { assignableWindow, document, location, userAgent, window } from './globals' - -const FACEBOOK = 'Facebook' -const MOBILE = 'Mobile' -const IOS = 'iOS' -const ANDROID = 'Android' -const ANDROID_TABLET = ANDROID + ' Tablet' -const IPAD = 'iPad' -const APPLE = 'Apple' -const APPLE_WATCH = APPLE + ' Watch' -const SAFARI = 'Safari' -const BLACKBERRY = 'BlackBerry' -const SAMSUNG = 'Samsung' -const SAMSUNG_BROWSER = SAMSUNG + 'Browser' -const SAMSUNG_INTERNET = SAMSUNG + ' Internet' -const CHROME = 'Chrome' -const CHROME_OS = CHROME + ' OS' -const CHROME_IOS = CHROME + ' ' + IOS -const INTERNET_EXPLORER = 'Internet Explorer' -const INTERNET_EXPLORER_MOBILE = INTERNET_EXPLORER + ' ' + MOBILE -const OPERA = 'Opera' -const OPERA_MINI = OPERA + ' Mini' -const EDGE = 'Edge' -const MICROSOFT_EDGE = 'Microsoft ' + EDGE -const FIREFOX = 'Firefox' -const FIREFOX_IOS = FIREFOX + ' ' + IOS -const NINTENDO = 'Nintendo' -const PLAYSTATION = 'PlayStation' -const XBOX = 'Xbox' -const ANDROID_MOBILE = ANDROID + ' ' + MOBILE -const MOBILE_SAFARI = MOBILE + ' ' + SAFARI - -const BROWSER_VERSION_REGEX_SUFFIX = '(\\d+(\\.\\d+)?)' -const DEFAULT_BROWSER_VERSION_REGEX = new RegExp('Version/' + BROWSER_VERSION_REGEX_SUFFIX) - -const XBOX_REGEX = new RegExp(XBOX, 'i') -const PLAYSTATION_REGEX = new RegExp(PLAYSTATION + ' \\w+', 'i') -const NINTENDO_REGEX = new RegExp(NINTENDO + ' \\w+', 'i') -const BLACKBERRY_REGEX = new RegExp(BLACKBERRY + '|PlayBook|BB10', 'i') +import { detectBrowser, detectBrowserVersion, detectDevice, detectDeviceType, detectOS } from './user-agent-utils' const URL_REGEX_PREFIX = 'https?://(.*)' -const windowsVersionMap: Record = { - 'NT3.51': 'NT 3.11', - 'NT4.0': 'NT 4.0', - '5.0': '2000', - '5.1': 'XP', - '5.2': 'XP', - '6.0': 'Vista', - '6.1': '7', - '6.2': '8', - '6.3': '8.1', - '6.4': '10', - '10.0': '10', -} - -/** - * Safari detection turns out to be complicated. For e.g. https://stackoverflow.com/a/29696509 - * We can be slightly loose because some options have been ruled out (e.g. firefox on iOS) - * before this check is made - */ -function isSafari(userAgent: string): boolean { - return _includes(userAgent, SAFARI) && !_includes(userAgent, CHROME) && !_includes(userAgent, ANDROID) -} - export const _info = { campaignParams: function (customParams?: string[]): Record { // Should be kept in sync with https://github.com/PostHog/posthog/blob/master/plugin-server/src/utils/db/utils.ts#L60 @@ -135,55 +75,9 @@ export const _info = { /** * This function detects which browser is running this script. * The order of the checks are important since many user agents - * include key words used in later checks. + * include keywords used in later checks. */ - browser: function (user_agent: string, vendor: string | undefined, opera?: any): string { - vendor = vendor || '' // vendor is undefined for at least IE9 - if (opera || _includes(user_agent, ' OPR/')) { - if (_includes(user_agent, 'Mini')) { - return OPERA_MINI - } - return OPERA - } else { - if (BLACKBERRY_REGEX.test(user_agent)) { - return BLACKBERRY - } else if (_includes(user_agent, 'IE' + MOBILE) || _includes(user_agent, 'WPDesktop')) { - return INTERNET_EXPLORER_MOBILE - } else if (_includes(user_agent, `${SAMSUNG_BROWSER}/`)) { - // https://developer.samsung.com/internet/user-agent-string-format - return SAMSUNG_INTERNET - } else if (_includes(user_agent, EDGE) || _includes(user_agent, 'Edg/')) { - return MICROSOFT_EDGE - } else if (_includes(user_agent, 'FBIOS')) { - return FACEBOOK + ' ' + MOBILE - } else if (_includes(user_agent, CHROME)) { - return CHROME - } else if (_includes(user_agent, 'CriOS')) { - return CHROME_IOS - } else if (_includes(user_agent, 'UCWEB') || _includes(user_agent, 'UCBrowser')) { - return 'UC Browser' - } else if (_includes(user_agent, 'FxiOS')) { - return FIREFOX_IOS - } else if (_includes(vendor, APPLE) || isSafari(user_agent)) { - if (_includes(user_agent, MOBILE)) { - return MOBILE_SAFARI - } - return SAFARI - } else if (_includes(user_agent, ANDROID)) { - return ANDROID_MOBILE - } else if (_includes(user_agent, 'Konqueror') || _includes(user_agent, 'konqueror')) { - return 'Konqueror' - } else if (_includes(user_agent, FIREFOX)) { - return FIREFOX - } else if (_includes(user_agent, 'MSIE') || _includes(user_agent, 'Trident/')) { - return INTERNET_EXPLORER - } else if (_includes(user_agent, 'Gecko')) { - return 'Mozilla' - } else { - return '' - } - } - }, + browser: detectBrowser, /** * This function detects which browser version is running this script, @@ -193,41 +87,7 @@ export const _info = { * `navigator.vendor` is passed in and used to help with detecting certain browsers * NB `navigator.vendor` is deprecated and not present in every browser */ - browserVersion: function (userAgent: string, vendor: string | undefined, opera: string): number | null { - const browser = _info.browser(userAgent, vendor, opera) - const versionRegexes: Record = { - [INTERNET_EXPLORER_MOBILE]: [new RegExp(`rv:${BROWSER_VERSION_REGEX_SUFFIX}`)], - [MICROSOFT_EDGE]: [new RegExp(`${EDGE}?\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - [CHROME]: [new RegExp(`${CHROME}/${BROWSER_VERSION_REGEX_SUFFIX}`)], - [CHROME_IOS]: [new RegExp(`CriOS\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - 'UC Browser': [new RegExp(`(UCBrowser|UCWEB)\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - [SAFARI]: [DEFAULT_BROWSER_VERSION_REGEX], - [MOBILE_SAFARI]: [DEFAULT_BROWSER_VERSION_REGEX], - [OPERA]: [new RegExp(`(${OPERA}|OPR)\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - [FIREFOX]: [new RegExp(`${FIREFOX}\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - [FIREFOX_IOS]: [new RegExp(`FxiOS\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - Konqueror: [new RegExp(`Konqueror[:/]?${BROWSER_VERSION_REGEX_SUFFIX}`, 'i')], - // not every blackberry user agent has the version after the name - [BLACKBERRY]: [new RegExp(`${BLACKBERRY} ${BROWSER_VERSION_REGEX_SUFFIX}`), DEFAULT_BROWSER_VERSION_REGEX], - [ANDROID_MOBILE]: [new RegExp(`android\\s${BROWSER_VERSION_REGEX_SUFFIX}`)], - [SAMSUNG_INTERNET]: [new RegExp(`${SAMSUNG_BROWSER}\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - [INTERNET_EXPLORER]: [new RegExp(`(rv:|MSIE )${BROWSER_VERSION_REGEX_SUFFIX}`)], - Mozilla: [new RegExp(`rv:${BROWSER_VERSION_REGEX_SUFFIX}`)], - } - const regexes: RegExp[] | undefined = versionRegexes[browser as keyof typeof versionRegexes] - if (_isUndefined(regexes)) { - return null - } - - for (let i = 0; i < regexes.length; i++) { - const regex = regexes[i] - const matches = userAgent.match(regex) - if (matches) { - return parseFloat(matches[matches.length - 2]) - } - } - return null - }, + browserVersion: detectBrowserVersion, browserLanguage: function (): string { return ( @@ -236,208 +96,11 @@ export const _info = { ) }, - os: function (user_agent: string): [string, string] { - // to avoid repeating regexes or calling them twice, we have an array of matches - // the first regex that matches uses its matcher function to return the result - const osMatchers: [RegExp, (match: RegExpMatchArray | null) => [string, string]][] = [ - [ - /xbox; xbox (.*?)[);]/i, - (match) => { - return [XBOX, (match && match[1]) || ''] - }, - ], - [ - /nintendo/i, - () => { - return [NINTENDO, ''] - }, - ], - [ - /playstation/i, - () => { - return [PLAYSTATION, ''] - }, - ], - [ - BLACKBERRY_REGEX, - () => { - return [BLACKBERRY, ''] - }, - ], - [ - /Windows/i, - () => { - if (/Phone/.test(user_agent) || /WPDesktop/.test(user_agent)) { - return ['Windows Phone', ''] - } - // not all JS versions support negative lookbehind, so we need two checks here - if (/Mobile\b/.test(user_agent) && !/IEMobile\b/.test(user_agent)) { - return ['Windows ' + MOBILE, ''] - } - const match = /Windows NT ([0-9.]+)/i.exec(user_agent) - if (match && match[1]) { - const version = match[1] - let osVersion = windowsVersionMap[version] || '' - if (/arm/i.test(user_agent)) { - osVersion = 'RT' - } - return ['Windows', osVersion] - } - return ['Windows', ''] - }, - ], - [ - /((iPhone|iPad|iPod).*?OS (\d+)_(\d+)_?(\d+)?|iPhone)/, - (match) => { - if (match && match[3]) { - const versionParts = [match[3], match[4], match[5] || '0'] - return [IOS, versionParts.join('.')] - } - return [IOS, ''] - }, - ], - [ - /(watch.*\/(\d+\.\d+\.\d+)|watch os,(\d+\.\d+),)/i, - (match) => { - // e.g. Watch4,3/5.3.8 (16U680) - let version = '' - if (match && match.length >= 3) { - version = _isUndefined(match[2]) ? match[3] : match[2] - } - return ['watchOS', version] - }, - ], - [ - /(Android (\d+)\.(\d+)\.?(\d+)?|Android)/i, - (match) => { - if (match && match[2]) { - const versionParts = [match[2], match[3], match[4] || '0'] - return [ANDROID, versionParts.join('.')] - } - return [ANDROID, ''] - }, - ], - [ - /Mac OS X (\d+)[_.](\d+)[_.]?(\d+)?/i, - (match) => { - const result: [string, string] = ['Mac OS X', ''] - if (match && match[1]) { - const versionParts = [match[1], match[2], match[3] || '0'] - result[1] = versionParts.join('.') - } - return result - }, - ], - [ - /Mac/i, - () => { - // mop up a few non-standard UAs that should match mac - return ['Mac OS X', ''] - }, - ], - [ - /CrOS/, - () => { - return [CHROME_OS, ''] - }, - ], - [ - /Linux|debian/i, - () => { - return ['Linux', ''] - }, - ], - ] - for (let i = 0; i < osMatchers.length; i++) { - const [rgex, matcherFn] = osMatchers[i] - const match = rgex.exec(user_agent) - const result = match && matcherFn(match) - if (result) { - return result - } - } - return ['', ''] - }, + os: detectOS, - // currently described as "the mobile device that was used" - device: function (user_agent: string): string { - if (NINTENDO_REGEX.test(user_agent)) { - return NINTENDO - } else if (PLAYSTATION_REGEX.test(user_agent)) { - return PLAYSTATION - } else if (XBOX_REGEX.test(user_agent)) { - return XBOX - } else if (/ouya/i.test(user_agent)) { - return 'Ouya' - } else if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) { - return 'Windows Phone' - } else if (/iPad/.test(user_agent)) { - return IPAD - } else if (/iPod/.test(user_agent)) { - return 'iPod Touch' - } else if (/iPhone/.test(user_agent)) { - return 'iPhone' - } else if (/(watch)(?: ?os[,/]|\d,\d\/)[\d.]+/i.test(user_agent)) { - return APPLE_WATCH - } else if (BLACKBERRY_REGEX.test(user_agent)) { - return BLACKBERRY - } else if (/(kobo)\s(ereader|touch)/i.test(user_agent)) { - return 'Kobo' - } else if (/Nokia/i.test(user_agent)) { - return 'Nokia' - } else if ( - // Kindle Fire without Silk / Echo Show - /(kf[a-z]{2}wi|aeo[c-r]{2})( bui|\))/i.test(user_agent) || - // Kindle Fire HD - /(kf[a-z]+)( bui|\)).+silk\//i.test(user_agent) - ) { - return 'Kindle Fire' - } else if (/(Android|ZTE)/i.test(user_agent)) { - if ( - !/Mobile/.test(user_agent) || - /(9138B|TB782B|Nexus [97]|pixel c|HUAWEISHT|BTV|noble nook|smart ultra 6)/i.test(user_agent) - ) { - if ( - (/pixel[\daxl ]{1,6}/i.test(user_agent) && !/pixel c/i.test(user_agent)) || - /(huaweimed-al00|tah-|APA|SM-G92|i980|zte|U304AA)/i.test(user_agent) || - (/lmy47v/i.test(user_agent) && !/QTAQZ3/i.test(user_agent)) - ) { - return ANDROID - } - return ANDROID_TABLET - } else { - return ANDROID - } - } else if (/(pda|mobile)/i.test(user_agent)) { - return 'Generic mobile' - } else if (/tablet/i.test(user_agent) && !/tablet pc/i.test(user_agent)) { - return 'Generic tablet' - } else { - return '' - } - }, + device: detectDevice, - // currently described as "the type of device that was used" - deviceType: function (user_agent: string): string { - const device = this.device(user_agent) - if ( - device === IPAD || - device === ANDROID_TABLET || - device === 'Kobo' || - device === 'Kindle Fire' || - device === 'Generic tablet' - ) { - return 'Tablet' - } else if (device === NINTENDO || device === XBOX || device === PLAYSTATION || device === 'Ouya') { - return 'Console' - } else if (device === APPLE_WATCH) { - return 'Wearable' - } else if (device) { - return 'Mobile' - } else { - return 'Desktop' - } - }, + deviceType: detectDeviceType, referrer: function (): string { return document?.referrer || '$direct' diff --git a/src/utils/user-agent-utils.ts b/src/utils/user-agent-utils.ts new file mode 100644 index 000000000..67e478226 --- /dev/null +++ b/src/utils/user-agent-utils.ts @@ -0,0 +1,365 @@ +import { _includes } from './index' +import { _isUndefined } from './type-utils' + +const FACEBOOK = 'Facebook' +const MOBILE = 'Mobile' +const IOS = 'iOS' +const ANDROID = 'Android' +const ANDROID_TABLET = ANDROID + ' Tablet' +const IPAD = 'iPad' +const APPLE = 'Apple' +const APPLE_WATCH = APPLE + ' Watch' +const SAFARI = 'Safari' +const BLACKBERRY = 'BlackBerry' +const SAMSUNG = 'Samsung' +const SAMSUNG_BROWSER = SAMSUNG + 'Browser' +const SAMSUNG_INTERNET = SAMSUNG + ' Internet' +const CHROME = 'Chrome' +const CHROME_OS = CHROME + ' OS' +const CHROME_IOS = CHROME + ' ' + IOS +const INTERNET_EXPLORER = 'Internet Explorer' +const INTERNET_EXPLORER_MOBILE = INTERNET_EXPLORER + ' ' + MOBILE +const OPERA = 'Opera' +const OPERA_MINI = OPERA + ' Mini' +const EDGE = 'Edge' +const MICROSOFT_EDGE = 'Microsoft ' + EDGE +const FIREFOX = 'Firefox' +const FIREFOX_IOS = FIREFOX + ' ' + IOS +const NINTENDO = 'Nintendo' +const PLAYSTATION = 'PlayStation' +const XBOX = 'Xbox' +const ANDROID_MOBILE = ANDROID + ' ' + MOBILE +const MOBILE_SAFARI = MOBILE + ' ' + SAFARI + +const BROWSER_VERSION_REGEX_SUFFIX = '(\\d+(\\.\\d+)?)' +const DEFAULT_BROWSER_VERSION_REGEX = new RegExp('Version/' + BROWSER_VERSION_REGEX_SUFFIX) + +const XBOX_REGEX = new RegExp(XBOX, 'i') +const PLAYSTATION_REGEX = new RegExp(PLAYSTATION + ' \\w+', 'i') +const NINTENDO_REGEX = new RegExp(NINTENDO + ' \\w+', 'i') +const BLACKBERRY_REGEX = new RegExp(BLACKBERRY + '|PlayBook|BB10', 'i') + +const windowsVersionMap: Record = { + 'NT3.51': 'NT 3.11', + 'NT4.0': 'NT 4.0', + '5.0': '2000', + '5.1': 'XP', + '5.2': 'XP', + '6.0': 'Vista', + '6.1': '7', + '6.2': '8', + '6.3': '8.1', + '6.4': '10', + '10.0': '10', +} + +/** + * Safari detection turns out to be complicated. For e.g. https://stackoverflow.com/a/29696509 + * We can be slightly loose because some options have been ruled out (e.g. firefox on iOS) + * before this check is made + */ +function isSafari(userAgent: string): boolean { + return _includes(userAgent, SAFARI) && !_includes(userAgent, CHROME) && !_includes(userAgent, ANDROID) +} + +/** + * This function detects which browser is running this script. + * The order of the checks are important since many user agents + * include keywords used in later checks. + */ +export const detectBrowser = function (user_agent: string, vendor: string | undefined, opera?: any): string { + vendor = vendor || '' // vendor is undefined for at least IE9 + if (opera || _includes(user_agent, ' OPR/')) { + if (_includes(user_agent, 'Mini')) { + return OPERA_MINI + } + return OPERA + } else { + if (BLACKBERRY_REGEX.test(user_agent)) { + return BLACKBERRY + } else if (_includes(user_agent, 'IE' + MOBILE) || _includes(user_agent, 'WPDesktop')) { + return INTERNET_EXPLORER_MOBILE + } else if (_includes(user_agent, `${SAMSUNG_BROWSER}/`)) { + // https://developer.samsung.com/internet/user-agent-string-format + return SAMSUNG_INTERNET + } else if (_includes(user_agent, EDGE) || _includes(user_agent, 'Edg/')) { + return MICROSOFT_EDGE + } else if (_includes(user_agent, 'FBIOS')) { + return FACEBOOK + ' ' + MOBILE + } else if (_includes(user_agent, CHROME)) { + return CHROME + } else if (_includes(user_agent, 'CriOS')) { + return CHROME_IOS + } else if (_includes(user_agent, 'UCWEB') || _includes(user_agent, 'UCBrowser')) { + return 'UC Browser' + } else if (_includes(user_agent, 'FxiOS')) { + return FIREFOX_IOS + } else if (_includes(vendor, APPLE) || isSafari(user_agent)) { + if (_includes(user_agent, MOBILE)) { + return MOBILE_SAFARI + } + return SAFARI + } else if (_includes(user_agent, ANDROID)) { + return ANDROID_MOBILE + } else if (_includes(user_agent, 'Konqueror') || _includes(user_agent, 'konqueror')) { + return 'Konqueror' + } else if (_includes(user_agent, FIREFOX)) { + return FIREFOX + } else if (_includes(user_agent, 'MSIE') || _includes(user_agent, 'Trident/')) { + return INTERNET_EXPLORER + } else if (_includes(user_agent, 'Gecko')) { + return 'Mozilla' + } else { + return '' + } + } +} + +/** + * This function detects which browser version is running this script, + * parsing major and minor version (e.g., 42.1). User agent strings from: + * http://www.useragentstring.com/pages/useragentstring.php + * + * `navigator.vendor` is passed in and used to help with detecting certain browsers + * NB `navigator.vendor` is deprecated and not present in every browser + */ +export const detectBrowserVersion = function ( + userAgent: string, + vendor: string | undefined, + opera: string +): number | null { + const browser = detectBrowser(userAgent, vendor, opera) + const versionRegexes: Record = { + [INTERNET_EXPLORER_MOBILE]: [new RegExp(`rv:${BROWSER_VERSION_REGEX_SUFFIX}`)], + [MICROSOFT_EDGE]: [new RegExp(`${EDGE}?\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [CHROME]: [new RegExp(`${CHROME}/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [CHROME_IOS]: [new RegExp(`CriOS\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + 'UC Browser': [new RegExp(`(UCBrowser|UCWEB)\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [SAFARI]: [DEFAULT_BROWSER_VERSION_REGEX], + [MOBILE_SAFARI]: [DEFAULT_BROWSER_VERSION_REGEX], + [OPERA]: [new RegExp(`(${OPERA}|OPR)\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [FIREFOX]: [new RegExp(`${FIREFOX}\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [FIREFOX_IOS]: [new RegExp(`FxiOS\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + Konqueror: [new RegExp(`Konqueror[:/]?${BROWSER_VERSION_REGEX_SUFFIX}`, 'i')], + // not every blackberry user agent has the version after the name + [BLACKBERRY]: [new RegExp(`${BLACKBERRY} ${BROWSER_VERSION_REGEX_SUFFIX}`), DEFAULT_BROWSER_VERSION_REGEX], + [ANDROID_MOBILE]: [new RegExp(`android\\s${BROWSER_VERSION_REGEX_SUFFIX}`)], + [SAMSUNG_INTERNET]: [new RegExp(`${SAMSUNG_BROWSER}\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [INTERNET_EXPLORER]: [new RegExp(`(rv:|MSIE )${BROWSER_VERSION_REGEX_SUFFIX}`)], + Mozilla: [new RegExp(`rv:${BROWSER_VERSION_REGEX_SUFFIX}`)], + } + const regexes: RegExp[] | undefined = versionRegexes[browser as keyof typeof versionRegexes] + if (_isUndefined(regexes)) { + return null + } + + for (let i = 0; i < regexes.length; i++) { + const regex = regexes[i] + const matches = userAgent.match(regex) + if (matches) { + return parseFloat(matches[matches.length - 2]) + } + } + return null +} + +export const detectOS = function (user_agent: string): [string, string] { + // to avoid repeating regexes or calling them twice, we have an array of matches + // the first regex that matches uses its matcher function to return the result + const osMatchers: [RegExp, (match: RegExpMatchArray | null) => [string, string]][] = [ + [ + /xbox; xbox (.*?)[);]/i, + (match) => { + return [XBOX, (match && match[1]) || ''] + }, + ], + [ + /nintendo/i, + () => { + return [NINTENDO, ''] + }, + ], + [ + /playstation/i, + () => { + return [PLAYSTATION, ''] + }, + ], + [ + BLACKBERRY_REGEX, + () => { + return [BLACKBERRY, ''] + }, + ], + [ + /Windows/i, + () => { + if (/Phone/.test(user_agent) || /WPDesktop/.test(user_agent)) { + return ['Windows Phone', ''] + } + // not all JS versions support negative lookbehind, so we need two checks here + if (/Mobile\b/.test(user_agent) && !/IEMobile\b/.test(user_agent)) { + return ['Windows ' + MOBILE, ''] + } + const match = /Windows NT ([0-9.]+)/i.exec(user_agent) + if (match && match[1]) { + const version = match[1] + let osVersion = windowsVersionMap[version] || '' + if (/arm/i.test(user_agent)) { + osVersion = 'RT' + } + return ['Windows', osVersion] + } + return ['Windows', ''] + }, + ], + [ + /((iPhone|iPad|iPod).*?OS (\d+)_(\d+)_?(\d+)?|iPhone)/, + (match) => { + if (match && match[3]) { + const versionParts = [match[3], match[4], match[5] || '0'] + return [IOS, versionParts.join('.')] + } + return [IOS, ''] + }, + ], + [ + /(watch.*\/(\d+\.\d+\.\d+)|watch os,(\d+\.\d+),)/i, + (match) => { + // e.g. Watch4,3/5.3.8 (16U680) + let version = '' + if (match && match.length >= 3) { + version = _isUndefined(match[2]) ? match[3] : match[2] + } + return ['watchOS', version] + }, + ], + [ + /(Android (\d+)\.(\d+)\.?(\d+)?|Android)/i, + (match) => { + if (match && match[2]) { + const versionParts = [match[2], match[3], match[4] || '0'] + return [ANDROID, versionParts.join('.')] + } + return [ANDROID, ''] + }, + ], + [ + /Mac OS X (\d+)[_.](\d+)[_.]?(\d+)?/i, + (match) => { + const result: [string, string] = ['Mac OS X', ''] + if (match && match[1]) { + const versionParts = [match[1], match[2], match[3] || '0'] + result[1] = versionParts.join('.') + } + return result + }, + ], + [ + /Mac/i, + () => { + // mop up a few non-standard UAs that should match mac + return ['Mac OS X', ''] + }, + ], + [ + /CrOS/, + () => { + return [CHROME_OS, ''] + }, + ], + [ + /Linux|debian/i, + () => { + return ['Linux', ''] + }, + ], + ] + for (let i = 0; i < osMatchers.length; i++) { + const [rgex, matcherFn] = osMatchers[i] + const match = rgex.exec(user_agent) + const result = match && matcherFn(match) + if (result) { + return result + } + } + return ['', ''] +} + +export const detectDevice = function (user_agent: string): string { + if (NINTENDO_REGEX.test(user_agent)) { + return NINTENDO + } else if (PLAYSTATION_REGEX.test(user_agent)) { + return PLAYSTATION + } else if (XBOX_REGEX.test(user_agent)) { + return XBOX + } else if (/ouya/i.test(user_agent)) { + return 'Ouya' + } else if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) { + return 'Windows Phone' + } else if (/iPad/.test(user_agent)) { + return IPAD + } else if (/iPod/.test(user_agent)) { + return 'iPod Touch' + } else if (/iPhone/.test(user_agent)) { + return 'iPhone' + } else if (/(watch)(?: ?os[,/]|\d,\d\/)[\d.]+/i.test(user_agent)) { + return APPLE_WATCH + } else if (BLACKBERRY_REGEX.test(user_agent)) { + return BLACKBERRY + } else if (/(kobo)\s(ereader|touch)/i.test(user_agent)) { + return 'Kobo' + } else if (/Nokia/i.test(user_agent)) { + return 'Nokia' + } else if ( + // Kindle Fire without Silk / Echo Show + /(kf[a-z]{2}wi|aeo[c-r]{2})( bui|\))/i.test(user_agent) || + // Kindle Fire HD + /(kf[a-z]+)( bui|\)).+silk\//i.test(user_agent) + ) { + return 'Kindle Fire' + } else if (/(Android|ZTE)/i.test(user_agent)) { + if ( + !/Mobile/.test(user_agent) || + /(9138B|TB782B|Nexus [97]|pixel c|HUAWEISHT|BTV|noble nook|smart ultra 6)/i.test(user_agent) + ) { + if ( + (/pixel[\daxl ]{1,6}/i.test(user_agent) && !/pixel c/i.test(user_agent)) || + /(huaweimed-al00|tah-|APA|SM-G92|i980|zte|U304AA)/i.test(user_agent) || + (/lmy47v/i.test(user_agent) && !/QTAQZ3/i.test(user_agent)) + ) { + return ANDROID + } + return ANDROID_TABLET + } else { + return ANDROID + } + } else if (/(pda|mobile)/i.test(user_agent)) { + return 'Generic mobile' + } else if (/tablet/i.test(user_agent) && !/tablet pc/i.test(user_agent)) { + return 'Generic tablet' + } else { + return '' + } +} + +export const detectDeviceType = function (user_agent: string): string { + const device = detectDevice(user_agent) + if ( + device === IPAD || + device === ANDROID_TABLET || + device === 'Kobo' || + device === 'Kindle Fire' || + device === 'Generic tablet' + ) { + return 'Tablet' + } else if (device === NINTENDO || device === XBOX || device === PLAYSTATION || device === 'Ouya') { + return 'Console' + } else if (device === APPLE_WATCH) { + return 'Wearable' + } else if (device) { + return 'Mobile' + } else { + return 'Desktop' + } +} From c0025aa7c9d651f247dbec62df187ac9f53bcef8 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 17:00:48 +0000 Subject: [PATCH 32/43] Add a comment --- src/utils/user-agent-utils.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/utils/user-agent-utils.ts b/src/utils/user-agent-utils.ts index 67e478226..fde9fc372 100644 --- a/src/utils/user-agent-utils.ts +++ b/src/utils/user-agent-utils.ts @@ -1,6 +1,16 @@ import { _includes } from './index' import { _isUndefined } from './type-utils' +/** + * this device detection code is (at time of writing) about 3% of the size of the entire library + * this is mostly because the identifiers user in regexes and results can't be minified away since + * they have meaning + * + * so, there are some pre-uglifying choices in the code to help reduce the size + * e.g. many repeated strings are stored as variables and then old-fashioned concatenated together + * + * TL;DR here be dragons + */ const FACEBOOK = 'Facebook' const MOBILE = 'Mobile' const IOS = 'iOS' From 4913b1b95a7e28a07082346cd404cae1f1f39cbd Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 17:19:14 +0000 Subject: [PATCH 33/43] does that help --- src/utils/user-agent-utils.ts | 75 +++++++++++++++-------------------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/src/utils/user-agent-utils.ts b/src/utils/user-agent-utils.ts index fde9fc372..3d01fb383 100644 --- a/src/utils/user-agent-utils.ts +++ b/src/utils/user-agent-utils.ts @@ -71,6 +71,29 @@ const windowsVersionMap: Record = { function isSafari(userAgent: string): boolean { return _includes(userAgent, SAFARI) && !_includes(userAgent, CHROME) && !_includes(userAgent, ANDROID) } +const safariCheck = (ua: string, vendor?: string) => (vendor && _includes(vendor, APPLE)) || isSafari(ua) + +const browserDetectionChecks: [(userAgent: string, navigatorVendor?: string) => boolean, string][] = [ + [(ua) => _includes(ua, ' OPR/') && _includes(ua, 'Mini'), OPERA_MINI], + [(ua) => _includes(ua, ' OPR/'), OPERA], + [(ua) => BLACKBERRY_REGEX.test(ua), BLACKBERRY], + [(ua) => _includes(ua, 'IE' + MOBILE) || _includes(ua, 'WPDesktop'), INTERNET_EXPLORER_MOBILE], + // https://developer.samsung.com/internet/user-agent-string-format + [(ua) => _includes(ua, SAMSUNG_BROWSER), SAMSUNG_INTERNET], + [(ua) => _includes(ua, EDGE) || _includes(ua, 'Edg/'), MICROSOFT_EDGE], + [(ua) => _includes(ua, 'FBIOS'), FACEBOOK + ' ' + MOBILE], + [(ua) => _includes(ua, CHROME), CHROME], + [(ua) => _includes(ua, 'CriOS'), CHROME_IOS], + [(ua) => _includes(ua, 'UCWEB') || _includes(ua, 'UCBrowser'), 'UC Browser'], + [(ua) => _includes(ua, 'FxiOS'), FIREFOX_IOS], + [(ua) => _includes(ua, ANDROID), ANDROID_MOBILE], + [(ua) => _includes(ua, 'Konqueror') || _includes(ua, 'konqueror'), 'Konqueror'], + [(ua, vendor) => safariCheck(ua, vendor) && _includes(ua, MOBILE), MOBILE_SAFARI], + [(ua, vendor) => safariCheck(ua, vendor), SAFARI], + [(ua) => _includes(ua, FIREFOX), FIREFOX], + [(ua) => _includes(ua, 'MSIE') || _includes(ua, 'Trident/'), INTERNET_EXPLORER], + [(ua) => _includes(ua, 'Gecko'), FIREFOX], +] /** * This function detects which browser is running this script. @@ -78,51 +101,19 @@ function isSafari(userAgent: string): boolean { * include keywords used in later checks. */ export const detectBrowser = function (user_agent: string, vendor: string | undefined, opera?: any): string { - vendor = vendor || '' // vendor is undefined for at least IE9 - if (opera || _includes(user_agent, ' OPR/')) { - if (_includes(user_agent, 'Mini')) { - return OPERA_MINI - } + if (opera) { return OPERA - } else { - if (BLACKBERRY_REGEX.test(user_agent)) { - return BLACKBERRY - } else if (_includes(user_agent, 'IE' + MOBILE) || _includes(user_agent, 'WPDesktop')) { - return INTERNET_EXPLORER_MOBILE - } else if (_includes(user_agent, `${SAMSUNG_BROWSER}/`)) { - // https://developer.samsung.com/internet/user-agent-string-format - return SAMSUNG_INTERNET - } else if (_includes(user_agent, EDGE) || _includes(user_agent, 'Edg/')) { - return MICROSOFT_EDGE - } else if (_includes(user_agent, 'FBIOS')) { - return FACEBOOK + ' ' + MOBILE - } else if (_includes(user_agent, CHROME)) { - return CHROME - } else if (_includes(user_agent, 'CriOS')) { - return CHROME_IOS - } else if (_includes(user_agent, 'UCWEB') || _includes(user_agent, 'UCBrowser')) { - return 'UC Browser' - } else if (_includes(user_agent, 'FxiOS')) { - return FIREFOX_IOS - } else if (_includes(vendor, APPLE) || isSafari(user_agent)) { - if (_includes(user_agent, MOBILE)) { - return MOBILE_SAFARI - } - return SAFARI - } else if (_includes(user_agent, ANDROID)) { - return ANDROID_MOBILE - } else if (_includes(user_agent, 'Konqueror') || _includes(user_agent, 'konqueror')) { - return 'Konqueror' - } else if (_includes(user_agent, FIREFOX)) { - return FIREFOX - } else if (_includes(user_agent, 'MSIE') || _includes(user_agent, 'Trident/')) { - return INTERNET_EXPLORER - } else if (_includes(user_agent, 'Gecko')) { - return 'Mozilla' - } else { - return '' + } + vendor = vendor || '' // vendor is undefined for at least IE9 + + for (let i = 0; i < browserDetectionChecks.length; i++) { + const [check, browser] = browserDetectionChecks[i] + if (check(user_agent)) { + return browser } } + + return '' } /** From d01e23a46f3d2be181c3341d66e7bb9d54efb8db Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 18:16:15 +0000 Subject: [PATCH 34/43] does that help --- src/utils/event-utils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 561d3f678..030171a00 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -41,13 +41,13 @@ export const _info = { if (!referrer) { return null } else { - if (referrer.search(`${URL_REGEX_PREFIX}google.([^/?]*)`) === 0) { + if (referrer.search(URL_REGEX_PREFIX + 'google.([^/?]*)') === 0) { return 'google' - } else if (referrer.search(`${URL_REGEX_PREFIX}bing.com`) === 0) { + } else if (referrer.search(URL_REGEX_PREFIX + 'bing.com') === 0) { return 'bing' - } else if (referrer.search(`${URL_REGEX_PREFIX}yahoo.com`) === 0) { + } else if (referrer.search(URL_REGEX_PREFIX + 'yahoo.com') === 0) { return 'yahoo' - } else if (referrer.search(`${URL_REGEX_PREFIX}duckduckgo.com`) === 0) { + } else if (referrer.search(URL_REGEX_PREFIX + 'duckduckgo.com') === 0) { return 'duckduckgo' } else { return null From 4eeaed4293c3cbe8bdc40e41010dbae2e317f483 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 23 Feb 2024 18:31:03 +0000 Subject: [PATCH 35/43] fix vendor detection --- src/__tests__/utils/event-utils.test.ts | 10 ++++++---- src/utils/user-agent-utils.ts | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/__tests__/utils/event-utils.test.ts b/src/__tests__/utils/event-utils.test.ts index b720c5b5c..98f3302bb 100644 --- a/src/__tests__/utils/event-utils.test.ts +++ b/src/__tests__/utils/event-utils.test.ts @@ -3,6 +3,7 @@ import * as globals from '../../utils/globals' import uaParserDeviceTestCases from './device.test.json' import uaParserOSTestCases from './os-test.json' import { _isUndefined } from '../../utils/type-utils' +import { detectBrowser } from '../../utils/user-agent-utils' describe(`event-utils`, () => { describe('properties', () => { @@ -256,12 +257,13 @@ describe(`event-utils`, () => { expect(result).toStrictEqual([expected.os_name, expected.os_version]) }) - test('wat', () => { - const ua = 'Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)' - expect(_info.os(ua)).toStrictEqual(['Windows', '']) + test('can rely on vendor string to detect safari', () => { + const ua = 'Mozilla/5.0 (darwin) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/16.7.0' + const vendor = 'Apple Computer, Inc.' + expect(detectBrowser(ua, vendor, '')).toBe('Safari') }) - it('osVersion', () => { + test('osVersion', () => { const osVersions = { // Windows Phone 'Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 635; BOOST) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537': diff --git a/src/utils/user-agent-utils.ts b/src/utils/user-agent-utils.ts index 3d01fb383..0cfefd394 100644 --- a/src/utils/user-agent-utils.ts +++ b/src/utils/user-agent-utils.ts @@ -108,7 +108,7 @@ export const detectBrowser = function (user_agent: string, vendor: string | unde for (let i = 0; i < browserDetectionChecks.length; i++) { const [check, browser] = browserDetectionChecks[i] - if (check(user_agent)) { + if (check(user_agent, vendor)) { return browser } } From 3608b5ea207cc318d2f6743013015064a3954611 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Mon, 26 Feb 2024 12:49:27 +0000 Subject: [PATCH 36/43] add source to test case files --- src/__tests__/utils/device.test.json | 3 +++ src/__tests__/utils/event-utils.test.ts | 4 ++-- src/__tests__/utils/os-test.json | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/__tests__/utils/device.test.json b/src/__tests__/utils/device.test.json index 998ce62bc..a8fcf0736 100644 --- a/src/__tests__/utils/device.test.json +++ b/src/__tests__/utils/device.test.json @@ -1,4 +1,7 @@ [ + { + "//": "test cases copied from https://github.com/faisalman/ua-parser-js#8087a1b4f0e25f1663ca3ddc2e06371d36642173" + }, { "desc": "K", "ua": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36", diff --git a/src/__tests__/utils/event-utils.test.ts b/src/__tests__/utils/event-utils.test.ts index 98f3302bb..648194f89 100644 --- a/src/__tests__/utils/event-utils.test.ts +++ b/src/__tests__/utils/event-utils.test.ts @@ -223,7 +223,7 @@ describe(`event-utils`, () => { * at "https://github.com/faisalman/ua-parser-js#8087a1b4f0e25f1663ca3ddc2e06371d36642173" * they were copied here */ - test.each(uaParserDeviceTestCases)('device - $ua', (testCase) => { + test.each(uaParserDeviceTestCases.filter((tc) => !tc['//']))('device - $ua', (testCase) => { if (testCase['expect']['type'] === 'smarttv') { // we'll test that separately return @@ -251,7 +251,7 @@ describe(`event-utils`, () => { * * we had to edit them a chunk because we don't aim for the same level of detail */ - test.each(uaParserOSTestCases)('OS - $ua', (testCase) => { + test.each(uaParserOSTestCases.filter((tc) => !tc['//']))('OS - $ua', (testCase) => { const result = _info.os(testCase['ua']) const expected = testCase['expect'] expect(result).toStrictEqual([expected.os_name, expected.os_version]) diff --git a/src/__tests__/utils/os-test.json b/src/__tests__/utils/os-test.json index cf4ead9e2..9dbf6da02 100644 --- a/src/__tests__/utils/os-test.json +++ b/src/__tests__/utils/os-test.json @@ -1,4 +1,7 @@ [ + { + "//": "test cases copied from https://github.com/faisalman/ua-parser-js#8087a1b4f0e25f1663ca3ddc2e06371d36642173" + }, { "desc": "Windows 95", "ua": "Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)", From b98e42b27d810fbe43bf8ed73c9e0b01ca25c020 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Mon, 26 Feb 2024 12:57:09 +0000 Subject: [PATCH 37/43] a little more code golf --- src/utils/user-agent-utils.ts | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/utils/user-agent-utils.ts b/src/utils/user-agent-utils.ts index 0cfefd394..a975c8174 100644 --- a/src/utils/user-agent-utils.ts +++ b/src/utils/user-agent-utils.ts @@ -15,7 +15,8 @@ const FACEBOOK = 'Facebook' const MOBILE = 'Mobile' const IOS = 'iOS' const ANDROID = 'Android' -const ANDROID_TABLET = ANDROID + ' Tablet' +const TABLET = 'Tablet' +const ANDROID_TABLET = ANDROID + ' ' + TABLET const IPAD = 'iPad' const APPLE = 'Apple' const APPLE_WATCH = APPLE + ' Watch' @@ -40,6 +41,8 @@ const PLAYSTATION = 'PlayStation' const XBOX = 'Xbox' const ANDROID_MOBILE = ANDROID + ' ' + MOBILE const MOBILE_SAFARI = MOBILE + ' ' + SAFARI +const WINDOWS = 'Windows' +const WINDOWS_PHONE = WINDOWS + ' Phone' const BROWSER_VERSION_REGEX_SUFFIX = '(\\d+(\\.\\d+)?)' const DEFAULT_BROWSER_VERSION_REGEX = new RegExp('Version/' + BROWSER_VERSION_REGEX_SUFFIX) @@ -169,19 +172,19 @@ export const detectOS = function (user_agent: string): [string, string] { // the first regex that matches uses its matcher function to return the result const osMatchers: [RegExp, (match: RegExpMatchArray | null) => [string, string]][] = [ [ - /xbox; xbox (.*?)[);]/i, + new RegExp(XBOX + '; ' + XBOX + ' (.*?)[);]', 'i'), (match) => { return [XBOX, (match && match[1]) || ''] }, ], [ - /nintendo/i, + new RegExp(NINTENDO, 'i'), () => { return [NINTENDO, ''] }, ], [ - /playstation/i, + new RegExp(PLAYSTATION, 'i'), () => { return [PLAYSTATION, ''] }, @@ -193,14 +196,14 @@ export const detectOS = function (user_agent: string): [string, string] { }, ], [ - /Windows/i, + new RegExp(WINDOWS, 'i'), () => { if (/Phone/.test(user_agent) || /WPDesktop/.test(user_agent)) { - return ['Windows Phone', ''] + return [WINDOWS_PHONE, ''] } // not all JS versions support negative lookbehind, so we need two checks here if (/Mobile\b/.test(user_agent) && !/IEMobile\b/.test(user_agent)) { - return ['Windows ' + MOBILE, ''] + return [WINDOWS + ' ' + MOBILE, ''] } const match = /Windows NT ([0-9.]+)/i.exec(user_agent) if (match && match[1]) { @@ -209,9 +212,9 @@ export const detectOS = function (user_agent: string): [string, string] { if (/arm/i.test(user_agent)) { osVersion = 'RT' } - return ['Windows', osVersion] + return [WINDOWS, osVersion] } - return ['Windows', ''] + return [WINDOWS, ''] }, ], [ @@ -236,7 +239,7 @@ export const detectOS = function (user_agent: string): [string, string] { }, ], [ - /(Android (\d+)\.(\d+)\.?(\d+)?|Android)/i, + new RegExp('(' + ANDROID + ' (\\d+)\\.(\\d+)\\.?(\\d+)?|' + ANDROID + ')', 'i'), (match) => { if (match && match[2]) { const versionParts = [match[2], match[3], match[4] || '0'] @@ -297,7 +300,7 @@ export const detectDevice = function (user_agent: string): string { } else if (/ouya/i.test(user_agent)) { return 'Ouya' } else if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) { - return 'Windows Phone' + return WINDOWS_PHONE } else if (/iPad/.test(user_agent)) { return IPAD } else if (/iPod/.test(user_agent)) { @@ -353,13 +356,13 @@ export const detectDeviceType = function (user_agent: string): string { device === 'Kindle Fire' || device === 'Generic tablet' ) { - return 'Tablet' + return TABLET } else if (device === NINTENDO || device === XBOX || device === PLAYSTATION || device === 'Ouya') { return 'Console' } else if (device === APPLE_WATCH) { return 'Wearable' } else if (device) { - return 'Mobile' + return MOBILE } else { return 'Desktop' } From d7a60d3cb7f2aa2571727e01cde6cb2c94c015da Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Mon, 26 Feb 2024 14:02:52 +0000 Subject: [PATCH 38/43] more code golf --- src/utils/user-agent-utils.ts | 239 +++++++++++++++++----------------- 1 file changed, 121 insertions(+), 118 deletions(-) diff --git a/src/utils/user-agent-utils.ts b/src/utils/user-agent-utils.ts index a975c8174..8e53566f0 100644 --- a/src/utils/user-agent-utils.ts +++ b/src/utils/user-agent-utils.ts @@ -43,6 +43,8 @@ const ANDROID_MOBILE = ANDROID + ' ' + MOBILE const MOBILE_SAFARI = MOBILE + ' ' + SAFARI const WINDOWS = 'Windows' const WINDOWS_PHONE = WINDOWS + ' Phone' +const NOKIA = 'Nokia' +const OUYA = 'Ouya' const BROWSER_VERSION_REGEX_SUFFIX = '(\\d+(\\.\\d+)?)' const DEFAULT_BROWSER_VERSION_REGEX = new RegExp('Version/' + BROWSER_VERSION_REGEX_SUFFIX) @@ -167,122 +169,123 @@ export const detectBrowserVersion = function ( return null } -export const detectOS = function (user_agent: string): [string, string] { - // to avoid repeating regexes or calling them twice, we have an array of matches - // the first regex that matches uses its matcher function to return the result - const osMatchers: [RegExp, (match: RegExpMatchArray | null) => [string, string]][] = [ - [ - new RegExp(XBOX + '; ' + XBOX + ' (.*?)[);]', 'i'), - (match) => { - return [XBOX, (match && match[1]) || ''] - }, - ], - [ - new RegExp(NINTENDO, 'i'), - () => { - return [NINTENDO, ''] - }, - ], - [ - new RegExp(PLAYSTATION, 'i'), - () => { - return [PLAYSTATION, ''] - }, - ], - [ - BLACKBERRY_REGEX, - () => { - return [BLACKBERRY, ''] - }, - ], - [ - new RegExp(WINDOWS, 'i'), - () => { - if (/Phone/.test(user_agent) || /WPDesktop/.test(user_agent)) { - return [WINDOWS_PHONE, ''] - } - // not all JS versions support negative lookbehind, so we need two checks here - if (/Mobile\b/.test(user_agent) && !/IEMobile\b/.test(user_agent)) { - return [WINDOWS + ' ' + MOBILE, ''] - } - const match = /Windows NT ([0-9.]+)/i.exec(user_agent) - if (match && match[1]) { - const version = match[1] - let osVersion = windowsVersionMap[version] || '' - if (/arm/i.test(user_agent)) { - osVersion = 'RT' - } - return [WINDOWS, osVersion] - } - return [WINDOWS, ''] - }, - ], - [ - /((iPhone|iPad|iPod).*?OS (\d+)_(\d+)_?(\d+)?|iPhone)/, - (match) => { - if (match && match[3]) { - const versionParts = [match[3], match[4], match[5] || '0'] - return [IOS, versionParts.join('.')] - } - return [IOS, ''] - }, - ], - [ - /(watch.*\/(\d+\.\d+\.\d+)|watch os,(\d+\.\d+),)/i, - (match) => { - // e.g. Watch4,3/5.3.8 (16U680) - let version = '' - if (match && match.length >= 3) { - version = _isUndefined(match[2]) ? match[3] : match[2] - } - return ['watchOS', version] - }, - ], - [ - new RegExp('(' + ANDROID + ' (\\d+)\\.(\\d+)\\.?(\\d+)?|' + ANDROID + ')', 'i'), - (match) => { - if (match && match[2]) { - const versionParts = [match[2], match[3], match[4] || '0'] - return [ANDROID, versionParts.join('.')] - } - return [ANDROID, ''] - }, - ], - [ - /Mac OS X (\d+)[_.](\d+)[_.]?(\d+)?/i, - (match) => { - const result: [string, string] = ['Mac OS X', ''] - if (match && match[1]) { - const versionParts = [match[1], match[2], match[3] || '0'] - result[1] = versionParts.join('.') +// to avoid repeating regexes or calling them twice, we have an array of matches +// the first regex that matches uses its matcher function to return the result +const osMatchers: [RegExp, (match: RegExpMatchArray | null, user_agent: string) => [string, string]][] = [ + [ + new RegExp(XBOX + '; ' + XBOX + ' (.*?)[);]', 'i'), + (match) => { + return [XBOX, (match && match[1]) || ''] + }, + ], + [ + new RegExp(NINTENDO, 'i'), + () => { + return [NINTENDO, ''] + }, + ], + [ + new RegExp(PLAYSTATION, 'i'), + () => { + return [PLAYSTATION, ''] + }, + ], + [ + BLACKBERRY_REGEX, + () => { + return [BLACKBERRY, ''] + }, + ], + [ + new RegExp(WINDOWS, 'i'), + (_, user_agent) => { + if (/Phone/.test(user_agent) || /WPDesktop/.test(user_agent)) { + return [WINDOWS_PHONE, ''] + } + // not all JS versions support negative lookbehind, so we need two checks here + if (/Mobile\b/.test(user_agent) && !/IEMobile\b/.test(user_agent)) { + return [WINDOWS + ' ' + MOBILE, ''] + } + const match = /Windows NT ([0-9.]+)/i.exec(user_agent) + if (match && match[1]) { + const version = match[1] + let osVersion = windowsVersionMap[version] || '' + if (/arm/i.test(user_agent)) { + osVersion = 'RT' } - return result - }, - ], - [ - /Mac/i, - () => { - // mop up a few non-standard UAs that should match mac - return ['Mac OS X', ''] - }, - ], - [ - /CrOS/, - () => { - return [CHROME_OS, ''] - }, - ], - [ - /Linux|debian/i, - () => { - return ['Linux', ''] - }, - ], - ] + return [WINDOWS, osVersion] + } + return [WINDOWS, ''] + }, + ], + [ + /((iPhone|iPad|iPod).*?OS (\d+)_(\d+)_?(\d+)?|iPhone)/, + (match) => { + if (match && match[3]) { + const versionParts = [match[3], match[4], match[5] || '0'] + return [IOS, versionParts.join('.')] + } + return [IOS, ''] + }, + ], + [ + /(watch.*\/(\d+\.\d+\.\d+)|watch os,(\d+\.\d+),)/i, + (match) => { + // e.g. Watch4,3/5.3.8 (16U680) + let version = '' + if (match && match.length >= 3) { + version = _isUndefined(match[2]) ? match[3] : match[2] + } + return ['watchOS', version] + }, + ], + [ + new RegExp('(' + ANDROID + ' (\\d+)\\.(\\d+)\\.?(\\d+)?|' + ANDROID + ')', 'i'), + (match) => { + if (match && match[2]) { + const versionParts = [match[2], match[3], match[4] || '0'] + return [ANDROID, versionParts.join('.')] + } + return [ANDROID, ''] + }, + ], + [ + /Mac OS X (\d+)[_.](\d+)[_.]?(\d+)?/i, + (match) => { + const result: [string, string] = ['Mac OS X', ''] + if (match && match[1]) { + const versionParts = [match[1], match[2], match[3] || '0'] + result[1] = versionParts.join('.') + } + return result + }, + ], + [ + /Mac/i, + () => { + // mop up a few non-standard UAs that should match mac + return ['Mac OS X', ''] + }, + ], + [ + /CrOS/, + () => { + return [CHROME_OS, ''] + }, + ], + [ + /Linux|debian/i, + () => { + return ['Linux', ''] + }, + ], +] + +export const detectOS = function (user_agent: string): [string, string] { for (let i = 0; i < osMatchers.length; i++) { const [rgex, matcherFn] = osMatchers[i] const match = rgex.exec(user_agent) - const result = match && matcherFn(match) + const result = match && matcherFn(match, user_agent) if (result) { return result } @@ -297,9 +300,9 @@ export const detectDevice = function (user_agent: string): string { return PLAYSTATION } else if (XBOX_REGEX.test(user_agent)) { return XBOX - } else if (/ouya/i.test(user_agent)) { - return 'Ouya' - } else if (/Windows Phone/i.test(user_agent) || /WPDesktop/.test(user_agent)) { + } else if (new RegExp(OUYA, 'i').test(user_agent)) { + return OUYA + } else if (new RegExp('(' + WINDOWS_PHONE + '|WPDesktop)', 'i').test(user_agent)) { return WINDOWS_PHONE } else if (/iPad/.test(user_agent)) { return IPAD @@ -313,8 +316,8 @@ export const detectDevice = function (user_agent: string): string { return BLACKBERRY } else if (/(kobo)\s(ereader|touch)/i.test(user_agent)) { return 'Kobo' - } else if (/Nokia/i.test(user_agent)) { - return 'Nokia' + } else if (new RegExp(NOKIA, 'i').test(user_agent)) { + return NOKIA } else if ( // Kindle Fire without Silk / Echo Show /(kf[a-z]{2}wi|aeo[c-r]{2})( bui|\))/i.test(user_agent) || @@ -357,7 +360,7 @@ export const detectDeviceType = function (user_agent: string): string { device === 'Generic tablet' ) { return TABLET - } else if (device === NINTENDO || device === XBOX || device === PLAYSTATION || device === 'Ouya') { + } else if (device === NINTENDO || device === XBOX || device === PLAYSTATION || device === OUYA) { return 'Console' } else if (device === APPLE_WATCH) { return 'Wearable' From 7bc501fd63decc1b80e7ef50bdda0657c77dd09c Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Mon, 26 Feb 2024 14:07:19 +0000 Subject: [PATCH 39/43] more code golf --- src/utils/user-agent-utils.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/utils/user-agent-utils.ts b/src/utils/user-agent-utils.ts index 8e53566f0..fe648fb62 100644 --- a/src/utils/user-agent-utils.ts +++ b/src/utils/user-agent-utils.ts @@ -45,6 +45,9 @@ const WINDOWS = 'Windows' const WINDOWS_PHONE = WINDOWS + ' Phone' const NOKIA = 'Nokia' const OUYA = 'Ouya' +const GENERIC = 'Generic' +const GENERIC_MOBILE = GENERIC + ' ' + MOBILE.toLowerCase() +const GENERIC_TABLET = GENERIC + ' ' + TABLET.toLowerCase() const BROWSER_VERSION_REGEX_SUFFIX = '(\\d+(\\.\\d+)?)' const DEFAULT_BROWSER_VERSION_REGEX = new RegExp('Version/' + BROWSER_VERSION_REGEX_SUFFIX) @@ -203,7 +206,7 @@ const osMatchers: [RegExp, (match: RegExpMatchArray | null, user_agent: string) return [WINDOWS_PHONE, ''] } // not all JS versions support negative lookbehind, so we need two checks here - if (/Mobile\b/.test(user_agent) && !/IEMobile\b/.test(user_agent)) { + if (new RegExp(MOBILE).test(user_agent) && !/IEMobile\b/.test(user_agent)) { return [WINDOWS + ' ' + MOBILE, ''] } const match = /Windows NT ([0-9.]+)/i.exec(user_agent) @@ -327,7 +330,7 @@ export const detectDevice = function (user_agent: string): string { return 'Kindle Fire' } else if (/(Android|ZTE)/i.test(user_agent)) { if ( - !/Mobile/.test(user_agent) || + !new RegExp(MOBILE).test(user_agent) || /(9138B|TB782B|Nexus [97]|pixel c|HUAWEISHT|BTV|noble nook|smart ultra 6)/i.test(user_agent) ) { if ( @@ -341,10 +344,10 @@ export const detectDevice = function (user_agent: string): string { } else { return ANDROID } - } else if (/(pda|mobile)/i.test(user_agent)) { - return 'Generic mobile' - } else if (/tablet/i.test(user_agent) && !/tablet pc/i.test(user_agent)) { - return 'Generic tablet' + } else if (new RegExp('(pda|' + MOBILE + ')', 'i').test(user_agent)) { + return GENERIC_MOBILE + } else if (new RegExp(TABLET, 'i').test(user_agent) && !new RegExp(TABLET + ' pc', 'i').test(user_agent)) { + return GENERIC_TABLET } else { return '' } @@ -357,7 +360,7 @@ export const detectDeviceType = function (user_agent: string): string { device === ANDROID_TABLET || device === 'Kobo' || device === 'Kindle Fire' || - device === 'Generic tablet' + device === GENERIC_TABLET ) { return TABLET } else if (device === NINTENDO || device === XBOX || device === PLAYSTATION || device === OUYA) { From d5da91db5fa08ecb0de7c4978e0999b19c8ec43d Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Mon, 26 Feb 2024 14:09:09 +0000 Subject: [PATCH 40/43] more code golf --- src/utils/user-agent-utils.ts | 39 ++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/utils/user-agent-utils.ts b/src/utils/user-agent-utils.ts index fe648fb62..8b51ca657 100644 --- a/src/utils/user-agent-utils.ts +++ b/src/utils/user-agent-utils.ts @@ -124,6 +124,26 @@ export const detectBrowser = function (user_agent: string, vendor: string | unde return '' } +const versionRegexes: Record = { + [INTERNET_EXPLORER_MOBILE]: [new RegExp(`rv:${BROWSER_VERSION_REGEX_SUFFIX}`)], + [MICROSOFT_EDGE]: [new RegExp(`${EDGE}?\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [CHROME]: [new RegExp(`${CHROME}/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [CHROME_IOS]: [new RegExp(`CriOS\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + 'UC Browser': [new RegExp(`(UCBrowser|UCWEB)\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [SAFARI]: [DEFAULT_BROWSER_VERSION_REGEX], + [MOBILE_SAFARI]: [DEFAULT_BROWSER_VERSION_REGEX], + [OPERA]: [new RegExp(`(${OPERA}|OPR)\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [FIREFOX]: [new RegExp(`${FIREFOX}\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [FIREFOX_IOS]: [new RegExp(`FxiOS\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + Konqueror: [new RegExp(`Konqueror[:/]?${BROWSER_VERSION_REGEX_SUFFIX}`, 'i')], + // not every blackberry user agent has the version after the name + [BLACKBERRY]: [new RegExp(`${BLACKBERRY} ${BROWSER_VERSION_REGEX_SUFFIX}`), DEFAULT_BROWSER_VERSION_REGEX], + [ANDROID_MOBILE]: [new RegExp(`android\\s${BROWSER_VERSION_REGEX_SUFFIX}`)], + [SAMSUNG_INTERNET]: [new RegExp(`${SAMSUNG_BROWSER}\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [INTERNET_EXPLORER]: [new RegExp(`(rv:|MSIE )${BROWSER_VERSION_REGEX_SUFFIX}`)], + Mozilla: [new RegExp(`rv:${BROWSER_VERSION_REGEX_SUFFIX}`)], +} + /** * This function detects which browser version is running this script, * parsing major and minor version (e.g., 42.1). User agent strings from: @@ -138,25 +158,6 @@ export const detectBrowserVersion = function ( opera: string ): number | null { const browser = detectBrowser(userAgent, vendor, opera) - const versionRegexes: Record = { - [INTERNET_EXPLORER_MOBILE]: [new RegExp(`rv:${BROWSER_VERSION_REGEX_SUFFIX}`)], - [MICROSOFT_EDGE]: [new RegExp(`${EDGE}?\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - [CHROME]: [new RegExp(`${CHROME}/${BROWSER_VERSION_REGEX_SUFFIX}`)], - [CHROME_IOS]: [new RegExp(`CriOS\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - 'UC Browser': [new RegExp(`(UCBrowser|UCWEB)\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - [SAFARI]: [DEFAULT_BROWSER_VERSION_REGEX], - [MOBILE_SAFARI]: [DEFAULT_BROWSER_VERSION_REGEX], - [OPERA]: [new RegExp(`(${OPERA}|OPR)\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - [FIREFOX]: [new RegExp(`${FIREFOX}\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - [FIREFOX_IOS]: [new RegExp(`FxiOS\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - Konqueror: [new RegExp(`Konqueror[:/]?${BROWSER_VERSION_REGEX_SUFFIX}`, 'i')], - // not every blackberry user agent has the version after the name - [BLACKBERRY]: [new RegExp(`${BLACKBERRY} ${BROWSER_VERSION_REGEX_SUFFIX}`), DEFAULT_BROWSER_VERSION_REGEX], - [ANDROID_MOBILE]: [new RegExp(`android\\s${BROWSER_VERSION_REGEX_SUFFIX}`)], - [SAMSUNG_INTERNET]: [new RegExp(`${SAMSUNG_BROWSER}\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - [INTERNET_EXPLORER]: [new RegExp(`(rv:|MSIE )${BROWSER_VERSION_REGEX_SUFFIX}`)], - Mozilla: [new RegExp(`rv:${BROWSER_VERSION_REGEX_SUFFIX}`)], - } const regexes: RegExp[] | undefined = versionRegexes[browser as keyof typeof versionRegexes] if (_isUndefined(regexes)) { return null From 2beb227a45a541ab5eb9e0a659a33fd7b22b383f Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Mon, 26 Feb 2024 17:05:15 +0000 Subject: [PATCH 41/43] is this smaller? --- src/utils/user-agent-utils.ts | 65 ++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/src/utils/user-agent-utils.ts b/src/utils/user-agent-utils.ts index 8b51ca657..4ccfb8f8a 100644 --- a/src/utils/user-agent-utils.ts +++ b/src/utils/user-agent-utils.ts @@ -48,6 +48,7 @@ const OUYA = 'Ouya' const GENERIC = 'Generic' const GENERIC_MOBILE = GENERIC + ' ' + MOBILE.toLowerCase() const GENERIC_TABLET = GENERIC + ' ' + TABLET.toLowerCase() +const KONQUEROR = 'Konqueror' const BROWSER_VERSION_REGEX_SUFFIX = '(\\d+(\\.\\d+)?)' const DEFAULT_BROWSER_VERSION_REGEX = new RegExp('Version/' + BROWSER_VERSION_REGEX_SUFFIX) @@ -79,29 +80,8 @@ const windowsVersionMap: Record = { function isSafari(userAgent: string): boolean { return _includes(userAgent, SAFARI) && !_includes(userAgent, CHROME) && !_includes(userAgent, ANDROID) } -const safariCheck = (ua: string, vendor?: string) => (vendor && _includes(vendor, APPLE)) || isSafari(ua) -const browserDetectionChecks: [(userAgent: string, navigatorVendor?: string) => boolean, string][] = [ - [(ua) => _includes(ua, ' OPR/') && _includes(ua, 'Mini'), OPERA_MINI], - [(ua) => _includes(ua, ' OPR/'), OPERA], - [(ua) => BLACKBERRY_REGEX.test(ua), BLACKBERRY], - [(ua) => _includes(ua, 'IE' + MOBILE) || _includes(ua, 'WPDesktop'), INTERNET_EXPLORER_MOBILE], - // https://developer.samsung.com/internet/user-agent-string-format - [(ua) => _includes(ua, SAMSUNG_BROWSER), SAMSUNG_INTERNET], - [(ua) => _includes(ua, EDGE) || _includes(ua, 'Edg/'), MICROSOFT_EDGE], - [(ua) => _includes(ua, 'FBIOS'), FACEBOOK + ' ' + MOBILE], - [(ua) => _includes(ua, CHROME), CHROME], - [(ua) => _includes(ua, 'CriOS'), CHROME_IOS], - [(ua) => _includes(ua, 'UCWEB') || _includes(ua, 'UCBrowser'), 'UC Browser'], - [(ua) => _includes(ua, 'FxiOS'), FIREFOX_IOS], - [(ua) => _includes(ua, ANDROID), ANDROID_MOBILE], - [(ua) => _includes(ua, 'Konqueror') || _includes(ua, 'konqueror'), 'Konqueror'], - [(ua, vendor) => safariCheck(ua, vendor) && _includes(ua, MOBILE), MOBILE_SAFARI], - [(ua, vendor) => safariCheck(ua, vendor), SAFARI], - [(ua) => _includes(ua, FIREFOX), FIREFOX], - [(ua) => _includes(ua, 'MSIE') || _includes(ua, 'Trident/'), INTERNET_EXPLORER], - [(ua) => _includes(ua, 'Gecko'), FIREFOX], -] +const safariCheck = (ua: string, vendor?: string) => (vendor && _includes(vendor, APPLE)) || isSafari(ua) /** * This function detects which browser is running this script. @@ -114,11 +94,42 @@ export const detectBrowser = function (user_agent: string, vendor: string | unde } vendor = vendor || '' // vendor is undefined for at least IE9 - for (let i = 0; i < browserDetectionChecks.length; i++) { - const [check, browser] = browserDetectionChecks[i] - if (check(user_agent, vendor)) { - return browser - } + if (_includes(user_agent, ' OPR/') && _includes(user_agent, 'Mini')) { + return OPERA_MINI + } else if (_includes(user_agent, ' OPR/')) { + return OPERA + } else if (BLACKBERRY_REGEX.test(user_agent)) { + return BLACKBERRY + } else if (_includes(user_agent, 'IE' + MOBILE) || _includes(user_agent, 'WPDesktop')) { + return INTERNET_EXPLORER_MOBILE + } + // https://developer.samsung.com/internet/user-agent-string-format + else if (_includes(user_agent, SAMSUNG_BROWSER)) { + return SAMSUNG_INTERNET + } else if (_includes(user_agent, EDGE) || _includes(user_agent, 'Edg/')) { + return MICROSOFT_EDGE + } else if (_includes(user_agent, 'FBIOS')) { + return FACEBOOK + ' ' + MOBILE + } else if (_includes(user_agent, CHROME)) { + return CHROME + } else if (_includes(user_agent, 'CriOS')) { + return CHROME_IOS + } else if (_includes(user_agent, 'UCWEB') || _includes(user_agent, 'UCBrowser')) { + return 'UC Browser' + } else if (_includes(user_agent, 'FxiOS')) { + return FIREFOX_IOS + } else if (_includes(user_agent, ANDROID)) { + return ANDROID_MOBILE + } else if (_includes(user_agent.toLowerCase(), KONQUEROR.toLowerCase())) { + return KONQUEROR + } else if (safariCheck(user_agent, vendor)) { + return _includes(user_agent, MOBILE) ? MOBILE_SAFARI : SAFARI + } else if (_includes(user_agent, FIREFOX)) { + return FIREFOX + } else if (_includes(user_agent, 'MSIE') || _includes(user_agent, 'Trident/')) { + return INTERNET_EXPLORER + } else if (_includes(user_agent, 'Gecko')) { + return FIREFOX } return '' From f108113a30c5819bfe2a1914830ef8e421ef12bc Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Mon, 26 Feb 2024 18:40:12 +0000 Subject: [PATCH 42/43] is this smaller? --- src/utils/user-agent-utils.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/utils/user-agent-utils.ts b/src/utils/user-agent-utils.ts index 4ccfb8f8a..6157ec436 100644 --- a/src/utils/user-agent-utils.ts +++ b/src/utils/user-agent-utils.ts @@ -136,23 +136,23 @@ export const detectBrowser = function (user_agent: string, vendor: string | unde } const versionRegexes: Record = { - [INTERNET_EXPLORER_MOBILE]: [new RegExp(`rv:${BROWSER_VERSION_REGEX_SUFFIX}`)], - [MICROSOFT_EDGE]: [new RegExp(`${EDGE}?\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - [CHROME]: [new RegExp(`${CHROME}/${BROWSER_VERSION_REGEX_SUFFIX}`)], - [CHROME_IOS]: [new RegExp(`CriOS\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - 'UC Browser': [new RegExp(`(UCBrowser|UCWEB)\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], + [INTERNET_EXPLORER_MOBILE]: [new RegExp('rv:' + BROWSER_VERSION_REGEX_SUFFIX)], + [MICROSOFT_EDGE]: [new RegExp(EDGE + '?\\/' + BROWSER_VERSION_REGEX_SUFFIX)], + [CHROME]: [new RegExp(CHROME + '/' + BROWSER_VERSION_REGEX_SUFFIX)], + [CHROME_IOS]: [new RegExp('CriOS\\/' + BROWSER_VERSION_REGEX_SUFFIX)], + 'UC Browser': [new RegExp('(UCBrowser|UCWEB)\\/' + BROWSER_VERSION_REGEX_SUFFIX)], [SAFARI]: [DEFAULT_BROWSER_VERSION_REGEX], [MOBILE_SAFARI]: [DEFAULT_BROWSER_VERSION_REGEX], - [OPERA]: [new RegExp(`(${OPERA}|OPR)\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - [FIREFOX]: [new RegExp(`${FIREFOX}\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - [FIREFOX_IOS]: [new RegExp(`FxiOS\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - Konqueror: [new RegExp(`Konqueror[:/]?${BROWSER_VERSION_REGEX_SUFFIX}`, 'i')], + [OPERA]: [new RegExp('(' + OPERA + '|OPR)\\/' + BROWSER_VERSION_REGEX_SUFFIX)], + [FIREFOX]: [new RegExp(FIREFOX + '\\/' + BROWSER_VERSION_REGEX_SUFFIX)], + [FIREFOX_IOS]: [new RegExp('FxiOS\\/' + BROWSER_VERSION_REGEX_SUFFIX)], + [KONQUEROR]: [new RegExp('Konqueror[:/]?' + BROWSER_VERSION_REGEX_SUFFIX, 'i')], // not every blackberry user agent has the version after the name - [BLACKBERRY]: [new RegExp(`${BLACKBERRY} ${BROWSER_VERSION_REGEX_SUFFIX}`), DEFAULT_BROWSER_VERSION_REGEX], - [ANDROID_MOBILE]: [new RegExp(`android\\s${BROWSER_VERSION_REGEX_SUFFIX}`)], - [SAMSUNG_INTERNET]: [new RegExp(`${SAMSUNG_BROWSER}\\/${BROWSER_VERSION_REGEX_SUFFIX}`)], - [INTERNET_EXPLORER]: [new RegExp(`(rv:|MSIE )${BROWSER_VERSION_REGEX_SUFFIX}`)], - Mozilla: [new RegExp(`rv:${BROWSER_VERSION_REGEX_SUFFIX}`)], + [BLACKBERRY]: [new RegExp(BLACKBERRY + ' ' + BROWSER_VERSION_REGEX_SUFFIX), DEFAULT_BROWSER_VERSION_REGEX], + [ANDROID_MOBILE]: [new RegExp('android\\s' + BROWSER_VERSION_REGEX_SUFFIX)], + [SAMSUNG_INTERNET]: [new RegExp(SAMSUNG_BROWSER + '\\/' + BROWSER_VERSION_REGEX_SUFFIX)], + [INTERNET_EXPLORER]: [new RegExp('(rv:|MSIE )' + BROWSER_VERSION_REGEX_SUFFIX)], + Mozilla: [new RegExp('rv:' + BROWSER_VERSION_REGEX_SUFFIX)], } /** From d79132e0268eb82673fe641940f717c22625fd72 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Mon, 26 Feb 2024 19:06:37 +0000 Subject: [PATCH 43/43] is this smaller? --- src/utils/user-agent-utils.ts | 52 ++++++++++------------------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/src/utils/user-agent-utils.ts b/src/utils/user-agent-utils.ts index 6157ec436..a67f946ef 100644 --- a/src/utils/user-agent-utils.ts +++ b/src/utils/user-agent-utils.ts @@ -1,5 +1,5 @@ import { _includes } from './index' -import { _isUndefined } from './type-utils' +import { _isFunction, _isUndefined } from './type-utils' /** * this device detection code is (at time of writing) about 3% of the size of the entire library @@ -186,31 +186,19 @@ export const detectBrowserVersion = function ( // to avoid repeating regexes or calling them twice, we have an array of matches // the first regex that matches uses its matcher function to return the result -const osMatchers: [RegExp, (match: RegExpMatchArray | null, user_agent: string) => [string, string]][] = [ +const osMatchers: [ + RegExp, + [string, string] | ((match: RegExpMatchArray | null, user_agent: string) => [string, string]) +][] = [ [ new RegExp(XBOX + '; ' + XBOX + ' (.*?)[);]', 'i'), (match) => { return [XBOX, (match && match[1]) || ''] }, ], - [ - new RegExp(NINTENDO, 'i'), - () => { - return [NINTENDO, ''] - }, - ], - [ - new RegExp(PLAYSTATION, 'i'), - () => { - return [PLAYSTATION, ''] - }, - ], - [ - BLACKBERRY_REGEX, - () => { - return [BLACKBERRY, ''] - }, - ], + [new RegExp(NINTENDO, 'i'), [NINTENDO, '']], + [new RegExp(PLAYSTATION, 'i'), [PLAYSTATION, '']], + [BLACKBERRY_REGEX, [BLACKBERRY, '']], [ new RegExp(WINDOWS, 'i'), (_, user_agent) => { @@ -277,30 +265,18 @@ const osMatchers: [RegExp, (match: RegExpMatchArray | null, user_agent: string) ], [ /Mac/i, - () => { - // mop up a few non-standard UAs that should match mac - return ['Mac OS X', ''] - }, - ], - [ - /CrOS/, - () => { - return [CHROME_OS, ''] - }, - ], - [ - /Linux|debian/i, - () => { - return ['Linux', ''] - }, + // mop up a few non-standard UAs that should match mac + ['Mac OS X', ''], ], + [/CrOS/, [CHROME_OS, '']], + [/Linux|debian/i, ['Linux', '']], ] export const detectOS = function (user_agent: string): [string, string] { for (let i = 0; i < osMatchers.length; i++) { - const [rgex, matcherFn] = osMatchers[i] + const [rgex, resultOrFn] = osMatchers[i] const match = rgex.exec(user_agent) - const result = match && matcherFn(match, user_agent) + const result = match && (_isFunction(resultOrFn) ? resultOrFn(match, user_agent) : resultOrFn) if (result) { return result }