@tramvai/module-page-render-mode 7.5.3 → 7.7.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.
Files changed (35) hide show
  1. package/lib/PageRenderWrapper.browser.js +4 -4
  2. package/lib/PageRenderWrapper.d.ts +1 -1
  3. package/lib/PageRenderWrapper.es.js +4 -4
  4. package/lib/PageRenderWrapper.js +3 -3
  5. package/lib/browser.js +27 -3
  6. package/lib/private-tokens.browser.js +9 -0
  7. package/lib/private-tokens.d.ts +27 -0
  8. package/lib/private-tokens.es.js +9 -0
  9. package/lib/private-tokens.js +17 -0
  10. package/lib/server.es.js +1 -1
  11. package/lib/server.js +5 -0
  12. package/lib/staticPages/backgroundFetchService.d.ts +9 -7
  13. package/lib/staticPages/backgroundFetchService.es.js +20 -20
  14. package/lib/staticPages/backgroundFetchService.js +20 -20
  15. package/lib/staticPages/fileSystemCache.d.ts +72 -0
  16. package/lib/staticPages/fileSystemCache.es.js +367 -0
  17. package/lib/staticPages/fileSystemCache.js +376 -0
  18. package/lib/staticPages/staticPagesService.d.ts +16 -9
  19. package/lib/staticPages/staticPagesService.es.js +124 -39
  20. package/lib/staticPages/staticPagesService.js +124 -39
  21. package/lib/staticPages.d.ts +410 -155
  22. package/lib/staticPages.es.js +233 -67
  23. package/lib/staticPages.js +232 -70
  24. package/lib/tokens.browser.js +15 -1
  25. package/lib/tokens.d.ts +90 -32
  26. package/lib/tokens.es.js +15 -1
  27. package/lib/tokens.js +19 -0
  28. package/lib/utils/cacheKey.d.ts +4 -6
  29. package/lib/utils/cacheKey.es.js +8 -3
  30. package/lib/utils/cacheKey.js +8 -2
  31. package/lib/utils/getPageRenderMode.browser.js +14 -2
  32. package/lib/utils/getPageRenderMode.d.ts +8 -3
  33. package/lib/utils/getPageRenderMode.es.js +14 -2
  34. package/lib/utils/getPageRenderMode.js +14 -2
  35. package/package.json +16 -14
