extension 4.0.15 → 4.0.16

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 CHANGED
@@ -8,12 +8,12 @@
8
8
  [action-url]: https://github.com/extension-js/extension.js/actions
9
9
  [discord-image]: https://img.shields.io/discord/1253608412890271755?label=Discord&logo=discord&style=flat&color=0971fe
10
10
  [discord-url]: https://discord.gg/v9h2RgeTSN
11
- [snyk-image]: https://snyk.io/test/github/extension-js/extension/badge.svg?color=0971fe
12
- [snyk-url]: https://snyk.io/test/github/extension-js/extension
11
+ [snyk-image]: https://snyk.io/test/github/extension-js/extension.js/badge.svg?color=0971fe
12
+ [snyk-url]: https://snyk.io/test/github/extension-js/extension.js
13
13
 
14
14
  # Extension.js [![Version][npm-version-image]][npm-version-url] [![Downloads][npm-downloads-image]][npm-downloads-url] [![Stars][stars-image]][stars-url] [![CI][action-image]][action-url] [![Discord][discord-image]][discord-url]
15
15
 
16
- > The cross-browser extension framework. Build for Chrome, Edge, and Firefox with no build config required.
16
+ > The cross-browser extension framework. Build for Chrome, Edge, Firefox, and Safari with no build config required.
17
17
 
18
18
  <img alt="Logo" align="right" src="https://avatars.githubusercontent.com/u/172809806" width="14.1%" />
19
19
 
@@ -32,7 +32,7 @@ Works with `npm`, `pnpm`, `yarn`, `bun`, and `deno`.
32
32
  Browser extensions ship with the worst dev experience in modern web. Manifest V3 fragmentation, browser-specific quirks, no hot reload for content scripts, and a separate build pipeline for every target. Extension.js fixes that.
33
33
 
34
34
  - **Hot Module Replacement** for background, content, popup, and options scripts, including React, Vue, Svelte, and Preact components
35
- - **Manifest V3 by default**, with automatic adapters for Chrome, Edge, and Firefox targets
35
+ - **Manifest V3 by default**, with automatic adapters for Chrome, Edge, Firefox, and Safari targets
36
36
  - **One CLI** for Chrome, Edge, Firefox, and any Chromium or Gecko binary
37
37
  - **Zero config**, no webpack, no rollup, no plugins to maintain
38
38
  - **First-class** TypeScript, React, Vue, Svelte, and Preact support
