@tramvai/module-client-hints 2.33.3 → 2.35.0

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
@@ -23,13 +23,25 @@ createApp({
23
23
 
24
24
  ## Explanation
25
25
 
26
+ ### User agent details parsing
27
+
28
+ Parsing is implemented with library [@tinkoff/user-agent](../libs/user-agent.md) that may use either user-agent header or client-hints headers.
29
+
30
+ 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
+
32
+ This logic implies next things worth to mention:
33
+ - 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
+ - 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
+ - client-hints is mostly more performant way to parse browser info and this is way it used if it's possible
36
+ - 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
+
26
38
  ### The problem with media on server and on client
27
39
 
28
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.
29
41
 
30
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.
31
43
 
32
- ### How does it work
44
+ ### How does media work
33
45
 
34
46
  #### First page loading
35
47
 
@@ -104,7 +116,7 @@ If some component if depend of screen size:
104
116
 
105
117
  #### userAgent
106
118
 
107
- Stores the result of the user-agent string parsing.
119
+ Stores the result of the user-agent string or client-hints headers parsing.
108
120
 
109
121
  #### media
110
122
 
package/lib/browser.js CHANGED
@@ -8,6 +8,7 @@ import { createEvent, createReducer, useSelector } from '@tramvai/state';
8
8
  import { CHILD_APP_INTERNAL_ROOT_STATE_ALLOWED_STORE_TOKEN } from '@tramvai/tokens-child-app';
9
9
 
10
10
  const USER_AGENT_TOKEN = createToken('userAgent');
11
+ const PARSER_CLIENT_HINTS_ENABLED = createToken('client-hints parserClientHints enabled');
11
12
 
12
13
  const setMedia = createEvent('setMedia');
13
14
  const initialState = {
@@ -132,4 +133,4 @@ ClientHintsModule = __decorate([
132
133
  })
133
134
  ], ClientHintsModule);
134
135
 
135
- export { ClientHintsChildAppModule, ClientHintsModule, MediaStore, USER_AGENT_TOKEN, UserAgentStore, fromClientHints, isRetina, isSupposed, setMedia, setUserAgent, useFromClientHints, useIsRetina, useIsSupposed, useMedia };
136
+ export { ClientHintsChildAppModule, ClientHintsModule, MediaStore, PARSER_CLIENT_HINTS_ENABLED, USER_AGENT_TOKEN, UserAgentStore, fromClientHints, isRetina, isSupposed, setMedia, setUserAgent, useFromClientHints, useIsRetina, useIsSupposed, useMedia };
@@ -0,0 +1,2 @@
1
+ import type { Provider } from '@tramvai/core';
2
+ export declare const userAgentProviders: Provider[];
package/lib/server.es.js CHANGED
@@ -1,19 +1,14 @@
1
1
  import { __decorate } from 'tslib';
2
- import { Module, provide, Scope, commandLineListTokens } from '@tramvai/core';
3
- import { COMBINE_REDUCERS, CREATE_CACHE_TOKEN, REQUEST_MANAGER_TOKEN, STORE_TOKEN, CONTEXT_TOKEN } from '@tramvai/tokens-common';
2
+ import { provide, Scope, commandLineListTokens, Module } from '@tramvai/core';
3
+ import { COMBINE_REDUCERS, CREATE_CACHE_TOKEN, STORE_TOKEN, RESPONSE_MANAGER_TOKEN, REQUEST_MANAGER_TOKEN, CONTEXT_TOKEN } from '@tramvai/tokens-common';
4
4
  import { COOKIE_MANAGER_TOKEN } from '@tramvai/tokens-cookie';
5
- import { parse } from '@tinkoff/user-agent';
6
- import { createEvent, createReducer, useSelector } from '@tramvai/state';
7
5
  import pathOr from '@tinkoff/utils/object/pathOr';
8
6
  import { safeParseJSON } from '@tramvai/safe-strings';
7
+ import { createEvent, createReducer, useSelector } from '@tramvai/state';
8
+ import { parseClientHints, parseUserAgentHeader } from '@tinkoff/user-agent';
9
9
  import { createToken } from '@tinkoff/dippy';
10
10
  import { CHILD_APP_INTERNAL_ROOT_STATE_ALLOWED_STORE_TOKEN } from '@tramvai/tokens-child-app';
11
11
 
12
- const setUserAgent = createEvent('setUserAgent');
13
- const UserAgentStore = createReducer('userAgent', null).on(setUserAgent, (state, userAgent) => {
14
- return userAgent;
15
- });
16
-
17
12
  const COOKIE_NAME_MEDIA_INFO = 'mediaInfo';
18
13
 
19
14
  const setMedia = createEvent('setMedia');
@@ -31,6 +26,11 @@ const MediaStore = createReducer('media', initialState).on(setMedia, (state, med
31
26
  };
32
27
  });
