@tramvai/module-client-hints 2.125.4 → 2.130.6

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.
Files changed (36) hide show
  1. package/README.md +33 -8
  2. package/lib/browser/loadUserAgent.browser.js +24 -0
  3. package/lib/browser/loadUserAgent.d.ts +2 -0
  4. package/lib/browser.d.ts +5 -3
  5. package/lib/browser.js +9 -37
  6. package/lib/{child-app → modules/child-app}/module.browser.js +2 -2
  7. package/lib/{child-app → modules/child-app}/module.es.js +2 -2
  8. package/lib/{child-app → modules/child-app}/module.js +2 -2
  9. package/lib/modules/csr/browser.browser.js +30 -0
  10. package/lib/modules/csr/browser.d.ts +3 -0
  11. package/lib/modules/csr/server.d.ts +3 -0
  12. package/lib/modules/csr/server.es.js +9 -0
  13. package/lib/modules/csr/server.js +13 -0
  14. package/lib/server/parseUserAgentWithCache.d.ts +6 -0
  15. package/lib/server/parseUserAgentWithCache.es.js +14 -0
  16. package/lib/server/parseUserAgentWithCache.js +18 -0
  17. package/lib/server/userAgent.d.ts +1 -1
  18. package/lib/server/userAgent.es.js +79 -31
  19. package/lib/server/userAgent.js +83 -31
  20. package/lib/server.d.ts +5 -3
  21. package/lib/server.es.js +16 -31
  22. package/lib/server.js +16 -29
  23. package/lib/shared/providers.browser.browser.js +32 -0
  24. package/lib/shared/providers.browser.d.ts +2 -0
  25. package/lib/shared/providers.browser.js +8 -3
  26. package/lib/shared/providers.d.ts +1 -1
  27. package/lib/shared/providers.es.js +8 -3
  28. package/lib/shared/providers.js +8 -3
  29. package/lib/shared/providers.server.d.ts +2 -0
  30. package/lib/shared/providers.server.es.js +21 -0
  31. package/lib/shared/providers.server.js +25 -0
  32. package/lib/shared/stores/userAgent.browser.js +1 -3
  33. package/lib/shared/stores/userAgent.es.js +1 -3
  34. package/lib/shared/stores/userAgent.js +1 -3
  35. package/package.json +11 -7
  36. /package/lib/{child-app → modules/child-app}/module.d.ts +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Client Hints
2
2
 
3
- Modules provides various parameters from the client device, e.g. type of the device, screen size, etc.
3
+ Module provides various parameters from the client device, e.g. type of the device, screen size, etc.
4
4
 
5
5
  ## Installation
6
6
 
@@ -10,7 +10,7 @@ First, install `@tramvai/module-client-hints`
10
10
  npm i --save @tramvai/module-client-hints
11
11
  ```
12
12
 
13
- Then add `ClientHintsModule` to the modules list
13
+ Then add `ClientHintsModule` to the modules list:
14
14
 
15
15
  ```tsx
16
16
  import { createApp } from '@tramvai/core';
@@ -21,25 +21,49 @@ createApp({
21
21
  });