@@ -2,72 +2,115 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- // It is critical to ignore cached Set-Cookie header and use fresh one from current request
6
- // COMMAND_LINE_EXECUTION_END_TOKEN with fresh server timings will not works for responses from cache
7
- const HEADERS_BLACKLIST = ['Set-Cookie', 'server-timing'];
5
+ var cacheKey = require('../utils/cacheKey.js');
6
+
7
+ // `Location` is required for 3xx responses
8
+ const DEFAULT_HEADERS_WHITELIST = ['Location', 'Content-Type', 'Content-Length', 'X-App-Id'];
8
9
  class StaticPagesService {
9
10
  key;
10
- path;
11
+ pathname;
12
+ cacheKey;
11
13
  port;
12
- deviceType;
13
14
  responseManager;
15
+ requestManager;
14
16
  response;
15
17
  log;
16
18
  cache;
19
+ fsCache;
20
+ fsCacheEnabled;
17
21
  modifyCache;
18
22
  backgroundFetchService;
19
23
  options;
20
24
  cache5xxResponse;
25
+ cacheControlFactory;
21
26
  shouldUseCache;
22
- constructor({ getCacheKey, requestManager, response, responseManager, environmentManager, userAgent, logger, cache, modifyCache, shouldUseCache, backgroundFetchService, options, cache5xxResponse, }) {
23
- this.key = getCacheKey();
24
- this.path = requestManager.getParsedUrl().pathname;
27
+ constructor({ staticPagesKey, requestManager, response, responseManager, environmentManager, logger, cache, fsCache, fsCacheEnabled, modifyCache, shouldUseCache, backgroundFetchService, options, cache5xxResponse, cacheControlFactory, }) {
28
+ this.key = staticPagesKey();
29
+ this.pathname = requestManager.getParsedUrl().pathname;
30
+ this.cacheKey = cacheKey.getCacheKey({ pathname: this.pathname, key: this.key });
25
31
  this.port = environmentManager.get('PORT');
26
- this.deviceType = userAgent.mobileOS ? 'mobile' : 'desktop';
27
32
  this.log = logger('static-pages');
28
33
  this.responseManager = responseManager;
34
+ this.requestManager = requestManager;
29
35
  this.response = response;
30
36
  this.cache = cache;
37
+ this.fsCacheEnabled = fsCacheEnabled();
38
+ this.fsCache = fsCache;
31
39
  this.modifyCache = modifyCache;
32
40
  this.shouldUseCache = () => shouldUseCache.every((fn) => fn());
33
41
  this.backgroundFetchService = backgroundFetchService;
34
42
  this.options = options;
35
43
  this.cache5xxResponse = cache5xxResponse;
44
+ this.cacheControlFactory = cacheControlFactory;
36
45
  }
37
- respond(onSuccess) {
46
+ async respond(onSuccess) {
38
47
  if (!this.hasCache()) {
39
48
  this.log.debug({
40
49
  event: 'no-cache',
41
- key: this.key,
50
+ cacheKey: this.cacheKey,
42
51
  });
52
+ setTimeout(() => {
53
+ // async revalidation, response is not delayed
54
+ this.revalidate();
55
+ }, 1).unref();
43
56
  return;
44
57
  }
45
- let cacheEntry = this.getCache();
58
+ let cacheEntry = await this.getCache();
46
59
  if (Array.isArray(this.modifyCache)) {
47
60
  cacheEntry = this.modifyCache.reduce((result, modifier) => {
48
61
  return modifier(result);
49
62
  }, cacheEntry);
50
63
  }
51
- const { status, headers, body } = cacheEntry;
64
+ const { ttl, allowStale } = this.options;
65
+ const { status, headers, body, source, updatedAt } = cacheEntry;
52
66
  const isOutdated = this.cacheOutdated(cacheEntry);
53
- const currentHeaders = this.responseManager.getHeaders();
54
- if (!isOutdated) {
67
+ if (!this.cache5xxResponse() && status >= 500) {
68
+ this.log.debug({
69
+ event: 'cache-5xx',
70
+ cacheKey: this.cacheKey,
71
+ status,
72
+ });
73
+ setTimeout(() => {
74
+ // it is possible that 5xx response is generated while "tramvai static" command,
75
+ // we can just remove it when `STATIC_PAGES_CACHE_5xx_RESPONSE` is disabled
76
+ // async revalidation, response is not delayed
77
+ this.revalidate();
78
+ }, 1).unref();
79
+ return;
80
+ }
81
+ const isStale = isOutdated && allowStale;
82
+ if (!isOutdated || isStale) {
55
83
  this.log.debug({
56
84
  event: 'cache-hit',
57
- key: this.key,
85
+ cacheKey: this.cacheKey,
86
+ stale: isStale,
58
87
  });
59
- HEADERS_BLACKLIST.forEach((header) => {
88
+ if (isStale) {
89
+ setTimeout(() => {
90
+ // async revalidation, response is not delayed
91
+ this.revalidate();
92
+ }, 1).unref();
93
+ }
94
+ const allowedHeaders = {};
95
+ this.options.allowedHeaders.concat(DEFAULT_HEADERS_WHITELIST).forEach((header) => {
96
+ const lowercaseHeader = header.toLowerCase();
60
97
  if (headers[header]) {
61
- delete headers[header];
98
+ allowedHeaders[header] = headers[header];
62
99
  }
63
- if (currentHeaders[header]) {
64
- headers[header] = currentHeaders[header];
100
+ else if (headers[lowercaseHeader]) {
101
+ allowedHeaders[lowercaseHeader] = headers[lowercaseHeader];
65
102
  }
66
103
  });
67
104
  this.response
105
+ .headers(allowedHeaders)
68
106
  .header('content-type', 'text/html')
107
+ .header('cache-control', this.cacheControlFactory({ ttl, updatedAt }))
108
+ // Vary header is required for correct cache behavior in CDNs and browsers,
109
+ // it indicates that response may vary based on `X-Tramvai-Static-Page-Key` header
110
+ .header('Vary', 'X-Tramvai-Static-Page-Key')
111
+ .header('X-Tramvai-Static-Page-Key', this.key)
69
112
  .header('X-Tramvai-Static-Page-From-Cache', 'true')
70
- .headers(headers)
113
+ .header('X-Tramvai-Static-Page-Cache-Source', source)
71
114
  .status(status)
72
115
  .send(body);
73
116
  onSuccess();
@@ -75,8 +118,12 @@ class StaticPagesService {
75
118
  else {
76
119
  this.log.debug({
77
120
  event: 'cache-outdated',
78
- key: this.key,
121
+ cacheKey: this.cacheKey,
79
122
  });
123
+ setTimeout(() => {
124
+ // async revalidation, response is not delayed
125
+ this.revalidate();
126
+ }, 1).unref();
80
127
  }
81
128
  }
82
129
  async revalidate() {
@@ -84,18 +131,28 @@ class StaticPagesService {
84
131
  return;
85
132
  }
86
133
  if (this.hasCache()) {
87
- const cacheEntry = this.getCache();
134
+ const cacheEntry = await this.getCache();
88
135
  const isOutdated = this.cacheOutdated(cacheEntry);
89
136
  if (!isOutdated) {
90
137
  return;
91
138
  }
92
139
  }
140
+ const incomingHeaders = this.requestManager.getHeaders();
141
+ const revalidateHeaders = {};
142
+ this.options.allowedHeaders.concat(DEFAULT_HEADERS_WHITELIST).forEach((header) => {
143
+ const lowercaseHeader = header.toLowerCase();
144
+ if (incomingHeaders[header]) {
145
+ revalidateHeaders[header] = incomingHeaders[header];
146
+ }
147
+ else if (incomingHeaders[lowercaseHeader]) {
148
+ revalidateHeaders[lowercaseHeader] = incomingHeaders[lowercaseHeader];
149
+ }
150
+ });
93
151
  await this.backgroundFetchService
94
152
  .revalidate({
95
- key: this.key,
96
- path: this.path,
97
- port: this.port,
98
- deviceType: this.deviceType,
153
+ cacheKey: this.cacheKey,
154
+ pathname: this.pathname,
155
+ headers: revalidateHeaders,
99
156
  })
100
157
  .then((response) => {
101
158
  if (!response) {
@@ -104,31 +161,59 @@ class StaticPagesService {
104
161
  if (!this.cache5xxResponse() && response.status >= 500) {
105
162
  this.log.debug({
106
163
  event: 'cache-set-5xx',
107
- key: this.key,
164
+ cacheKey: this.cacheKey,
108
165
  });
109
166
  return;
110
167
  }
111
- this.setCache(response);
168
+ const cachedHeaders = {};
169
+ const responseHeaders = response.headers;
170
+ this.options.allowedHeaders.concat(DEFAULT_HEADERS_WHITELIST).forEach((header) => {
171
+ const lowercaseHeader = header.toLowerCase();
172
+ if (responseHeaders[header]) {
173
+ cachedHeaders[header] = responseHeaders[header];
174
+ }
175
+ else if (responseHeaders[lowercaseHeader]) {
176
+ cachedHeaders[lowercaseHeader] = responseHeaders[lowercaseHeader];
177
+ }
178
+ });
179
+ return this.setCache({ ...response, headers: cachedHeaders });
112
180
  });
113
181
  }
114
182
  hasCache() {
115
- return this.cache.has(this.path) && this.cache.get(this.path).has(this.key);
183
+ let result = this.cache.has(this.cacheKey);
184
+ if (!result && this.fsCacheEnabled) {
185
+ result = this.fsCache.has(this.cacheKey);
186
+ }
187
+ return result;
116
188
  }
117
- getCache() {
118
- return this.cache.get(this.path).get(this.key);
189
+ async getCache() {
190
+ let result = this.cache.get(this.cacheKey);
191
+ // update item recency in FS-cache
192
+ if (result && this.fsCacheEnabled) {
193
+ this.fsCache.moveToHead(this.cacheKey);
194
+ }
195
+ if (!result && this.fsCacheEnabled) {
196
+ result = await this.fsCache.get(this.cacheKey);
197
+ // always fill memory cache
198
+ if (result) {
199
+ this.cache.set(this.cacheKey, { ...result, source: 'memory' });
200
+ }
201
+ }
202
+ return result;
119
203
  }
120
- setCache(cacheEntry) {
204
+ async setCache(cacheEntry) {
121
205
  this.log.debug({
122
206
  event: 'cache-set',
123
- key: this.key,
207
+ cacheKey: this.cacheKey,
124
208
  });
125
- if (!this.cache.has(this.path)) {
126
- this.cache.set(this.path, new Map());
127
- }
128
- this.cache.get(this.path).set(this.key, {
209
+ const entry = {
129
210
  ...cacheEntry,
130
211
  updatedAt: Date.now(),
131
- });
212
+ };
213
+ this.cache.set(this.cacheKey, { ...entry, source: 'memory' });
214
+ if (this.fsCacheEnabled) {
215
+ await this.fsCache.set(this.cacheKey, { ...entry, source: 'fs' });
216
+ }
132
217
  }
133
218
  cacheOutdated(cacheEntry) {
134
219
  const { ttl } = this.options;