33
28
 
29
+ const setUserAgent = createEvent('setUserAgent');
30
+ const UserAgentStore = createReducer('userAgent', null).on(setUserAgent, (state, userAgent) => {
31
+ return userAgent;
32
+ });
33
+
34
34
  const SUPPOSED_MEDIA = {
35
35
  mobile: {
36
36
  width: 300,
@@ -71,12 +71,71 @@ const readMediaCommand = ({ context, cookieManager, }) => {
71
71
  };
72
72
  };
73
73
 
74
- const USER_AGENT_TOKEN = createToken('userAgent');
75
-
76
74
  const providers = [
77
75
  { provide: COMBINE_REDUCERS, multi: true, useValue: [UserAgentStore, MediaStore] },
78
76
  ];
79
77
 
78
+ const USER_AGENT_TOKEN = createToken('userAgent');
79
+ const PARSER_CLIENT_HINTS_ENABLED = createToken('client-hints parserClientHints enabled');
80
+
81
+ const userAgentProviders = [
82
+ provide({
83
+ provide: 'userAgentLruCache',
84
+ scope: Scope.SINGLETON,
85
+ useFactory: ({ createCache }) => {
86
+ return createCache('memory', { max: 50 });
87
+ },
88
+ deps: {
89
+ createCache: CREATE_CACHE_TOKEN,
90
+ },
91
+ }),
92
+ provide({
93
+ provide: commandLineListTokens.customerStart,
94
+ multi: true,
95
+ useFactory: ({ store, userAgent }) => {
96
+ return function initUserAgent() {
97
+ return store.dispatch(setUserAgent(userAgent));
98
+ };
99
+ },
100
+ deps: {
101
+ userAgent: USER_AGENT_TOKEN,
102
+ store: STORE_TOKEN,
103
+ },
104
+ }),
105
+ provide({
106
+ provide: commandLineListTokens.customerStart,
107
+ multi: true,
108
+ useFactory: ({ responseManager }) => {
109
+ return function setClientHintsHeaders() {
110
+ responseManager.setHeader('Accept-CH', 'Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch, Sec-CH-UA-Model');
111
+ };
112
+ },
113
+ deps: {
114
+ responseManager: RESPONSE_MANAGER_TOKEN,
115
+ },
116
+ }),
117
+ provide({
118
+ provide: USER_AGENT_TOKEN,
119
+ useFactory: ({ requestManager, cache, parserClientHintsEnabled }) => {
120
+ if (parserClientHintsEnabled && requestManager.getHeader('sec-ch-ua')) {
121
+ return parseClientHints(requestManager.getHeaders());
122
+ }
123
+ const userAgentHeader = requestManager.getHeader('user-agent');
124
+ if (cache.has(userAgentHeader)) {
125
+ return cache.get(userAgentHeader);
126
+ }
127
+ const result = parseUserAgentHeader(userAgentHeader);
128
+ cache.set(userAgentHeader, result);
129
+ return result;
130
+ },
131
+ deps: {
132
+ requestManager: REQUEST_MANAGER_TOKEN,
133
+ parserClientHintsEnabled: PARSER_CLIENT_HINTS_ENABLED,
134
+ cache: 'userAgentLruCache',
135
+ },
136
+ }),
137
+ ];
138
+
80
139
  function fromClientHints(media) {
81
140
  return !!media.synchronized;
82
141
  }
@@ -120,46 +179,8 @@ ClientHintsModule = __decorate([
120
179
  Module({
121
180
  providers: [
122
181
  ...providers,
182
+ ...userAgentProviders,
123
183
  provide({
124
- provide: 'userAgentLruCache',
125
- scope: Scope.SINGLETON,
126
- useFactory: ({ createCache }) => {
127
- return createCache('memory', { max: 50 });
128
- },
129
- deps: {
130
- createCache: CREATE_CACHE_TOKEN,
131
- },
132
- }),
133
- {
134
- provide: USER_AGENT_TOKEN,
135
- useFactory: ({ requestManager, cache }) => {
136
- const reqUserAgent = requestManager.getHeader('user-agent');
137
- if (cache.has(reqUserAgent)) {
138
- return cache.get(reqUserAgent);
139
- }
140
- const result = parse(reqUserAgent);
141
- cache.set(reqUserAgent, result);
142
- return result;
143
- },
144
- deps: {
145
- requestManager: REQUEST_MANAGER_TOKEN,
146
- cache: 'userAgentLruCache',
147
- },
148
- },
149
- {
150
- provide: commandLineListTokens.customerStart,
151
- multi: true,
152
- useFactory: ({ store, userAgent }) => {
153
- return function initUserAgent() {
154
- return store.dispatch(setUserAgent(userAgent));
155
- };
156
- },
157
- deps: {
158
- userAgent: USER_AGENT_TOKEN,
159
- store: STORE_TOKEN,
160
- },
161
- },
162
- {
163
184
  provide: commandLineListTokens.resolveUserDeps,
164
185
  multi: true,
165
186
  useFactory: readMediaCommand,
@@ -167,9 +188,13 @@ ClientHintsModule = __decorate([
167
188
  context: CONTEXT_TOKEN,
168
189
  cookieManager: COOKIE_MANAGER_TOKEN,
169
190
  },
170
- },
191
+ }),
192
+ provide({
193
+ provide: PARSER_CLIENT_HINTS_ENABLED,
194
+ useValue: true,
195
+ }),
171
196
  ],
172
197
  })
173
198
  ], ClientHintsModule);
