@tinkoff/user-agent 0.4.176 → 0.4.181
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client-hints.es.js +88 -0
- package/lib/client-hints.js +92 -0
- package/lib/constants.es.js +57 -0
- package/lib/constants.js +62 -0
- package/lib/index.es.js +3 -333
- package/lib/index.js +7 -343
- package/lib/isSameSiteNoneCompatible.es.js +45 -0
- package/lib/isSameSiteNoneCompatible.js +49 -0
- package/lib/satisfies.es.js +80 -0
- package/lib/satisfies.js +91 -0
- package/lib/userAgent.es.js +49 -0
- package/lib/userAgent.js +59 -0
- package/lib/utils.es.js +26 -0
- package/lib/utils.js +31 -0
- package/package.json +4 -5
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { getBrowserEngine, getMobileOs } from './utils.es.js';
|
|
2
|
+
|
|
3
|
+
const KNOWN_VENDORS = new Set(['Opera', 'Google Chrome', 'Microsoft Edge', 'Firefox', 'Safari']);
|
|
4
|
+
const KNOWN_ENGINES = new Set(['Chromium']);
|
|
5
|
+
const parseQuotedString = (str) => {
|
|
6
|
+
var _a;
|
|
7
|
+
if (!str) {
|
|
8
|
+
return str;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
return (_a = JSON.parse(str)) === null || _a === void 0 ? void 0 : _a.trim();
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
return str;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const parseBrowser = (brandsList) => {
|
|
18
|
+
const browser = {
|
|
19
|
+
name: undefined,
|
|
20
|
+
version: undefined,
|
|
21
|
+
major: undefined,
|
|
22
|
+
browserEngine: '',
|
|
23
|
+
};
|
|
24
|
+
const engine = {
|
|
25
|
+
name: undefined,
|
|
26
|
+
version: undefined,
|
|
27
|
+
};
|
|
28
|
+
brandsList.split(',').forEach((entry) => {
|
|
29
|
+
const [name, version] = entry.split(/;\s*v=/).map(parseQuotedString);
|
|
30
|
+
if (name && KNOWN_VENDORS.has(name)) {
|
|
31
|
+
browser.name = name.toLowerCase();
|
|
32
|
+
browser.version = version;
|
|
33
|
+
browser.major = version;
|
|
34
|
+
}
|
|
35
|
+
if (name && KNOWN_ENGINES.has(name)) {
|
|
36
|
+
engine.name = name.toLowerCase();
|
|
37
|
+
engine.version = version;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
if (!browser.name && engine.name) {
|
|
41
|
+
browser.name = engine.name;
|
|
42
|
+
browser.version = engine.version;
|
|
43
|
+
}
|
|
44
|
+
browser.browserEngine = getBrowserEngine(browser.name, engine.name);
|
|
45
|
+
return { browser, engine };
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
*
|
|
49
|
+
* @description
|
|
50
|
+
*
|
|
51
|
+
* Some of the data are available only when additional headers for client-hints were sent from server:
|
|
52
|
+
* - full browser version (only major version is available by default)
|
|
53
|
+
* - OS version
|
|
54
|
+
* - CPU architecture
|
|
55
|
+
* - device model
|
|
56
|
+
*
|
|
57
|
+
* To able to use data you should first provide header `Accept-CH` with the list of headers that client should send.
|
|
58
|
+
*
|
|
59
|
+
* @param headers
|
|
60
|
+
* @returns
|
|
61
|
+
*/
|
|
62
|
+
const parseClientHintsHeaders = (headers) => {
|
|
63
|
+
const { browser, engine } = parseBrowser(headers['sec-ch-ua-full-version-list'] || headers['sec-ch-ua']);
|
|
64
|
+
const osName = parseQuotedString(headers['sec-ch-ua-platform']);
|
|
65
|
+
const mobileOS = getMobileOs(osName);
|
|
66
|
+
return {
|
|
67
|
+
browser,
|
|
68
|
+
engine,
|
|
69
|
+
os: {
|
|
70
|
+
name: osName,
|
|
71
|
+
version: parseQuotedString(headers['sec-ch-ua-platform-version']),
|
|
72
|
+
},
|
|
73
|
+
cpu: {
|
|
74
|
+
architecture: parseQuotedString(headers['sec-ch-ua-arch']),
|
|
75
|
+
},
|
|
76
|
+
mobileOS,
|
|
77
|
+
device: {
|
|
78
|
+
model: parseQuotedString(headers['sec-ch-ua-model']),
|
|
79
|
+
type: headers['sec-ch-ua-mobile'] === '?1' ? 'mobile' : 'desktop',
|
|
80
|
+
vendor: undefined,
|
|
81
|
+
},
|
|
82
|
+
// basically all of the browsers with client-hints support
|
|
83
|
+
// also compatible with SameSite=None
|
|
84
|
+
sameSiteNoneCompatible: true,
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export { parseClientHintsHeaders };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var utils = require('./utils.js');
|
|
6
|
+
|
|
7
|
+
const KNOWN_VENDORS = new Set(['Opera', 'Google Chrome', 'Microsoft Edge', 'Firefox', 'Safari']);
|
|
8
|
+
const KNOWN_ENGINES = new Set(['Chromium']);
|
|
9
|
+
const parseQuotedString = (str) => {
|
|
10
|
+
var _a;
|
|
11
|
+
if (!str) {
|
|
12
|
+
return str;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
return (_a = JSON.parse(str)) === null || _a === void 0 ? void 0 : _a.trim();
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
return str;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const parseBrowser = (brandsList) => {
|
|
22
|
+
const browser = {
|
|
23
|
+
name: undefined,
|
|
24
|
+
version: undefined,
|
|
25
|
+
major: undefined,
|
|
26
|
+
browserEngine: '',
|
|
27
|
+
};
|
|
28
|
+
const engine = {
|
|
29
|
+
name: undefined,
|
|
30
|
+
version: undefined,
|
|
31
|
+
};
|
|
32
|
+
brandsList.split(',').forEach((entry) => {
|
|
33
|
+
const [name, version] = entry.split(/;\s*v=/).map(parseQuotedString);
|
|
34
|
+
if (name && KNOWN_VENDORS.has(name)) {
|
|
35
|
+
browser.name = name.toLowerCase();
|
|
36
|
+
browser.version = version;
|
|
37
|
+
browser.major = version;
|
|
38
|
+
}
|
|
39
|
+
if (name && KNOWN_ENGINES.has(name)) {
|
|
40
|
+
engine.name = name.toLowerCase();
|
|
41
|
+
engine.version = version;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
if (!browser.name && engine.name) {
|
|
45
|
+
browser.name = engine.name;
|
|
46
|
+
browser.version = engine.version;
|
|
47
|
+
}
|
|
48
|
+
browser.browserEngine = utils.getBrowserEngine(browser.name, engine.name);
|
|
49
|
+
return { browser, engine };
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
*
|
|
53
|
+
* @description
|
|
54
|
+
*
|
|
55
|
+
* Some of the data are available only when additional headers for client-hints were sent from server:
|
|
56
|
+
* - full browser version (only major version is available by default)
|
|
57
|
+
* - OS version
|
|
58
|
+
* - CPU architecture
|
|
59
|
+
* - device model
|
|
60
|
+
*
|
|
61
|
+
* To able to use data you should first provide header `Accept-CH` with the list of headers that client should send.
|
|
62
|
+
*
|
|
63
|
+
* @param headers
|
|
64
|
+
* @returns
|
|
65
|
+
*/
|
|
66
|
+
const parseClientHintsHeaders = (headers) => {
|
|
67
|
+
const { browser, engine } = parseBrowser(headers['sec-ch-ua-full-version-list'] || headers['sec-ch-ua']);
|
|
68
|
+
const osName = parseQuotedString(headers['sec-ch-ua-platform']);
|
|
69
|
+
const mobileOS = utils.getMobileOs(osName);
|
|
70
|
+
return {
|
|
71
|
+
browser,
|
|
72
|
+
engine,
|
|
73
|
+
os: {
|
|
74
|
+
name: osName,
|
|
75
|
+
version: parseQuotedString(headers['sec-ch-ua-platform-version']),
|
|
76
|
+
},
|
|
77
|
+
cpu: {
|
|
78
|
+
architecture: parseQuotedString(headers['sec-ch-ua-arch']),
|
|
79
|
+
},
|
|
80
|
+
mobileOS,
|
|
81
|
+
device: {
|
|
82
|
+
model: parseQuotedString(headers['sec-ch-ua-model']),
|
|
83
|
+
type: headers['sec-ch-ua-mobile'] === '?1' ? 'mobile' : 'desktop',
|
|
84
|
+
vendor: undefined,
|
|
85
|
+
},
|
|
86
|
+
// basically all of the browsers with client-hints support
|
|
87
|
+
// also compatible with SameSite=None
|
|
88
|
+
sameSiteNoneCompatible: true,
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
exports.parseClientHintsHeaders = parseClientHintsHeaders;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const BROWSERS_LIST_MAP = {
|
|
2
|
+
chrome: {
|
|
3
|
+
type: 'desktop',
|
|
4
|
+
name: 'chrome',
|
|
5
|
+
},
|
|
6
|
+
safari: {
|
|
7
|
+
type: 'desktop',
|
|
8
|
+
name: 'safari',
|
|
9
|
+
},
|
|
10
|
+
firefox: {
|
|
11
|
+
type: 'desktop',
|
|
12
|
+
name: 'firefox',
|
|
13
|
+
},
|
|
14
|
+
opera: {
|
|
15
|
+
type: 'desktop',
|
|
16
|
+
name: 'opera',
|
|
17
|
+
},
|
|
18
|
+
ie: {
|
|
19
|
+
type: 'desktop',
|
|
20
|
+
name: 'ie',
|
|
21
|
+
},
|
|
22
|
+
edge: {
|
|
23
|
+
type: 'any',
|
|
24
|
+
name: 'edge',
|
|
25
|
+
},
|
|
26
|
+
and_chr: {
|
|
27
|
+
type: 'mobile',
|
|
28
|
+
name: 'chrome',
|
|
29
|
+
},
|
|
30
|
+
ios_saf: {
|
|
31
|
+
type: 'mobile',
|
|
32
|
+
name: 'mobile safari',
|
|
33
|
+
},
|
|
34
|
+
android: {
|
|
35
|
+
type: 'mobile',
|
|
36
|
+
name: 'android browser',
|
|
37
|
+
},
|
|
38
|
+
op_mob: {
|
|
39
|
+
type: 'mobile',
|
|
40
|
+
name: 'opera',
|
|
41
|
+
},
|
|
42
|
+
and_uc: {
|
|
43
|
+
type: 'mobile',
|
|
44
|
+
name: 'ucbrowser',
|
|
45
|
+
},
|
|
46
|
+
and_ff: {
|
|
47
|
+
type: 'mobile',
|
|
48
|
+
name: 'firefox',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
const CHROMIUM_BASED_BROWSERS = [
|
|
52
|
+
'android browser',
|
|
53
|
+
'yandex',
|
|
54
|
+
'vivaldi' /* , 'chrome webview', 'opera', 'samsung' */,
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
export { BROWSERS_LIST_MAP, CHROMIUM_BASED_BROWSERS };
|
package/lib/constants.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
const BROWSERS_LIST_MAP = {
|
|
6
|
+
chrome: {
|
|
7
|
+
type: 'desktop',
|
|
8
|
+
name: 'chrome',
|
|
9
|
+
},
|
|
10
|
+
safari: {
|
|
11
|
+
type: 'desktop',
|
|
12
|
+
name: 'safari',
|
|
13
|
+
},
|
|
14
|
+
firefox: {
|
|
15
|
+
type: 'desktop',
|
|
16
|
+
name: 'firefox',
|
|
17
|
+
},
|
|
18
|
+
opera: {
|
|
19
|
+
type: 'desktop',
|
|
20
|
+
name: 'opera',
|
|
21
|
+
},
|
|
22
|
+
ie: {
|
|
23
|
+
type: 'desktop',
|
|
24
|
+
name: 'ie',
|
|
25
|
+
},
|
|
26
|
+
edge: {
|
|
27
|
+
type: 'any',
|
|
28
|
+
name: 'edge',
|
|
29
|
+
},
|
|
30
|
+
and_chr: {
|
|
31
|
+
type: 'mobile',
|
|
32
|
+
name: 'chrome',
|
|
33
|
+
},
|
|
34
|
+
ios_saf: {
|
|
35
|
+
type: 'mobile',
|
|
36
|
+
name: 'mobile safari',
|
|
37
|
+
},
|
|
38
|
+
android: {
|
|
39
|
+
type: 'mobile',
|
|
40
|
+
name: 'android browser',
|
|
41
|
+
},
|
|
42
|
+
op_mob: {
|
|
43
|
+
type: 'mobile',
|
|
44
|
+
name: 'opera',
|
|
45
|
+
},
|
|
46
|
+
and_uc: {
|
|
47
|
+
type: 'mobile',
|
|
48
|
+
name: 'ucbrowser',
|
|
49
|
+
},
|
|
50
|
+
and_ff: {
|
|
51
|
+
type: 'mobile',
|
|
52
|
+
name: 'firefox',
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const CHROMIUM_BASED_BROWSERS = [
|
|
56
|
+
'android browser',
|
|
57
|
+
'yandex',
|
|
58
|
+
'vivaldi' /* , 'chrome webview', 'opera', 'samsung' */,
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
exports.BROWSERS_LIST_MAP = BROWSERS_LIST_MAP;
|
|
62
|
+
exports.CHROMIUM_BASED_BROWSERS = CHROMIUM_BASED_BROWSERS;
|
package/lib/index.es.js
CHANGED
|
@@ -1,333 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { UAParser } from 'ua-parser-js';
|
|
5
|
-
import isString from '@tinkoff/utils/is/string';
|
|
6
|
-
import browserslist from 'browserslist';
|
|
7
|
-
import browserslistTinkoffConfig from '@tinkoff/browserslist-config';
|
|
8
|
-
import browserslistFileConfig from '@tramvai/cli/lib/external/browserslist-normalized-file-config';
|
|
9
|
-
|
|
10
|
-
const processUserAgentAttributeName = (attributeName = '') => attributeName.toLowerCase();
|
|
11
|
-
const splitUserAgentAttributeVersion = (attributeVersion = '-1.-1.-1') => attributeVersion.split('.').map(Number);
|
|
12
|
-
// https://www.chromium.org/updates/same-site/incompatible-clients
|
|
13
|
-
const isSameSiteNoneCompatible = (userAgent) => {
|
|
14
|
-
// На случай неполных данных из ua-parser-js
|
|
15
|
-
try {
|
|
16
|
-
const browserName = processUserAgentAttributeName(userAgent.browser.name);
|
|
17
|
-
const [browserMajor, browserMinor, browserBuild] = splitUserAgentAttributeVersion(userAgent.browser.version);
|
|
18
|
-
const osName = processUserAgentAttributeName(userAgent.os.name);
|
|
19
|
-
const [osMajor, osMinor] = splitUserAgentAttributeVersion(userAgent.os.version);
|
|
20
|
-
const engineName = processUserAgentAttributeName(userAgent.engine.name);
|
|
21
|
-
const [engineMajor] = splitUserAgentAttributeVersion(userAgent.engine.version);
|
|
22
|
-
if (osName === 'ios') {
|
|
23
|
-
// На iOS 12 все браузеры не совместимы с samesite=none
|
|
24
|
-
// При этом полагается, что если на iOS!=12 - любые браузеры совместимы с samesite=none, включая UCBrowser<12.13.2 и 51<=Chrome<=66 (WebKit)
|
|
25
|
-
return osMajor !== 12;
|
|
26
|
-
}
|
|
27
|
-
// Встроенный браузер Mac OS парсится как Webkit
|
|
28
|
-
if (browserName === 'safari' || browserName === 'webkit') {
|
|
29
|
-
return !(osName === 'mac os' && osMajor === 10 && osMinor === 14);
|
|
30
|
-
}
|
|
31
|
-
if (browserName === 'ucbrowser') {
|
|
32
|
-
const [major, minor, build] = [12, 13, 2];
|
|
33
|
-
if (browserMajor !== major) {
|
|
34
|
-
return browserMajor > major;
|
|
35
|
-
}
|
|
36
|
-
if (browserMinor !== minor) {
|
|
37
|
-
return browserMinor > minor;
|
|
38
|
-
}
|
|
39
|
-
return browserBuild >= build;
|
|
40
|
-
}
|
|
41
|
-
if (browserName === 'chrome' || browserName === 'chromium') {
|
|
42
|
-
return browserMajor < 51 || browserMajor > 66;
|
|
43
|
-
}
|
|
44
|
-
if (engineName === 'blink') {
|
|
45
|
-
return engineMajor < 51 || engineMajor > 66;
|
|
46
|
-
}
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const getMobileOs = (osName) => {
|
|
55
|
-
switch (osName) {
|
|
56
|
-
case 'Windows Phone':
|
|
57
|
-
return 'winphone';
|
|
58
|
-
case 'Android':
|
|
59
|
-
return 'android';
|
|
60
|
-
case 'iOS':
|
|
61
|
-
return 'ios';
|
|
62
|
-
case 'BlackBerry':
|
|
63
|
-
case 'RIM Tablet OS':
|
|
64
|
-
return 'blackberry';
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
const getBrowserEngine = (browserName, engineName) => {
|
|
68
|
-
switch (true) {
|
|
69
|
-
case browserName === 'firefox':
|
|
70
|
-
return 'firefox';
|
|
71
|
-
case browserName === 'safari':
|
|
72
|
-
return 'safari';
|
|
73
|
-
case engineName === 'webkit' || engineName === 'blink' || engineName === 'chromium':
|
|
74
|
-
return 'chrome';
|
|
75
|
-
}
|
|
76
|
-
return 'other';
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const toLowerName = compose(toLower, propOr('name', ''));
|
|
80
|
-
const uaParserExtensions = [
|
|
81
|
-
// добавляем отдельные регекспы для ботов гугла и т.п.
|
|
82
|
-
// это позволит для них получить отдельное имя браузера и обработать специальным образом
|
|
83
|
-
// https://github.com/faisalman/ua-parser-js/issues/227
|
|
84
|
-
// google, bing, msn
|
|
85
|
-
[/((?:\S+)bot(?:-[imagevdo]{5})?)\/([\w.]+)/i],
|
|
86
|
-
[UAParser.BROWSER.NAME, UAParser.BROWSER.VERSION, ['type', 'bot']],
|
|
87
|
-
// google adsbot под видом обычного браузера
|
|
88
|
-
[/[\s;(](adsbot[-\w]*?[\s;)])/i],
|
|
89
|
-
[UAParser.BROWSER.NAME, [UAParser.BROWSER.VERSION, 'unknown'], ['type', 'bot']],
|
|
90
|
-
// добавляем регекспы для браузеров которые пытаются казаться другими браузерами
|
|
91
|
-
// например ua-parser-js Firefox Focus для ios считает как просто Firefox, что ломает проверки на версии
|
|
92
|
-
// Firefox for iOS
|
|
93
|
-
[/fxios\/([\w\\.-]+)/i],
|
|
94
|
-
[[UAParser.BROWSER.NAME, 'Firefox Focus'], UAParser.BROWSER.VERSION],
|
|
95
|
-
];
|
|
96
|
-
const parseUserAgentHeader = (userAgent) => {
|
|
97
|
-
const uaParser = new UAParser('', { browser: uaParserExtensions });
|
|
98
|
-
const { ua, ...result } = uaParser.setUA(userAgent).getResult();
|
|
99
|
-
const { browser, os, engine } = result;
|
|
100
|
-
const browserName = toLowerName(browser);
|
|
101
|
-
const engineName = toLowerName(engine);
|
|
102
|
-
const sameSiteNoneCompatible = isSameSiteNoneCompatible(result);
|
|
103
|
-
const mobileOS = getMobileOs(os.name);
|
|
104
|
-
if (browserName === 'opera mobi') {
|
|
105
|
-
result.device.type = 'mobile';
|
|
106
|
-
}
|
|
107
|
-
const browserEngine = getBrowserEngine(browserName, engineName);
|
|
108
|
-
return {
|
|
109
|
-
...result,
|
|
110
|
-
mobileOS,
|
|
111
|
-
sameSiteNoneCompatible,
|
|
112
|
-
browser: {
|
|
113
|
-
...browser,
|
|
114
|
-
browserEngine,
|
|
115
|
-
name: browserName,
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const BROWSERS_LIST_MAP = {
|
|
121
|
-
chrome: {
|
|
122
|
-
type: 'desktop',
|
|
123
|
-
name: 'chrome',
|
|
124
|
-
},
|
|
125
|
-
safari: {
|
|
126
|
-
type: 'desktop',
|
|
127
|
-
name: 'safari',
|
|
128
|
-
},
|
|
129
|
-
firefox: {
|
|
130
|
-
type: 'desktop',
|
|
131
|
-
name: 'firefox',
|
|
132
|
-
},
|
|
133
|
-
opera: {
|
|
134
|
-
type: 'desktop',
|
|
135
|
-
name: 'opera',
|
|
136
|
-
},
|
|
137
|
-
ie: {
|
|
138
|
-
type: 'desktop',
|
|
139
|
-
name: 'ie',
|
|
140
|
-
},
|
|
141
|
-
edge: {
|
|
142
|
-
type: 'any',
|
|
143
|
-
name: 'edge',
|
|
144
|
-
},
|
|
145
|
-
and_chr: {
|
|
146
|
-
type: 'mobile',
|
|
147
|
-
name: 'chrome',
|
|
148
|
-
},
|
|
149
|
-
ios_saf: {
|
|
150
|
-
type: 'mobile',
|
|
151
|
-
name: 'mobile safari',
|
|
152
|
-
},
|
|
153
|
-
android: {
|
|
154
|
-
type: 'mobile',
|
|
155
|
-
name: 'android browser',
|
|
156
|
-
},
|
|
157
|
-
op_mob: {
|
|
158
|
-
type: 'mobile',
|
|
159
|
-
name: 'opera',
|
|
160
|
-
},
|
|
161
|
-
and_uc: {
|
|
162
|
-
type: 'mobile',
|
|
163
|
-
name: 'ucbrowser',
|
|
164
|
-
},
|
|
165
|
-
and_ff: {
|
|
166
|
-
type: 'mobile',
|
|
167
|
-
name: 'firefox',
|
|
168
|
-
},
|
|
169
|
-
};
|
|
170
|
-
const CHROMIUM_BASED_BROWSERS = [
|
|
171
|
-
'android browser',
|
|
172
|
-
'yandex',
|
|
173
|
-
'vivaldi' /* , 'chrome webview', 'opera', 'samsung' */,
|
|
174
|
-
];
|
|
175
|
-
|
|
176
|
-
const deviceTypes = {
|
|
177
|
-
mobile: 'mobile',
|
|
178
|
-
tablet: 'mobile',
|
|
179
|
-
desktop: 'desktop',
|
|
180
|
-
};
|
|
181
|
-
const normalizedBrowserslist = (query) => {
|
|
182
|
-
const resolved = browserslist(query, {
|
|
183
|
-
// ставим в true, чтобы browserslist возвращал полный список для мобильных браузеров
|
|
184
|
-
// т.к. по умолчанию, из-за того что `Can I use` хранит только последнюю версию мобильных браузеров
|
|
185
|
-
// то и browserslist возвращал только самую последнюю версию для мобилок, а так он будет маппить
|
|
186
|
-
// соответствие к десктопной версии
|
|
187
|
-
mobileToDesktop: true,
|
|
188
|
-
})
|
|
189
|
-
// так как ie убрали из конфига @tinkoff/browserslist-config, то для того чтобы отсечь ie
|
|
190
|
-
// добавляем фиктивную версию, чтобы вся функция вернула false для любой версии ie
|
|
191
|
-
// причем если обычная версия ie была задана в кастомном конфиге или в конфиге переданном как аргумент
|
|
192
|
-
// эта фиктивная версия ни на что не повлияет и будет использована кастомная конфигурация
|
|
193
|
-
.concat(['ie 999']);
|
|
194
|
-
const result = {};
|
|
195
|
-
for (let i = 0; i < resolved.length; i++) {
|
|
196
|
-
const [name, version] = resolved[i].split(' ');
|
|
197
|
-
if (BROWSERS_LIST_MAP[name]) {
|
|
198
|
-
const { type: mapType, name: mapName } = BROWSERS_LIST_MAP[name];
|
|
199
|
-
const mapVersion = parseFloat(version);
|
|
200
|
-
if (result[mapName]) {
|
|
201
|
-
if (!result[mapName][mapType] || result[mapName][mapType] > mapVersion) {
|
|
202
|
-
result[mapName][mapType] = mapVersion;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
result[mapName] = {
|
|
207
|
-
[mapType]: mapVersion,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return result;
|
|
213
|
-
};
|
|
214
|
-
const satisfies = (userAgent, browserslistConfig, { env = 'defaults' } = {}) => {
|
|
215
|
-
var _a, _b;
|
|
216
|
-
const ua = isString(userAgent) ? parseUserAgentHeader(userAgent) : userAgent;
|
|
217
|
-
const { engine: { name: engineName = '', version: engineVersion }, device: { type = '' } = {}, } = ua;
|
|
218
|
-
let { browser: { name: browserName = '', version: browserVersion = '' } = {} } = ua;
|
|
219
|
-
// Chromium based браузеры (yandex, samsung, opera, vivaldi, edge) указывают версию движка как `Chrome/*`.
|
|
220
|
-
// А версия движка (blink) матчится в версию chromium один к одному
|
|
221
|
-
// https://github.com/faisalman/ua-parser-js/pull/390
|
|
222
|
-
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent#Rendering_engine
|
|
223
|
-
if (engineName === 'chromium' ||
|
|
224
|
-
(engineName.toLowerCase() === 'blink' && CHROMIUM_BASED_BROWSERS.indexOf(browserName) !== -1)) {
|
|
225
|
-
browserName = 'chrome';
|
|
226
|
-
browserVersion = engineVersion || '';
|
|
227
|
-
}
|
|
228
|
-
// parseFloat - заберет мажорную + минорную версии, остальное отбросит
|
|
229
|
-
const checkVersion = parseFloat(browserVersion);
|
|
230
|
-
const deviceType = deviceTypes[type] || type || deviceTypes.desktop; // по умолчанию считаем устройство десктопом
|
|
231
|
-
if (!browserName) {
|
|
232
|
-
return null;
|
|
233
|
-
}
|
|
234
|
-
const targets = (_a = browserslistConfig !== null && browserslistConfig !== void 0 ? browserslistConfig : browserslistFileConfig[env]) !== null && _a !== void 0 ? _a : browserslistTinkoffConfig[env];
|
|
235
|
-
const browsers = normalizedBrowserslist(targets);
|
|
236
|
-
let hasEntry = false;
|
|
237
|
-
if (browserName in browsers) {
|
|
238
|
-
const browserInfo = browsers[browserName];
|
|
239
|
-
const browserInfoVersion = (_b = browserInfo.any) !== null && _b !== void 0 ? _b : browserInfo[deviceType];
|
|
240
|
-
hasEntry = !!browserInfoVersion;
|
|
241
|
-
if (checkVersion >= browserInfoVersion) {
|
|
242
|
-
return true;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
return hasEntry ? false : null; // null означает что не нашли соответствия браузеру в списке browserslist
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
const KNOWN_VENDORS = new Set(['Opera', 'Google Chrome', 'Microsoft Edge', 'Firefox', 'Safari']);
|
|
249
|
-
const KNOWN_ENGINES = new Set(['Chromium']);
|
|
250
|
-
const parseQuotedString = (str) => {
|
|
251
|
-
var _a;
|
|
252
|
-
if (!str) {
|
|
253
|
-
return str;
|
|
254
|
-
}
|
|
255
|
-
try {
|
|
256
|
-
return (_a = JSON.parse(str)) === null || _a === void 0 ? void 0 : _a.trim();
|
|
257
|
-
}
|
|
258
|
-
catch (err) {
|
|
259
|
-
return str;
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
const parseBrowser = (brandsList) => {
|
|
263
|
-
const browser = {
|
|
264
|
-
name: undefined,
|
|
265
|
-
version: undefined,
|
|
266
|
-
major: undefined,
|
|
267
|
-
browserEngine: '',
|
|
268
|
-
};
|
|
269
|
-
const engine = {
|
|
270
|
-
name: undefined,
|
|
271
|
-
version: undefined,
|
|
272
|
-
};
|
|
273
|
-
brandsList.split(',').forEach((entry) => {
|
|
274
|
-
const [name, version] = entry.split(/;\s*v=/).map(parseQuotedString);
|
|
275
|
-
if (name && KNOWN_VENDORS.has(name)) {
|
|
276
|
-
browser.name = name.toLowerCase();
|
|
277
|
-
browser.version = version;
|
|
278
|
-
browser.major = version;
|
|
279
|
-
}
|
|
280
|
-
if (name && KNOWN_ENGINES.has(name)) {
|
|
281
|
-
engine.name = name.toLowerCase();
|
|
282
|
-
engine.version = version;
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
if (!browser.name && engine.name) {
|
|
286
|
-
browser.name = engine.name;
|
|
287
|
-
browser.version = engine.version;
|
|
288
|
-
}
|
|
289
|
-
browser.browserEngine = getBrowserEngine(browser.name, engine.name);
|
|
290
|
-
return { browser, engine };
|
|
291
|
-
};
|
|
292
|
-
/**
|
|
293
|
-
*
|
|
294
|
-
* @description
|
|
295
|
-
*
|
|
296
|
-
* Some of the data are available only when additional headers for client-hints were sent from server:
|
|
297
|
-
* - full browser version (only major version is available by default)
|
|
298
|
-
* - OS version
|
|
299
|
-
* - CPU architecture
|
|
300
|
-
* - device model
|
|
301
|
-
*
|
|
302
|
-
* To able to use data you should first provide header `Accept-CH` with the list of headers that client should send.
|
|
303
|
-
*
|
|
304
|
-
* @param headers
|
|
305
|
-
* @returns
|
|
306
|
-
*/
|
|
307
|
-
const parseClientHintsHeaders = (headers) => {
|
|
308
|
-
const { browser, engine } = parseBrowser(headers['sec-ch-ua-full-version-list'] || headers['sec-ch-ua']);
|
|
309
|
-
const osName = parseQuotedString(headers['sec-ch-ua-platform']);
|
|
310
|
-
const mobileOS = getMobileOs(osName);
|
|
311
|
-
return {
|
|
312
|
-
browser,
|
|
313
|
-
engine,
|
|
314
|
-
os: {
|
|
315
|
-
name: osName,
|
|
316
|
-
version: parseQuotedString(headers['sec-ch-ua-platform-version']),
|
|
317
|
-
},
|
|
318
|
-
cpu: {
|
|
319
|
-
architecture: parseQuotedString(headers['sec-ch-ua-arch']),
|
|
320
|
-
},
|
|
321
|
-
mobileOS,
|
|
322
|
-
device: {
|
|
323
|
-
model: parseQuotedString(headers['sec-ch-ua-model']),
|
|
324
|
-
type: headers['sec-ch-ua-mobile'] === '?1' ? 'mobile' : 'desktop',
|
|
325
|
-
vendor: undefined,
|
|
326
|
-
},
|
|
327
|
-
// basically all of the browsers with client-hints support
|
|
328
|
-
// also compatible with SameSite=None
|
|
329
|
-
sameSiteNoneCompatible: true,
|
|
330
|
-
};
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
export { parseUserAgentHeader as parse, parseClientHintsHeaders as parseClientHints, parseUserAgentHeader, satisfies };
|
|
1
|
+
export { parseUserAgentHeader as parse, parseUserAgentHeader } from './userAgent.es.js';
|
|
2
|
+
export { satisfies } from './satisfies.es.js';
|
|
3
|
+
export { parseClientHintsHeaders as parseClientHints } from './client-hints.es.js';
|