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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  set -e
4
4
 
5
- CORE_PACKAGE="npm:single-file-core@1.5.113"
5
+ CORE_PACKAGE="npm:single-file-core@1.5.115"
6
6
  ESBUILD_PACKAGE="npm:esbuild@0.27.7"
7
7
 
8
8
  project_dir=$(pwd)
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@single-file/single-file-cli",
3
- "version": "2.6.0",
3
+ "version": "2.6.1",
4
4
  "description": "SingleFile CLI",
5
5
  "exports": {
6
6
  ".": "./single-file-cli-api.js"
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
- options.apiUrl = singleFileOptions.browserServer;
81
+ cdpOptions.apiUrl = singleFileOptions.browserServer;
81
82
  } else {
82
83
  browserOptions = getBrowserOptions(singleFileOptions);
83
- options.apiUrl = LOCALHOST + (await launchBrowser(browserOptions));
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
- options.apiUrl = LOCALHOST + (await launchBrowser(browserOptions));
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 setupNetworkInterception(cdp, pageContext);
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
- await setupUserAgent({ Browser, Emulation }, { options, debugMessages });
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 setupNetworkInterception({ Browser, Emulation, Fetch, Network }, { options, debugMessages, httpInfo, blockedURLPatterns }) {
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 (options.userAgent) {
784
- headers["user-agent"] = options.userAgent;
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();