single-file-cli 2.0.69 → 2.0.70
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 +8 -3
- package/deno.json +2 -2
- package/lib/cdp-client.js +124 -21
- package/lib/constants.js +3 -1
- package/lib/single-file-bundle.js +1 -1
- package/lib/version.js +1 -1
- package/options.js +35 -21
- package/package.json +1 -1
- package/single-file-cli-api.js +13 -1
- package/single-file-launcher.js +1 -1
package/build.sh
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
mv package.json package.json.tmp
|
|
4
|
+
mv deno.json deno.json.tmp
|
|
5
|
+
mv deno.lock deno.lock.tmp
|
|
6
|
+
deno install --vendor --quiet "npm:single-file-core@1.5.40"
|
|
7
|
+
mv package.json.tmp package.json
|
|
8
|
+
mv deno.json.tmp deno.json
|
|
9
|
+
mv deno.lock.tmp deno.lock
|
|
4
10
|
|
|
5
11
|
echo "
|
|
6
12
|
import { build } from 'npm:esbuild';
|
|
@@ -82,5 +88,4 @@ const version = JSON.parse(await Deno.readTextFile('./deno.json')).version;
|
|
|
82
88
|
await Deno.writeTextFile('lib/version.js', 'export const version = ' + JSON.stringify(version) + ';');
|
|
83
89
|
" | deno run --allow-read --allow-write --allow-net --allow-run --allow-env --lock=node_modules/deno.lock.tmp -
|
|
84
90
|
|
|
85
|
-
rm -rf node_modules
|
|
86
|
-
rm -rf vendor
|
|
91
|
+
rm -rf node_modules
|
package/deno.json
CHANGED
package/lib/cdp-client.js
CHANGED
|
@@ -50,12 +50,19 @@ async function initialize(singleFileOptions) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
async function getPageData(options) {
|
|
53
|
+
const debugMessages = [];
|
|
53
54
|
let targetInfo;
|
|
54
55
|
try {
|
|
56
|
+
if (options.debugMessagesFile) {
|
|
57
|
+
debugMessages.push([Date.now(), ["Loading page", EMPTY_PAGE_URL]]);
|
|
58
|
+
}
|
|
55
59
|
targetInfo = await CDP.createTarget(EMPTY_PAGE_URL);
|
|
56
60
|
const consoleMessages = [];
|
|
57
61
|
const { Browser, Security, Page, Emulation, Fetch, Network, Runtime, Debugger, Console } = new CDP(targetInfo);
|
|
58
62
|
if (options.consoleMessagesFile) {
|
|
63
|
+
if (options.debugMessagesFile) {
|
|
64
|
+
debugMessages.push([Date.now(), ["Enabling console messages"]]);
|
|
65
|
+
}
|
|
59
66
|
await Console.enable();
|
|
60
67
|
Console.addEventListener("messageAdded", ({ params }) => {
|
|
61
68
|
const { message } = params;
|
|
@@ -65,13 +72,22 @@ async function getPageData(options) {
|
|
|
65
72
|
if (options.browserStartMinimized) {
|
|
66
73
|
const { windowId, bounds } = await Browser.getWindowForTarget({ targetId: targetInfo.id });
|
|
67
74
|
if (bounds.windowState !== MINIMIZED_WINDOW_STATE) {
|
|
75
|
+
if (options.debugMessagesFile) {
|
|
76
|
+
debugMessages.push([Date.now(), ["Minimizing window"]]);
|
|
77
|
+
}
|
|
68
78
|
await Browser.setWindowBounds({ windowId, bounds: { windowState: MINIMIZED_WINDOW_STATE } });
|
|
69
79
|
}
|
|
70
80
|
}
|
|
71
81
|
if (options.browserIgnoreHTTPSErrors !== undefined && options.browserIgnoreHTTPSErrors) {
|
|
82
|
+
if (options.debugMessagesFile) {
|
|
83
|
+
debugMessages.push([Date.now(), ["Ignoring HTTPS errors"]]);
|
|
84
|
+
}
|
|
72
85
|
await Security.setIgnoreCertificateErrors({ ignore: true });
|
|
73
86
|
}
|
|
74
87
|
if (options.browserByPassCSP === undefined || options.browserByPassCSP) {
|
|
88
|
+
if (options.debugMessagesFile) {
|
|
89
|
+
debugMessages.push([Date.now(), ["Bypassing CSP"]]);
|
|
90
|
+
}
|
|
75
91
|
await Page.setBypassCSP({ enabled: true });
|
|
76
92
|
}
|
|
77
93
|
if (options.browserMobileEmulation || options.browserDeviceWidth || options.browserDeviceHeight || options.browserDeviceScaleFactor || options.platform || options.acceptLanguage) {
|
|
@@ -91,12 +107,16 @@ async function getPageData(options) {
|
|
|
91
107
|
const { result } = await Runtime.evaluate({ expression: "window.devicePixelRatio" });
|
|
92
108
|
browserDeviceScaleFactor = result.value;
|
|
93
109
|
}
|
|
94
|
-
|
|
110
|
+
const deviceMetricsOptions = {
|
|
95
111
|
mobile: Boolean(options.browserMobileEmulation),
|
|
96
112
|
width: options.browserDeviceWidth || (options.browserMobileEmulation ? 360 : options.width || browserDeviceWidth),
|
|
97
113
|
height: options.browserDeviceHeight || (options.browserMobileEmulation ? 800 : options.height || browserDeviceHeight),
|
|
98
114
|
deviceScaleFactor: options.browserDeviceScaleFactor || (options.browserMobileEmulation ? 2 : browserDeviceScaleFactor)
|
|
99
|
-
}
|
|
115
|
+
};
|
|
116
|
+
if (options.debugMessagesFile) {
|
|
117
|
+
debugMessages.push([Date.now(), ["Emulating device metrics", JSON.stringify(deviceMetricsOptions)]]);
|
|
118
|
+
}
|
|
119
|
+
await Emulation.setDeviceMetricsOverride(deviceMetricsOptions);
|
|
100
120
|
}
|
|
101
121
|
if (options.browserMobileEmulation || options.platform || options.acceptLanguage) {
|
|
102
122
|
const { userAgent, product } = await Browser.getVersion();
|
|
@@ -109,21 +129,29 @@ async function getPageData(options) {
|
|
|
109
129
|
if (options.platform || options.browserMobileEmulation) {
|
|
110
130
|
agentOptions.platform = options.platform || "Android";
|
|
111
131
|
}
|
|
132
|
+
if (options.debugMessagesFile) {
|
|
133
|
+
debugMessages.push([Date.now(), ["Emulating user agent", JSON.stringify(agentOptions)]]);
|
|
134
|
+
}
|
|
112
135
|
await Emulation.setUserAgentOverride(agentOptions);
|
|
113
136
|
}
|
|
114
137
|
}
|
|
115
138
|
const handleAuthRequests = Boolean(options.httpProxyUsername);
|
|
116
|
-
const patterns = handleAuthRequests ? [{ requestStage: "Request" }, { requestStage: "Response"
|
|
139
|
+
const patterns = handleAuthRequests ? [{ requestStage: "Request" }, { requestStage: "Response" }] : [{ requestStage: "Response" }];
|
|
117
140
|
await Fetch.enable({ handleAuthRequests, patterns });
|
|
118
141
|
if (handleAuthRequests) {
|
|
119
|
-
Fetch.addEventListener("authRequired", ({ params }) =>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
response: "ProvideCredentials",
|
|
123
|
-
username: options.httpProxyUsername,
|
|
124
|
-
password: options.httpProxyPassword
|
|
142
|
+
Fetch.addEventListener("authRequired", async ({ params }) => {
|
|
143
|
+
if (options.debugMessagesFile) {
|
|
144
|
+
debugMessages.push([Date.now(), ["Authenticating"]]);
|
|
125
145
|
}
|
|
126
|
-
|
|
146
|
+
await Fetch.continueWithAuth({
|
|
147
|
+
requestId: params.requestId,
|
|
148
|
+
authChallengeResponse: {
|
|
149
|
+
response: "ProvideCredentials",
|
|
150
|
+
username: options.httpProxyUsername,
|
|
151
|
+
password: options.httpProxyPassword
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|
|
127
155
|
}
|
|
128
156
|
let url = options.url;
|
|
129
157
|
let alternativeUrl = new URL(url);
|
|
@@ -134,12 +162,15 @@ async function getPageData(options) {
|
|
|
134
162
|
let httpInfo;
|
|
135
163
|
Fetch.addEventListener("requestPaused", async ({ params }) => {
|
|
136
164
|
const { requestId, request, resourceType, responseHeaders, responseStatusCode, responseStatusText } = params;
|
|
137
|
-
if (options.outputJson && !httpInfo && (request.url == url || request.url == alternativeUrl) && responseStatusCode !== undefined) {
|
|
165
|
+
if (resourceType == "Document" && (options.outputJson && !httpInfo && (request.url == url || request.url == alternativeUrl) && responseStatusCode !== undefined)) {
|
|
138
166
|
if (REDIRECT_STATUS_CODES.includes(responseStatusCode)) {
|
|
139
167
|
const redirect = responseHeaders.find(header => header.name.toLowerCase() == "location").value;
|
|
140
168
|
if (redirect) {
|
|
141
169
|
url = new URL(redirect, url).href;
|
|
142
170
|
}
|
|
171
|
+
if (options.debugMessagesFile) {
|
|
172
|
+
debugMessages.push([Date.now(), ["Redirecting", url]]);
|
|
173
|
+
}
|
|
143
174
|
} else {
|
|
144
175
|
httpInfo = {
|
|
145
176
|
request: {
|
|
@@ -156,6 +187,18 @@ async function getPageData(options) {
|
|
|
156
187
|
}
|
|
157
188
|
};
|
|
158
189
|
}
|
|
190
|
+
} else if (options.blockedURLPatterns && options.blockedURLPatterns.length) {
|
|
191
|
+
const blockedURL = options.blockedURLPatterns.find(blockedURL => new RegExp(blockedURL).test(request.url));
|
|
192
|
+
if (blockedURL) {
|
|
193
|
+
try {
|
|
194
|
+
if (options.debugMessagesFile) {
|
|
195
|
+
debugMessages.push([Date.now(), ["Blocking request", request.url]]);
|
|
196
|
+
}
|
|
197
|
+
await Fetch.failRequest({ requestId, errorReason: "Aborted" });
|
|
198
|
+
} catch {
|
|
199
|
+
// ignored
|
|
200
|
+
}
|
|
201
|
+
}
|
|
159
202
|
}
|
|
160
203
|
try {
|
|
161
204
|
await Fetch.continueRequest({ requestId });
|
|
@@ -164,10 +207,16 @@ async function getPageData(options) {
|
|
|
164
207
|
}
|
|
165
208
|
});
|
|
166
209
|
if (options.httpHeaders && options.httpHeaders.length) {
|
|
210
|
+
if (options.debugMessagesFile) {
|
|
211
|
+
debugMessages.push([Date.now(), ["Setting HTTP headers", JSON.stringify(options.httpHeaders)]]);
|
|
212
|
+
}
|
|
167
213
|
await Network.setExtraHTTPHeaders({ headers: options.httpHeaders });
|
|
168
214
|
}
|
|
169
215
|
if (options.emulateMediaFeatures) {
|
|
170
216
|
for (const mediaFeature of options.emulateMediaFeatures) {
|
|
217
|
+
if (options.debugMessagesFile) {
|
|
218
|
+
debugMessages.push([Date.now(), ["Emulating media feature", mediaFeature.name, mediaFeature.value]]);
|
|
219
|
+
}
|
|
171
220
|
await Emulation.setEmulatedMedia({
|
|
172
221
|
media: mediaFeature.name,
|
|
173
222
|
features: mediaFeature.value.split(",").map(feature => feature.trim())
|
|
@@ -175,6 +224,9 @@ async function getPageData(options) {
|
|
|
175
224
|
}
|
|
176
225
|
}
|
|
177
226
|
if (options.browserCookies && options.browserCookies.length) {
|
|
227
|
+
if (options.debugMessagesFile) {
|
|
228
|
+
debugMessages.push([Date.now(), ["Setting cookies", JSON.stringify(options.browserCookies)]]);
|
|
229
|
+
}
|
|
178
230
|
await Network.setCookies({ cookies: options.browserCookies });
|
|
179
231
|
}
|
|
180
232
|
await Browser.setDownloadBehavior({ behavior: "deny" });
|
|
@@ -188,7 +240,7 @@ async function getPageData(options) {
|
|
|
188
240
|
worldName: SINGLE_FILE_WORLD_NAME
|
|
189
241
|
});
|
|
190
242
|
const [contextId] = await Promise.all([
|
|
191
|
-
loadPage({ Page, Runtime }, options),
|
|
243
|
+
loadPage({ Page, Runtime }, options, debugMessages),
|
|
192
244
|
options.browserDebug ? waitForDebuggerReady({ Debugger }) : Promise.resolve()
|
|
193
245
|
]);
|
|
194
246
|
await Runtime.addBinding({ name: SET_PAGE_DATA_FUNCTION_NAME, executionContextId: contextId });
|
|
@@ -196,6 +248,9 @@ async function getPageData(options) {
|
|
|
196
248
|
await Runtime.addBinding({ name: CAPTURE_SCREENSHOT_FUNCTION_NAME, executionContextId: contextId });
|
|
197
249
|
Runtime.addEventListener("bindingCalled", async ({ params }) => {
|
|
198
250
|
if (params.name === CAPTURE_SCREENSHOT_FUNCTION_NAME) {
|
|
251
|
+
if (options.debugMessagesFile) {
|
|
252
|
+
debugMessages.push([Date.now(), ["Capturing screenshot"]]);
|
|
253
|
+
}
|
|
199
254
|
try {
|
|
200
255
|
let screenshotOptions = { captureBeyondViewport: true };
|
|
201
256
|
if (options.embedScreenshotOptions) {
|
|
@@ -218,6 +273,9 @@ async function getPageData(options) {
|
|
|
218
273
|
await Runtime.addBinding({ name: PRINT_TO_PDF_FUNCTION_NAME, executionContextId: contextId });
|
|
219
274
|
Runtime.addEventListener("bindingCalled", async ({ params }) => {
|
|
220
275
|
if (params.name === PRINT_TO_PDF_FUNCTION_NAME) {
|
|
276
|
+
if (options.debugMessagesFile) {
|
|
277
|
+
debugMessages.push([Date.now(), ["Printing to PDF", options.embedPdfOptions || ""]]);
|
|
278
|
+
}
|
|
221
279
|
let pdfOptions = {};
|
|
222
280
|
if (options.embedPdfOptions) {
|
|
223
281
|
try {
|
|
@@ -243,6 +301,9 @@ async function getPageData(options) {
|
|
|
243
301
|
if (payload.length) {
|
|
244
302
|
pageDataResponse += payload;
|
|
245
303
|
} else {
|
|
304
|
+
if (options.debugMessagesFile) {
|
|
305
|
+
debugMessages.push([Date.now(), ["Setting page data"]]);
|
|
306
|
+
}
|
|
246
307
|
const result = JSON.parse(pageDataResponse);
|
|
247
308
|
if (result.content instanceof Array) {
|
|
248
309
|
result.content = new Uint8Array(result.content);
|
|
@@ -253,8 +314,14 @@ async function getPageData(options) {
|
|
|
253
314
|
});
|
|
254
315
|
});
|
|
255
316
|
if (options.browserWaitDelay) {
|
|
317
|
+
if (options.debugMessagesFile) {
|
|
318
|
+
debugMessages.push([Date.now(), [`Waiting ${options.browserWaitDelay} ms`]]);
|
|
319
|
+
}
|
|
256
320
|
await new Promise(resolve => setTimeout(resolve, options.browserWaitDelay));
|
|
257
321
|
}
|
|
322
|
+
if (options.debugMessagesFile) {
|
|
323
|
+
debugMessages.push([Date.now(), ["Capturing page"]]);
|
|
324
|
+
}
|
|
258
325
|
const { result } = await Runtime.evaluate({
|
|
259
326
|
expression: `(${getPageDataScriptSource.toString()})(${JSON.stringify(options)},${JSON.stringify([SET_SCREENSHOT_FUNCTION_NAME, SET_PDF_FUNCTION_NAME, SET_PAGE_DATA_FUNCTION_NAME, CAPTURE_SCREENSHOT_FUNCTION_NAME, PRINT_TO_PDF_FUNCTION_NAME])})`,
|
|
260
327
|
awaitPromise: true,
|
|
@@ -270,19 +337,35 @@ async function getPageData(options) {
|
|
|
270
337
|
await Console.disable();
|
|
271
338
|
pageData.consoleMessages = consoleMessages;
|
|
272
339
|
}
|
|
340
|
+
if (options.debugMessagesFile) {
|
|
341
|
+
pageData.debugMessages = debugMessages;
|
|
342
|
+
debugMessages.push([Date.now(), ["Returning page data"]]);
|
|
343
|
+
}
|
|
273
344
|
Object.assign(pageData, httpInfo);
|
|
274
345
|
return pageData;
|
|
275
346
|
} catch (error) {
|
|
276
347
|
if (error.code === LOAD_TIMEOUT_ERROR && options.browserWaitUntilFallback && options.browserWaitUntil) {
|
|
277
348
|
const browserWaitUntil = NETWORK_STATES[(NETWORK_STATES.indexOf(options.browserWaitUntil) + 1)];
|
|
278
349
|
if (browserWaitUntil) {
|
|
350
|
+
if (options.debugMessagesFile) {
|
|
351
|
+
debugMessages.push([Date.now(), ["Retrying with waitUntil", browserWaitUntil]]);
|
|
352
|
+
}
|
|
279
353
|
options.browserWaitUntil = browserWaitUntil;
|
|
280
354
|
await closeTarget();
|
|
281
355
|
return await getPageData(options);
|
|
282
356
|
}
|
|
283
357
|
}
|
|
358
|
+
if (options.consoleMessagesFile) {
|
|
359
|
+
error.consoleMessages = consoleMessages;
|
|
360
|
+
}
|
|
361
|
+
if (options.debugMessagesFile) {
|
|
362
|
+
error.debugMessages = debugMessages;
|
|
363
|
+
}
|
|
284
364
|
throw error;
|
|
285
365
|
} finally {
|
|
366
|
+
if (options.debugMessagesFile) {
|
|
367
|
+
debugMessages.push([Date.now(), ["Closing page"]]);
|
|
368
|
+
}
|
|
286
369
|
await closeTarget();
|
|
287
370
|
}
|
|
288
371
|
|
|
@@ -335,14 +418,17 @@ function getPageDataScriptSource(options, [SET_SCREENSHOT_FUNCTION_NAME, SET_PDF
|
|
|
335
418
|
});
|
|
336
419
|
}
|
|
337
420
|
|
|
338
|
-
async function loadPage({ Page, Runtime }, options) {
|
|
421
|
+
async function loadPage({ Page, Runtime }, options, debugMessages) {
|
|
339
422
|
await Runtime.enable();
|
|
340
423
|
await Page.enable();
|
|
341
424
|
const loadTimeoutAbortController = new AbortController();
|
|
342
425
|
const loadTimeoutAbortSignal = loadTimeoutAbortController.signal;
|
|
343
426
|
try {
|
|
427
|
+
if (options.debugMessagesFile) {
|
|
428
|
+
debugMessages.push([Date.now(), ["Loading page", options.url]]);
|
|
429
|
+
}
|
|
344
430
|
const [contextId] = await Promise.race([
|
|
345
|
-
Promise.all([getTopFrameContextId({ Page, Runtime }, options), Page.navigate({ url: options.url })]),
|
|
431
|
+
Promise.all([getTopFrameContextId({ Page, Runtime }, options, debugMessages), Page.navigate({ url: options.url })]),
|
|
346
432
|
waitForLoadTimeout(loadTimeoutAbortSignal, options.browserLoadMaxTime)
|
|
347
433
|
]);
|
|
348
434
|
return contextId;
|
|
@@ -355,13 +441,16 @@ async function loadPage({ Page, Runtime }, options) {
|
|
|
355
441
|
}
|
|
356
442
|
}
|
|
357
443
|
|
|
358
|
-
async function getTopFrameContextId({ Page, Runtime }, options) {
|
|
444
|
+
async function getTopFrameContextId({ Page, Runtime }, options, debugMessages) {
|
|
359
445
|
const CONTEXT_CREATED_EVENT = "executionContextCreated";
|
|
360
446
|
const contextIds = [];
|
|
361
447
|
let topFrameId;
|
|
362
448
|
try {
|
|
363
449
|
Runtime.addEventListener(CONTEXT_CREATED_EVENT, onExecutionContextCreated);
|
|
364
450
|
await waitForPageReady({ Page }, options);
|
|
451
|
+
if (options.debugMessagesFile) {
|
|
452
|
+
debugMessages.push([Date.now(), ["Getting execution context"]]);
|
|
453
|
+
}
|
|
365
454
|
const contextId = await getContextId();
|
|
366
455
|
if (contextId === undefined) {
|
|
367
456
|
throw new Error("Execution context not found");
|
|
@@ -392,12 +481,23 @@ async function getTopFrameContextId({ Page, Runtime }, options) {
|
|
|
392
481
|
|
|
393
482
|
function onLifecycleEvent({ params }) {
|
|
394
483
|
const { frameId, name } = params;
|
|
395
|
-
if (frameId === topFrameId
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
484
|
+
if (frameId === topFrameId) {
|
|
485
|
+
if (options.debugMessagesFile) {
|
|
486
|
+
debugMessages.push([Date.now(), ["Detecting lifecycle event", name]]);
|
|
487
|
+
}
|
|
488
|
+
if (name === options.browserWaitUntil || (resolveCallbackTimeout && NETWORK_STATES.indexOf(name) < NETWORK_STATES.indexOf(options.browserWaitUntil))) {
|
|
489
|
+
clearTimeout(resolveCallbackTimeout);
|
|
490
|
+
if (options.debugMessagesFile) {
|
|
491
|
+
debugMessages.push([Date.now(), [`Waiting ${options.browserWaitUntilDelay} ms`]]);
|
|
492
|
+
}
|
|
493
|
+
setTimeout(() => {
|
|
494
|
+
if (options.debugMessagesFile) {
|
|
495
|
+
debugMessages.push([Date.now(), ["Detecting page ready"]]);
|
|
496
|
+
}
|
|
497
|
+
removeListeners();
|
|
498
|
+
resolve();
|
|
499
|
+
}, options.browserWaitUntilDelay);
|
|
500
|
+
}
|
|
401
501
|
}
|
|
402
502
|
}
|
|
403
503
|
|
|
@@ -409,6 +509,9 @@ async function getTopFrameContextId({ Page, Runtime }, options) {
|
|
|
409
509
|
removeListeners();
|
|
410
510
|
reject(new Error("Unreachable URL: " + frame.unreachableUrl));
|
|
411
511
|
} else {
|
|
512
|
+
if (options.debugMessagesFile) {
|
|
513
|
+
debugMessages.push([Date.now(), ["Detecting top frame ID"]]);
|
|
514
|
+
}
|
|
412
515
|
topFrameId = frame.id;
|
|
413
516
|
}
|
|
414
517
|
}
|
package/lib/constants.js
CHANGED