@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.
- package/lib/PageRenderWrapper.browser.js +4 -4
- package/lib/PageRenderWrapper.d.ts +1 -1
- package/lib/PageRenderWrapper.es.js +4 -4
- package/lib/PageRenderWrapper.js +3 -3
- package/lib/browser.js +27 -3
- package/lib/private-tokens.browser.js +9 -0
- package/lib/private-tokens.d.ts +27 -0
- package/lib/private-tokens.es.js +9 -0
- package/lib/private-tokens.js +17 -0
- package/lib/server.es.js +1 -1
- package/lib/server.js +5 -0
- package/lib/staticPages/backgroundFetchService.d.ts +9 -7
- package/lib/staticPages/backgroundFetchService.es.js +20 -20
- package/lib/staticPages/backgroundFetchService.js +20 -20
- package/lib/staticPages/fileSystemCache.d.ts +72 -0
- package/lib/staticPages/fileSystemCache.es.js +367 -0
- package/lib/staticPages/fileSystemCache.js +376 -0
- package/lib/staticPages/staticPagesService.d.ts +16 -9
- package/lib/staticPages/staticPagesService.es.js +124 -39
- package/lib/staticPages/staticPagesService.js +124 -39
- package/lib/staticPages.d.ts +410 -155
- package/lib/staticPages.es.js +233 -67
- package/lib/staticPages.js +232 -70
- package/lib/tokens.browser.js +15 -1
- package/lib/tokens.d.ts +90 -32
- package/lib/tokens.es.js +15 -1
- package/lib/tokens.js +19 -0
- package/lib/utils/cacheKey.d.ts +4 -6
- package/lib/utils/cacheKey.es.js +8 -3
- package/lib/utils/cacheKey.js +8 -2
- package/lib/utils/getPageRenderMode.browser.js +14 -2
- package/lib/utils/getPageRenderMode.d.ts +8 -3
- package/lib/utils/getPageRenderMode.es.js +14 -2
- package/lib/utils/getPageRenderMode.js +14 -2
- package/package.json +16 -14
|
@@ -2,72 +2,115 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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({
|
|
23
|
-
this.key =
|
|
24
|
-
this.
|
|
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
|
-
|
|
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 {
|
|
64
|
+
const { ttl, allowStale } = this.options;
|
|
65
|
+
const { status, headers, body, source, updatedAt } = cacheEntry;
|
|
52
66
|
const isOutdated = this.cacheOutdated(cacheEntry);
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
85
|
+
cacheKey: this.cacheKey,
|
|
86
|
+
stale: isStale,
|
|
58
87
|
});
|
|
59
|
-
|
|
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
|
-
|
|
98
|
+
allowedHeaders[header] = headers[header];
|
|
62
99
|
}
|
|
63
|
-
if (
|
|
64
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
164
|
+
cacheKey: this.cacheKey,
|
|
108
165
|
});
|
|
109
166
|
return;
|
|
110
167
|
}
|
|
111
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
207
|
+
cacheKey: this.cacheKey,
|
|
124
208
|
});
|
|
125
|
-
|
|
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;
|