single-file-cli 2.0.17 → 2.0.19
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/README.MD +0 -7
- package/deno.json +1 -1
- package/lib/cdp-client.js +119 -100
- package/lib/version.js +1 -1
- package/options.js +1 -1
- package/package.json +1 -1
package/README.MD
CHANGED
|
@@ -130,13 +130,6 @@ Make sure Chrome or a Chromium-based browser is installed in the default folder.
|
|
|
130
130
|
single-file https://www.wikipedia.org wikipedia.html
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
- Save https://www.wikipedia.org into `wikipedia.html` in the current folder
|
|
134
|
-
with Firefox instead of Chrome
|
|
135
|
-
|
|
136
|
-
```sh
|
|
137
|
-
single-file https://www.wikipedia.org wikipedia.html --back-end=webdriver-gecko
|
|
138
|
-
```
|
|
139
|
-
|
|
140
133
|
- Save a list of URLs stored into `list-urls.txt` in the current folder
|
|
141
134
|
|
|
142
135
|
```sh
|
package/deno.json
CHANGED
package/lib/cdp-client.js
CHANGED
|
@@ -28,8 +28,8 @@ import { getScriptSource, getHookScriptSource } from "./single-file-script.js";
|
|
|
28
28
|
import { CDP } from "simple-cdp";
|
|
29
29
|
|
|
30
30
|
const LOAD_TIMEOUT_ERROR = "ERR_LOAD_TIMEOUT";
|
|
31
|
-
const
|
|
32
|
-
const NETWORK_STATES = ["networkAlmostIdle", "load", "DOMContentLoaded"];
|
|
31
|
+
const INTERACTIVE_TIME_STATE = "InteractiveTime";
|
|
32
|
+
const NETWORK_STATES = ["InteractiveTime", "networkIdle", "networkAlmostIdle", "load", "DOMContentLoaded"];
|
|
33
33
|
const MINIMIZED_WINDOW_STATE = "minimized";
|
|
34
34
|
const SINGLE_FILE_WORLD_NAME = "singlefile";
|
|
35
35
|
const EMPTY_PAGE_URL = "about:blank";
|
|
@@ -62,16 +62,14 @@ async function getPageData(options) {
|
|
|
62
62
|
}
|
|
63
63
|
if (options.httpProxyServer && options.httpProxyUsername) {
|
|
64
64
|
await Fetch.enable({ handleAuthRequests: true });
|
|
65
|
-
Fetch.addEventListener("authRequired",
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
});
|
|
74
|
-
});
|
|
65
|
+
Fetch.addEventListener("authRequired", ({ params }) => Fetch.continueWithAuth({
|
|
66
|
+
requestId: params.requestId,
|
|
67
|
+
authChallengeResponse: {
|
|
68
|
+
response: "ProvideCredentials",
|
|
69
|
+
username: options.httpProxyUsername,
|
|
70
|
+
password: options.httpProxyPassword
|
|
71
|
+
}
|
|
72
|
+
}));
|
|
75
73
|
Fetch.addEventListener("requestPaused", ({ params }) => Fetch.continueRequest({ requestId: params.requestId }));
|
|
76
74
|
}
|
|
77
75
|
if (options.httpHeaders && options.httpHeaders.length) {
|
|
@@ -88,27 +86,6 @@ async function getPageData(options) {
|
|
|
88
86
|
if (options.browserCookies && options.browserCookies.length) {
|
|
89
87
|
await Network.setCookies({ cookies: options.browserCookies });
|
|
90
88
|
}
|
|
91
|
-
let debuggerReady;
|
|
92
|
-
if (options.browserDebug) {
|
|
93
|
-
await Debugger.enable();
|
|
94
|
-
debuggerReady = new Promise(resolve => {
|
|
95
|
-
const SCRIPT_PARSED_EVENT = "scriptParsed";
|
|
96
|
-
const RESUMED_EVENT = "resumed";
|
|
97
|
-
Debugger.addEventListener(SCRIPT_PARSED_EVENT, onScriptParsed);
|
|
98
|
-
|
|
99
|
-
async function onScriptParsed() {
|
|
100
|
-
Debugger.removeEventListener(SCRIPT_PARSED_EVENT, onScriptParsed);
|
|
101
|
-
Debugger.addEventListener(RESUMED_EVENT, onResumed);
|
|
102
|
-
await Debugger.pause();
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async function onResumed() {
|
|
106
|
-
Debugger.removeEventListener(RESUMED_EVENT, onResumed);
|
|
107
|
-
await Debugger.disable();
|
|
108
|
-
resolve();
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
89
|
await Page.addScriptToEvaluateOnNewDocument({
|
|
113
90
|
source: getHookScriptSource(),
|
|
114
91
|
runImmediately: true
|
|
@@ -118,66 +95,10 @@ async function getPageData(options) {
|
|
|
118
95
|
runImmediately: true,
|
|
119
96
|
worldName: SINGLE_FILE_WORLD_NAME
|
|
120
97
|
});
|
|
121
|
-
await
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
Runtime.addEventListener(CONTEXT_CREATED_EVENT, executionContextCreated);
|
|
126
|
-
await Page.enable();
|
|
127
|
-
await Page.setLifecycleEventsEnabled({ enabled: true });
|
|
128
|
-
const pageNavigated = Page.navigate({ url: options.url });
|
|
129
|
-
const pageReady = new Promise(resolve => {
|
|
130
|
-
const LIFE_CYCLE_EVENT = "lifecycleEvent";
|
|
131
|
-
const FRAME_NAVIGATED_EVENT = "frameNavigated";
|
|
132
|
-
let contentLoaded;
|
|
133
|
-
Page.addEventListener(LIFE_CYCLE_EVENT, onLifecycleEvent);
|
|
134
|
-
Page.addEventListener(FRAME_NAVIGATED_EVENT, onFrameNavigated);
|
|
135
|
-
|
|
136
|
-
async function onLifecycleEvent({ params }) {
|
|
137
|
-
const { name, frameId } = params;
|
|
138
|
-
if (frameId === topFrameId) {
|
|
139
|
-
if (name === "DOMContentLoaded") {
|
|
140
|
-
contentLoaded = true;
|
|
141
|
-
}
|
|
142
|
-
if (contentLoaded && name === (options.browserWaitUntil || NETWORK_IDLE_STATE)) {
|
|
143
|
-
if (options.browserWaitDelay) {
|
|
144
|
-
setTimeout(() => resolve, options.browserWaitDelay);
|
|
145
|
-
} else {
|
|
146
|
-
Page.removeEventListener(LIFE_CYCLE_EVENT, onLifecycleEvent);
|
|
147
|
-
Page.removeEventListener(FRAME_NAVIGATED_EVENT, onFrameNavigated);
|
|
148
|
-
await Page.setLifecycleEventsEnabled({ enabled: false });
|
|
149
|
-
await Page.disable();
|
|
150
|
-
resolve();
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async function onFrameNavigated({ params }) {
|
|
157
|
-
const { frame } = params;
|
|
158
|
-
if (!frame.parentId) {
|
|
159
|
-
contextId = undefined;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
let timeoutReady, timeoutId, resolveTimeoutReady;
|
|
164
|
-
if (options.browserLoadMaxTime) {
|
|
165
|
-
timeoutReady = new Promise((resolve, reject) => {
|
|
166
|
-
resolveTimeoutReady = resolve;
|
|
167
|
-
timeoutId = setTimeout(() => {
|
|
168
|
-
const error = new Error("Load timeout");
|
|
169
|
-
error.code = LOAD_TIMEOUT_ERROR;
|
|
170
|
-
reject(error);
|
|
171
|
-
}, options.browserLoadMaxTime);
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
await Promise.race([Promise.all([pageNavigated, pageReady, debuggerReady]), timeoutReady]);
|
|
175
|
-
Runtime.removeEventListener(CONTEXT_CREATED_EVENT, executionContextCreated);
|
|
176
|
-
await Runtime.disable();
|
|
177
|
-
if (timeoutId) {
|
|
178
|
-
clearTimeout(timeoutId);
|
|
179
|
-
resolveTimeoutReady();
|
|
180
|
-
}
|
|
98
|
+
const [contextId] = await Promise.all([
|
|
99
|
+
navigate({ Page, Runtime }, options),
|
|
100
|
+
options.browserDebug ? waitForDebuggerReady({ Debugger }) : Promise.resolve()
|
|
101
|
+
]);
|
|
181
102
|
const { result } = await Runtime.evaluate({
|
|
182
103
|
expression: `singlefile.getPageData(${JSON.stringify(options)})`,
|
|
183
104
|
awaitPromise: true,
|
|
@@ -189,13 +110,6 @@ async function getPageData(options) {
|
|
|
189
110
|
value.content = new Uint8Array(value.content);
|
|
190
111
|
}
|
|
191
112
|
return value;
|
|
192
|
-
|
|
193
|
-
async function executionContextCreated({ params }) {
|
|
194
|
-
const { context } = params;
|
|
195
|
-
if (contextId === undefined && context.name === SINGLE_FILE_WORLD_NAME && context.auxData && context.auxData.frameId === topFrameId) {
|
|
196
|
-
contextId = context.id;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
113
|
} catch (error) {
|
|
200
114
|
if (error.code === LOAD_TIMEOUT_ERROR && options.browserWaitUntilFallback && options.browserWaitUntil) {
|
|
201
115
|
const browserWaitUntil = NETWORK_STATES[(NETWORK_STATES.indexOf(options.browserWaitUntil) + 1)];
|
|
@@ -212,6 +126,111 @@ async function getPageData(options) {
|
|
|
212
126
|
}
|
|
213
127
|
}
|
|
214
128
|
|
|
129
|
+
async function navigate({ Page, Runtime }, options) {
|
|
130
|
+
await Runtime.enable();
|
|
131
|
+
await Page.enable();
|
|
132
|
+
try {
|
|
133
|
+
const loadTimeoutAbortController = new AbortController();
|
|
134
|
+
const loadTimeoutAbortSignal = loadTimeoutAbortController.signal;
|
|
135
|
+
const [contextId] = await Promise.race([
|
|
136
|
+
Promise.all([getTopFrameContextId({ Page, Runtime }, options), Page.navigate({ url: options.url })]),
|
|
137
|
+
options.browserLoadMaxTime ? waitForLoadTimeout(loadTimeoutAbortSignal, options.browserLoadMaxTime) : Promise.resolve()
|
|
138
|
+
]);
|
|
139
|
+
if (!loadTimeoutAbortSignal.aborted) {
|
|
140
|
+
loadTimeoutAbortController.abort();
|
|
141
|
+
}
|
|
142
|
+
return contextId;
|
|
143
|
+
} finally {
|
|
144
|
+
await Runtime.disable();
|
|
145
|
+
await Page.disable();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function getTopFrameContextId({ Page, Runtime }, options) {
|
|
150
|
+
let frameId, contextId;
|
|
151
|
+
await new Promise(resolve => {
|
|
152
|
+
const FRAME_NAVIGATED_EVENT = "frameNavigated";
|
|
153
|
+
const CONTEXT_CREATED_EVENT = "executionContextCreated";
|
|
154
|
+
Page.addEventListener(FRAME_NAVIGATED_EVENT, onFrameNavigated);
|
|
155
|
+
Runtime.addEventListener(CONTEXT_CREATED_EVENT, executionContextCreated);
|
|
156
|
+
|
|
157
|
+
async function onFrameNavigated({ params }) {
|
|
158
|
+
const { frame } = params;
|
|
159
|
+
if (!frame.parentId) {
|
|
160
|
+
frameId = frame.id;
|
|
161
|
+
await waitForPageReady({ Page }, frameId, options);
|
|
162
|
+
Page.removeEventListener(FRAME_NAVIGATED_EVENT, onFrameNavigated);
|
|
163
|
+
Runtime.removeEventListener(CONTEXT_CREATED_EVENT, executionContextCreated);
|
|
164
|
+
resolve();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function executionContextCreated({ params }) {
|
|
169
|
+
const { context } = params;
|
|
170
|
+
const { name, origin, auxData } = context;
|
|
171
|
+
if (frameId !== undefined && origin === "://" && name === SINGLE_FILE_WORLD_NAME && auxData && auxData.frameId === frameId) {
|
|
172
|
+
contextId = context.id;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
return contextId;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function waitForLoadTimeout(abortSignal, maxDelay) {
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
const ABORT_EVENT = "abort";
|
|
182
|
+
abortSignal.addEventListener(ABORT_EVENT, onAbort);
|
|
183
|
+
const timeoutId = setTimeout(() => {
|
|
184
|
+
const error = new Error("Load timeout");
|
|
185
|
+
error.code = LOAD_TIMEOUT_ERROR;
|
|
186
|
+
reject(error);
|
|
187
|
+
}, maxDelay);
|
|
188
|
+
|
|
189
|
+
function onAbort() {
|
|
190
|
+
abortSignal.removeEventListener(ABORT_EVENT, onAbort);
|
|
191
|
+
clearTimeout(timeoutId);
|
|
192
|
+
resolve();
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function waitForPageReady({ Page }, topFrameId, options) {
|
|
198
|
+
await Page.setLifecycleEventsEnabled({ enabled: true });
|
|
199
|
+
await new Promise(resolve => {
|
|
200
|
+
const LIFE_CYCLE_EVENT = "lifecycleEvent";
|
|
201
|
+
Page.addEventListener(LIFE_CYCLE_EVENT, onLifecycleEvent);
|
|
202
|
+
|
|
203
|
+
function onLifecycleEvent({ params }) {
|
|
204
|
+
const { name, frameId } = params;
|
|
205
|
+
if (frameId === topFrameId) {
|
|
206
|
+
if ((name === options.browserWaitUntil || name === INTERACTIVE_TIME_STATE)) {
|
|
207
|
+
Page.removeEventListener(LIFE_CYCLE_EVENT, onLifecycleEvent);
|
|
208
|
+
resolve();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
await Page.setLifecycleEventsEnabled({ enabled: false });
|
|
214
|
+
if (options.browserWaitDelay) {
|
|
215
|
+
setTimeout(() => resolve, options.browserWaitDelay);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function waitForDebuggerReady({ Debugger }) {
|
|
220
|
+
await Debugger.enable();
|
|
221
|
+
await Debugger.pause();
|
|
222
|
+
await new Promise(resolve => {
|
|
223
|
+
const RESUMED_EVENT = "resumed";
|
|
224
|
+
Debugger.addEventListener(RESUMED_EVENT, onResumed);
|
|
225
|
+
|
|
226
|
+
function onResumed() {
|
|
227
|
+
Debugger.removeEventListener(RESUMED_EVENT, onResumed);
|
|
228
|
+
resolve();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
await Debugger.disable();
|
|
232
|
+
}
|
|
233
|
+
|
|
215
234
|
function getBrowserOptions(options) {
|
|
216
235
|
const browserOptions = {};
|
|
217
236
|
browserOptions.args = options.browserArgs;
|
package/lib/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "2.0.
|
|
1
|
+
export const version = "2.0.19";
|
package/options.js
CHANGED
|
@@ -49,7 +49,7 @@ const OPTIONS_INFO = {
|
|
|
49
49
|
"browser-height": { description: "Height of the browser viewport in pixels", type: "number", defaultValue: 720 },
|
|
50
50
|
"browser-load-max-time": { description: "Maximum delay of time to wait for page loading in ms", type: "number", defaultValue: 60000 },
|
|
51
51
|
"browser-wait-delay": { description: "Time to wait before capturing the page in ms", type: "number" },
|
|
52
|
-
"browser-wait-until": { description: "When to consider the page is loaded (
|
|
52
|
+
"browser-wait-until": { description: "When to consider the page is loaded (InteractiveTime, networkIdle, networkAlmostIdle, load, domContentLoaded)", type: "string", defaultValue: "networkIdle" },
|
|
53
53
|
"browser-wait-until-fallback": { description: "Retry with the next value of --browser-wait-until when a timeout error is thrown", type: "boolean", defaultValue: true },
|
|
54
54
|
"browser-debug": { description: "Enable debug mode", type: "boolean" },
|
|
55
55
|
"browser-script": { description: "Path of a script executed in the page (and all the frames) before it is loaded", type: "string[]" },
|