single-file-cli 2.0.48 → 2.0.49

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/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@single-file/single-file-cli",
3
- "version": "2.0.48",
3
+ "version": "2.0.49",
4
4
  "description": "SingleFile CLI",
5
5
  "nodeModulesDir": true,
6
6
  "exports": {
package/lib/cdp-client.js CHANGED
@@ -35,6 +35,7 @@ const EMPTY_PAGE_URL = "about:blank";
35
35
  const CAPTURE_SCREENSHOT_FUNCTION_NAME = "captureScreenshot";
36
36
  const SET_SCREENSHOT_FUNCTION_NAME = "setScreenshot";
37
37
  const SET_PAGE_DATA_FUNCTION_NAME = "setPageData";
38
+ const REDIRECT_STATUS_CODES = [301, 302, 303, 307, 308];
38
39
 
39
40
  export { initialize, getPageData, closeBrowser };
40
41
 
@@ -104,8 +105,10 @@ async function getPageData(options) {
104
105
  await Emulation.setUserAgentOverride(agentOptions);
105
106
  }
106
107
  }
107
- if (options.httpProxyUsername) {
108
- await Fetch.enable({ handleAuthRequests: true });
108
+ const handleAuthRequests = Boolean(options.httpProxyUsername);
109
+ const patterns = handleAuthRequests ? [{ requestStage: "Request" }, { requestStage: "Response" }] : [{ requestStage: "Response" }];
110
+ await Fetch.enable({ handleAuthRequests, patterns });
111
+ if (handleAuthRequests) {
109
112
  Fetch.addEventListener("authRequired", ({ params }) => Fetch.continueWithAuth({
110
113
  requestId: params.requestId,
111
114
  authChallengeResponse: {
@@ -114,8 +117,42 @@ async function getPageData(options) {
114
117
  password: options.httpProxyPassword
115
118
  }
116
119
  }));
117
- Fetch.addEventListener("requestPaused", ({ params }) => Fetch.continueRequest({ requestId: params.requestId }));
118
120
  }
121
+ let url = options.url;
122
+ let alternativeUrl = new URL(url);
123
+ if (!alternativeUrl.pathname.endsWith("/")) {
124
+ alternativeUrl.pathname = alternativeUrl.pathname + "/";
125
+ }
126
+ alternativeUrl = alternativeUrl.href;
127
+ let httpInfo;
128
+ delete options.zipScript;
129
+ Fetch.addEventListener("requestPaused", async ({ params }) => {
130
+ const { requestId, request, resourceType, responseHeaders, responseStatusCode, responseStatusText } = params;
131
+ if (options.outputJson && !httpInfo && (request.url == url || request.url == alternativeUrl) && responseStatusCode !== undefined) {
132
+ if (REDIRECT_STATUS_CODES.includes(responseStatusCode)) {
133
+ const redirect = responseHeaders.find(header => header.name.toLowerCase() == "location").value;
134
+ if (redirect) {
135
+ url = new URL(redirect, url).href;
136
+ }
137
+ } else {
138
+ httpInfo = {
139
+ request: {
140
+ url: request.url,
141
+ method: request.method,
142
+ headers: request.headers,
143
+ referrerPolicy: request.referrerPolicy
144
+ },
145
+ resourceType,
146
+ response: {
147
+ status: responseStatusCode,
148
+ statusText: responseStatusText,
149
+ headers: responseHeaders
150
+ }
151
+ };
152
+ }
153
+ }
154
+ await Fetch.continueRequest({ requestId });
155
+ });
119
156
  if (options.httpHeaders && options.httpHeaders.length) {
120
157
  await Network.setExtraHTTPHeaders({ headers: options.httpHeaders });
121
158
  }
@@ -182,7 +219,9 @@ async function getPageData(options) {
182
219
  if (subtype === "error") {
183
220
  throw new Error(description);
184
221
  }
185
- return pageDataPromise;
222
+ const pageData = await pageDataPromise;
223
+ Object.assign(pageData, httpInfo);
224
+ return pageData;
186
225
  } catch (error) {
187
226
  if (error.code === LOAD_TIMEOUT_ERROR && options.browserWaitUntilFallback && options.browserWaitUntil) {
188
227
  const browserWaitUntil = NETWORK_STATES[(NETWORK_STATES.indexOf(options.browserWaitUntil) + 1)];
package/lib/version.js CHANGED
@@ -1 +1 @@
1
- export const version = "2.0.48";
1
+ export const version = "2.0.49";
package/options.js CHANGED
@@ -131,7 +131,8 @@ const OPTIONS_INFO = {
131
131
  "prevent-appended-data": { description: "Prevent appending data after the compressed data when creating self-extracting HTML files", type: "boolean" },
132
132
  "embed-screenshot": { description: "Embed a screenshot of the page as a PNG file in the compressed file (self-extracting HTML or ZIP file). When enabled, the resulting file can be read as a ZIP file or a PNG image.", type: "boolean" },
133
133
  "output-directory": { description: "Path to where to save files, this path must exist.", type: "string" },
134
- "version": { description: "Print the version number and exit.", type: "boolean" }
134
+ "version": { description: "Print the version number and exit.", type: "boolean" },
135
+ "output-json": { description: "Output the result as a JSON string containing the page and network info", type: "boolean" },
135
136
  };
136
137
 
137
138
  const { args, exit } = Deno;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-cli",
3
- "version": "2.0.48",
3
+ "version": "2.0.49",
4
4
  "description": "SingleFile CLI",
5
5
  "author": "Gildas Lormeau",
6
6
  "engines": {
@@ -21,7 +21,7 @@
21
21
  * Source.
22
22
  */
23
23
 
24
- /* global URL */
24
+ /* global URL, Blob, FileReader */
25
25
 
26
26
  import * as backend from "./lib/cdp-client.js";
27
27
  import { getZipScriptSource } from "./lib/single-file-script.js";
@@ -244,29 +244,48 @@ function getHostURL(url) {
244
244
 
245
245
  async function capturePage(options) {
246
246
  try {
247
- let filename;
247
+ let filename, content;
248
248
  options.zipScript = getZipScriptSource();
249
249
  const pageData = await backend.getPageData(options);
250
+ filename = pageData.filename;
251
+ content = pageData.content;
252
+ if (options.outputJson) {
253
+ if (content instanceof Uint8Array) {
254
+ const fileReader = new FileReader();
255
+ fileReader.readAsDataURL(new Blob([content]));
256
+ content = await new Promise(resolve => {
257
+ fileReader.onload = () => resolve(fileReader.result);
258
+ });
259
+ content = content.replace(/^data:.*?;base64,/, "");
260
+ pageData.content = undefined;
261
+ pageData.binaryContent = content;
262
+ }
263
+ pageData.doctype = undefined;
264
+ pageData.viewport = undefined;
265
+ pageData.comment = undefined;
266
+ filename += filename.endsWith(".json") ? "" : ".json";
267
+ content = JSON.stringify(pageData, null, 2);
268
+ }
250
269
  if (options.output) {
251
270
  filename = await getFilename(options.output, options);
252
271
  } else if (options.dumpContent) {
253
272
  if (options.compressContent) {
254
- await stdout.write(pageData.content);
273
+ await stdout.write(content);
255
274
  } else {
256
- console.log(pageData.content || ""); // eslint-disable-line no-console
275
+ console.log(content || ""); // eslint-disable-line no-console
257
276
  }
258
277
  } else {
259
- filename = await getFilename(pageData.filename, options);
278
+ filename = await getFilename(filename, options);
260
279
  }
261
- if (filename) {
280
+ if (filename && !options.dumpContent) {
262
281
  const directoryName = await path.dirname(filename);
263
282
  if (directoryName !== ".") {
264
283
  await mkdir(directoryName, { recursive: true });
265
284
  }
266
- if (pageData.content instanceof Uint8Array) {
267
- await writeFile(filename, pageData.content);
285
+ if (content instanceof Uint8Array) {
286
+ await writeFile(filename, content);
268
287
  } else {
269
- await writeTextFile(filename, pageData.content);
288
+ await writeTextFile(filename, content);
270
289
  }
271
290
  }
272
291
  return pageData;