single-file-cli 1.1.53 → 2.0.2

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 (43) hide show
  1. package/{.eslintrc.js → .eslintrc.json} +5 -6
  2. package/Dockerfile +2 -1
  3. package/README.MD +56 -60
  4. package/args.js +252 -334
  5. package/back-ends/chromium-back-end.js +223 -0
  6. package/back-ends/chromium-browser.js +89 -0
  7. package/back-ends/chromium-constants.js +157 -0
  8. package/back-ends/{common/scripts.js → single-file-script.js} +21 -49
  9. package/build-lib-dev.sh +84 -0
  10. package/build-lib.sh +80 -3
  11. package/compile.sh +13 -0
  12. package/deno-polyfill.js +223 -0
  13. package/deno.json +13 -0
  14. package/deno.lock +49 -0
  15. package/lib/single-file-bundle.js +1 -0
  16. package/package.json +9 -33
  17. package/single-file +39 -14
  18. package/single-file-cli-api.js +32 -44
  19. package/back-ends/extensions/bypass-csp/index.js +0 -36
  20. package/back-ends/extensions/bypass-csp/manifest.json +0 -20
  21. package/back-ends/extensions/disable-web-security/index.js +0 -54
  22. package/back-ends/extensions/disable-web-security/manifest.json +0 -20
  23. package/back-ends/extensions/network-idle/bg.js +0 -61
  24. package/back-ends/extensions/network-idle/content.js +0 -29
  25. package/back-ends/extensions/network-idle/manifest.json +0 -31
  26. package/back-ends/extensions/signed/bypass_csp-0.0.3-an+fx.xpi +0 -0
  27. package/back-ends/extensions/signed/disable_web_security-0.0.3-an+fx.xpi +0 -0
  28. package/back-ends/extensions/signed/network_idle-0.0.2-an+fx.xpi +0 -0
  29. package/back-ends/jsdom.js +0 -159
  30. package/back-ends/playwright-chromium.js +0 -126
  31. package/back-ends/playwright-firefox.js +0 -115
  32. package/back-ends/playwright-webkit.js +0 -126
  33. package/back-ends/puppeteer-firefox.js +0 -172
  34. package/back-ends/puppeteer.js +0 -201
  35. package/back-ends/webdriver-chromium.js +0 -172
  36. package/back-ends/webdriver-gecko.js +0 -190
  37. package/lib/single-file-bootstrap.js +0 -1
  38. package/lib/single-file-frames.js +0 -1
  39. package/lib/single-file-hooks-frames.js +0 -1
  40. package/lib/single-file-zip.min.js +0 -1
  41. package/lib/single-file.js +0 -1
  42. package/rollup.config.dev.js +0 -57
  43. package/rollup.config.js +0 -58
