single-file-cli 2.6.0 → 2.6.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/build.sh +1 -1
- package/deno.json +1 -1
- package/lib/cdp-client.js +29 -18
- package/lib/single-file-archive.js +4 -4
- package/lib/single-file-bundle.js +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
- package/test/e2e/backend-fetch.test.js +92 -0
package/build.sh
CHANGED
package/deno.json
CHANGED
package/lib/cdp-client.js
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
} from "./browser.js";
|
|
32
32
|
import {
|
|
33
33
|
CDP,
|
|
34
|
-
options
|
|
34
|
+
options as cdpOptions
|
|
35
35
|
} from "simple-cdp";
|
|
36
36
|
import {
|
|
37
37
|
FETCH_FUNCTION_NAME,
|
|
@@ -66,6 +66,7 @@ const CORS_SAFELISTED_HEADER_NAMES = ["accept", "accept-language", "content-lang
|
|
|
66
66
|
const CORS_UNSAFE_HEADER_VALUE = /[^\t\x20-\x7e]|[():<>?@[\\\]{}]/;
|
|
67
67
|
const CORS_SAFELISTED_HEADER_VALUE_MAX_LENGTH = 128;
|
|
68
68
|
const SERVICE_WORKER_TARGET_TYPE = "service_worker";
|
|
69
|
+
const USER_AGENT_HEADER_NAME = "user-agent";
|
|
69
70
|
|
|
70
71
|
let browserOptions, relaunchBrowserPromise;
|
|
71
72
|
|
|
@@ -77,10 +78,10 @@ export {
|
|
|
77
78
|
|
|
78
79
|
async function initialize(singleFileOptions) {
|
|
79
80
|
if (singleFileOptions.browserServer) {
|
|
80
|
-
|
|
81
|
+
cdpOptions.apiUrl = singleFileOptions.browserServer;
|
|
81
82
|
} else {
|
|
82
83
|
browserOptions = getBrowserOptions(singleFileOptions);
|
|
83
|
-
|
|
84
|
+
cdpOptions.apiUrl = LOCALHOST + (await launchBrowser(browserOptions));
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
|
|
@@ -90,7 +91,7 @@ function relaunchBrowser() {
|
|
|
90
91
|
relaunchBrowserPromise = (async () => {
|
|
91
92
|
await closeBrowser();
|
|
92
93
|
browserOptions.singleProcess = false;
|
|
93
|
-
|
|
94
|
+
cdpOptions.apiUrl = LOCALHOST + (await launchBrowser(browserOptions));
|
|
94
95
|
})();
|
|
95
96
|
}
|
|
96
97
|
return relaunchBrowserPromise;
|
|
@@ -101,7 +102,7 @@ async function getPageData(options) {
|
|
|
101
102
|
// compiled here so that an invalid pattern is reported before the page is
|
|
102
103
|
// loaded, instead of throwing for every request that is intercepted
|
|
103
104
|
const blockedURLPatterns = (options.blockedURLPatterns || []).map(pattern => new RegExp(pattern));
|
|
104
|
-
const pageContext = { options, consoleMessages: [], debugMessages: [], httpInfo: {}, blockedURLPatterns, fetchAbortController: new AbortController() };
|
|
105
|
+
const pageContext = { options, consoleMessages: [], debugMessages: [], httpInfo: {}, browserInfo: {}, blockedURLPatterns, fetchAbortController: new AbortController() };
|
|
105
106
|
let targetInfo, cdp;
|
|
106
107
|
try {
|
|
107
108
|
logData(["Loading page", EMPTY_PAGE_URL], pageContext);
|
|
@@ -111,7 +112,8 @@ async function getPageData(options) {
|
|
|
111
112
|
await setupBrowserWindow(cdp, targetInfo.id, pageContext);
|
|
112
113
|
await setupSecurity(cdp, pageContext);
|
|
113
114
|
await setupDeviceEmulation(cdp, pageContext);
|
|
114
|
-
await
|
|
115
|
+
await setupNetwork(cdp, pageContext);
|
|
116
|
+
await setupDownloadBehavior(cdp);
|
|
115
117
|
await setupScriptInjection(cdp, pageContext);
|
|
116
118
|
const contextId = await getContextId(cdp, pageContext);
|
|
117
119
|
const pageDataPromise = setupPageDataCapture(cdp, pageContext);
|
|
@@ -211,13 +213,18 @@ async function setupSecurity({ Security }, { options, debugMessages }, sessionId
|
|
|
211
213
|
}
|
|
212
214
|
}
|
|
213
215
|
|
|
214
|
-
async function setupDeviceEmulation({ Browser, Emulation, Runtime }, { options, debugMessages }) {
|
|
216
|
+
async function setupDeviceEmulation({ Browser, Emulation, Runtime }, { options, debugMessages, browserInfo }) {
|
|
215
217
|
const needsDeviceMetrics = options.browserMobileEmulation || options.browserDeviceWidth ||
|
|
216
218
|
options.browserDeviceHeight || options.browserDeviceScaleFactor;
|
|
217
219
|
if (needsDeviceMetrics) {
|
|
218
220
|
await setupDeviceMetrics({ Emulation, Runtime }, { options, debugMessages });
|
|
219
221
|
}
|
|
220
|
-
|
|
222
|
+
if (options.emulateMediaFeatures) {
|
|
223
|
+
await setupMediaFeatures({ Emulation }, { options, debugMessages });
|
|
224
|
+
}
|
|
225
|
+
// kept so that the resources fetched outside the browser present the same
|
|
226
|
+
// identity as the browser itself
|
|
227
|
+
browserInfo.userAgent = await setupUserAgent({ Browser, Emulation }, { options, debugMessages });
|
|
221
228
|
}
|
|
222
229
|
|
|
223
230
|
function needsUserAgentOverride(options) {
|
|
@@ -261,14 +268,14 @@ async function setupUserAgent({ Browser, Emulation }, { options, debugMessages }
|
|
|
261
268
|
logData(["Emulating user agent", JSON.stringify(agentOptions)], { options, debugMessages });
|
|
262
269
|
await Emulation.setUserAgentOverride(agentOptions, sessionId);
|
|
263
270
|
}
|
|
271
|
+
return agentOptions.userAgent;
|
|
264
272
|
}
|
|
265
273
|
|
|
266
274
|
function removeHeadlessToken(userAgent) {
|
|
267
275
|
return userAgent.replace(HEADLESS_USER_AGENT_TOKEN, "");
|
|
268
276
|
}
|
|
269
277
|
|
|
270
|
-
async function
|
|
271
|
-
const DENY_BEHAVIOR = "deny";
|
|
278
|
+
async function setupNetwork({ Fetch, Network }, { options, debugMessages, httpInfo, blockedURLPatterns }) {
|
|
272
279
|
const { handleAuthRequests, patterns } = getInterceptionOptions(options);
|
|
273
280
|
await Fetch.enable({ handleAuthRequests, patterns });
|
|
274
281
|
if (handleAuthRequests) {
|
|
@@ -278,12 +285,13 @@ async function setupNetworkInterception({ Browser, Emulation, Fetch, Network },
|
|
|
278
285
|
if (options.httpHeaders && !hasUnsafeHttpHeaders(options)) {
|
|
279
286
|
await setupHttpHeaders({ Network }, { options, debugMessages });
|
|
280
287
|
}
|
|
281
|
-
if (options.emulateMediaFeatures) {
|
|
282
|
-
await setupMediaFeatures({ Emulation }, { options, debugMessages });
|
|
283
|
-
}
|
|
284
288
|
if (options.browserCookies && options.browserCookies.length) {
|
|
285
289
|
await setupCookies({ Network }, { options, debugMessages });
|
|
286
290
|
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function setupDownloadBehavior({ Browser }) {
|
|
294
|
+
const DENY_BEHAVIOR = "deny";
|
|
287
295
|
await Browser.setDownloadBehavior({ behavior: DENY_BEHAVIOR });
|
|
288
296
|
}
|
|
289
297
|
|
|
@@ -693,7 +701,7 @@ function setupPageDataCapture({ Runtime }, { options, debugMessages }) {
|
|
|
693
701
|
});
|
|
694
702
|
}
|
|
695
703
|
|
|
696
|
-
async function setupBindings({ Page, Runtime }, { options, debugMessages, blockedURLPatterns, fetchAbortController }) {
|
|
704
|
+
async function setupBindings({ Page, Runtime }, { options, debugMessages, browserInfo, blockedURLPatterns, fetchAbortController }) {
|
|
697
705
|
await Runtime.addBinding({ name: SET_PAGE_DATA_FUNCTION_NAME, executionContextName: SINGLE_FILE_WORLD_NAME });
|
|
698
706
|
if (options.embedScreenshot && options.compressContent) {
|
|
699
707
|
await setupScreenshotCapture({ Page, Runtime }, { options, debugMessages });
|
|
@@ -704,7 +712,7 @@ async function setupBindings({ Page, Runtime }, { options, debugMessages, blocke
|
|
|
704
712
|
await Runtime.addBinding({ name: FETCH_FUNCTION_NAME, executionContextName: SINGLE_FILE_WORLD_NAME });
|
|
705
713
|
Runtime.addEventListener(BINDING_CALLED_EVENT_TYPE, ignoringErrors(async ({ params, sessionId }) => {
|
|
706
714
|
if (params.name === FETCH_FUNCTION_NAME) {
|
|
707
|
-
await handleFetchRequest({ Runtime }, params, { options, debugMessages, blockedURLPatterns, fetchAbortController }, sessionId);
|
|
715
|
+
await handleFetchRequest({ Runtime }, params, { options, debugMessages, browserInfo, blockedURLPatterns, fetchAbortController }, sessionId);
|
|
708
716
|
}
|
|
709
717
|
}, { options, debugMessages }));
|
|
710
718
|
}
|
|
@@ -767,7 +775,7 @@ async function setupPdfCapture({ Page, Runtime }, { options, debugMessages }) {
|
|
|
767
775
|
}
|
|
768
776
|
}
|
|
769
777
|
|
|
770
|
-
async function handleFetchRequest({ Runtime }, params, { options, debugMessages, blockedURLPatterns, fetchAbortController }, sessionId) {
|
|
778
|
+
async function handleFetchRequest({ Runtime }, params, { options, debugMessages, browserInfo, blockedURLPatterns, fetchAbortController }, sessionId) {
|
|
771
779
|
const BLOCKED_URL_ERROR_MESSAGE = "Blocked URL";
|
|
772
780
|
const { executionContextId: contextId, payload } = params;
|
|
773
781
|
const { requestId, url, options: fetchOptions } = JSON.parse(payload);
|
|
@@ -779,9 +787,12 @@ async function handleFetchRequest({ Runtime }, params, { options, debugMessages,
|
|
|
779
787
|
logData(["Blocking request", url], { options, debugMessages });
|
|
780
788
|
throw new Error(BLOCKED_URL_ERROR_MESSAGE);
|
|
781
789
|
}
|
|
790
|
+
// this fetch runs outside the browser, so without the user agent below it
|
|
791
|
+
// would reach the server under the runtime's own identity while every
|
|
792
|
+
// request the browser made presented another one
|
|
782
793
|
const headers = Object.assign({}, fetchOptions.headers, options.httpHeaders);
|
|
783
|
-
if (
|
|
784
|
-
headers[
|
|
794
|
+
if (browserInfo.userAgent && !Object.keys(headers).some(name => name.toLowerCase() == USER_AGENT_HEADER_NAME)) {
|
|
795
|
+
headers[USER_AGENT_HEADER_NAME] = browserInfo.userAgent;
|
|
785
796
|
}
|
|
786
797
|
const response = await fetch(url, Object.assign({}, fetchOptions, { headers, signal: fetchAbortController.signal }));
|
|
787
798
|
const arrayBuffer = await response.arrayBuffer();
|