174
199
 
175
- export { ClientHintsChildAppModule, ClientHintsModule, MediaStore, USER_AGENT_TOKEN, UserAgentStore, fromClientHints, isRetina, isSupposed, setMedia, setUserAgent, useFromClientHints, useIsRetina, useIsSupposed, useMedia };
200
+ export { ClientHintsChildAppModule, ClientHintsModule, MediaStore, PARSER_CLIENT_HINTS_ENABLED, USER_AGENT_TOKEN, UserAgentStore, fromClientHints, isRetina, isSupposed, setMedia, setUserAgent, useFromClientHints, useIsRetina, useIsSupposed, useMedia };
package/lib/server.js CHANGED
@@ -6,10 +6,10 @@ var tslib = require('tslib');
6
6
  var core = require('@tramvai/core');
7
7
  var tokensCommon = require('@tramvai/tokens-common');
8
8
  var tokensCookie = require('@tramvai/tokens-cookie');
9
- var userAgent = require('@tinkoff/user-agent');
10
- var state = require('@tramvai/state');
11
9
  var pathOr = require('@tinkoff/utils/object/pathOr');
12
10
  var safeStrings = require('@tramvai/safe-strings');
11
+ var state = require('@tramvai/state');
12
+ var userAgent = require('@tinkoff/user-agent');
13
13
  var dippy = require('@tinkoff/dippy');
14
14
  var tokensChildApp = require('@tramvai/tokens-child-app');
15
15
 
@@ -17,11 +17,6 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
17
17
 
18
18
  var pathOr__default = /*#__PURE__*/_interopDefaultLegacy(pathOr);
19
19
 
20
- const setUserAgent = state.createEvent('setUserAgent');
21
- const UserAgentStore = state.createReducer('userAgent', null).on(setUserAgent, (state, userAgent) => {
22
- return userAgent;
23
- });
24
-
25
20
  const COOKIE_NAME_MEDIA_INFO = 'mediaInfo';
26
21
 
27
22
  const setMedia = state.createEvent('setMedia');
