@speedkit/cli 4.19.0 → 4.20.1
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/CHANGELOG.md +19 -0
- package/README.md +1 -1
- package/dist/services/customer-config/templates/config_SpeedKit.js.hbs +20 -29
- package/dist/services/customer-config/templates/config_dynamicBlocks.es6.hbs +10 -3
- package/dist/services/onboarding/fetch-events/customer-domain-document-response.d.ts +0 -8
- package/dist/services/onboarding/fetch-events/customer-domain-document-response.js +4 -29
- package/dist/services/onboarding/html-rewrites.d.ts +19 -0
- package/dist/services/onboarding/html-rewrites.js +34 -0
- package/dist/services/onboarding/onboarding-service-factory.js +1 -1
- package/dist/services/onboarding/virtual-orestes-app/index.d.ts +12 -2
- package/dist/services/onboarding/virtual-orestes-app/index.js +51 -8
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## [4.20.1](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v4.20.0...v4.20.1) (2026-08-07)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **customer-config:** marker detectDevice reads the passed doc, not document ([6f1b697](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/6f1b697b52cc6786c7e975edfcbc85c52750749b))
|
|
7
|
+
|
|
8
|
+
# [4.20.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v4.19.0...v4.20.0) (2026-08-07)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* reuse origin nonce for forceInstall script in local mode ([dd1d4d8](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/dd1d4d82e796d65708dd586ba25979c5d2dbc652))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* carry over origin security headers in onboarding local mode ([cb7e7da](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/cb7e7da7165b4071449270ee2baf71989dc16636))
|
|
19
|
+
|
|
1
20
|
# [4.19.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v4.18.0...v4.19.0) (2026-08-06)
|
|
2
21
|
|
|
3
22
|
|
package/README.md
CHANGED
|
@@ -34,14 +34,23 @@
|
|
|
34
34
|
|
|
35
35
|
{{#if isShopify}}
|
|
36
36
|
import { ShopifyPlugin } from "speed-kit-config/ShopifyPlugin";
|
|
37
|
+
{{/if}}
|
|
38
|
+
{{#if addSSR}}
|
|
39
|
+
import { SSRPlugin } from "speed-kit-config/SSRPlugin";
|
|
40
|
+
{{/if}}
|
|
37
41
|
|
|
38
42
|
(function() {
|
|
43
|
+
{{#if isShopify}}
|
|
39
44
|
SpeedKit.configPlugins = SpeedKit.configPlugins || [];
|
|
40
45
|
SpeedKit.configPlugins.push(new ShopifyPlugin());
|
|
41
|
-
|
|
42
|
-
{{else}}
|
|
43
|
-
(function() {
|
|
44
46
|
{{/if}}
|
|
47
|
+
{{#if addSSR}}
|
|
48
|
+
{{#unless isShopify}}
|
|
49
|
+
SpeedKit.configPlugins = SpeedKit.configPlugins || [];
|
|
50
|
+
{{/unless}}
|
|
51
|
+
SpeedKit.configPlugins.push(new SSRPlugin());
|
|
52
|
+
{{/if}}
|
|
53
|
+
|
|
45
54
|
var ENABLED_HOSTS_CONFIGS = [
|
|
46
55
|
// Production:
|
|
47
56
|
{
|
|
@@ -98,15 +107,20 @@ import { ShopifyPlugin } from "speed-kit-config/ShopifyPlugin";
|
|
|
98
107
|
rumTracking: true,
|
|
99
108
|
{{#if addDeviceDetection}}
|
|
100
109
|
{{#unless addSSR}}
|
|
110
|
+
// Server-side responsive: origin returns different HTML per device (UA-keyed).
|
|
101
111
|
userAgentDetection: true,
|
|
102
112
|
detectDevice: function(doc) {
|
|
103
|
-
// TODO:
|
|
113
|
+
// TODO: set a marker present only in the mobile variation. Read it from `doc`.
|
|
104
114
|
return doc.querySelector('<mobile-specific-element>') ? "mobile" : "desktop";
|
|
105
115
|
},
|
|
106
116
|
{{/unless}}
|
|
107
117
|
{{#if addSSR}}
|
|
108
|
-
|
|
109
|
-
|
|
118
|
+
// SSR: pick the device by viewport width; SSRPlugin (above) handles resize/zoom/bfcache.
|
|
119
|
+
detectDevice: function(doc) {
|
|
120
|
+
// TODO: set the real breakpoint — discover it, don't guess (tools/sk-breakpoints.mjs).
|
|
121
|
+
var width = document.documentElement.clientWidth;
|
|
122
|
+
if (!width) return null;
|
|
123
|
+
return width <= 820 ? "mobile" : "desktop";
|
|
110
124
|
},
|
|
111
125
|
{{/if}}
|
|
112
126
|
{{/if}}
|
|
@@ -458,29 +472,6 @@ import { ShopifyPlugin } from "speed-kit-config/ShopifyPlugin";
|
|
|
458
472
|
// Utility Functions
|
|
459
473
|
//================================================================================
|
|
460
474
|
|
|
461
|
-
{{#if addSSR}}
|
|
462
|
-
function customDevice() {
|
|
463
|
-
var width = window.innerWidth || 1025;
|
|
464
|
-
// TODO: adjust devices + breakpoints to your needs
|
|
465
|
-
if (width <= 820) {
|
|
466
|
-
return "mobile";
|
|
467
|
-
}
|
|
468
|
-
return "desktop";
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// Re-evaluate device on orientation change and push to service worker
|
|
472
|
-
window.addEventListener("orientationchange", function () {
|
|
473
|
-
if (typeof SpeedKit !== "undefined" && SpeedKit.updateDevice) {
|
|
474
|
-
SpeedKit.updateDevice(customDevice());
|
|
475
|
-
}
|
|
476
|
-
});
|
|
477
|
-
window.addEventListener("resize", function () {
|
|
478
|
-
if (typeof SpeedKit !== "undefined" && SpeedKit.updateDevice) {
|
|
479
|
-
SpeedKit.updateDevice(customDevice());
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
|
-
{{/if}}
|
|
483
|
-
|
|
484
475
|
function getEnabledHosts(enabledHostsConfigs) {
|
|
485
476
|
var allHosts = [];
|
|
486
477
|
enabledHostsConfigs.forEach(function (enabledHostsConfig) {
|
|
@@ -7,12 +7,19 @@
|
|
|
7
7
|
{{#if addDeviceDetection}}
|
|
8
8
|
{{#unless addSSR}}
|
|
9
9
|
detectDevice: (doc) => {
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
// TODO: adjust condition to your needs
|
|
10
|
+
// Only if you need a DOM marker; for width checks the SK config's detectDevice suffices.
|
|
11
|
+
// TODO: set a marker present only in the mobile variation. Read it from `doc`.
|
|
13
12
|
return doc.querySelector('<mobile-specific-element>') ? "mobile" : "desktop";
|
|
14
13
|
},
|
|
15
14
|
{{/unless}}
|
|
15
|
+
{{#if addSSR}}
|
|
16
|
+
// Mirror config_SpeedKit.js detectDevice — keep the breakpoint identical.
|
|
17
|
+
detectDevice: (doc) => {
|
|
18
|
+
let width = document.documentElement.clientWidth;
|
|
19
|
+
if (!width) return null;
|
|
20
|
+
return width <= 820 ? "mobile" : "desktop";
|
|
21
|
+
},
|
|
22
|
+
{{/if}}
|
|
16
23
|
{{/if}}
|
|
17
24
|
blocks: [
|
|
18
25
|
// TODO: adjust/replace this exemplary list
|
|
@@ -19,7 +19,6 @@ export declare class CustomerDomainDocumentResponse implements FetchRequestPause
|
|
|
19
19
|
private isHtmlContentHeader;
|
|
20
20
|
private getHtmlContent;
|
|
21
21
|
private isSpeedKitResponse;
|
|
22
|
-
private rewriteInstallResource;
|
|
23
22
|
private rewriteHtmlToLocalConfig;
|
|
24
23
|
/**
|
|
25
24
|
* Removes content policy headers from the given array of headers.
|
|
@@ -28,11 +27,4 @@ export declare class CustomerDomainDocumentResponse implements FetchRequestPause
|
|
|
28
27
|
* @returns {Protocol.Fetch.HeaderEntry[]} - The modified array of header entries with content policy headers removed.
|
|
29
28
|
*/
|
|
30
29
|
private removeContentSecurityHeaderFromResponse;
|
|
31
|
-
/**
|
|
32
|
-
* Removes the content security meta tag from the provided HTML content.
|
|
33
|
-
*
|
|
34
|
-
* @param {string} htmlContent - The HTML content from which to remove the meta tag.
|
|
35
|
-
* @returns {string} - The HTML content without the content security meta tag.
|
|
36
|
-
*/
|
|
37
|
-
private removeContentSecurityMetaTag;
|
|
38
30
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { META_CONTENT_TYPE_REGEX,
|
|
1
|
+
import { META_CONTENT_TYPE_REGEX, } from "../onboarding-model.js";
|
|
2
2
|
import { OriginResponse } from "../browser/origin-response.js";
|
|
3
|
+
import { removeContentSecurityMetaTag, rewriteInstallResource, } from "../html-rewrites.js";
|
|
3
4
|
import iconv from "iconv-lite";
|
|
4
5
|
import Encoding from "encoding-japanese";
|
|
5
6
|
import { UnsupportedEncodingError } from "../error/unsupported-encoding-error.js";
|
|
@@ -67,13 +68,13 @@ export class CustomerDomainDocumentResponse {
|
|
|
67
68
|
? this.removeContentSecurityHeaderFromResponse(event.responseHeaders)
|
|
68
69
|
: event.responseHeaders;
|
|
69
70
|
if (this.customerConfig.forceInstall) {
|
|
70
|
-
text =
|
|
71
|
+
text = rewriteInstallResource(text, this.customerConfig);
|
|
71
72
|
}
|
|
72
73
|
if (this.isSpeedKitResponse(text)) {
|
|
73
74
|
text = this.rewriteHtmlToLocalConfig(text);
|
|
74
75
|
}
|
|
75
76
|
const content = this.removeContentSecurityPolicy
|
|
76
|
-
?
|
|
77
|
+
? removeContentSecurityMetaTag(text)
|
|
77
78
|
: text;
|
|
78
79
|
// replace origin contentTypeCharset with utf-8 as this is what it's converted to now
|
|
79
80
|
let detectedEncoding = "utf-8";
|
|
@@ -140,23 +141,6 @@ export class CustomerDomainDocumentResponse {
|
|
|
140
141
|
isSpeedKitResponse(text) {
|
|
141
142
|
return text.includes("speed-kit-dynamic");
|
|
142
143
|
}
|
|
143
|
-
rewriteInstallResource(text) {
|
|
144
|
-
text = text.replace(SpeedKitInstallRegex, "");
|
|
145
|
-
const matchOpeningHead = /<\s*head\b[^>]*>/;
|
|
146
|
-
const matchClosingHead = /<\/\s*head\s*>/;
|
|
147
|
-
const nonceMatch = text.match(/<script\s(?:[^>]*?\s)?nonce\s*=\s*["']?([^\s"'>]*)/i);
|
|
148
|
-
if (matchOpeningHead.test(text)) {
|
|
149
|
-
return text.replace(matchOpeningHead, (head) => {
|
|
150
|
-
return `${head}\n<script ${nonceMatch ? 'nonce="' + nonceMatch[1] + '"' : ""} src="${this.customerConfig.installPath}" ${this.customerConfig.installParams}></script>`;
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
if (matchClosingHead.test(text)) {
|
|
154
|
-
return text.replace(matchClosingHead, (head) => {
|
|
155
|
-
return `<script ${nonceMatch ? 'nonce="' + nonceMatch[1] + '"' : ""} src="${this.customerConfig.installPath}" ${this.customerConfig.installParams}></script>${head}\n`;
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
return text;
|
|
159
|
-
}
|
|
160
144
|
rewriteHtmlToLocalConfig(html) {
|
|
161
145
|
const DF_CONFIG_PATTERN = /(<script[^>]* id="speed-kit-df-config"[^>]*>)([\S\s]*?)(<\/script>)/g;
|
|
162
146
|
const DF_STYLES_PATTERN = /(<style[^>]* id="speed-kit-df-styles"[^>]*>)(?:(?!<\/style>)[\S\s])*(<\/style>)/g;
|
|
@@ -178,13 +162,4 @@ export class CustomerDomainDocumentResponse {
|
|
|
178
162
|
return !header.name.toLowerCase().includes("content-security-policy");
|
|
179
163
|
});
|
|
180
164
|
}
|
|
181
|
-
/**
|
|
182
|
-
* Removes the content security meta tag from the provided HTML content.
|
|
183
|
-
*
|
|
184
|
-
* @param {string} htmlContent - The HTML content from which to remove the meta tag.
|
|
185
|
-
* @returns {string} - The HTML content without the content security meta tag.
|
|
186
|
-
*/
|
|
187
|
-
removeContentSecurityMetaTag(htmlContent) {
|
|
188
|
-
return htmlContent.replace(/<meta[^>]*http-equiv=["']content-security-policy[^>]*>/is, "");
|
|
189
|
-
}
|
|
190
165
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CustomerConfig } from "../integration-api/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Injects the Speed Kit install script into the given HTML, after removing any
|
|
4
|
+
* existing install snippet. The origin's script nonce is reused so a
|
|
5
|
+
* nonce-based CSP does not block the injected script. Injection prefers the
|
|
6
|
+
* opening `<head>`, falling back to before the closing `</head>`.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} text - the HTML to rewrite
|
|
9
|
+
* @param {CustomerConfig} customerConfig - provides the install path/params
|
|
10
|
+
* @returns {string} - the HTML with the install script injected
|
|
11
|
+
*/
|
|
12
|
+
export declare function rewriteInstallResource(text: string, customerConfig: CustomerConfig): string;
|
|
13
|
+
/**
|
|
14
|
+
* Removes the content security meta tag from the provided HTML content.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} htmlContent - The HTML content from which to remove the meta tag.
|
|
17
|
+
* @returns {string} - The HTML content without the content security meta tag.
|
|
18
|
+
*/
|
|
19
|
+
export declare function removeContentSecurityMetaTag(htmlContent: string): string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { SpeedKitInstallRegex } from "./onboarding-model.js";
|
|
2
|
+
/**
|
|
3
|
+
* Injects the Speed Kit install script into the given HTML, after removing any
|
|
4
|
+
* existing install snippet. The origin's script nonce is reused so a
|
|
5
|
+
* nonce-based CSP does not block the injected script. Injection prefers the
|
|
6
|
+
* opening `<head>`, falling back to before the closing `</head>`.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} text - the HTML to rewrite
|
|
9
|
+
* @param {CustomerConfig} customerConfig - provides the install path/params
|
|
10
|
+
* @returns {string} - the HTML with the install script injected
|
|
11
|
+
*/
|
|
12
|
+
export function rewriteInstallResource(text, customerConfig) {
|
|
13
|
+
text = text.replace(SpeedKitInstallRegex, "");
|
|
14
|
+
const matchOpeningHead = /<\s*head\b[^>]*>/;
|
|
15
|
+
const matchClosingHead = /<\/\s*head\s*>/;
|
|
16
|
+
const nonceMatch = text.match(/<script\s(?:[^>]*?\s)?nonce\s*=\s*["']?([^\s"'>]*)/i);
|
|
17
|
+
const installScript = `<script ${nonceMatch ? 'nonce="' + nonceMatch[1] + '"' : ""} src="${customerConfig.installPath}" ${customerConfig.installParams}></script>`;
|
|
18
|
+
if (matchOpeningHead.test(text)) {
|
|
19
|
+
return text.replace(matchOpeningHead, (head) => `${head}\n${installScript}`);
|
|
20
|
+
}
|
|
21
|
+
if (matchClosingHead.test(text)) {
|
|
22
|
+
return text.replace(matchClosingHead, (head) => `${installScript}${head}\n`);
|
|
23
|
+
}
|
|
24
|
+
return text;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Removes the content security meta tag from the provided HTML content.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} htmlContent - The HTML content from which to remove the meta tag.
|
|
30
|
+
* @returns {string} - The HTML content without the content security meta tag.
|
|
31
|
+
*/
|
|
32
|
+
export function removeContentSecurityMetaTag(htmlContent) {
|
|
33
|
+
return htmlContent.replace(/<meta[^>]*http-equiv=["']content-security-policy[^>]*>/is, "");
|
|
34
|
+
}
|
|
@@ -166,7 +166,7 @@ export class OnboardingServiceFactory {
|
|
|
166
166
|
new DashboardRequest(new Dashboard(customerConfig, parameterQueryBuilder, athenaClient, requestDiffService, new DiffAgainstCurrentPage(DiffService, documentHandler, crawler, cli), cache)),
|
|
167
167
|
];
|
|
168
168
|
if (this.context.local) {
|
|
169
|
-
const orestesApp = new VirtualOrestesApp(customerConfig, crawler, documentHandler, cache, cli, messageHandler);
|
|
169
|
+
const orestesApp = new VirtualOrestesApp(customerConfig, crawler, documentHandler, cache, cli, messageHandler, this.context.ignoreContentSecurityPolicy);
|
|
170
170
|
// let config-file changes re-warm the dh-cache in the background
|
|
171
171
|
fileWatcher.setCacheRefresher(orestesApp);
|
|
172
172
|
handlers.push(new SpeedKitAssetRequest(customerConfig, orestesApp), new SpeedKitRumPiRequest(customerConfig, orestesApp));
|
|
@@ -13,8 +13,9 @@ export declare class VirtualOrestesApp {
|
|
|
13
13
|
private cache;
|
|
14
14
|
private cli;
|
|
15
15
|
private developmentToolsMessages;
|
|
16
|
+
private removeContentSecurityPolicy;
|
|
16
17
|
private localCacheErrors;
|
|
17
|
-
constructor(customerConfig: CustomerConfig, crawler: Crawler, documentHandler: DocumentHandlerServer, cache: Cache, cli: CliService, developmentToolsMessages: DevtoolsExtensionApi);
|
|
18
|
+
constructor(customerConfig: CustomerConfig, crawler: Crawler, documentHandler: DocumentHandlerServer, cache: Cache, cli: CliService, developmentToolsMessages: DevtoolsExtensionApi, removeContentSecurityPolicy?: boolean);
|
|
18
19
|
fetchAssetResponse(event: Protocol.Fetch.RequestPausedEvent): Promise<Partial<Protocol.Fetch.FulfillRequestRequest> | AbortResponse>;
|
|
19
20
|
/**
|
|
20
21
|
* Re-warm all cached entries for the given origin url in the background.
|
|
@@ -60,5 +61,14 @@ export declare class VirtualOrestesApp {
|
|
|
60
61
|
private prepareCustomHeaders;
|
|
61
62
|
private isHtmlContentHeader;
|
|
62
63
|
private isValidHtmlContent;
|
|
63
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Carries over the origin response's security headers onto the accelerated
|
|
66
|
+
* response, mirroring the Orestes server. The origin value wins over any
|
|
67
|
+
* default set in `prepareCustomHeaders`. Content-Security-Policy headers are
|
|
68
|
+
* skipped when `--ignoreContentSecurityPolicy` is set.
|
|
69
|
+
*
|
|
70
|
+
* @param {Response} response - the origin fetch response
|
|
71
|
+
* @param {{ name: string; value: string }[]} customHeaders - headers being built for the response (mutated in place)
|
|
72
|
+
*/
|
|
73
|
+
private carryOverSecurityHeaders;
|
|
64
74
|
}
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import { BaqendResponse } from "../browser/baqend-response.js";
|
|
2
2
|
import { safe } from "../../../helpers/safe.js";
|
|
3
|
-
import { META_CONTENT_TYPE_REGEX,
|
|
3
|
+
import { META_CONTENT_TYPE_REGEX, } from "../onboarding-model.js";
|
|
4
4
|
import iconv from "iconv-lite";
|
|
5
5
|
import { UnsupportedEncodingError } from "../error/unsupported-encoding-error.js";
|
|
6
|
+
import { removeContentSecurityMetaTag, rewriteInstallResource, } from "../html-rewrites.js";
|
|
6
7
|
// for none encodable chars use a space, so the broken char is invisible.
|
|
7
8
|
// We are using the same trick in our node.js server
|
|
8
9
|
iconv.defaultCharSingleByte = " ";
|
|
9
10
|
iconv.defaultCharUnicode = " ";
|
|
11
|
+
// Security headers the Orestes server carries over from the origin response
|
|
12
|
+
// onto the accelerated response. Kept in sync with the server's fixed lists.
|
|
13
|
+
const CONTENT_SECURITY_POLICY_HEADERS = [
|
|
14
|
+
"content-security-policy",
|
|
15
|
+
"content-security-policy-report-only",
|
|
16
|
+
];
|
|
17
|
+
const SECURITY_HEADERS = [
|
|
18
|
+
"x-content-type-options",
|
|
19
|
+
"strict-transport-security",
|
|
20
|
+
"x-frame-options",
|
|
21
|
+
];
|
|
10
22
|
export class VirtualOrestesApp {
|
|
11
23
|
customerConfig;
|
|
12
24
|
crawler;
|
|
@@ -14,18 +26,20 @@ export class VirtualOrestesApp {
|
|
|
14
26
|
cache;
|
|
15
27
|
cli;
|
|
16
28
|
developmentToolsMessages;
|
|
29
|
+
removeContentSecurityPolicy;
|
|
17
30
|
// Local-mode transform errors keyed by `${url}|${variation}`. Emitted to
|
|
18
31
|
// the devtools panel via DevtoolsExtensionApi.upsertMessage so the panel
|
|
19
32
|
// can surface an "error for this URL" affordance in the Causes & sources
|
|
20
33
|
// card. Cleared per-URL on the next successful transform.
|
|
21
34
|
localCacheErrors = new Map();
|
|
22
|
-
constructor(customerConfig, crawler, documentHandler, cache, cli, developmentToolsMessages) {
|
|
35
|
+
constructor(customerConfig, crawler, documentHandler, cache, cli, developmentToolsMessages, removeContentSecurityPolicy = false) {
|
|
23
36
|
this.customerConfig = customerConfig;
|
|
24
37
|
this.crawler = crawler;
|
|
25
38
|
this.documentHandler = documentHandler;
|
|
26
39
|
this.cache = cache;
|
|
27
40
|
this.cli = cli;
|
|
28
41
|
this.developmentToolsMessages = developmentToolsMessages;
|
|
42
|
+
this.removeContentSecurityPolicy = removeContentSecurityPolicy;
|
|
29
43
|
}
|
|
30
44
|
async fetchAssetResponse(event) {
|
|
31
45
|
const cachedResponse = this.getCachedResponse(event.request.url);
|
|
@@ -131,7 +145,10 @@ export class VirtualOrestesApp {
|
|
|
131
145
|
this.clearLocalCacheError(originUrl, variation);
|
|
132
146
|
const skResponse = customResponse.data;
|
|
133
147
|
if (!Buffer.isBuffer(skResponse.body) && this.customerConfig.forceInstall) {
|
|
134
|
-
skResponse.body =
|
|
148
|
+
skResponse.body = rewriteInstallResource(skResponse.body, this.customerConfig);
|
|
149
|
+
}
|
|
150
|
+
if (!Buffer.isBuffer(skResponse.body) && this.removeContentSecurityPolicy) {
|
|
151
|
+
skResponse.body = removeContentSecurityMetaTag(skResponse.body);
|
|
135
152
|
}
|
|
136
153
|
for (const key in skResponse?.headers) {
|
|
137
154
|
customHeaders.push({
|
|
@@ -139,6 +156,11 @@ export class VirtualOrestesApp {
|
|
|
139
156
|
value: skResponse?.headers[key],
|
|
140
157
|
});
|
|
141
158
|
}
|
|
159
|
+
// The document handler does not emit the origin's security headers, so —
|
|
160
|
+
// like the Orestes server — carry them over from the origin response onto
|
|
161
|
+
// the accelerated response. Content-Security-Policy is only carried over
|
|
162
|
+
// when `--ignoreContentSecurityPolicy` is not set.
|
|
163
|
+
this.carryOverSecurityHeaders(response, customHeaders);
|
|
142
164
|
const originHeaders = [];
|
|
143
165
|
response?.headers.forEach((value, key) => {
|
|
144
166
|
originHeaders.push({ name: `x-origin-${key}`, value: value });
|
|
@@ -309,10 +331,31 @@ export class VirtualOrestesApp {
|
|
|
309
331
|
isValidHtmlContent(text) {
|
|
310
332
|
return text.includes("<html");
|
|
311
333
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
334
|
+
/**
|
|
335
|
+
* Carries over the origin response's security headers onto the accelerated
|
|
336
|
+
* response, mirroring the Orestes server. The origin value wins over any
|
|
337
|
+
* default set in `prepareCustomHeaders`. Content-Security-Policy headers are
|
|
338
|
+
* skipped when `--ignoreContentSecurityPolicy` is set.
|
|
339
|
+
*
|
|
340
|
+
* @param {Response} response - the origin fetch response
|
|
341
|
+
* @param {{ name: string; value: string }[]} customHeaders - headers being built for the response (mutated in place)
|
|
342
|
+
*/
|
|
343
|
+
carryOverSecurityHeaders(response, customHeaders) {
|
|
344
|
+
const headersToCarryOver = this.removeContentSecurityPolicy
|
|
345
|
+
? SECURITY_HEADERS
|
|
346
|
+
: [...CONTENT_SECURITY_POLICY_HEADERS, ...SECURITY_HEADERS];
|
|
347
|
+
for (const name of headersToCarryOver) {
|
|
348
|
+
const value = response?.headers?.get(name);
|
|
349
|
+
if (value == null) {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
const existing = customHeaders.findIndex((header) => header.name.toLowerCase() === name);
|
|
353
|
+
if (existing === -1) {
|
|
354
|
+
customHeaders.push({ name, value });
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
customHeaders[existing] = { name, value };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
317
360
|
}
|
|
318
361
|
}
|
package/oclif.manifest.json
CHANGED