@@ -72,7 +72,7 @@ If you have used [Plasmo](https://www.plasmo.com), [WXT](https://wxt.dev), or [C
72
72
 
73
73
  Use these flags with `extension dev`, `extension start`, or `extension preview`:
74
74
 
75
- - Select a browser: `--browser <chrome | edge | firefox>`
75
+ - Select a browser: `--browser <chrome | edge | firefox | safari>`
76
76
  - Custom Chromium binary: `--chromium-binary <path-to-binary>`
77
77
  - Custom Gecko (Firefox) binary: `--gecko-binary <path-to-binary>`
78
78
 
@@ -94,7 +94,7 @@ npx extension@latest dev --gecko-binary "/Applications/Firefox.app/Contents/MacO
94
94
 
95
95
  | <img alt="Chrome" src="https://media.extension.land/logos/browsers/chrome.svg" width="70"> | <img alt="Edge" src="https://media.extension.land/logos/browsers/edge.svg" width="70"> | <img alt="Firefox" src="https://media.extension.land/logos/browsers/firefox.svg" width="70"> | <img alt="Safari" src="https://media.extension.land/logos/browsers/safari.svg" width="70"> | <img alt="Chromium" src="https://media.extension.land/logos/browsers/chromium.svg" width="70"> | <img alt="Gecko" src="https://media.extension.land/logos/browsers/firefox.svg" width="70"> |
96
96
  | :-: | :-: | :-: | :-: | :-: | :-: |
97
- | Google Chrome<br>✅ Supported | Microsoft Edge<br>✅ Supported | Mozilla Firefox<br>✅ Supported | Apple Safari<br> 🧪 Alpha | Chromium-based<br>✅ Supported | Gecko-based<br>✅ Supported |
97
+ | Google Chrome<br>✅ Supported | Microsoft Edge<br>✅ Supported | Mozilla Firefox<br>✅ Supported | Apple Safari<br>✅ Supported | Chromium-based<br>✅ Supported | Gecko-based<br>✅ Supported |
98
98
 
99
99
  </div>
100
100
 
@@ -165,6 +165,16 @@ npx extension@latest dev https://github.com/GoogleChrome/chrome-extensions-sampl
165
165
 
166
166
  [Watch demo](https://github.com/cezaraugusto/extension/assets/4672033/ee221a94-6ec7-4e04-8553-8812288927f1)
167
167
 
168
+ ## Package managers
169
+
170
+ <div align="center">
171
+
172
+ | <img alt="npm" src="https://media.extension.land/logos/package-managers/npm.svg" width="70"> | <img alt="pnpm" src="https://media.extension.land/logos/package-managers/pnpm.svg" width="70"> | <img alt="Yarn" src="https://media.extension.land/logos/package-managers/yarn.svg" width="70"> | <img alt="Bun" src="https://media.extension.land/logos/package-managers/bun.svg" width="70"> | <picture><source media="(prefers-color-scheme: dark)" srcset="https://media.extension.land/logos/package-managers/deno-dark.svg"><img alt="Deno" src="https://media.extension.land/logos/package-managers/deno.svg" width="70"></picture> |
173
+ | :-: | :-: | :-: | :-: | :-: |
174
+ | npm<br>✅ Supported | pnpm<br>✅ Supported | Yarn<br>✅ Supported | Bun<br>✅ Supported | Deno<br>✅ Supported |
175
+
176
+ </div>
177
+
168
178
  ## Community
169
179
 
170
180
  - Star the repo if Extension.js helped you ship faster
package/dist/49.cjs CHANGED
@@ -18,20 +18,74 @@ exports.modules = {
18
18
  } catch {}
19
19
  return removed;
20
20
  }
21
- async function loadUnpackedIfNeeded(cdp, outPath) {
21
+ function declaresBackgroundContext(outPath) {
22
22
  try {
23
- const response = await cdp.sendCommand('Extensions.loadUnpacked', {
24
- extensionPath: outPath,
25
- options: {
26
- failOnError: false
27
- }
28
- });
29
- const id = String(response?.extensionId || '');
30
- return id || null;
23
+ const manifest = JSON.parse(node_fs__rspack_import_0.readFileSync(node_path__rspack_import_1.join(outPath, 'manifest.json'), 'utf-8'));
24
+ const background = manifest?.background;
25
+ if (!background || 'object' != typeof background) return false;
26
+ return Boolean(background.service_worker || Array.isArray(background.scripts) && background.scripts.length || background.page);
31
27
  } catch {
32
- return null;
28
+ return false;
33
29
  }
34
30
  }
31
+ function parseCdpError(error) {
32
+ const raw = String(error?.message || error || '').trim();
33
+ try {
34
+ const parsed = JSON.parse(raw);
35
+ const message = String(parsed?.message || '').trim();
36
+ if (message) return {
37
+ code: 'number' == typeof parsed.code ? parsed.code : void 0,
38
+ message
39
+ };
40
+ } catch {}
41
+ return {
42
+ message: raw
43
+ };
44
+ }
45
+ const PROTOCOL_ERROR_CODES = new Set([
46
+ -32601,
47
+ -32602
48
+ ]);
49
+ async function callLoadUnpacked(cdp, params) {
50
+ try {
51
+ const response = await cdp.sendCommand('Extensions.loadUnpacked', params);
52
+ const id = String(response?.id || response?.extensionId || '');
53
+ return {
54
+ ok: true,
55
+ id
56
+ };
57
+ } catch (error) {
58
+ return {
59
+ ok: false,
60
+ ...parseCdpError(error)
61
+ };
62
+ }
63
+ }
64
+ async function loadUnpacked(cdp, outPath) {
65
+ let result = await callLoadUnpacked(cdp, {
66
+ path: outPath
67
+ });
68
+ if (!result.ok && -32602 === result.code) result = await callLoadUnpacked(cdp, {
69
+ extensionPath: outPath
70
+ });
71
+ if (result.ok) return result.id ? {
72
+ status: 'loaded',
73
+ extensionId: result.id
74
+ } : {
75
+ status: 'unknown'
76
+ };
77
+ if (PROTOCOL_ERROR_CODES.has(result.code) || !result.message) return {
78
+ status: 'unknown'
79
+ };
80
+ return {
81
+ status: 'refused',
82
+ reason: result.message
83
+ };
84
+ }
85
+ async function loadUnpackedIfNeeded(cdp, outPath) {
86
+ const outcome = await loadUnpacked(cdp, outPath);
87
+ return 'loaded' === outcome.status ? outcome.extensionId : null;
88
+ }
35
89
  function readManifestInfo(outPath) {
36
90
  try {
37
91
  const manifestPath = node_path__rspack_import_1.join(outPath, 'manifest.json');
@@ -45,8 +99,10 @@ exports.modules = {
45
99
  }
46
100
  }
47
101
  __webpack_require__.d(__webpack_exports__, {
48
- M: ()=>readManifestInfo,
102
+ MB: ()=>readManifestInfo,
103
+ iF: ()=>loadUnpacked,
49
104
  loadUnpackedIfNeeded: ()=>loadUnpackedIfNeeded,
105
+ tg: ()=>declaresBackgroundContext,
50
106
  uninstallStaleUnpackedLoads: ()=>uninstallStaleUnpackedLoads
51
107
  });
52
108
  },
@@ -133,6 +189,7 @@ exports.modules = {
133
189
  var external_node_path_ = __webpack_require__("node:path");
134
190
  var banner = __webpack_require__("./browsers/browsers-lib/banner.ts");
135
191
  var messages = __webpack_require__("./browsers/browsers-lib/messages.ts");
192
+ var ready_stamp = __webpack_require__("./browsers/browsers-lib/ready-stamp.ts");
136
193
  var shared_utils = __webpack_require__("./browsers/browsers-lib/shared-utils.ts");
137
194
  var external_ws_ = __webpack_require__("ws");
138
195
  var external_ws_default = /*#__PURE__*/ __webpack_require__.n(external_ws_);
@@ -737,6 +794,50 @@ exports.modules = {
737
794
  url
738
795
  });
739
796
  }
797
+ async verifyGuestLoaded() {
798
+ if (!this.cdp) return {
799
+ status: 'unknown'
800
+ };
801
+ if (!external_node_fs_.existsSync(this.outPath)) return {
802
+ status: 'unknown'
803
+ };
804
+ const expectedId = (0, banner.vy)(this.outPath);
805
+ if (expectedId && (0, ensure.tg)(this.outPath)) {
806
+ const present = await this.waitForExtensionTarget(expectedId, 12, 200);
807
+ if (present) {
808
+ if (!this.extensionId) this.extensionId = expectedId;
809
+ return {
810
+ status: 'loaded',
811
+ extensionId: expectedId
812
+ };
813
+ }
814
+ }
815
+ let outcome;
816
+ try {
817
+ outcome = await (0, ensure.iF)(this.cdp, this.outPath);
818
+ } catch {
819
+ return {
820
+ status: 'unknown'
821
+ };
822
+ }
823
+ if ('loaded' === outcome.status && !this.extensionId) this.extensionId = outcome.extensionId;
824
+ if ('refused' === outcome.status) this.loadRefusalReason = outcome.reason;
825
+ return outcome;
826
+ }
827
+ getLoadRefusalReason() {
828
+ return this.loadRefusalReason;
829
+ }
830
+ async waitForExtensionTarget(extensionId, maxRetries, backoffMs) {
831
+ const prefix = `chrome-extension://${extensionId}/`;
832
+ for(let attempt = 0; attempt <= maxRetries; attempt++){
833
+ try {
834
+ const targets = await this.cdp?.getTargets();
835
+ if ((targets || []).some((t)=>String(t?.url || '').startsWith(prefix))) return true;
836
+ } catch {}
837
+ if (attempt < maxRetries) await new Promise((r)=>setTimeout(r, backoffMs));
838
+ }
839
+ return false;
840
+ }
740
841
  async ensureLoaded() {
741
842
  if (!this.cdp) throw new Error('CDP not connected');
742
843
  const exists = external_node_fs_.existsSync(this.outPath);
@@ -803,7 +904,7 @@ exports.modules = {
803
904
  version = info?.extensionInfo?.version;
804
905
  } catch (error) {
805
906
  try {
806
- const manifest = (0, ensure.M)(this.outPath);
907
+ const manifest = (0, ensure.MB)(this.outPath);
807
908
  name = String(manifest?.name || '') || void 0;
808
909
  version = String(manifest?.version || '') || void 0;
809
910
  } catch (e2) {
@@ -947,6 +1048,7 @@ exports.modules = {
947
1048
  cdp_extension_controller_define_property(this, "logSink", void 0);
948
1049
  cdp_extension_controller_define_property(this, "cdp", null);
949
1050
  cdp_extension_controller_define_property(this, "extensionId", null);
1051
+ cdp_extension_controller_define_property(this, "loadRefusalReason", null);
950
1052
  this.outPath = args.outPath;
951
1053
  this.cdpPort = args.cdpPort;
952
1054
  this.profilePath = args.profilePath;
@@ -1005,6 +1107,34 @@ exports.modules = {
1005
1107
  }
1006
1108
  }
1007
1109
  } catch {}
1110
+ const loadOutcome = await cdpExtensionController.verifyGuestLoaded?.() ?? {
1111
+ status: 'unknown'
1112
+ };
1113
+ if ('refused' === loadOutcome.status) {
1114
+ const refusedPath = extensionOutputPath || selectedExtensionPaths[0] || '';
1115
+ console.error(messages.M6(refusedPath, loadOutcome.reason));
1116
+ plugin.logSink?.({
1117
+ level: 'error',
1118
+ text: `extension_load_refused: ${refusedPath}${loadOutcome.reason ? ` - ${loadOutcome.reason}` : ''}`,
1119
+ source: 'browser'
1120
+ });
1121
+ (0, ready_stamp.sf)(extensionOutputPath, loadOutcome.reason);
1122
+ plugin.extensionLoadRefused = loadOutcome.reason;
1123
+ plugin.cdpController = cdpExtensionController;
1124
+ plugin.printBannerOnRecovery = async ()=>{
1125
+ await (0, banner.ai)({
1126
+ outPath: extensionOutputPath,
1127
+ browser: plugin.browser,
1128
+ hostPort: {
1129
+ host: '127.0.0.1',
1130
+ port: chromeRemoteDebugPort
1131
+ },
1132
+ getInfo: async ()=>cdpExtensionController.getInfoBestEffort(),
1133
+ browserVersionLine: plugin.browserVersionLine
1134
+ });
1135
+ };
1136
+ return;
1137
+ }
1008
1138
  const mode = compilation?.options?.mode || 'development';
1009
1139
  let earlyBannerPrinted = false;
1010
1140
  if ('development' === mode) try {