@tinkoff/user-agent 0.4.76 → 0.4.78

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/README.md CHANGED
@@ -1,3 +1,5 @@
1
1
  # User agent
2
2
 
3
- Library for parsing and executing check to userAgent string. Based on [ua-parser-js](https://github.com/faisalman/ua-parser-js)
3
+ Library for parsing and executing checks by:
4
+ - userAgent string. Based on [ua-parser-js](https://github.com/faisalman/ua-parser-js)
5
+ - [Client Hints](https://developer.mozilla.org/en-US/docs/Web/HTTP/Client_hints) headers
@@ -0,0 +1,17 @@
1
+ import type { UserAgent } from './types';
2
+ /**
3
+ *
4
+ * @description
5
+ *
6
+ * Some of the data are available only when additional headers for client-hints were sent from server:
7
+ * - full browser version (only major version is available by default)
8
+ * - OS version
9
+ * - CPU architecture
10
+ * - device model
11
+ *
12
+ * To able to use data you should first provide header `Accept-CH` with the list of headers that client should send.
13
+ *
14
+ * @param headers
15
+ * @returns
16
+ */
17
+ export declare const parseClientHintsHeaders: (headers: Record<string, string | string[]>) => UserAgent;
package/lib/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
- export { parse } from './userAgent';
1
+ export { parseUserAgentHeader } from './userAgent';
2
+ export { parseUserAgentHeader as parse } from './userAgent';
2
3
  export { satisfies } from './satisfies';
4
+ export { parseClientHintsHeaders as parseClientHints } from './client-hints';
3
5
  export * from './types';
package/lib/index.es.js CHANGED
@@ -51,6 +51,31 @@ const isSameSiteNoneCompatible = (userAgent) => {
51
51
  }
52
52
  };
53
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
+
54
79
  const toLowerName = compose(toLower, propOr('name', ''));
55
80
  const uaParserExtensions = [
56
81
  // добавляем отдельные регекспы для ботов гугла и т.п.
@@ -68,46 +93,18 @@ const uaParserExtensions = [
68
93
  [/fxios\/([\w\\.-]+)/i],
69
94
  [[UAParser.BROWSER.NAME, 'Firefox Focus'], UAParser.BROWSER.VERSION],
70
95
  ];
71
- const parse = (userAgent) => {
96
+ const parseUserAgentHeader = (userAgent) => {
72
97
  const uaParser = new UAParser('', { browser: uaParserExtensions });
73
98
  const { ua, ...result } = uaParser.setUA(userAgent).getResult();
74
99
  const { browser, os, engine } = result;
75
100
  const browserName = toLowerName(browser);
76
101
  const engineName = toLowerName(engine);
77
102
  const sameSiteNoneCompatible = isSameSiteNoneCompatible(result);
78
- let mobileOS;
103
+ const mobileOS = getMobileOs(os.name);
79
104
  if (browserName === 'opera mobi') {
80
105
  result.device.type = 'mobile';
81
106
  }
82
- switch (os.name) {
83
- case 'Windows Phone':
84
- mobileOS = 'winphone';
85
- break;
86
- case 'Android':
87
- mobileOS = 'android';
88
- break;
89
- case 'iOS':
90
- mobileOS = 'ios';
91
- break;
92
- case 'BlackBerry':
93
- case 'RIM Tablet OS':
94
- mobileOS = 'blackberry';
95
- break;
96
- }
97
- let browserEngine;
98
- switch (true) {
99
- case browserName === 'firefox':
100
- browserEngine = 'firefox';
101
- break;
102
- case browserName === 'safari':
103
- browserEngine = 'safari';
104
- break;
105
- case engineName === 'webkit' || engineName === 'blink':
106
- browserEngine = 'chrome';
107
- break;
108
- default:
109
- browserEngine = 'other';
110
- }
107
+ const browserEngine = getBrowserEngine(browserName, engineName);
111
108
  return {
112
109
  ...result,
113
110
  mobileOS,
@@ -216,14 +213,15 @@ const normalizedBrowserslist = (query) => {
216
213
  };
217
214
  const satisfies = (userAgent, browserslistConfig, { env = 'defaults' } = {}) => {
218
215
  var _a, _b;
219
- const ua = isString(userAgent) ? parse(userAgent) : userAgent;
216
+ const ua = isString(userAgent) ? parseUserAgentHeader(userAgent) : userAgent;
220
217
  const { engine: { name: engineName = '', version: engineVersion }, device: { type = '' } = {}, } = ua;
221
218
  let { browser: { name: browserName = '', version: browserVersion = '' } = {} } = ua;
222
219
  // Chromium based браузеры (yandex, samsung, opera, vivaldi, edge) указывают версию движка как `Chrome/*`.
223
220
  // А версия движка (blink) матчится в версию chromium один к одному
224
221
  // https://github.com/faisalman/ua-parser-js/pull/390
225
222
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent#Rendering_engine
226
- if (engineName.toLowerCase() === 'blink' && CHROMIUM_BASED_BROWSERS.indexOf(browserName) !== -1) {
223
+ if (engineName === 'chromium' ||
224
+ (engineName.toLowerCase() === 'blink' && CHROMIUM_BASED_BROWSERS.indexOf(browserName) !== -1)) {
227
225
  browserName = 'chrome';
228
226
  browserVersion = engineVersion || '';
229
227
  }
@@ -247,4 +245,89 @@ const satisfies = (userAgent, browserslistConfig, { env = 'defaults' } = {}) =>
247
245
  return hasEntry ? false : null; // null означает что не нашли соответствия браузеру в списке browserslist
248
246
  };
249
247
 
250
- export { parse, satisfies };
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 };
package/lib/index.js CHANGED
@@ -65,6 +65,31 @@ const isSameSiteNoneCompatible = (userAgent) => {
65
65
  }
66
66
  };
67
67
 
68
+ const getMobileOs = (osName) => {
69
+ switch (osName) {
70
+ case 'Windows Phone':
71
+ return 'winphone';
72
+ case 'Android':
73
+ return 'android';
74
+ case 'iOS':
75
+ return 'ios';
76
+ case 'BlackBerry':
77
+ case 'RIM Tablet OS':
78
+ return 'blackberry';
79
+ }
80
+ };
81
+ const getBrowserEngine = (browserName, engineName) => {
82
+ switch (true) {
83
+ case browserName === 'firefox':
84
+ return 'firefox';
85
+ case browserName === 'safari':
86
+ return 'safari';
87
+ case engineName === 'webkit' || engineName === 'blink' || engineName === 'chromium':
88
+ return 'chrome';
89
+ }
90
+ return 'other';
91
+ };
92
+
68
93
  const toLowerName = compose__default["default"](toLower__default["default"], propOr__default["default"]('name', ''));
69
94
  const uaParserExtensions = [
70
95
  // добавляем отдельные регекспы для ботов гугла и т.п.
@@ -82,46 +107,18 @@ const uaParserExtensions = [
82
107
  [/fxios\/([\w\\.-]+)/i],
83
108
  [[uaParserJs.UAParser.BROWSER.NAME, 'Firefox Focus'], uaParserJs.UAParser.BROWSER.VERSION],
84
109
  ];
85
- const parse = (userAgent) => {
110
+ const parseUserAgentHeader = (userAgent) => {
86
111
  const uaParser = new uaParserJs.UAParser('', { browser: uaParserExtensions });
87
112
  const { ua, ...result } = uaParser.setUA(userAgent).getResult();
88
113
  const { browser, os, engine } = result;
89
114
  const browserName = toLowerName(browser);
90
115
  const engineName = toLowerName(engine);
91
116
  const sameSiteNoneCompatible = isSameSiteNoneCompatible(result);
92
- let mobileOS;
117
+ const mobileOS = getMobileOs(os.name);
93
118
  if (browserName === 'opera mobi') {
94
119
  result.device.type = 'mobile';
95
120
  }
96
- switch (os.name) {
97
- case 'Windows Phone':
98
- mobileOS = 'winphone';
99
- break;
100
- case 'Android':
101
- mobileOS = 'android';
102
- break;
103
- case 'iOS':
104
- mobileOS = 'ios';
105
- break;
106
- case 'BlackBerry':
107
- case 'RIM Tablet OS':
108
- mobileOS = 'blackberry';
109
- break;
110
- }
111
- let browserEngine;
112
- switch (true) {
113
- case browserName === 'firefox':
114
- browserEngine = 'firefox';
115
- break;
116
- case browserName === 'safari':
117
- browserEngine = 'safari';
118
- break;
119
- case engineName === 'webkit' || engineName === 'blink':
120
- browserEngine = 'chrome';
121
- break;
122
- default:
123
- browserEngine = 'other';
124
- }
121
+ const browserEngine = getBrowserEngine(browserName, engineName);
125
122
  return {
126
123
  ...result,
127
124
  mobileOS,
@@ -230,14 +227,15 @@ const normalizedBrowserslist = (query) => {
230
227
  };
231
228
  const satisfies = (userAgent, browserslistConfig, { env = 'defaults' } = {}) => {
232
229
  var _a, _b;
233
- const ua = isString__default["default"](userAgent) ? parse(userAgent) : userAgent;
230
+ const ua = isString__default["default"](userAgent) ? parseUserAgentHeader(userAgent) : userAgent;
234
231
  const { engine: { name: engineName = '', version: engineVersion }, device: { type = '' } = {}, } = ua;
235
232
  let { browser: { name: browserName = '', version: browserVersion = '' } = {} } = ua;
236
233
  // Chromium based браузеры (yandex, samsung, opera, vivaldi, edge) указывают версию движка как `Chrome/*`.
237
234
  // А версия движка (blink) матчится в версию chromium один к одному
238
235
  // https://github.com/faisalman/ua-parser-js/pull/390
239
236
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent#Rendering_engine
240
- if (engineName.toLowerCase() === 'blink' && CHROMIUM_BASED_BROWSERS.indexOf(browserName) !== -1) {
237
+ if (engineName === 'chromium' ||
238
+ (engineName.toLowerCase() === 'blink' && CHROMIUM_BASED_BROWSERS.indexOf(browserName) !== -1)) {
241
239
  browserName = 'chrome';
242
240
  browserVersion = engineVersion || '';
243
241
  }
@@ -261,5 +259,92 @@ const satisfies = (userAgent, browserslistConfig, { env = 'defaults' } = {}) =>
261
259
  return hasEntry ? false : null; // null означает что не нашли соответствия браузеру в списке browserslist
262
260
  };
263
261
 
264
- exports.parse = parse;
262
+ const KNOWN_VENDORS = new Set(['Opera', 'Google Chrome', 'Microsoft Edge', 'Firefox', 'Safari']);
263
+ const KNOWN_ENGINES = new Set(['Chromium']);
264
+ const parseQuotedString = (str) => {
265
+ var _a;
266
+ if (!str) {
267
+ return str;
268
+ }
269
+ try {
270
+ return (_a = JSON.parse(str)) === null || _a === void 0 ? void 0 : _a.trim();
271
+ }
272
+ catch (err) {
273
+ return str;
274
+ }
275
+ };
276
+ const parseBrowser = (brandsList) => {
277
+ const browser = {
278
+ name: undefined,
279
+ version: undefined,
280
+ major: undefined,
281
+ browserEngine: '',
282
+ };
283
+ const engine = {
284
+ name: undefined,
285
+ version: undefined,
286
+ };
287
+ brandsList.split(',').forEach((entry) => {
288
+ const [name, version] = entry.split(/;\s*v=/).map(parseQuotedString);
289
+ if (name && KNOWN_VENDORS.has(name)) {
290
+ browser.name = name.toLowerCase();
291
+ browser.version = version;
292
+ browser.major = version;
293
+ }
294
+ if (name && KNOWN_ENGINES.has(name)) {
295
+ engine.name = name.toLowerCase();
296
+ engine.version = version;
297
+ }
298
+ });
299
+ if (!browser.name && engine.name) {
300
+ browser.name = engine.name;
301
+ browser.version = engine.version;
302
+ }
303
+ browser.browserEngine = getBrowserEngine(browser.name, engine.name);
304
+ return { browser, engine };
305
+ };
306
+ /**
307
+ *
308
+ * @description
309
+ *
310
+ * Some of the data are available only when additional headers for client-hints were sent from server:
311
+ * - full browser version (only major version is available by default)
312
+ * - OS version
313
+ * - CPU architecture
314
+ * - device model
315
+ *
316
+ * To able to use data you should first provide header `Accept-CH` with the list of headers that client should send.
317
+ *
318
+ * @param headers
319
+ * @returns
320
+ */
321
+ const parseClientHintsHeaders = (headers) => {
322
+ const { browser, engine } = parseBrowser(headers['sec-ch-ua-full-version-list'] || headers['sec-ch-ua']);
323
+ const osName = parseQuotedString(headers['sec-ch-ua-platform']);
324
+ const mobileOS = getMobileOs(osName);
325
+ return {
326
+ browser,
327
+ engine,
328
+ os: {
329
+ name: osName,
330
+ version: parseQuotedString(headers['sec-ch-ua-platform-version']),
331
+ },
332
+ cpu: {
333
+ architecture: parseQuotedString(headers['sec-ch-ua-arch']),
334
+ },
335
+ mobileOS,
336
+ device: {
337
+ model: parseQuotedString(headers['sec-ch-ua-model']),
338
+ type: headers['sec-ch-ua-mobile'] === '?1' ? 'mobile' : 'desktop',
339
+ vendor: undefined,
340
+ },
341
+ // basically all of the browsers with client-hints support
342
+ // also compatible with SameSite=None
343
+ sameSiteNoneCompatible: true,
344
+ };
345
+ };
346
+
347
+ exports.parse = parseUserAgentHeader;
348
+ exports.parseClientHints = parseClientHintsHeaders;
349
+ exports.parseUserAgentHeader = parseUserAgentHeader;
265
350
  exports.satisfies = satisfies;
package/lib/types.d.ts CHANGED
@@ -1,10 +1,46 @@
1
- import type { UAParser } from 'ua-parser-js';
2
- declare type ParserResult = ReturnType<UAParser['getResult']>;
3
- export interface UserAgent extends Omit<ParserResult, 'ua'> {
4
- browser: ParserResult['browser'] & {
5
- browserEngine: string;
6
- };
1
+ export interface Browser {
2
+ name: string | undefined;
3
+ /**
4
+ * With client-hints there will be only major version
5
+ */
6
+ version: string | undefined;
7
+ /**
8
+ * @deprecated use version
9
+ */
10
+ major: string | undefined;
11
+ browserEngine: string;
12
+ }
13
+ export interface Engine {
14
+ name: string | undefined;
15
+ version: string | undefined;
16
+ }
17
+ export interface Device {
18
+ type: string | undefined;
19
+ model: string | undefined;
20
+ /**
21
+ * @deprecated This info is not provided by client-hints
22
+ */
23
+ vendor: string | undefined;
24
+ }
25
+ export interface OS {
26
+ name: string | undefined;
27
+ version: string | undefined;
28
+ }
29
+ export interface Cpu {
30
+ /**
31
+ * @deprecated This is not provided by default with client-hints
32
+ */
33
+ architecture: string | undefined;
34
+ }
35
+ export interface UserAgent {
36
+ browser: Browser;
37
+ engine: Engine;
38
+ device: Device;
39
+ os: OS;
40
+ /**
41
+ * @deprecated This is not provided by default with client-hints
42
+ */
43
+ cpu: Cpu;
7
44
  mobileOS?: string;
8
45
  sameSiteNoneCompatible: boolean;
9
46
  }
10
- export type { UAParser };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,2 +1,2 @@
1
1
  import type { UserAgent } from './types';
2
- export declare const parse: (userAgent: string) => UserAgent;
2
+ export declare const parseUserAgentHeader: (userAgent: string) => UserAgent;
package/lib/utils.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare const getMobileOs: (osName?: string) => string | undefined;
2
+ export declare const getBrowserEngine: (browserName?: string, engineName?: string) => string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinkoff/user-agent",
3
- "version": "0.4.76",
3
+ "version": "0.4.78",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib/index.es.js",
@@ -29,7 +29,7 @@
29
29
  "@types/ua-parser-js": "^0.7.33"
30
30
  },
31
31
  "peerDependencies": {
32
- "@tramvai/cli": "2.33.3",
32
+ "@tramvai/cli": "2.35.0",
33
33
  "@types/ua-parser-js": "^0.7.33"
34
34
  },
35
35
  "license": "Apache-2.0"