@@ -39,6 +34,11 @@ const MediaStore = state.createReducer('media', initialState).on(setMedia, (stat
39
34
  };
40
35
  });
41
36
 
37
+ const setUserAgent = state.createEvent('setUserAgent');
38
+ const UserAgentStore = state.createReducer('userAgent', null).on(setUserAgent, (state, userAgent) => {
39
+ return userAgent;
40
+ });
41
+
42
42
  const SUPPOSED_MEDIA = {
43
43
  mobile: {
44
44
  width: 300,
@@ -79,12 +79,71 @@ const readMediaCommand = ({ context, cookieManager, }) => {
79
79
  };
80
80
  };
81
81
 
82
- const USER_AGENT_TOKEN = dippy.createToken('userAgent');
83
-
84
82
  const providers = [
85
83
  { provide: tokensCommon.COMBINE_REDUCERS, multi: true, useValue: [UserAgentStore, MediaStore] },
86
84
  ];
87
85
 
86
+ const USER_AGENT_TOKEN = dippy.createToken('userAgent');
87
+ const PARSER_CLIENT_HINTS_ENABLED = dippy.createToken('client-hints parserClientHints enabled');
88
+
89
+ const userAgentProviders = [
90
+ core.provide({
91
+ provide: 'userAgentLruCache',
92
+ scope: core.Scope.SINGLETON,
93
+ useFactory: ({ createCache }) => {
94
+ return createCache('memory', { max: 50 });
95
+ },
96
+ deps: {
97
+ createCache: tokensCommon.CREATE_CACHE_TOKEN,
98
+ },
99
+ }),
100
+ core.provide({
101
+ provide: core.commandLineListTokens.customerStart,
102
+ multi: true,
103
+ useFactory: ({ store, userAgent }) => {
104
+ return function initUserAgent() {
105
+ return store.dispatch(setUserAgent(userAgent));
106
+ };
107
+ },
108
+ deps: {
109
+ userAgent: USER_AGENT_TOKEN,
110
+ store: tokensCommon.STORE_TOKEN,
111
+ },
112
+ }),
113
+ core.provide({
114
+ provide: core.commandLineListTokens.customerStart,
115
+ multi: true,
116
+ useFactory: ({ responseManager }) => {
117
+ return function setClientHintsHeaders() {
118
+ responseManager.setHeader('Accept-CH', 'Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch, Sec-CH-UA-Model');
119
+ };
120
+ },
121
+ deps: {
122
+ responseManager: tokensCommon.RESPONSE_MANAGER_TOKEN,
123
+ },
124
+ }),
125
+ core.provide({
126
+ provide: USER_AGENT_TOKEN,
127
+ useFactory: ({ requestManager, cache, parserClientHintsEnabled }) => {
128
+ if (parserClientHintsEnabled && requestManager.getHeader('sec-ch-ua')) {
129
+ return userAgent.parseClientHints(requestManager.getHeaders());
130
+ }
131
+ const userAgentHeader = requestManager.getHeader('user-agent');
132
+ if (cache.has(userAgentHeader)) {
133
+ return cache.get(userAgentHeader);
134
+ }
135
+ const result = userAgent.parseUserAgentHeader(userAgentHeader);
136
+ cache.set(userAgentHeader, result);
137
+ return result;
138
+ },
139
+ deps: {
140
+ requestManager: tokensCommon.REQUEST_MANAGER_TOKEN,
141
+ parserClientHintsEnabled: PARSER_CLIENT_HINTS_ENABLED,
142
+ cache: 'userAgentLruCache',
143
+ },
144
+ }),
145
+ ];
146
+
88
147
  function fromClientHints(media) {
89
148
  return !!media.synchronized;
90
149
  }
@@ -128,46 +187,8 @@ exports.ClientHintsModule = tslib.__decorate([
128
187
  core.Module({
129
188
  providers: [
130
189
  ...providers,
190
+ ...userAgentProviders,
131
191
  core.provide({
132
- provide: 'userAgentLruCache',
133
- scope: core.Scope.SINGLETON,
134
- useFactory: ({ createCache }) => {
135
- return createCache('memory', { max: 50 });
136
- },
137
- deps: {
138
- createCache: tokensCommon.CREATE_CACHE_TOKEN,
139
- },
140
- }),
141
- {
142
- provide: USER_AGENT_TOKEN,
143
- useFactory: ({ requestManager, cache }) => {
144
- const reqUserAgent = requestManager.getHeader('user-agent');
145
- if (cache.has(reqUserAgent)) {
146
- return cache.get(reqUserAgent);
147
- }
148
- const result = userAgent.parse(reqUserAgent);
149
- cache.set(reqUserAgent, result);
150
- return result;
151
- },
152
- deps: {
153
- requestManager: tokensCommon.REQUEST_MANAGER_TOKEN,
154
- cache: 'userAgentLruCache',
155
- },
156
- },
157
- {
158
- provide: core.commandLineListTokens.customerStart,
159
- multi: true,
160
- useFactory: ({ store, userAgent }) => {
161
- return function initUserAgent() {
162
- return store.dispatch(setUserAgent(userAgent));
163
- };
164
- },
165
- deps: {
166
- userAgent: USER_AGENT_TOKEN,
167
- store: tokensCommon.STORE_TOKEN,
168
- },
169
- },
170
- {
171
192
  provide: core.commandLineListTokens.resolveUserDeps,
172
193
  multi: true,
173
194
  useFactory: readMediaCommand,
@@ -175,12 +196,17 @@ exports.ClientHintsModule = tslib.__decorate([
175
196
  context: tokensCommon.CONTEXT_TOKEN,
176
197
  cookieManager: tokensCookie.COOKIE_MANAGER_TOKEN,
177
198
  },
178
- },
199
+ }),
200
+ core.provide({
201
+ provide: PARSER_CLIENT_HINTS_ENABLED,
202
+ useValue: true,
203
+ }),
179
204
  ],
180
205
  })
181
206
  ], exports.ClientHintsModule);