22
22
  ```
23
23
 
24
+ It will enable server side user agent parsing. If you are using [CSR fallback](03-features/010-rendering/05-csr.md#csr-fallback) feature, then you should use `ClientHintsCSRModule` from this package instead.
25
+
24
26
  ## Explanation
25
27
 
26
- ### User agent details parsing
28
+ ### The problem with media on server and on client
29
+
30
+ One of the SSR problem is render of the component which depends on current screen size, e.g. image carousel that should render specific number of images depending on screen width. By default, the exact screen size can be figured out only on client-side and we can't render such content on server identical to the client render. If this content is not important for the SEO we can use skeletons and spinners, but they are not suitable for every case.
31
+
32
+ Client Hints modules provides the way to solve this problem in some way. It stores data about client devices in cookies and then use these cookies on server in next page loading.
33
+
34
+ ### Server-side rendering
27
35
 
28
- Parsing is implemented with library [@tinkoff/user-agent](../libs/user-agent.md) that may use either user-agent header or client-hints headers.
36
+ Module will parse client hints/user agent only on the server by default. Parsing is implemented with library [@tinkoff/user-agent](../libs/user-agent.md) that may use either user-agent header or client-hints headers.
29
37
 
30
38
  If there is a `sec-ch-ua` header in request than user agent parsing will be based on [Client Hints](https://developer.mozilla.org/en-US/docs/Web/HTTP/Client_hints) headers. If there is no such header than old school parsing of user-agent string will be used.
31
39
 
32
40
  This logic implies next things worth to mention:
41
+
33
42
  - by default, only part of client-hints is sent by browser and you can get only partial info about user browser (no cpu spec, platform version or device model). Although, we send an additional header `accept-ch` with response from server to request this data from client - on first request from current browser there will be no such data in any case and they will appear only on subsequent requests
34
43
  - if you need to use additional info, you may specify the header [`accept-ch`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-CH) in your app with `REQUEST_MANAGER_TOKEN`
35
44
  - client-hints is mostly more performant way to parse browser info and this is way it used if it's possible
36
45
  - currently only chromium based browsers support client hints, so for other browsers and bots user-agent header will be used to gather browser info
37
46
 
38
- ### The problem with media on server and on client
47
+ ### Client-side rendering
39
48
 
40
- One of the SSR problem is render of the component which depends of current screen size, e.g. image carousel that should render specific number of images depending of screen width. By default, the exact screen size can be figured out only on client-side and we can't render such content on server identical to the client render. If this content is not important for the SEO we can use skeletons and spinners, but they are not suitable for every case.
49
+ If you want to parse user agent [on the client](03-features/010-rendering/05-csr.md#csr-fallback), then you should use `ClientHintsCSRModule`:
41
50
 
42
- Client Hints modules provides the way to solve this problem in some way. It stores data about client devices in cookies and then use this cookies on server in next page loading.
51
+ ```tsx
52
+ import { createApp } from '@tramvai/core';
53
+ import { ClientHintsCSRModule } from '@tramvai/module-client-hints';
54
+
55
+ createApp({
56
+ modules: [ClientHintsCSRModule],
57
+ // Also, there will be no conflict with ClientHintsModule, but ClientHintsCSRModule must be registered after ClientHintsModule strictly.
58
+ // modules: [ClientHintsModule, ClientHintsCSRModule],
59
+ });
60
+ ```
61
+
62
+ :::warning
63
+
64
+ Usage of `ClientHintsCSRModule` will increase bundle size for ~ 18kb raw and 8kb gzip
65
+
66
+ :::
43
67
 
44
68
  ### How does media work
45
69
 
@@ -52,6 +76,7 @@ When user enters the app for the first time, information about **real** device s
52
76
  :::
53
77
 
54
78
  This module tries to determine type of the user device using user-agent string, and separates the devices into three groups:
79
+
55
80
  - `mobile`
56
81
  - `tablet`
57
82
  - `desktop`
@@ -104,7 +129,7 @@ const state = {
104
129
 
105
130
  #### Use ClientHints in component
106
131
 
107
- If some component if depend of screen size:
132
+ If some component depends on the screen size:
108
133
 
109
134
  1. When user loads app for the first time **is not possible** to guarantee the same exact render on server and client
110
135
  2. On first app load you may show some skeleton to the user by checking `supposed: true` property
@@ -0,0 +1,24 @@
1
+ import { parse, parseClientHintsUserAgentData } from '@tinkoff/user-agent';
2
+
3
+ const getFromUserAgentData = () => {
4
+ try {
5
+ // @ts-expect-error. We are handling error here, that's why we can force TS
6
+ const { brands, mobile, platform } = window.navigator.userAgentData;
7
+ // chrome User-Agent emulation doesn't sync with `navigator.userAgentData`,
8
+ // and `navigator.userAgentData` return incorrect object for Safari User-Agents instead of `undefined`
9
+ // example of this problem with Playwright tests - https://github.com/microsoft/playwright/issues/14361
10
+ if (brands && brands.length === 0 && platform === '') {
11
+ return null;
12
+ }
13
+ return parseClientHintsUserAgentData({ brands, mobile, platform });
14
+ }
15
+ catch (error) {
16
+ return null;
17
+ }
18
+ };
19
+ function loadUserAgent() {
20
+ const fromUserAgentData = getFromUserAgentData();
21
+ return fromUserAgentData !== null && fromUserAgentData !== void 0 ? fromUserAgentData : parse(navigator.userAgent);
22
+ }
23
+
24
+ export { loadUserAgent };
@@ -0,0 +1,2 @@
1
+ import type { UserAgent } from '@tinkoff/user-agent';
2
+ export declare function loadUserAgent(): UserAgent;
package/lib/browser.d.ts CHANGED
@@ -3,6 +3,8 @@ export * from './shared/stores/mediaCheckers';
3
3
  export * from './shared/stores/mediaSelectors';
4
4
  export * from './shared/stores/media';
5
5
  export * from './shared/stores/userAgent';
6
- export { ClientHintsChildAppModule } from './child-app/module';
7
- export declare class ClientHintsModule {
8
- }
6
+ export { ClientHintsChildAppModule } from './modules/child-app/module';
7
+ export { ClientHintsCSRModule } from './modules/csr/browser';
8
+ export declare const ClientHintsModule: import("@tinkoff/dippy/lib/modules/module.h").ModuleClass & Partial<import("@tinkoff/dippy/lib/modules/module.h").ModuleSecretParameters> & {
9
+ [x: string]: (...args: any[]) => import("@tramvai/core").ModuleType<import("@tinkoff/dippy/lib/modules/module.h").ModuleClass>;
10
+ };
package/lib/browser.js CHANGED
@@ -1,44 +1,16 @@
1
- import { __decorate } from 'tslib';
2
- import { Module, provide, commandLineListTokens } from '@tramvai/core';
3
- import { STORE_TOKEN, CONTEXT_TOKEN } from '@tramvai/tokens-common';
4
- import { COOKIE_MANAGER_TOKEN } from '@tramvai/tokens-cookie';
5
- import { USER_AGENT_TOKEN } from './tokens.browser.js';
1
+ import { declareModule } from '@tramvai/core';
2
+ import { browserProviders } from './shared/providers.browser.browser.js';
6
3
  export { PARSER_CLIENT_HINTS_ENABLED, USER_AGENT_TOKEN } from './tokens.browser.js';
7
- import { matchMediaCommand } from './browser/matchMedia.browser.js';
8
- import { providers } from './shared/providers.browser.js';
9
- import { UserAgentStore } from './shared/stores/userAgent.browser.js';
10
- export { UserAgentStore, setUserAgent } from './shared/stores/userAgent.browser.js';
11
4
  export { fromClientHints, isRetina, isSupposed } from './shared/stores/mediaCheckers.browser.js';
12
5
  export { useFromClientHints, useIsRetina, useIsSupposed, useMedia } from './shared/stores/mediaSelectors.browser.js';
13
6
  export { MediaStore, setMedia } from './shared/stores/media.browser.js';
14
- export { ClientHintsChildAppModule } from './child-app/module.browser.js';
7
+ export { UserAgentStore, setUserAgent } from './shared/stores/userAgent.browser.js';
8
+ export { ClientHintsChildAppModule } from './modules/child-app/module.browser.js';
9
+ export { ClientHintsCSRModule } from './modules/csr/browser.browser.js';
15
10
 
16
- let ClientHintsModule = class ClientHintsModule {
17
- };
18
- ClientHintsModule = __decorate([
19
- Module({
20
- providers: [
21
- ...providers,
22
- provide({
23
- provide: USER_AGENT_TOKEN,
24
- useFactory: ({ store }) => {
25
- return store.getState(UserAgentStore);
26
- },
27
- deps: {
28
- store: STORE_TOKEN,
29
- },
30
- }),
31
- provide({
32
- provide: commandLineListTokens.clear,
33
- multi: true,
34
- useFactory: matchMediaCommand,
35
- deps: {
36
- context: CONTEXT_TOKEN,
37
- cookieManager: COOKIE_MANAGER_TOKEN,
38
- },
39
- }),
40
- ],
41
- })
42
- ], ClientHintsModule);
11
+ const ClientHintsModule = /* @__PURE__ */ declareModule({
12
+ name: 'ClientHintsModule',
13
+ providers: browserProviders,
14
+ });
43
15
 
44
16
  export { ClientHintsModule };
@@ -1,8 +1,8 @@
1
1
  import { __decorate } from 'tslib';
2
2
  import { Module } from '@tramvai/core';
3
3
  import { CHILD_APP_INTERNAL_ROOT_STATE_ALLOWED_STORE_TOKEN } from '@tramvai/tokens-child-app';
4
- import { MediaStore } from '../shared/stores/media.browser.js';
5
- import { UserAgentStore } from '../shared/stores/userAgent.browser.js';
4
+ import { MediaStore } from '../../shared/stores/media.browser.js';
5
+ import { UserAgentStore } from '../../shared/stores/userAgent.browser.js';
6
6
 
7
7
  let ClientHintsChildAppModule = class ClientHintsChildAppModule {
8
8
  };
@@ -1,8 +1,8 @@
1
1
  import { __decorate } from 'tslib';
2
2
  import { Module } from '@tramvai/core';
3
3
  import { CHILD_APP_INTERNAL_ROOT_STATE_ALLOWED_STORE_TOKEN } from '@tramvai/tokens-child-app';
4
- import { MediaStore } from '../shared/stores/media.es.js';
5
- import { UserAgentStore } from '../shared/stores/userAgent.es.js';
4
+ import { MediaStore } from '../../shared/stores/media.es.js';
5
+ import { UserAgentStore } from '../../shared/stores/userAgent.es.js';
6
6
 
7
7
  let ClientHintsChildAppModule = class ClientHintsChildAppModule {
8
8
  };
@@ -5,8 +5,8 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var tslib = require('tslib');
6
6
  var core = require('@tramvai/core');
7
7
  var tokensChildApp = require('@tramvai/tokens-child-app');
8
- var media = require('../shared/stores/media.js');
9
- var userAgent = require('../shared/stores/userAgent.js');
8
+ var media = require('../../shared/stores/media.js');
9
+ var userAgent = require('../../shared/stores/userAgent.js');
10
10
 
11
11
  exports.ClientHintsChildAppModule = class ClientHintsChildAppModule {
12
12
  };
@@ -0,0 +1,30 @@
1
+ import { declareModule, provide } from '@tinkoff/dippy';
2
+ import { STORE_TOKEN, ENV_MANAGER_TOKEN } from '@tramvai/tokens-common';
3
+ import { browserProviders } from '../../shared/providers.browser.browser.js';
4
+ import { setUserAgent, UserAgentStore } from '../../shared/stores/userAgent.browser.js';
5
+ import { loadUserAgent } from '../../browser/loadUserAgent.browser.js';
6
+ import { USER_AGENT_TOKEN } from '../../tokens.browser.js';
7
+
8
+ const ClientHintsCSRModule = /* @__PURE__ */ declareModule({
9
+ name: 'ClientHintsCSRModule',
10
+ providers: [
11
+ ...browserProviders,
12
+ provide({
13
+ provide: USER_AGENT_TOKEN,
14
+ useFactory: ({ store, envManager }) => {
15
+ if (envManager.get('TRAMVAI_FORCE_CLIENT_SIDE_RENDERING') === 'true') {
16
+ const userAgent = loadUserAgent();
17
+ store.dispatch(setUserAgent(userAgent));
18
+ return userAgent;
19
+ }
20
+ return store.getState(UserAgentStore);
21
+ },
22
+ deps: {
23
+ store: STORE_TOKEN,
24
+ envManager: ENV_MANAGER_TOKEN,
25
+ },
26
+ }),
27
+ ],
28
+ });
29
+
30
+ export { ClientHintsCSRModule };
@@ -0,0 +1,3 @@
1
+ export declare const ClientHintsCSRModule: import("@tinkoff/dippy/lib/modules/module.h").ModuleClass & Partial<import("@tinkoff/dippy/lib/modules/module.h").ModuleSecretParameters> & {
2
+ [x: string]: (...args: any[]) => import("@tinkoff/dippy").ModuleType<import("@tinkoff/dippy/lib/modules/module.h").ModuleClass>;
3
+ };
@@ -0,0 +1,3 @@
1
+ export declare const ClientHintsCSRModule: import("@tinkoff/dippy/lib/modules/module.h").ModuleClass & Partial<import("@tinkoff/dippy/lib/modules/module.h").ModuleSecretParameters> & {
2
+ [x: string]: (...args: any[]) => import("@tinkoff/dippy").ModuleType<import("@tinkoff/dippy/lib/modules/module.h").ModuleClass>;
3
+ };
@@ -0,0 +1,9 @@
1
+ import { declareModule } from '@tinkoff/dippy';
2
+ import { serverProviders } from '../../shared/providers.server.es.js';
3
+
4
+ const ClientHintsCSRModule = /* @__PURE__ */ declareModule({
5
+ name: 'ClientHintsCSRModule',
6
+ providers: serverProviders,
7
+ });
8
+
9
+ export { ClientHintsCSRModule };
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var dippy = require('@tinkoff/dippy');
6
+ var providers_server = require('../../shared/providers.server.js');
7
+
8
+ const ClientHintsCSRModule = /* @__PURE__ */ dippy.declareModule({
9
+ name: 'ClientHintsCSRModule',
10
+ providers: providers_server.serverProviders,
11
+ });
12
+
13
+ exports.ClientHintsCSRModule = ClientHintsCSRModule;
@@ -0,0 +1,6 @@
1
+ import type { Cache } from '@tramvai/tokens-common';
2
+ import type { UserAgent } from '@tinkoff/user-agent';
3
+ export declare const parseUserAgentWithCache: (cache: Cache, userAgent: string, metrics: {
4
+ hit: Function;
5
+ miss: Function;
6
+ }) => UserAgent;
@@ -0,0 +1,14 @@
1
+ import { parse } from '@tinkoff/user-agent';
2
+
3
+ const parseUserAgentWithCache = (cache, userAgent, metrics) => {
4
+ if (cache.has(userAgent)) {
5
+ metrics.hit();
6
+ return cache.get(userAgent);
7
+ }
8
+ metrics.miss();
9
+ const result = parse(userAgent);
10
+ cache.set(userAgent, result);
11
+ return result;
12
+ };
13
+
14
+ export { parseUserAgentWithCache };
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var userAgent = require('@tinkoff/user-agent');
6
+
7
+ const parseUserAgentWithCache = (cache, userAgent$1, metrics) => {
8
+ if (cache.has(userAgent$1)) {
9
+ metrics.hit();
10
+ return cache.get(userAgent$1);
11
+ }
12
+ metrics.miss();
13
+ const result = userAgent.parse(userAgent$1);
14
+ cache.set(userAgent$1, result);
15
+ return result;
16
+ };
17
+
18
+ exports.parseUserAgentWithCache = parseUserAgentWithCache;
@@ -1,2 +1,2 @@
1
1
  import type { Provider } from '@tramvai/core';
2
- export declare const userAgentProviders: Provider[];
2
+ export declare const serverUserAgentProviders: Provider[];
@@ -1,65 +1,113 @@
1
1
  import { provide, Scope, commandLineListTokens } from '@tramvai/core';
2
- import { CREATE_CACHE_TOKEN, STORE_TOKEN, RESPONSE_MANAGER_TOKEN, REQUEST_MANAGER_TOKEN } from '@tramvai/tokens-common';
3
- import { parseClientHints, parseUserAgentHeader } from '@tinkoff/user-agent';
2
+ import { ENV_MANAGER_TOKEN, ENV_USED_TOKEN, CREATE_CACHE_TOKEN, RESPONSE_MANAGER_TOKEN, REQUEST_MANAGER_TOKEN, STORE_TOKEN } from '@tramvai/tokens-common';
3
+ import { METRICS_MODULE_TOKEN } from '@tramvai/module-metrics';
4
+ import { parseClientHints } from '@tinkoff/user-agent';
5
+ import noop from '@tinkoff/utils/function/noop';
6
+ import { isNumber } from '@tinkoff/env-validators';
4
7
  import { USER_AGENT_TOKEN, PARSER_CLIENT_HINTS_ENABLED } from '../tokens.es.js';
5
8
  import { setUserAgent } from '../shared/stores/userAgent.es.js';
9
+ import { parseUserAgentWithCache } from './parseUserAgentWithCache.es.js';
6
10
 
7
- const userAgentProviders = [
11
+ const serverUserAgentProviders = [
8
12
  provide({
9
- provide: 'userAgentLruCache',
10
- scope: Scope.SINGLETON,
11
- useFactory: ({ createCache }) => {
12
- return createCache('memory', { max: 50 });
13
- },
13
+ provide: 'userAgentCacheType',
14
+ useFactory: ({ envManager }) => envManager.get('TRAMVAI_USER_AGENT_CACHE_TYPE'),
14
15
  deps: {
15
- createCache: CREATE_CACHE_TOKEN,
16
+ envManager: ENV_MANAGER_TOKEN,
16
17
  },
17
18
  }),
18
19
  provide({
19
- provide: commandLineListTokens.customerStart,
20
- multi: true,
21
- useFactory: ({ store, userAgent }) => {
22
- return function initUserAgent() {
23
- return store.dispatch(setUserAgent(userAgent));
24
- };
20
+ provide: ENV_USED_TOKEN,
21
+ useValue: [
22
+ {
23
+ key: 'TRAMVAI_USER_AGENT_CACHE_TYPE',
24
+ value: 'memory',
25
+ optional: true,
26
+ validator: (value) => value === 'memory' || value === 'memory-lfu',
27
+ dehydrate: false,
28
+ },
29
+ {
30
+ key: 'TRAMVAI_USER_AGENT_CACHE_MAX',
31
+ value: '50',
32
+ optional: true,
33
+ validator: isNumber,
34
+ dehydrate: false,
35
+ },
36
+ ],
37
+ }),
38
+ provide({
39
+ provide: 'userAgentMemoryCache',
40
+ scope: Scope.SINGLETON,
41
+ useFactory: ({ createCache, envManager, cacheType }) => {
42
+ return createCache(cacheType, {
43
+ max: Number(envManager.get('TRAMVAI_USER_AGENT_CACHE_MAX')),
44
+ });
25
45
  },
26
46
  deps: {
27
- userAgent: USER_AGENT_TOKEN,
28
- store: STORE_TOKEN,
47
+ createCache: CREATE_CACHE_TOKEN,
48
+ envManager: ENV_MANAGER_TOKEN,
49
+ cacheType: 'userAgentCacheType',
29
50
  },
30
51
  }),
31
52
  provide({
32
53
  provide: commandLineListTokens.customerStart,
33
54
  multi: true,
34
- useFactory: ({ responseManager }) => {
55
+ useFactory: ({ responseManager, envManager }) => {
56
+ if (envManager.get('TRAMVAI_FORCE_CLIENT_SIDE_RENDERING') === 'true') {
57
+ return noop;
58
+ }
35
59
  return function setClientHintsHeaders() {
36
60
  responseManager.setHeader('Accept-CH', 'Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch, Sec-CH-UA-Model');
37
61
  };
38
62
  },
39
63
  deps: {
40
64
  responseManager: RESPONSE_MANAGER_TOKEN,
65
+ envManager: ENV_MANAGER_TOKEN,
41
66
  },
42
67
  }),
43
68
  provide({
44
69
  provide: USER_AGENT_TOKEN,
45
- useFactory: ({ requestManager, cache, parserClientHintsEnabled }) => {
46
- if (parserClientHintsEnabled && requestManager.getHeader('sec-ch-ua')) {
47
- return parseClientHints(requestManager.getHeaders());
48
- }
49
- const userAgentHeader = requestManager.getHeader('user-agent');
50
- if (cache.has(userAgentHeader)) {
51
- return cache.get(userAgentHeader);
52
- }
53
- const result = parseUserAgentHeader(userAgentHeader);
54
- cache.set(userAgentHeader, result);
55
- return result;
70
+ useFactory: ({ requestManager, cache, parserClientHintsEnabled, store, metrics }) => {
71
+ const userAgent = parserClientHintsEnabled && requestManager.getHeader('sec-ch-ua')
72
+ ? parseClientHints(requestManager.getHeaders())
73
+ : parseUserAgentWithCache(cache, requestManager.getHeader('user-agent'), metrics);
74
+ store.dispatch(setUserAgent(userAgent));
75
+ return userAgent;
56
76
  },
57
77
  deps: {
58
78
  requestManager: REQUEST_MANAGER_TOKEN,
59
79
  parserClientHintsEnabled: PARSER_CLIENT_HINTS_ENABLED,
60
- cache: 'userAgentLruCache',
80
+ store: STORE_TOKEN,
81
+ cache: 'userAgentMemoryCache',
82
+ metrics: 'userAgentCacheMetrics',
83
+ },
84
+ }),
85
+ provide({
86
+ provide: 'userAgentCacheMetrics',
87
+ scope: Scope.SINGLETON,
88
+ useFactory: ({ metrics }) => {
89
+ const getCounter = metrics.counter({
90
+ name: 'user_agent_cache_gets',
91
+ help: 'Total attempts to get user agent parsed results from cache',
92
+ labelNames: ['result'],
93
+ });
94
+ return {
95
+ hit() {
96
+ getCounter.inc({
97
+ result: 'hit',
98
+ });
99
+ },
100
+ miss() {
101
+ getCounter.inc({
102
+ result: 'miss',
103
+ });
104
+ },
105
+ };
106
+ },
107
+ deps: {
108
+ metrics: METRICS_MODULE_TOKEN,
61
109
  },
62
110
  }),
63
111
  ];
64
112
 
65
- export { userAgentProviders };
113
+ export { serverUserAgentProviders };
@@ -4,66 +4,118 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var core = require('@tramvai/core');
6
6
  var tokensCommon = require('@tramvai/tokens-common');
7
- var userAgent$1 = require('@tinkoff/user-agent');
7
+ var moduleMetrics = require('@tramvai/module-metrics');
8
+ var userAgent = require('@tinkoff/user-agent');
9
+ var noop = require('@tinkoff/utils/function/noop');
10
+ var envValidators = require('@tinkoff/env-validators');
8
11
  var tokens = require('../tokens.js');
9
- var userAgent = require('../shared/stores/userAgent.js');
12
+ var userAgent$1 = require('../shared/stores/userAgent.js');
13
+ var parseUserAgentWithCache = require('./parseUserAgentWithCache.js');
10
14
 
11
- const userAgentProviders = [
15
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
16
+
17
+ var noop__default = /*#__PURE__*/_interopDefaultLegacy(noop);
18
+
19
+ const serverUserAgentProviders = [
12
20
  core.provide({
13
- provide: 'userAgentLruCache',
14
- scope: core.Scope.SINGLETON,
15
- useFactory: ({ createCache }) => {
16
- return createCache('memory', { max: 50 });
17
- },
21
+ provide: 'userAgentCacheType',
22
+ useFactory: ({ envManager }) => envManager.get('TRAMVAI_USER_AGENT_CACHE_TYPE'),
18
23
  deps: {
19
- createCache: tokensCommon.CREATE_CACHE_TOKEN,
24
+ envManager: tokensCommon.ENV_MANAGER_TOKEN,
20
25
  },
21
26
  }),
22
27
  core.provide({
23
- provide: core.commandLineListTokens.customerStart,
24
- multi: true,
25
- useFactory: ({ store, userAgent: userAgent$1 }) => {
26
- return function initUserAgent() {
27
- return store.dispatch(userAgent.setUserAgent(userAgent$1));
28
- };
28
+ provide: tokensCommon.ENV_USED_TOKEN,
29
+ useValue: [
30
+ {
31
+ key: 'TRAMVAI_USER_AGENT_CACHE_TYPE',
32
+ value: 'memory',
33
+ optional: true,
34
+ validator: (value) => value === 'memory' || value === 'memory-lfu',
35
+ dehydrate: false,
36
+ },
37
+ {
38
+ key: 'TRAMVAI_USER_AGENT_CACHE_MAX',
39
+ value: '50',
40
+ optional: true,
41
+ validator: envValidators.isNumber,
42
+ dehydrate: false,
43
+ },
44
+ ],
45
+ }),
46
+ core.provide({
47
+ provide: 'userAgentMemoryCache',
48
+ scope: core.Scope.SINGLETON,
49
+ useFactory: ({ createCache, envManager, cacheType }) => {
50
+ return createCache(cacheType, {
51
+ max: Number(envManager.get('TRAMVAI_USER_AGENT_CACHE_MAX')),
52
+ });
29
53
  },
30
54
  deps: {
31
- userAgent: tokens.USER_AGENT_TOKEN,
32
- store: tokensCommon.STORE_TOKEN,
55
+ createCache: tokensCommon.CREATE_CACHE_TOKEN,
56
+ envManager: tokensCommon.ENV_MANAGER_TOKEN,
57
+ cacheType: 'userAgentCacheType',
33
58
  },
34
59
  }),
35
60
  core.provide({
36
61
  provide: core.commandLineListTokens.customerStart,
37
62
  multi: true,
38
- useFactory: ({ responseManager }) => {
63
+ useFactory: ({ responseManager, envManager }) => {
64
+ if (envManager.get('TRAMVAI_FORCE_CLIENT_SIDE_RENDERING') === 'true') {
65
+ return noop__default["default"];
66
+ }
39
67
  return function setClientHintsHeaders() {
40
68
  responseManager.setHeader('Accept-CH', 'Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch, Sec-CH-UA-Model');
41
69
  };
42
70
  },
43
71
  deps: {
44
72
  responseManager: tokensCommon.RESPONSE_MANAGER_TOKEN,
73
+ envManager: tokensCommon.ENV_MANAGER_TOKEN,
45
74
  },
46
75
  }),
47
76
  core.provide({
48
77
  provide: tokens.USER_AGENT_TOKEN,
49
- useFactory: ({ requestManager, cache, parserClientHintsEnabled }) => {
50
- if (parserClientHintsEnabled && requestManager.getHeader('sec-ch-ua')) {
51
- return userAgent$1.parseClientHints(requestManager.getHeaders());
52
- }
53
- const userAgentHeader = requestManager.getHeader('user-agent');
54
- if (cache.has(userAgentHeader)) {
55
- return cache.get(userAgentHeader);
56
- }
57
- const result = userAgent$1.parseUserAgentHeader(userAgentHeader);
58
- cache.set(userAgentHeader, result);
59
- return result;
78
+ useFactory: ({ requestManager, cache, parserClientHintsEnabled, store, metrics }) => {
79
+ const userAgent$2 = parserClientHintsEnabled && requestManager.getHeader('sec-ch-ua')
80
+ ? userAgent.parseClientHints(requestManager.getHeaders())
81
+ : parseUserAgentWithCache.parseUserAgentWithCache(cache, requestManager.getHeader('user-agent'), metrics);
82
+ store.dispatch(userAgent$1.setUserAgent(userAgent$2));
83
+ return userAgent$2;
60
84
  },
61
85
  deps: {
62
86
  requestManager: tokensCommon.REQUEST_MANAGER_TOKEN,
63
87
  parserClientHintsEnabled: tokens.PARSER_CLIENT_HINTS_ENABLED,
64
- cache: 'userAgentLruCache',
88
+ store: tokensCommon.STORE_TOKEN,
89
+ cache: 'userAgentMemoryCache',
90
+ metrics: 'userAgentCacheMetrics',
91
+ },
92
+ }),
93
+ core.provide({
94
+ provide: 'userAgentCacheMetrics',
95
+ scope: core.Scope.SINGLETON,
96
+ useFactory: ({ metrics }) => {
97
+ const getCounter = metrics.counter({
98
+ name: 'user_agent_cache_gets',
99
+ help: 'Total attempts to get user agent parsed results from cache',
100
+ labelNames: ['result'],
101
+ });
102
+ return {
103
+ hit() {
104
+ getCounter.inc({
105
+ result: 'hit',
106
+ });
107
+ },
108
+ miss() {
109
+ getCounter.inc({
110
+ result: 'miss',
111
+ });
112
+ },
113
+ };
114
+ },
115
+ deps: {
116
+ metrics: moduleMetrics.METRICS_MODULE_TOKEN,
65
117
  },
66
118
  }),
67
119
  ];
68
120
 
69
- exports.userAgentProviders = userAgentProviders;
121
+ exports.serverUserAgentProviders = serverUserAgentProviders;
package/lib/server.d.ts CHANGED
@@ -3,6 +3,8 @@ export * from './shared/stores/mediaCheckers';
3
3
  export * from './shared/stores/mediaSelectors';
4
4
  export * from './shared/stores/media';
5
5
  export * from './shared/stores/userAgent';
6
- export { ClientHintsChildAppModule } from './child-app/module';
7
- export declare class ClientHintsModule {
8
- }
6
+ export { ClientHintsChildAppModule } from './modules/child-app/module';
7
+ export { ClientHintsCSRModule } from './modules/csr/server';
8
+ export declare const ClientHintsModule: import("@tinkoff/dippy/lib/modules/module.h").ModuleClass & Partial<import("@tinkoff/dippy/lib/modules/module.h").ModuleSecretParameters> & {
9
+ [x: string]: (...args: any[]) => import("@tramvai/core").ModuleType<import("@tinkoff/dippy/lib/modules/module.h").ModuleClass>;
10
+ };
package/lib/server.es.js CHANGED
@@ -1,40 +1,25 @@
1
- import { __decorate } from 'tslib';
2
- import { Module, provide, commandLineListTokens } from '@tramvai/core';
3
- import { CONTEXT_TOKEN } from '@tramvai/tokens-common';
4
- import { COOKIE_MANAGER_TOKEN } from '@tramvai/tokens-cookie';
5
- import { readMediaCommand } from './server/readMedia.es.js';
6
- import { providers } from './shared/providers.es.js';
7
- import { userAgentProviders } from './server/userAgent.es.js';
1
+ import { declareModule, provide } from '@tramvai/core';
2
+ import { serverProviders } from './shared/providers.server.es.js';
3
+ import { serverUserAgentProviders } from './server/userAgent.es.js';
8
4
  import { PARSER_CLIENT_HINTS_ENABLED } from './tokens.es.js';
9
5
  export { PARSER_CLIENT_HINTS_ENABLED, USER_AGENT_TOKEN } from './tokens.es.js';
10
6
  export { fromClientHints, isRetina, isSupposed } from './shared/stores/mediaCheckers.es.js';
11
7
  export { useFromClientHints, useIsRetina, useIsSupposed, useMedia } from './shared/stores/mediaSelectors.es.js';
12
8
  export { MediaStore, setMedia } from './shared/stores/media.es.js';
13
9
  export { UserAgentStore, setUserAgent } from './shared/stores/userAgent.es.js';
14
- export { ClientHintsChildAppModule } from './child-app/module.es.js';
10
+ export { ClientHintsChildAppModule } from './modules/child-app/module.es.js';
11
+ export { ClientHintsCSRModule } from './modules/csr/server.es.js';
15
12
 
16
- let ClientHintsModule = class ClientHintsModule {
17
- };
18
- ClientHintsModule = __decorate([
19
- Module({
20
- providers: [
21
- ...providers,
22
- ...userAgentProviders,
23
- provide({
24
- provide: commandLineListTokens.resolveUserDeps,
25
- multi: true,
26
- useFactory: readMediaCommand,
27
- deps: {
28
- context: CONTEXT_TOKEN,
29
- cookieManager: COOKIE_MANAGER_TOKEN,
30
- },
31
- }),
32
- provide({
33
- provide: PARSER_CLIENT_HINTS_ENABLED,
34
- useValue: true,
35
- }),
36
- ],
37
- })
38
- ], ClientHintsModule);
13
+ const ClientHintsModule = /* @__PURE__ */ declareModule({
14
+ name: 'ClientHintsModule',
15
+ providers: [
16
+ ...serverProviders,
17
+ ...serverUserAgentProviders,
18
+ provide({
19
+ provide: PARSER_CLIENT_HINTS_ENABLED,
20
+ useValue: true,
21
+ }),
22
+ ],
23
+ });
39
24
 
40
25
  export { ClientHintsModule };
package/lib/server.js CHANGED
@@ -2,43 +2,28 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var tslib = require('tslib');
6
5
  var core = require('@tramvai/core');
7
- var tokensCommon = require('@tramvai/tokens-common');
8
- var tokensCookie = require('@tramvai/tokens-cookie');
9
- var readMedia = require('./server/readMedia.js');
10
- var providers = require('./shared/providers.js');
6
+ var providers_server = require('./shared/providers.server.js');
11
7
  var userAgent$1 = require('./server/userAgent.js');
12
8
  var tokens = require('./tokens.js');
13
9
  var mediaCheckers = require('./shared/stores/mediaCheckers.js');
14
10
  var mediaSelectors = require('./shared/stores/mediaSelectors.js');
15
11
  var media = require('./shared/stores/media.js');
16
12
  var userAgent = require('./shared/stores/userAgent.js');
17
- var module$1 = require('./child-app/module.js');
13
+ var module$1 = require('./modules/child-app/module.js');
14
+ var server = require('./modules/csr/server.js');
18
15
 
19
- exports.ClientHintsModule = class ClientHintsModule {
20
- };
21
- exports.ClientHintsModule = tslib.__decorate([
22
- core.Module({
23
- providers: [
24
- ...providers.providers,
25
- ...userAgent$1.userAgentProviders,
26
- core.provide({
27
- provide: core.commandLineListTokens.resolveUserDeps,
28
- multi: true,
29
- useFactory: readMedia.readMediaCommand,
30
- deps: {
31
- context: tokensCommon.CONTEXT_TOKEN,
32
- cookieManager: tokensCookie.COOKIE_MANAGER_TOKEN,
33
- },
34
- }),
35
- core.provide({
36
- provide: tokens.PARSER_CLIENT_HINTS_ENABLED,
37
- useValue: true,
38
- }),
39
- ],
40
- })
41
- ], exports.ClientHintsModule);
16
+ const ClientHintsModule = /* @__PURE__ */ core.declareModule({
17
+ name: 'ClientHintsModule',
18
+ providers: [
19
+ ...providers_server.serverProviders,
20
+ ...userAgent$1.serverUserAgentProviders,
21
+ core.provide({
22
+ provide: tokens.PARSER_CLIENT_HINTS_ENABLED,
23
+ useValue: true,
24
+ }),
25
+ ],
26
+ });
42
27
 
43
28
  exports.PARSER_CLIENT_HINTS_ENABLED = tokens.PARSER_CLIENT_HINTS_ENABLED;
44
29
  exports.USER_AGENT_TOKEN = tokens.USER_AGENT_TOKEN;
@@ -57,3 +42,5 @@ Object.defineProperty(exports, 'ClientHintsChildAppModule', {
57
42
  enumerable: true,
58
43
  get: function () { return module$1.ClientHintsChildAppModule; }
59
44
  });
45
+ exports.ClientHintsCSRModule = server.ClientHintsCSRModule;
46
+ exports.ClientHintsModule = ClientHintsModule;
@@ -0,0 +1,32 @@
1
+ import { provide } from '@tinkoff/dippy';
2
+ import { commandLineListTokens } from '@tramvai/core';
3
+ import { CONTEXT_TOKEN, STORE_TOKEN } from '@tramvai/tokens-common';
4
+ import { COOKIE_MANAGER_TOKEN } from '@tramvai/tokens-cookie';
5
+ import { matchMediaCommand } from '../browser/matchMedia.browser.js';
6
+ import { USER_AGENT_TOKEN } from '../tokens.browser.js';
7
+ import { UserAgentStore } from './stores/userAgent.browser.js';
8
+ import { commonProviders } from './providers.browser.js';
9
+
10
+ const browserProviders = [
11
+ ...commonProviders,
12
+ provide({
13
+ provide: commandLineListTokens.clear,
14
+ multi: true,
15
+ useFactory: matchMediaCommand,
16
+ deps: {
17
+ context: CONTEXT_TOKEN,
18
+ cookieManager: COOKIE_MANAGER_TOKEN,
19
+ },
20
+ }),
21
+ provide({
22
+ provide: USER_AGENT_TOKEN,
23
+ useFactory: ({ store }) => {
24
+ return store.getState(UserAgentStore);
25
+ },
26
+ deps: {
27
+ store: STORE_TOKEN,
28
+ },
29
+ }),
30
+ ];
31
+
32
+ export { browserProviders };
@@ -0,0 +1,2 @@
1
+ import type { Provider } from '@tinkoff/dippy';
2
+ export declare const browserProviders: Provider[];
@@ -1,9 +1,14 @@
1
+ import { provide } from '@tramvai/core';
1
2
  import { COMBINE_REDUCERS } from '@tramvai/tokens-common';
2
3
  import { UserAgentStore } from './stores/userAgent.browser.js';
3
4
  import { MediaStore } from './stores/media.browser.js';
4
5
 
5
- const providers = [
6
- { provide: COMBINE_REDUCERS, multi: true, useValue: [UserAgentStore, MediaStore] },
6
+ const commonProviders = [
7
+ provide({
8
+ provide: COMBINE_REDUCERS,
9
+ multi: true,
10
+ useValue: [UserAgentStore, MediaStore],
11
+ }),
7
12
  ];
8
13
 
9
- export { providers };
14
+ export { commonProviders };
@@ -1,2 +1,2 @@
1
1
  import type { Provider } from '@tramvai/core';
2
- export declare const providers: Provider[];
2
+ export declare const commonProviders: Provider[];
@@ -1,9 +1,14 @@
1
+ import { provide } from '@tramvai/core';
1
2
  import { COMBINE_REDUCERS } from '@tramvai/tokens-common';
2
3
  import { UserAgentStore } from './stores/userAgent.es.js';
3
4
  import { MediaStore } from './stores/media.es.js';
4
5
 
5
- const providers = [
6
- { provide: COMBINE_REDUCERS, multi: true, useValue: [UserAgentStore, MediaStore] },
6
+ const commonProviders = [
7
+ provide({
8
+ provide: COMBINE_REDUCERS,
9
+ multi: true,
10
+ useValue: [UserAgentStore, MediaStore],
11
+ }),
7
12
  ];
8
13
 
9
- export { providers };
14
+ export { commonProviders };
@@ -2,12 +2,17 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var core = require('@tramvai/core');
5
6
  var tokensCommon = require('@tramvai/tokens-common');
6
7
  var userAgent = require('./stores/userAgent.js');
7
8
  var media = require('./stores/media.js');
8
9
 
9
- const providers = [
10
- { provide: tokensCommon.COMBINE_REDUCERS, multi: true, useValue: [userAgent.UserAgentStore, media.MediaStore] },
10
+ const commonProviders = [
11
+ core.provide({
12
+ provide: tokensCommon.COMBINE_REDUCERS,
13
+ multi: true,
14
+ useValue: [userAgent.UserAgentStore, media.MediaStore],
15
+ }),
11
16
  ];
12
17
 
13
- exports.providers = providers;
18
+ exports.commonProviders = commonProviders;
@@ -0,0 +1,2 @@
1
+ import type { Provider } from '@tinkoff/dippy';
2
+ export declare const serverProviders: Provider[];
@@ -0,0 +1,21 @@
1
+ import { provide } from '@tinkoff/dippy';
2
+ import { commandLineListTokens } from '@tramvai/core';
3
+ import { CONTEXT_TOKEN } from '@tramvai/tokens-common';
4
+ import { COOKIE_MANAGER_TOKEN } from '@tramvai/tokens-cookie';
5
+ import { readMediaCommand } from '../server/readMedia.es.js';
6
+ import { commonProviders } from './providers.es.js';
7
+
8
+ const serverProviders = [
9
+ ...commonProviders,
10
+ provide({
11
+ provide: commandLineListTokens.resolveUserDeps,
12
+ multi: true,
13
+ useFactory: readMediaCommand,
14
+ deps: {
15
+ context: CONTEXT_TOKEN,
16
+ cookieManager: COOKIE_MANAGER_TOKEN,
17
+ },
18
+ }),
19
+ ];
20
+
21
+ export { serverProviders };
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var dippy = require('@tinkoff/dippy');
6
+ var core = require('@tramvai/core');
7
+ var tokensCommon = require('@tramvai/tokens-common');
8
+ var tokensCookie = require('@tramvai/tokens-cookie');
9
+ var readMedia = require('../server/readMedia.js');
10
+ var providers = require('./providers.js');
11
+
12
+ const serverProviders = [
13
+ ...providers.commonProviders,
14
+ dippy.provide({
15
+ provide: core.commandLineListTokens.resolveUserDeps,
16
+ multi: true,
17
+ useFactory: readMedia.readMediaCommand,
18
+ deps: {
19
+ context: tokensCommon.CONTEXT_TOKEN,
20
+ cookieManager: tokensCookie.COOKIE_MANAGER_TOKEN,
21
+ },
22
+ }),
23
+ ];
24
+
25
+ exports.serverProviders = serverProviders;
@@ -1,8 +1,6 @@
1
1
  import { createEvent, createReducer } from '@tramvai/state';
2
2
 
3
3
  const setUserAgent = createEvent('setUserAgent');
4
- const UserAgentStore = createReducer('userAgent', null).on(setUserAgent, (state, userAgent) => {
5
- return userAgent;
6
- });
4
+ const UserAgentStore = createReducer('userAgent', null).on(setUserAgent, (state, userAgent) => userAgent);
7
5
 
8
6
  export { UserAgentStore, setUserAgent };
@@ -1,8 +1,6 @@
1
1
  import { createEvent, createReducer } from '@tramvai/state';
2
2
 
3
3
  const setUserAgent = createEvent('setUserAgent');
4
- const UserAgentStore = createReducer('userAgent', null).on(setUserAgent, (state, userAgent) => {
5
- return userAgent;
6
- });
4
+ const UserAgentStore = createReducer('userAgent', null).on(setUserAgent, (state, userAgent) => userAgent);
7
5
 
8
6
  export { UserAgentStore, setUserAgent };
@@ -5,9 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var state = require('@tramvai/state');
6
6
 
7
7
  const setUserAgent = state.createEvent('setUserAgent');
8
- const UserAgentStore = state.createReducer('userAgent', null).on(setUserAgent, (state, userAgent) => {
9
- return userAgent;
10
- });
8
+ const UserAgentStore = state.createReducer('userAgent', null).on(setUserAgent, (state, userAgent) => userAgent);
11
9
 
12
10
  exports.UserAgentStore = UserAgentStore;
13
11
  exports.setUserAgent = setUserAgent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tramvai/module-client-hints",
3
- "version": "2.125.4",
3
+ "version": "2.130.6",
4
4
  "description": "",
5
5
  "main": "lib/server.js",
6
6
  "module": "lib/server.es.js",
@@ -19,17 +19,21 @@
19
19
  "watch": "tsc -w"
20
20
  },
21
21
  "dependencies": {
22
- "@tinkoff/user-agent": "0.4.365",
22
+ "@tinkoff/user-agent": "0.4.380",
23
23
  "@tinkoff/utils": "^2.1.2",
24
+ "@tinkoff/env-validators": "0.1.6",
24
25
  "@tramvai/safe-strings": "0.5.10",
25
- "@tramvai/tokens-common": "2.125.4",
26
- "@tramvai/tokens-cookie": "2.125.4",
27
- "@tramvai/tokens-child-app": "2.125.4"
26
+ "@tramvai/module-metrics": "2.130.6",
27
+ "@tramvai/tokens-child-app": "2.130.6",
28
+ "@tramvai/tokens-common": "2.130.6",
29
+ "@tramvai/tokens-cookie": "2.130.6",
30
+ "user-agent-data-types": "^0.3.1"
28
31
  },
29
32
  "peerDependencies": {
30
- "@tramvai/core": "2.125.4",
31
- "@tramvai/state": "2.125.4",
32
33
  "@tinkoff/dippy": "0.8.15",
34
+ "@tinkoff/logger": "0.10.65",
35
+ "@tramvai/core": "2.130.6",
36
+ "@tramvai/state": "2.130.6",
33
37
  "react": ">=16.14.0",
34
38
  "react-dom": ">=16.14.0",
35
39
  "tslib": "^2.4.0"