single-file-cli 1.0.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/.eslintrc.js +43 -0
  2. package/Dockerfile +12 -0
  3. package/README.MD +163 -0
  4. package/args.js +305 -0
  5. package/back-ends/common/scripts.js +66 -0
  6. package/back-ends/extensions/bypass-csp/index.js +36 -0
  7. package/back-ends/extensions/bypass-csp/manifest.json +20 -0
  8. package/back-ends/extensions/disable-web-security/index.js +54 -0
  9. package/back-ends/extensions/disable-web-security/manifest.json +20 -0
  10. package/back-ends/extensions/network-idle/bg.js +61 -0
  11. package/back-ends/extensions/network-idle/content.js +29 -0
  12. package/back-ends/extensions/network-idle/manifest.json +31 -0
  13. package/back-ends/extensions/signed/bypass_csp-0.0.3-an+fx.xpi +0 -0
  14. package/back-ends/extensions/signed/disable_web_security-0.0.3-an+fx.xpi +0 -0
  15. package/back-ends/extensions/signed/network_idle-0.0.2-an+fx.xpi +0 -0
  16. package/back-ends/jsdom.js +175 -0
  17. package/back-ends/playwright-chromium.js +113 -0
  18. package/back-ends/playwright-firefox.js +113 -0
  19. package/back-ends/puppeteer-firefox.js +170 -0
  20. package/back-ends/puppeteer.js +189 -0
  21. package/back-ends/webdriver-chromium.js +168 -0
  22. package/back-ends/webdriver-gecko.js +181 -0
  23. package/build-lib.sh +3 -0
  24. package/lib/single-file-bootstrap.js +1 -0
  25. package/lib/single-file-frames.js +1 -0
  26. package/lib/single-file-hooks-frames.js +1 -0
  27. package/lib/single-file-hooks.js +1 -0
  28. package/lib/single-file-infobar.js +1 -0
  29. package/lib/single-file.js +1 -0
  30. package/package.json +37 -0
  31. package/rollup.config.dev.js +64 -0
  32. package/rollup.config.js +64 -0
  33. package/single-file +81 -0
  34. package/single-file-cli-api.js +324 -0
  35. package/single-file.bat +2 -0