182
207
 
183
208
  exports.MediaStore = MediaStore;
209
+ exports.PARSER_CLIENT_HINTS_ENABLED = PARSER_CLIENT_HINTS_ENABLED;
184
210
  exports.USER_AGENT_TOKEN = USER_AGENT_TOKEN;
185
211
  exports.UserAgentStore = UserAgentStore;
186
212
  exports.fromClientHints = fromClientHints;
package/lib/tokens.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  import type { UserAgent } from '@tinkoff/user-agent';
2
2
  export declare const USER_AGENT_TOKEN: import("@tinkoff/dippy").BaseTokenInterface<UserAgent>;
3
+ export declare const PARSER_CLIENT_HINTS_ENABLED: import("@tinkoff/dippy").BaseTokenInterface<boolean>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tramvai/module-client-hints",
3
- "version": "2.33.3",
3
+ "version": "2.35.0",
4
4
  "description": "",
5
5
  "main": "lib/server.js",
6
6
  "module": "lib/server.es.js",
@@ -20,16 +20,16 @@
20
20
  "build-for-publish": "true"
21
21
  },
22
22
  "dependencies": {
23
- "@tinkoff/user-agent": "0.4.76",
23
+ "@tinkoff/user-agent": "0.4.78",
24
24
  "@tinkoff/utils": "^2.1.2",
25
25
  "@tramvai/safe-strings": "0.5.5",
26
- "@tramvai/tokens-common": "2.33.3",
27
- "@tramvai/tokens-cookie": "2.33.3",
28
- "@tramvai/tokens-child-app": "2.33.3"
26
+ "@tramvai/tokens-common": "2.35.0",
27
+ "@tramvai/tokens-cookie": "2.35.0",
28
+ "@tramvai/tokens-child-app": "2.35.0"
29
29
  },
30
30
  "peerDependencies": {
31
- "@tramvai/core": "2.33.3",
32
- "@tramvai/state": "2.33.3",
31
+ "@tramvai/core": "2.35.0",
32
+ "@tramvai/state": "2.35.0",
33
33
  "@tinkoff/dippy": "0.8.8",
34
34
  "react": ">=16.14.0",
35
35
  "react-dom": ">=16.14.0",