package/package.json CHANGED
@@ -1,41 +1,17 @@
1
1
  {
2
2
  "name": "single-file-cli",
3
- "version": "1.1.53",
3
+ "version": "2.0.2",
4
4
  "description": "SingleFile CLI",
5
5
  "author": "Gildas Lormeau",
6
- "license": "AGPL-3.0-or-later",
7
- "main": "single-file-cli-api.js",
8
- "bin": {
9
- "single-file": "./single-file"
6
+ "engines": {
7
+ "deno": ">=1.4",
8
+ "bun": ">=1.0",
9
+ "node": ">=20"
10
10
  },
11
- "scripts": {
12
- "build": "./build-lib.sh",
13
- "prepublishOnly": "./build-lib.sh"
14
- },
15
- "repository": {
16
- "type": "git",
17
- "url": "git+https://github.com/gildas-lormeau/single-file-cli.git"
18
- },
19
- "bugs": {
20
- "url": "https://github.com/gildas-lormeau/single-file-cli/issues"
21
- },
22
- "homepage": "https://github.com/gildas-lormeau/single-file-cli#readme",
11
+ "type": "module",
12
+ "bin": "./single-file",
23
13
  "dependencies": {
24
- "file-url": "3.0.0",
25
- "iconv-lite": "0.6.3",
26
- "jsdom": "24.0.0",
27
- "puppeteer-core": "22.0.0",
28
- "selenium-webdriver": "4.17.0",
29
- "single-file-core": "1.3.23",
30
- "strong-data-uri": "1.0.6",
31
- "yargs": "17.7.2"
32
- },
33
- "devDependencies": {
34
- "@rollup/plugin-node-resolve": "15.0.1",
35
- "rollup": "2.79.1",
36
- "rollup-plugin-terser": "7.0.2"
37
- },
38
- "overrides": {
39
- "terser": "^5.15.0"
14
+ "simple-cdp": "^1.8.2",
15
+ "ws": "^8.16.0"
40
16
  }
41
17
  }
package/single-file CHANGED
@@ -1,7 +1,7 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env -S deno run --allow-read --allow-write --allow-net --allow-env --allow-run
2
2
 
3
3
  /*
4
- * Copyright 2010-2020 Gildas Lormeau
4
+ * Copyright 2010-2024 Gildas Lormeau
5
5
  * contact : gildas.lormeau <at> gmail.com
6
6
  *
7
7
  * This file is part of SingleFile.
@@ -23,34 +23,59 @@
23
23
  * Source.
24
24
  */
25
25
 
26
- /* global require */
26
+ import { VALID_URL_TEST, initialize } from "./single-file-cli-api.js";
27
+ import { initGlobalThisProperties, readTextFile, toFileUrl } from "./deno-polyfill.js";
28
+ import args from "./args.js";
27
29
 
28
- const fileUrl = require("file-url");
29
- const fs = require("fs");
30
- const api = require("./single-file-cli-api");
31
- run(require("./args"))
32
- .catch(error => console.error(error.message || error)); // eslint-disable-line no-console
30
+ await initGlobalThisProperties();
31
+ await run(args);
33
32
 
34
- async function run(options) {
33
+ async function run(options = {}) {
35
34
  let urls;
36
- if (options.url && !api.VALID_URL_TEST.test(options.url)) {
37
- options.url = fileUrl(options.url);
35
+ if (options.url && !VALID_URL_TEST.test(options.url)) {
36
+ options.url = (await toFileUrl(new URL(options.url, import.meta.url).pathname)).href;
38
37
  }
39
38
  if (options.urlsFile) {
40
- urls = fs.readFileSync(options.urlsFile).toString().split("\n");
39
+ urls = (await readTextFile(options.urlsFile)).split("\n");
41
40
  } else {
42
41
  urls = [options.url];
43
42
  }
43
+ if (options.browserCookies) {
44
+ const cookies = [];
45
+ for (const cookie of options.browserCookies) {
46
+ const [name, value, domain, path, expires, httpOnly, secure, sameSite, url] = cookie.split(",");
47
+ cookies.push({
48
+ name,
49
+ value,
50
+ url,
51
+ domain,
52
+ path,
53
+ secure,
54
+ httpOnly,
55
+ sameSite,
56
+ expires
57
+ });
58
+ }
59
+ options.browserCookies = cookies;
60
+ }
44
61
  if (options.browserCookiesFile) {
45
- const cookiesContent = fs.readFileSync(options.browserCookiesFile).toString();
62
+ const cookiesContent = await readTextFile(options.browserCookiesFile);
46
63
  try {
47
64
  options.browserCookies = JSON.parse(cookiesContent);
48
65
  } catch (error) {
49
66
  options.browserCookies = parseCookies(cookiesContent);
50
67
  }
51
68
  }
69
+ if (options.httpHeaders) {
70
+ const headers = {};
71
+ for (const header of options.httpHeaders) {
72
+ const [name, value] = header.split("=");
73
+ headers[name] = value.trim();
74
+ }
75
+ options.httpHeaders = headers;
76
+ }
52
77
  options.retrieveLinks = true;
53
- const singlefile = await api.initialize(options);
78
+ const singlefile = await initialize(options);
54
79
  await singlefile.capture(urls);
55
80
  await singlefile.finish();
56
81
  }
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2010-2020 Gildas Lormeau
2
+ * Copyright 2010-2024 Gildas Lormeau
3
3
  * contact : gildas.lormeau <at> gmail.com
4
4
  *
5
5
  * This file is part of SingleFile.
@@ -21,10 +21,12 @@
21
21
  * Source.
22
22
  */
23
23
 
24
- /* global require, exports, URL, process */
24
+ /* global URL */
25
+
26
+ import * as backend from "./back-ends/chromium-back-end.js";
27
+ import { getZipScriptSource } from "./back-ends/single-file-script.js";
28
+ import { readTextFile, writeTextFile, stdout, mkdir, stat, dirname } from "./deno-polyfill.js";
25
29
 
26
- const fs = require("fs");
27
- const path = require("path");
28
30
  const VALID_URL_TEST = /^(https?|file):\/\//;
29
31
 
30
32
  const DEFAULT_OPTIONS = {
@@ -74,32 +76,17 @@ const DEFAULT_OPTIONS = {
74
76
  const STATE_PROCESSING = "processing";
75
77
  const STATE_PROCESSED = "processed";
76
78
 
77
- const backEnds = {
78
- jsdom: "./back-ends/jsdom.js",
79
- puppeteer: "./back-ends/puppeteer.js",
80
- "puppeteer-firefox": "./back-ends/puppeteer-firefox.js",
81
- "webdriver-chromium": "./back-ends/webdriver-chromium.js",
82
- "webdriver-gecko": "./back-ends/webdriver-gecko.js",
83
- "playwright-firefox": "./back-ends/playwright-firefox.js",
84
- "playwright-chromium": "./back-ends/playwright-chromium.js",
85
- "playwright-webkit": "./back-ends/playwright-webkit.js"
86
- };
87
-
88
- let backend, tasks = [], maxParallelWorkers = 8, sessionFilename;
79
+ let tasks = [], maxParallelWorkers, sessionFilename;
89
80
 
90
- exports.getBackEnd = backEndName => require(backEnds[backEndName]);
91
- exports.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
92
- exports.VALID_URL_TEST = VALID_URL_TEST;
93
- exports.initialize = initialize;
81
+ export { DEFAULT_OPTIONS, VALID_URL_TEST, initialize };
94
82
 
95
83
  async function initialize(options) {
96
84
  options = Object.assign({}, DEFAULT_OPTIONS, options);
97
- maxParallelWorkers = options.maxParallelWorkers;
98
- backend = require(backEnds[options.backEnd]);
85
+ maxParallelWorkers = options.maxParallelWorkers || 8;
99
86
  await backend.initialize(options);
100
87
  if (options.crawlSyncSession || options.crawlLoadSession) {
101
88
  try {
102
- tasks = JSON.parse(fs.readFileSync(options.crawlSyncSession || options.crawlLoadSession).toString());
89
+ tasks = JSON.parse(await readTextFile(options.crawlSyncSession || options.crawlLoadSession));
103
90
  } catch (error) {
104
91
  if (options.crawlLoadSession) {
105
92
  throw error;
@@ -122,7 +109,7 @@ async function capture(urls, options) {
122
109
  newTasks = newTasks.filter(task => task && !taskUrls.includes(task.url));
123
110
  if (newTasks.length) {
124
111
  tasks = tasks.concat(newTasks);
125
- saveTasks();
112
+ await saveTasks();
126
113
  }
127
114
  await runTasks();
128
115
  }
@@ -131,9 +118,9 @@ async function finish(options) {
131
118
  const promiseTasks = tasks.map(task => task.promise);
132
119
  await Promise.all(promiseTasks);
133
120
  if (options.crawlReplaceURLs && !options.compressContent) {
134
- tasks.forEach(task => {
121
+ for (const task of tasks) {
135
122
  try {
136
- let pageContent = fs.readFileSync(task.filename).toString();
123
+ let pageContent = await readTextFile(task.filename);
137
124
  tasks.forEach(otherTask => {
138
125
  if (otherTask.filename) {
139
126
  pageContent = pageContent.replace(new RegExp(escapeRegExp("\"" + otherTask.originalUrl + "\""), "gi"), "\"" + otherTask.filename + "\"");
@@ -143,11 +130,11 @@ async function finish(options) {
143
130
  pageContent = pageContent.replace(new RegExp(escapeRegExp("=" + otherTask.originalUrl + ">"), "gi"), "=" + filename + ">");
144
131
  }
145
132
  });
146
- fs.writeFileSync(task.filename, pageContent);
133
+ await writeTextFile(task.filename, pageContent);
147
134
  } catch (error) {
148
135
  // ignored
149
136
  }
150
- });
137
+ }
151
138
  }
152
139
  if (!options.browserDebug) {
153
140
  return backend.closeBrowser();
@@ -171,7 +158,7 @@ async function runNextTask() {
171
158
  let taskOptions = JSON.parse(JSON.stringify(options));
172
159
  taskOptions.url = task.url;
173
160
  task.status = STATE_PROCESSING;
174
- saveTasks();
161
+ await saveTasks();
175
162
  task.promise = capturePage(taskOptions);
176
163
  const pageData = await task.promise;
177
164
  task.status = STATE_PROCESSED;
@@ -188,7 +175,7 @@ async function runNextTask() {
188
175
  tasks.splice(tasks.length, 0, ...newTasks);
189
176
  }
190
177
  }
191
- saveTasks();
178
+ await saveTasks();
192
179
  await runTasks();
193
180
  }
194
181
  }
@@ -218,9 +205,9 @@ function createTask(url, options, parentTask, rootTask) {
218
205
  }
219
206
  }
220
207
 
221
- function saveTasks() {
208
+ async function saveTasks() {
222
209
  if (sessionFilename) {
223
- fs.writeFileSync(sessionFilename, JSON.stringify(
210
+ await writeTextFile(sessionFilename, JSON.stringify(
224
211
  tasks.map(task => Object.assign({}, task, {
225
212
  status: task.status == STATE_PROCESSING ? undefined : task.status,
226
213
  promise: undefined,
@@ -252,38 +239,38 @@ function getHostURL(url) {
252
239
  async function capturePage(options) {
253
240
  try {
254
241
  let filename;
255
- options.zipScript = fs.readFileSync(require.resolve("./lib/single-file-zip.min.js")).toString();
242
+ options.zipScript = getZipScriptSource();
256
243
  const pageData = await backend.getPageData(options);
257
244
  if (options.output) {
258
- filename = getFilename(options.output, options);
245
+ filename = await getFilename(options.output, options);
259
246
  } else if (options.dumpContent) {
260
247
  if (options.compressContent) {
261
- await new Promise(resolve => process.stdout.write(pageData.content, resolve));
248
+ await stdout.write(pageData.content);
262
249
  } else {
263
250
  console.log(pageData.content); // eslint-disable-line no-console
264
251
  }
265
252
  } else {
266
- filename = getFilename(pageData.filename, options);
253
+ filename = await getFilename(pageData.filename, options);
267
254
  }
268
255
  if (filename) {
269
- const dirname = path.dirname(filename);
270
- if (dirname) {
271
- fs.mkdirSync(dirname, { recursive: true });
256
+ const directoryName = await dirname(filename);
257
+ if (directoryName !== ".") {
258
+ await mkdir(directoryName, { recursive: true });
272
259
  }
273
- fs.writeFileSync(filename, pageData.content);
260
+ await writeTextFile(filename, pageData.content);
274
261
  }
275
262
  return pageData;
276
263
  } catch (error) {
277
264
  const message = "URL: " + options.url + "\nStack: " + error.stack + "\n";
278
265
  if (options.errorFile) {
279
- fs.writeFileSync(options.errorFile, message, { flag: "a" });
266
+ await writeTextFile(options.errorFile, message, { append: true });
280
267
  } else {
281
268
  console.error(error.message || error, message); // eslint-disable-line no-console
282
269
  }
283
270
  }
284
271
  }
285
272
 
286
- function getFilename(filename, options, index = 1) {
273
+ async function getFilename(filename, options, index = 1) {
287
274
  if (Array.isArray(options.outputDirectory)) {
288
275
  const outputDirectory = options.outputDirectory.pop();
289
276
  if (outputDirectory.startsWith("/")) {
@@ -308,11 +295,12 @@ function getFilename(filename, options, index = 1) {
308
295
  newFilename += " (" + index + ")";
309
296
  }
310
297
  }
311
- if (fs.existsSync(newFilename)) {
298
+ try {
299
+ await stat(newFilename);
312
300
  if (options.filenameConflictAction != "skip") {
313
301
  return getFilename(filename, options, index + 1);
314
302
  }
315
- } else {
303
+ } catch (_) {
316
304
  return newFilename;
317
305
  }
318
306
  }
@@ -1,36 +0,0 @@
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 = ["content-security-policy"];
25
-
26
- const browserAPI = this.browser || this.chrome;
27
-
28
- browserAPI.webRequest.onHeadersReceived.addListener(
29
- function (details) {
30
- let responseHeaders = details.responseHeaders;
31
- responseHeaders = responseHeaders.filter(responseHeader => !removedHeaders.includes(responseHeader.name.toLowerCase()));
32
- return { responseHeaders };
33
- },
34
- { urls: ["<all_urls>"] },
35
- ["blocking", "responseHeaders"]
36
- );
@@ -1,20 +0,0 @@
1
- {
2
- "name": "bypass-csp",
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": "{55e2789c-817b-4d75-815b-df6921c84ed8}"
17
- }
18
- },
19
- "manifest_version": 2
20
- }
@@ -1,54 +0,0 @@
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
- );
@@ -1,20 +0,0 @@
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
- }
@@ -1,61 +0,0 @@
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
- }
@@ -1,29 +0,0 @@
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)));
@@ -1,31 +0,0 @@
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
- }