@@ -0,0 +1,54 @@
1
+ /*
2
+ * Copyright 2010-2020 Gildas Lormeau
3
+ * contact : gildas.lormeau <at> gmail.com
4
+ *
5
+ * This file is part of SingleFile.
6
+ *
7
+ * The code in this file is free software: you can redistribute it and/or
8
+ * modify it under the terms of the GNU Affero General Public License
9
+ * (GNU AGPL) as published by the Free Software Foundation, either version 3
10
+ * of the License, or (at your option) any later version.
11
+ *
12
+ * The code in this file is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
15
+ * General Public License for more details.
16
+ *
17
+ * As additional permission under GNU AGPL version 3 section 7, you may
18
+ * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
19
+ * AGPL normally required by section 4, provided you include this license
20
+ * notice and a URL through which recipients can access the Corresponding
21
+ * Source.
22
+ */
23
+
24
+ const removedHeaders = ["access-control-allow-methods", "access-control-allow-headers"];
25
+ const updatedHeaders = { "access-control-allow-origin": "*", "access-control-allow-credentials": "true" };
26
+
27
+ const browserAPI = this.browser || this.chrome;
28
+
29
+ browserAPI.webRequest.onHeadersReceived.addListener(
30
+ function (details) {
31
+ let responseHeaders = details.responseHeaders;
32
+ let processedHeaders = [];
33
+ responseHeaders = responseHeaders.filter(responseHeader => !removedHeaders.includes(responseHeader.name.toLowerCase()));
34
+ responseHeaders.forEach(responseHeader => {
35
+ const name = responseHeader.name.toLowerCase();
36
+ const value = updatedHeaders[name];
37
+ if (value) {
38
+ responseHeader.value = value;
39
+ processedHeaders.push(name);
40
+ }
41
+ });
42
+ Object.keys(updatedHeaders).forEach(name => {
43
+ if (!processedHeaders.includes(name)) {
44
+ const value = updatedHeaders[name.toLowerCase()];
45
+ if (value) {
46
+ responseHeaders.push({ name, value });
47
+ }
48
+ }
49
+ });
50
+ return { responseHeaders };
51
+ },
52
+ { urls: ["<all_urls>"] },
53
+ ["blocking", "responseHeaders"]
54
+ );
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "disable-web-security",
3
+ "version": "0.0.3",
4
+ "background": {
5
+ "scripts": [
6
+ "index.js"
7
+ ]
8
+ },
9
+ "permissions": [
10
+ "webRequest",
11
+ "webRequestBlocking",
12
+ "<all_urls>"
13
+ ],
14
+ "browser_specific_settings": {
15
+ "gecko": {
16
+ "id": "{588434c2-67c3-4f77-9828-c30c7d63e8f9}"
17
+ }
18
+ },
19
+ "manifest_version": 2
20
+ }
@@ -0,0 +1,61 @@
1
+ /*
2
+ * Copyright 2010-2020 Gildas Lormeau
3
+ * contact : gildas.lormeau <at> gmail.com
4
+ *
5
+ * This file is part of SingleFile.
6
+ *
7
+ * The code in this file is free software: you can redistribute it and/or
8
+ * modify it under the terms of the GNU Affero General Public License
9
+ * (GNU AGPL) as published by the Free Software Foundation, either version 3
10
+ * of the License, or (at your option) any later version.
11
+ *
12
+ * The code in this file is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
15
+ * General Public License for more details.
16
+ *
17
+ * As additional permission under GNU AGPL version 3 section 7, you may
18
+ * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
19
+ * AGPL normally required by section 4, provided you include this license
20
+ * notice and a URL through which recipients can access the Corresponding
21
+ * Source.
22
+ */
23
+
24
+ /* global setTimeout, clearTimeout */
25
+
26
+ const browserAPI = this.browser || this.chrome;
27
+
28
+ const IDLE_DELAY = 1000;
29
+ const watchDogs = [];
30
+ let pendingRequests = new Set();
31
+
32
+ browserAPI.webRequest.onSendHeaders.addListener(onRequest, { urls: ["<all_urls>"] }, []);
33
+ browserAPI.webRequest.onResponseStarted.addListener(onResponse, { urls: ["<all_urls>"] }, []);
34
+ browserAPI.webRequest.onErrorOccurred.addListener(onResponse, { urls: ["<all_urls>"] });
35
+
36
+ function onRequest(details) {
37
+ if (details.tabId != -1) {
38
+ pendingRequests.add(details.requestId);
39
+ if (pendingRequests.size > 2) {
40
+ clearTimeout(watchDogs[2]);
41
+ }
42
+ clearTimeout(watchDogs[0]);
43
+ }
44
+ }
45
+
46
+ function onResponse(details) {
47
+ if (details.tabId != -1) {
48
+ pendingRequests.delete(details.requestId);
49
+ if (pendingRequests.size == 2) {
50
+ maybeIdle(2, details.tabId);
51
+ }
52
+ if (pendingRequests.size == 0) {
53
+ maybeIdle(0, details.tabId);
54
+ }
55
+ }
56
+ }
57
+
58
+ function maybeIdle(idleLevel, tabId) {
59
+ clearTimeout(watchDogs[idleLevel]);
60
+ watchDogs[idleLevel] = setTimeout(() => browserAPI.tabs.sendMessage(tabId, "network-idle-" + idleLevel), IDLE_DELAY);
61
+ }
@@ -0,0 +1,29 @@
1
+ /*
2
+ * Copyright 2010-2020 Gildas Lormeau
3
+ * contact : gildas.lormeau <at> gmail.com
4
+ *
5
+ * This file is part of SingleFile.
6
+ *
7
+ * The code in this file is free software: you can redistribute it and/or
8
+ * modify it under the terms of the GNU Affero General Public License
9
+ * (GNU AGPL) as published by the Free Software Foundation, either version 3
10
+ * of the License, or (at your option) any later version.
11
+ *
12
+ * The code in this file is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
15
+ * General Public License for more details.
16
+ *
17
+ * As additional permission under GNU AGPL version 3 section 7, you may
18
+ * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
19
+ * AGPL normally required by section 4, provided you include this license
20
+ * notice and a URL through which recipients can access the Corresponding
21
+ * Source.
22
+ */
23
+
24
+ /* global window, CustomEvent */
25
+
26
+ const browserAPI = this.browser || this.chrome;
27
+ const dispatchEvent = event => { try { window.dispatchEvent(event); } catch (error) { /* ignored */ } };
28
+
29
+ browserAPI.runtime.onMessage.addListener(message => dispatchEvent(new CustomEvent("single-file-" + message)));
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "network-idle",
3
+ "version": "0.0.2",
4
+ "background": {
5
+ "scripts": [
6
+ "bg.js"
7
+ ]
8
+ },
9
+ "content_scripts": [
10
+ {
11
+ "matches": [
12
+ "<all_urls>"
13
+ ],
14
+ "run_at": "document_start",
15
+ "js": [
16
+ "content.js"
17
+ ]
18
+ }
19
+ ],
20
+ "permissions": [
21
+ "tabs",
22
+ "webRequest",
23
+ "<all_urls>"
24
+ ],
25
+ "browser_specific_settings": {
26
+ "gecko": {
27
+ "id": "{caf9306a-8951-4f0c-beb0-bab690d00caf}"
28
+ }
29
+ },
30
+ "manifest_version": 2
31
+ }
@@ -0,0 +1,175 @@
1
+ /*
2
+ * Copyright 2010-2020 Gildas Lormeau
3
+ * contact : gildas.lormeau <at> gmail.com
4
+ *
5
+ * This file is part of SingleFile.
6
+ *
7
+ * The code in this file is free software: you can redistribute it and/or
8
+ * modify it under the terms of the GNU Affero General Public License
9
+ * (GNU AGPL) as published by the Free Software Foundation, either version 3
10
+ * of the License, or (at your option) any later version.
11
+ *
12
+ * The code in this file is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
15
+ * General Public License for more details.
16
+ *
17
+ * As additional permission under GNU AGPL version 3 section 7, you may
18
+ * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
19
+ * AGPL normally required by section 4, provided you include this license
20
+ * notice and a URL through which recipients can access the Corresponding
21
+ * Source.
22
+ */
23
+
24
+ /* global require, exports, Buffer, setTimeout */
25
+
26
+ const crypto = require("crypto");
27
+
28
+ const jsdom = require("jsdom");
29
+ const { JSDOM, VirtualConsole } = jsdom;
30
+ const iconv = require("iconv-lite");
31
+
32
+ exports.initialize = async () => { };
33
+
34
+ exports.getPageData = async options => {
35
+ let win;
36
+ try {
37
+ const dom = await JSDOM.fromURL(options.url, await getBrowserOptions(options));
38
+ win = dom.window;
39
+ return await getPageData(win, options);
40
+ } finally {
41
+ if (win) {
42
+ win.close();
43
+ }
44
+ }
45
+ };
46
+
47
+ exports.closeBrowser = () => { };
48
+
49
+ async function getPageData(win, options) {
50
+ const doc = win.document;
51
+ const scripts = await require("./common/scripts.js").get(options);
52
+ win.TextDecoder = class {
53
+ constructor(utfLabel) {
54
+ this.utfLabel = utfLabel;
55
+ }
56
+ decode(buffer) {
57
+ return iconv.decode(Buffer.from(buffer), this.utfLabel);
58
+ }
59
+ };
60
+ win.crypto = {
61
+ subtle: {
62
+ digest: async function digestText(algo, text) {
63
+ const hash = crypto.createHash(algo.replace("-", "").toLowerCase());
64
+ hash.update(text, "utf-8");
65
+ return hash.digest();
66
+ }
67
+ }
68
+ };
69
+ win.Element.prototype.getBoundingClientRect = undefined;
70
+ win.getComputedStyle = () => { };
71
+ win.eval(scripts);
72
+ if (win.document.readyState == "loading" || win.document.readyState == "interactive") {
73
+ await new Promise(resolve => win.onload = resolve);
74
+ }
75
+ executeFrameScripts(doc, scripts);
76
+ options.removeHiddenElements = false;
77
+ options.loadDeferredImages = false;
78
+ if (options.browserWaitDelay) {
79
+ await new Promise(resolve => setTimeout(resolve, options.browserWaitDelay));
80
+ }
81
+ const pageData = await win.singlefile.getPageData(options, { fetch: url => fetchResource(url, options) }, doc, win);
82
+ if (options.includeInfobar) {
83
+ await win.infobar.includeScript(pageData);
84
+ }
85
+ return pageData;
86
+
87
+ async function fetchResource(resourceURL) {
88
+ return new Promise((resolve, reject) => {
89
+ const xhrRequest = new win.XMLHttpRequest();
90
+ xhrRequest.withCredentials = true;
91
+ xhrRequest.responseType = "arraybuffer";
92
+ xhrRequest.onerror = event => reject(new Error(event.detail));
93
+ xhrRequest.onreadystatechange = () => {
94
+ if (xhrRequest.readyState == win.XMLHttpRequest.DONE) {
95
+ resolve({
96
+ arrayBuffer: async () => new Uint8Array(xhrRequest.response).buffer,
97
+ headers: {
98
+ get: headerName => xhrRequest.getResponseHeader(headerName)
99
+ },
100
+ status: xhrRequest.status
101
+ });
102
+ }
103
+ };
104
+ xhrRequest.open("GET", resourceURL, true);
105
+ xhrRequest.send();
106
+ });
107
+ }
108
+ }
109
+
110
+ async function getBrowserOptions(options) {
111
+ class ResourceLoader extends jsdom.ResourceLoader {
112
+ _getRequestOptions(fetchOptions) {
113
+ const requestOptions = super._getRequestOptions(fetchOptions);
114
+ if (options.httpHeaders) {
115
+ requestOptions.headers = Object.assign(requestOptions.headers, options.httpHeaders);
116
+ }
117
+ return requestOptions;
118
+ }
119
+ }
120
+ const resourceLoader = new ResourceLoader({
121
+ userAgent: options.userAgent
122
+ });
123
+ const jsdomOptions = {
124
+ virtualConsole: new VirtualConsole(),
125
+ userAgent: options.userAgent,
126
+ pretendToBeVisual: true,
127
+ runScripts: "outside-only",
128
+ resources: resourceLoader
129
+ };
130
+ if (options.browserWidth && options.browserHeight) {
131
+ jsdomOptions.beforeParse = function (window) {
132
+ window.outerWidth = window.innerWidth = options.browserWidth;
133
+ window.outerHeight = window.innerHeight = options.browserHeight;
134
+ };
135
+ }
136
+ if (options.browserCookies && options.browserCookies.length) {
137
+ jsdomOptions.cookieJar = new jsdom.CookieJar();
138
+ await Promise.all(options.browserCookies.map(cookie => {
139
+ let cookieString = cookie.name + "=" + cookie.value;
140
+ if (cookie.path) {
141
+ cookieString += ";path=" + cookie.path;
142
+ }
143
+ if (cookie.domain) {
144
+ cookieString += ";domain=" + cookie.domain;
145
+ }
146
+ if (cookie.expires) {
147
+ cookieString += ";max-age=" + cookie.expires;
148
+ }
149
+ if (cookie.secure) {
150
+ cookieString += ";secure";
151
+ }
152
+ if (cookie.sameSite) {
153
+ cookieString += ";samesite=" + options.sameSite;
154
+ }
155
+ const cookieOptions = {
156
+ http: Boolean(cookie.httpOnly)
157
+ };
158
+ return new Promise((resolve, reject) => jsdomOptions.cookieJar.setCookie(cookieString, options.url, cookieOptions, error => error ? reject(error) : resolve()));
159
+ }));
160
+ }
161
+ return jsdomOptions;
162
+ }
163
+
164
+ function executeFrameScripts(doc, scripts) {
165
+ const frameElements = doc.querySelectorAll("iframe, frame");
166
+ frameElements.forEach(frameElement => {
167
+ try {
168
+ frameElement.contentWindow.Element.prototype.getBoundingClientRect = undefined;
169
+ frameElement.contentWindow.eval(scripts);
170
+ executeFrameScripts(frameElement.contentDocument, scripts);
171
+ } catch (error) {
172
+ // ignored
173
+ }
174
+ });
175
+ }
@@ -0,0 +1,113 @@
1
+ /*
2
+ * Copyright 2010-2020 Gildas Lormeau
3
+ * contact : gildas.lormeau <at> gmail.com
4
+ *
5
+ * This file is part of SingleFile.
6
+ *
7
+ * The code in this file is free software: you can redistribute it and/or
8
+ * modify it under the terms of the GNU Affero General Public License
9
+ * (GNU AGPL) as published by the Free Software Foundation, either version 3
10
+ * of the License, or (at your option) any later version.
11
+ *
12
+ * The code in this file is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
15
+ * General Public License for more details.
16
+ *
17
+ * As additional permission under GNU AGPL version 3 section 7, you may
18
+ * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
19
+ * AGPL normally required by section 4, provided you include this license
20
+ * notice and a URL through which recipients can access the Corresponding
21
+ * Source.
22
+ */
23
+
24
+ /* global singlefile, infobar, require, exports */
25
+
26
+ const playwright = require("playwright").chromium;
27
+ const scripts = require("./common/scripts.js");
28
+
29
+ const NETWORK_IDLE_STATE = "networkidle";
30
+
31
+ let browser;
32
+
33
+ exports.initialize = async options => {
34
+ browser = await playwright.launch(getBrowserOptions(options));
35
+ };
36
+
37
+ exports.getPageData = async options => {
38
+ let page, context;
39
+ try {
40
+ context = await browser.newContext({
41
+ bypassCSP: options.browserBypassCSP === undefined || options.browserBypassCSP
42
+ });
43
+ await setContextOptions(context, options);
44
+ page = await context.newPage();
45
+ await setPageOptions(page, options);
46
+ return await getPageData(page, options);
47
+ } finally {
48
+ if (page) {
49
+ await page.close();
50
+ }
51
+ }
52
+ };
53
+
54
+ exports.closeBrowser = () => {
55
+ if (browser) {
56
+ return browser.close();
57
+ }
58
+ };
59
+
60
+ function getBrowserOptions(options) {
61
+ const browserOptions = {};
62
+ if (options.browserHeadless !== undefined) {
63
+ browserOptions.headless = options.browserHeadless && !options.browserDebug;
64
+ }
65
+ browserOptions.args = options.browserArgs ? JSON.parse(options.browserArgs) : [];
66
+ if (options.browserExecutablePath) {
67
+ browserOptions.executablePath = options.browserExecutablePath || "chrome";
68
+ }
69
+ return browserOptions;
70
+ }
71
+
72
+ async function setContextOptions(context, options) {
73
+ if (options.browserCookies && options.browserCookies.length) {
74
+ await context.addCookies(options.browserCookies);
75
+ }
76
+ }
77
+
78
+ async function setPageOptions(page, options) {
79
+ if (options.browserWidth && options.browserHeight) {
80
+ await page.setViewportSize({
81
+ width: options.browserWidth,
82
+ height: options.browserHeight
83
+ });
84
+ }
85
+ if (options.httpHeaders) {
86
+ page.setExtraHTTPHeaders(options.httpHeaders);
87
+ }
88
+ if (options.emulateMediaFeatures) {
89
+ await page.emulateMediaFeatures(options.emulateMediaFeatures);
90
+ }
91
+ }
92
+
93
+ async function getPageData(page, options) {
94
+ const injectedScript = await scripts.get(options);
95
+ await page.addInitScript(injectedScript);
96
+ if (options.browserDebug) {
97
+ await page.waitForTimeout(3000);
98
+ }
99
+ await page.goto(options.url, {
100
+ timeout: options.browserLoadMaxTime || 0,
101
+ waitUntil: options.browserWaitUntil && options.browserWaitUntil.startsWith("networkidle") ? NETWORK_IDLE_STATE : options.browserWaitUntil || NETWORK_IDLE_STATE
102
+ });
103
+ if (options.browserWaitDelay) {
104
+ await page.waitForTimeout(options.browserWaitDelay);
105
+ }
106
+ return await page.evaluate(async options => {
107
+ const pageData = await singlefile.getPageData(options);
108
+ if (options.includeInfobar) {
109
+ await infobar.includeScript(pageData);
110
+ }
111
+ return pageData;
112
+ }, options);
113
+ }
@@ -0,0 +1,113 @@
1
+ /*
2
+ * Copyright 2010-2020 Gildas Lormeau
3
+ * contact : gildas.lormeau <at> gmail.com
4
+ *
5
+ * This file is part of SingleFile.
6
+ *
7
+ * The code in this file is free software: you can redistribute it and/or
8
+ * modify it under the terms of the GNU Affero General Public License
9
+ * (GNU AGPL) as published by the Free Software Foundation, either version 3
10
+ * of the License, or (at your option) any later version.
11
+ *
12
+ * The code in this file is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
15
+ * General Public License for more details.
16
+ *
17
+ * As additional permission under GNU AGPL version 3 section 7, you may
18
+ * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
19
+ * AGPL normally required by section 4, provided you include this license
20
+ * notice and a URL through which recipients can access the Corresponding
21
+ * Source.
22
+ */
23
+
24
+ /* global singlefile, infobar, require, exports */
25
+
26
+ const playwright = require("playwright").firefox;
27
+ const scripts = require("./common/scripts.js");
28
+
29
+ const NETWORK_IDLE_STATE = "networkidle";
30
+
31
+ let browser;
32
+
33
+ exports.initialize = async options => {
34
+ browser = await playwright.launch(getBrowserOptions(options));
35
+ };
36
+
37
+ exports.getPageData = async options => {
38
+ let page, context;
39
+ try {
40
+ context = await browser.newContext({
41
+ bypassCSP: options.browserBypassCSP === undefined || options.browserBypassCSP
42
+ });
43
+ await setContextOptions(context, options);
44
+ page = await context.newPage();
45
+ await setPageOptions(page, options);
46
+ return await getPageData(page, options);
47
+ } finally {
48
+ if (page) {
49
+ await page.close();
50
+ }
51
+ }
52
+ };
53
+
54
+ exports.closeBrowser = () => {
55
+ if (browser) {
56
+ return browser.close();
57
+ }
58
+ };
59
+
60
+ function getBrowserOptions(options) {
61
+ const browserOptions = {};
62
+ if (options.browserHeadless !== undefined) {
63
+ browserOptions.headless = options.browserHeadless && !options.browserDebug;
64
+ }
65
+ browserOptions.args = options.browserArgs ? JSON.parse(options.browserArgs) : [];
66
+ if (options.browserExecutablePath) {
67
+ browserOptions.executablePath = options.browserExecutablePath || "firefox";
68
+ }
69
+ return browserOptions;
70
+ }
71
+
72
+ async function setContextOptions(context, options) {
73
+ if (options.browserCookies && options.browserCookies.length) {
74
+ await context.addCookies(options.browserCookies);
75
+ }
76
+ }
77
+
78
+ async function setPageOptions(page, options) {
79
+ if (options.browserWidth && options.browserHeight) {
80
+ await page.setViewportSize({
81
+ width: options.browserWidth,
82
+ height: options.browserHeight
83
+ });
84
+ }
85
+ if (options.httpHeaders) {
86
+ page.setExtraHTTPHeaders(options.httpHeaders);
87
+ }
88
+ if (options.emulateMediaFeatures) {
89
+ await page.emulateMediaFeatures(options.emulateMediaFeatures);
90
+ }
91
+ }
92
+
93
+ async function getPageData(page, options) {
94
+ const injectedScript = await scripts.get(options);
95
+ await page.addInitScript(injectedScript);
96
+ if (options.browserDebug) {
97
+ await page.waitForTimeout(3000);
98
+ }
99
+ await page.goto(options.url, {
100
+ timeout: options.browserLoadMaxTime || 0,
101
+ waitUntil: options.browserWaitUntil && options.browserWaitUntil.startsWith("networkidle") ? NETWORK_IDLE_STATE : options.browserWaitUntil || NETWORK_IDLE_STATE
102
+ });
103
+ if (options.browserWaitDelay) {
104
+ await page.waitForTimeout(options.browserWaitDelay);
105
+ }
106
+ return await page.evaluate(async options => {
107
+ const pageData = await singlefile.getPageData(options);
108
+ if (options.includeInfobar) {
109
+ await infobar.includeScript(pageData);
110
+ }
111
+ return pageData;
112
+ }, options);
113
+ }