extension-from-store 0.2.0 → 0.2.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.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [npm-version-image]: https://img.shields.io/npm/v/extension-from-store.svg?color=0078D7
1
+ [npm-version-image]: https://img.shields.io/npm/v/extension-from-store.svg?color=0971fe
2
2
  [npm-version-url]: https://www.npmjs.com/package/extension-from-store
3
3
  [npm-downloads-image]: https://img.shields.io/npm/dm/extension-from-store.svg?color=2ecc40
4
4
  [npm-downloads-url]: https://www.npmjs.com/package/extension-from-store
@@ -151,6 +151,14 @@ Library logging is opt-in via the `logger` hooks. The library never writes direc
151
151
  - `6` filesystem conflict
152
152
  - `7` store incompatibility
153
153
 
154
+ ## Related projects
155
+
156
+ * [browser-extension-manifest-fields](https://github.com/cezaraugusto/browser-extension-manifest-fields)
157
+ * [browser-extension-capabilities](https://github.com/cezaraugusto/browser-extension-capabilities)
158
+ * [browser-extension-compat-data](https://github.com/cezaraugusto/browser-extension-compat-data)
159
+ * [chrome-extension-manifest-json-schema](https://github.com/cezaraugusto/chrome-extension-manifest-json-schema)
160
+ * [parse5-asset-patcher](https://github.com/cezaraugusto/parse5-asset-patcher)
161
+
154
162
  ## License
155
163
 
156
164
  MIT (c) Cezar Augusto.
package/bin.cjs CHANGED
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';
3
2
 
4
- const mod = require('./dist/index.cjs');
5
- const fetchExtensionFromStore = mod.fetchExtensionFromStore;
6
- const extensionFromStoreError = mod.extensionFromStoreError;
3
+ 'use strict'
4
+
5
+ const mod = require('./dist/index.cjs')
6
+
7
+ const {fetchExtensionFromStore} = mod
8
+ const {extensionFromStoreError} = mod
7
9
 
8
10
  const EXIT_CODES = {
9
11
  InvalidInput: 1,
@@ -13,12 +15,13 @@ const EXIT_CODES = {
13
15
  DownloadFailed: 4,
14
16
  ExtractionFailed: 5,
15
17
  FilesystemConflict: 6,
16
- StoreIncompatibility: 7,
17
- };
18
+ StoreIncompatibility: 7
19
+ }
20
+
21
+ function parseArgs (argv) {
22
+ const args = [...argv]
18
23
 
19
- function parseArgs(argv) {
20
- const args = [...argv];
21
- if (args[0] === 'fetch') args.shift();
24
+ if (args[0] === 'fetch') args.shift()
22
25
 
23
26
  const result = {
24
27
  url: '',
@@ -28,126 +31,130 @@ function parseArgs(argv) {
28
31
  extract: false,
29
32
  quiet: false,
30
33
  verbose: false,
31
- json: false,
32
- };
34
+ json: false
35
+ }
33
36
 
34
37
  for (let i = 0; i < args.length; i += 1) {
35
- const arg = args[i];
38
+ const arg = args[i]
36
39
 
37
40
  if (arg === '--url') {
38
- result.url = args[++i] || '';
39
- continue;
41
+ result.url = args[++i] || ''
42
+ continue
40
43
  }
41
44
 
42
45
  if (arg === '--out') {
43
- result.out = args[++i] || '';
44
- continue;
46
+ result.out = args[++i] || ''
47
+ continue
45
48
  }
46
49
 
47
50
  if (arg === '--version') {
48
- result.version = args[++i] || '';
49
- continue;
51
+ result.version = args[++i] || ''
52
+ continue
50
53
  }
54
+
51
55
  if (arg === '--extract') {
52
- result.extract = true;
53
- continue;
56
+ result.extract = true
57
+ continue
54
58
  }
55
59
 
56
60
  if (arg === '--user-agent') {
57
- result.userAgent = args[++i] || '';
58
- continue;
61
+ result.userAgent = args[++i] || ''
62
+ continue
59
63
  }
60
64
 
61
65
  if (arg === '--quiet') {
62
- result.quiet = true;
63
- continue;
66
+ result.quiet = true
67
+ continue
64
68
  }
65
69
 
66
70
  if (arg === '--verbose') {
67
- result.verbose = true;
68
- continue;
71
+ result.verbose = true
72
+ continue
69
73
  }
70
74
 
71
75
  if (arg === '--json') {
72
- result.json = true;
73
- continue;
76
+ result.json = true
77
+ continue
74
78
  }
75
79
 
76
- throw new Error(`Unknown flag: ${arg}`);
80
+ throw new Error(`Unknown flag: ${arg}`)
77
81
  }
78
82
 
79
83
  if (!result.url) {
80
- throw new Error('Missing required flag: --url');
84
+ throw new Error('Missing required flag: --url')
81
85
  }
82
86
 
83
- return result;
87
+ return result
84
88
  }
85
89
 
86
- function createCliLogger(opts) {
90
+ function createCliLogger (opts) {
87
91
  const emit = (level, message, error) => {
88
- const payload = { level, message };
92
+ const payload = {level, message}
89
93
 
90
- if (error) payload.error = String(error);
94
+ if (error) payload.error = String(error)
91
95
 
92
- console.log(JSON.stringify(payload));
93
- };
96
+ console.log(JSON.stringify(payload))
97
+ }
94
98
 
95
99
  if (opts.json) {
96
100
  return {
97
101
  onInfo: (message) => emit('info', message),
98
102
  onWarn: (message) => emit('warn', message),
99
- onError: (message, error) => emit('error', message, error),
100
- };
103
+ onError: (message, error) => emit('error', message, error)
104
+ }
101
105
  }
102
106
 
103
107
  return {
104
108
  onInfo: opts.quiet ? undefined : (message) => console.log(message),
105
109
  onWarn: opts.quiet ? undefined : (message) => console.error(message),
106
110
  onError: (message, error) => {
107
- const text = error ? `${message}\n${String(error)}` : message;
108
- console.error(text);
109
- },
110
- };
111
+ const text = error ? `${message}\n${String(error)}` : message
112
+
113
+ console.error(text)
114
+ }
115
+ }
111
116
  }
112
117
 
113
- async function main() {
114
- let args = null;
118
+ async function main () {
119
+ let args = null
115
120
 
116
121
  try {
117
- args = parseArgs(process.argv.slice(2));
118
- const logger = createCliLogger(args);
122
+ args = parseArgs(process.argv.slice(2))
123
+ const logger = createCliLogger(args)
124
+
119
125
  await fetchExtensionFromStore(args.url, {
120
126
  outDir: args.out || undefined,
121
127
  userAgent: args.userAgent || undefined,
122
128
  version: args.version || undefined,
123
129
  extract: args.extract,
124
- logger,
125
- });
130
+ logger
131
+ })
126
132
 
127
- process.exit(0);
133
+ process.exit(0)
128
134
  } catch (error) {
129
135
  if (error instanceof extensionFromStoreError) {
130
- const code = EXIT_CODES[error.code] || 1;
131
- const message = error.message || 'Extension fetch failed';
136
+ const code = EXIT_CODES[error.code] || 1
137
+ const message = error.message || 'Extension fetch failed'
132
138
 
133
139
  if (args?.json) {
134
- console.log(JSON.stringify({ level: 'error', message }));
140
+ console.log(JSON.stringify({level: 'error', message}))
135
141
  } else if (code !== 0) {
136
- console.error(message);
142
+ console.error(message)
137
143
  }
138
144
 
139
- process.exit(code);
145
+ process.exit(code)
140
146
  }
141
- const message = String(error?.message || error);
147
+
148
+ const message = String(error?.message || error)
142
149
 
143
150
  if (args?.json) {
144
- console.log(JSON.stringify({ level: 'error', message }));
151
+ console.log(JSON.stringify({level: 'error', message}))
145
152
  } else {
146
- console.error(message);
153
+ console.error(message)
147
154
  }
148
155
 
149
- process.exit(1);
156
+ process.exit(1)
150
157
  }
151
158
  }
152
159
 
153
- main();
160
+ main()
package/bin.js CHANGED
@@ -1,8 +1,8 @@
1
- #!/usr/bin/env node
2
1
 
3
- const mod = require('./dist/index.cjs');
4
- const fetchExtensionFromStore = mod.fetchExtensionFromStore;
5
- const extensionFromStoreError = mod.extensionFromStoreError;
2
+ const mod = require('./dist/index.cjs')
3
+
4
+ const {fetchExtensionFromStore} = mod
5
+ const {extensionFromStoreError} = mod
6
6
 
7
7
  const EXIT_CODES = {
8
8
  InvalidInput: 1,
@@ -12,13 +12,13 @@ const EXIT_CODES = {
12
12
  DownloadFailed: 4,
13
13
  ExtractionFailed: 5,
14
14
  FilesystemConflict: 6,
15
- StoreIncompatibility: 7,
16
- };
15
+ StoreIncompatibility: 7
16
+ }
17
17
 
18
- function parseArgs(argv) {
19
- const args = [...argv];
18
+ function parseArgs (argv) {
19
+ const args = [...argv]
20
20
 
21
- if (args[0] === 'fetch') args.shift();
21
+ if (args[0] === 'fetch') args.shift()
22
22
 
23
23
  const result = {
24
24
  url: '',
@@ -28,123 +28,129 @@ function parseArgs(argv) {
28
28
  extract: false,
29
29
  quiet: false,
30
30
  verbose: false,
31
- json: false,
32
- };
31
+ json: false
32
+ }
33
33
 
34
34
  for (let i = 0; i < args.length; i += 1) {
35
- const arg = args[i];
35
+ const arg = args[i]
36
36
 
37
37
  if (arg === '--url') {
38
- result.url = args[++i] || '';
39
- continue;
38
+ result.url = args[++i] || ''
39
+ continue
40
40
  }
41
41
 
42
42
  if (arg === '--out') {
43
- result.out = args[++i] || '';
44
- continue;
43
+ result.out = args[++i] || ''
44
+ continue
45
45
  }
46
46
 
47
47
  if (arg === '--version') {
48
- result.version = args[++i] || '';
49
- continue;
48
+ result.version = args[++i] || ''
49
+ continue
50
50
  }
51
+
51
52
  if (arg === '--extract') {
52
- result.extract = true;
53
- continue;
53
+ result.extract = true
54
+ continue
54
55
  }
55
56
 
56
57
  if (arg === '--user-agent') {
57
- result.userAgent = args[++i] || '';
58
- continue;
58
+ result.userAgent = args[++i] || ''
59
+ continue
59
60
  }
60
61
 
61
62
  if (arg === '--quiet') {
62
- result.quiet = true;
63
- continue;
63
+ result.quiet = true
64
+ continue
64
65
  }
65
66
 
66
67
  if (arg === '--verbose') {
67
- result.verbose = true;
68
- continue;
68
+ result.verbose = true
69
+ continue
69
70
  }
70
71
 
71
72
  if (arg === '--json') {
72
- result.json = true;
73
- continue;
73
+ result.json = true
74
+ continue
74
75
  }
75
76
 
76
- throw new Error(`Unknown flag: ${arg}`);
77
+ throw new Error(`Unknown flag: ${arg}`)
77
78
  }
78
79
 
79
80
  if (!result.url) {
80
- throw new Error('Missing required flag: --url');
81
+ throw new Error('Missing required flag: --url')
81
82
  }
82
83
 
83
- return result;
84
+ return result
84
85
  }
85
86
 
86
- function createCliLogger(opts) {
87
+ function createCliLogger (opts) {
87
88
  const emit = (level, message, error) => {
88
- const payload = { level, message };
89
- if (error) payload.error = String(error);
90
- console.log(JSON.stringify(payload));
91
- };
89
+ const payload = {level, message}
90
+
91
+ if (error) payload.error = String(error)
92
+
93
+ console.log(JSON.stringify(payload))
94
+ }
92
95
 
93
96
  if (opts.json) {
94
97
  return {
95
98
  onInfo: (message) => emit('info', message),
96
99
  onWarn: (message) => emit('warn', message),
97
- onError: (message, error) => emit('error', message, error),
98
- };
100
+ onError: (message, error) => emit('error', message, error)
101
+ }
99
102
  }
100
103
 
101
104
  return {
102
105
  onInfo: opts.quiet ? undefined : (message) => console.log(message),
103
106
  onWarn: opts.quiet ? undefined : (message) => console.error(message),
104
107
  onError: (message, error) => {
105
- const text = error ? `${message}\n${String(error)}` : message;
106
- console.error(text);
107
- },
108
- };
108
+ const text = error ? `${message}\n${String(error)}` : message
109
+
110
+ console.error(text)
111
+ }
112
+ }
109
113
  }
110
114
 
111
- async function main() {
112
- let args = null;
115
+ async function main () {
116
+ let args = null
113
117
 
114
118
  try {
115
- args = parseArgs(process.argv.slice(2));
116
- const logger = createCliLogger(args);
119
+ args = parseArgs(process.argv.slice(2))
120
+ const logger = createCliLogger(args)
121
+
117
122
  await fetchExtensionFromStore(args.url, {
118
123
  outDir: args.out || undefined,
119
124
  userAgent: args.userAgent || undefined,
120
125
  version: args.version || undefined,
121
126
  extract: args.extract,
122
- logger,
123
- });
124
- process.exit(0);
127
+ logger
128
+ })
129
+ process.exit(0)
125
130
  } catch (error) {
126
131
  if (error instanceof extensionFromStoreError) {
127
- const code = EXIT_CODES[error.code] || 1;
128
- const message = error.message || 'Extension fetch failed';
132
+ const code = EXIT_CODES[error.code] || 1
133
+ const message = error.message || 'Extension fetch failed'
129
134
 
130
135
  if (args?.json) {
131
- console.log(JSON.stringify({ level: 'error', message }));
136
+ console.log(JSON.stringify({level: 'error', message}))
132
137
  } else if (code !== 0) {
133
- console.error(message);
138
+ console.error(message)
134
139
  }
135
140
 
136
- process.exit(code);
141
+ process.exit(code)
137
142
  }
138
- const message = String(error?.message || error);
143
+
144
+ const message = String(error?.message || error)
139
145
 
140
146
  if (args?.json) {
141
- console.log(JSON.stringify({ level: 'error', message }));
147
+ console.log(JSON.stringify({level: 'error', message}))
142
148
  } else {
143
- console.error(message);
149
+ console.error(message)
144
150
  }
145
151
 
146
- process.exit(1);
152
+ process.exit(1)
147
153
  }
148
154
  }
149
155
 
150
- main();
156
+ main()
package/dist/588.js ADDED
@@ -0,0 +1,211 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) Object.defineProperty(obj, key, {
3
+ value: value,
4
+ enumerable: true,
5
+ configurable: true,
6
+ writable: true
7
+ });
8
+ else obj[key] = value;
9
+ return obj;
10
+ }
11
+ class extensionFromStoreError extends Error {
12
+ constructor(code, message, cause){
13
+ super(message), _define_property(this, "code", void 0), _define_property(this, "cause", void 0);
14
+ this.code = code;
15
+ this.cause = cause;
16
+ }
17
+ }
18
+ function asExtensionFromStoreError(error, fallback) {
19
+ if (error instanceof extensionFromStoreError) return error;
20
+ return new extensionFromStoreError(fallback.code, fallback.message, error);
21
+ }
22
+ function readUInt32LE(bytes, offset) {
23
+ const view = new DataView(bytes.buffer, bytes.byteOffset + offset, Uint32Array.BYTES_PER_ELEMENT);
24
+ return view.getUint32(0, true);
25
+ }
26
+ function readMagic(bytes) {
27
+ let out = '';
28
+ const slice = bytes.subarray(0, 4);
29
+ for(let index = 0; index < slice.length; index += 1)out += String.fromCharCode(slice[index]);
30
+ return out;
31
+ }
32
+ function stripCrxHeader(buffer) {
33
+ if (buffer.length < 16) throw new extensionFromStoreError('ExtractionFailed', 'CRX file too small');
34
+ const magic = readMagic(buffer);
35
+ if ('Cr24' !== magic) throw new extensionFromStoreError('ExtractionFailed', 'Invalid CRX header');
36
+ const version = readUInt32LE(buffer, 4);
37
+ if (2 === version) {
38
+ const publicKeyLength = readUInt32LE(buffer, 8);
39
+ const signatureLength = readUInt32LE(buffer, 12);
40
+ const headerSize = 16 + publicKeyLength + signatureLength;
41
+ return buffer.subarray(headerSize);
42
+ }
43
+ if (3 === version) {
44
+ const headerSize = readUInt32LE(buffer, 8);
45
+ return buffer.subarray(12 + headerSize);
46
+ }
47
+ throw new extensionFromStoreError('ExtractionFailed', `Unsupported CRX version ${version}`);
48
+ }
49
+ function createLogger(logger) {
50
+ return {
51
+ info: (message)=>logger?.onInfo?.(message),
52
+ warn: (message)=>logger?.onWarn?.(message),
53
+ error: (message, error)=>logger?.onError?.(message, error)
54
+ };
55
+ }
56
+ function parseManifestInfo(raw) {
57
+ let parsed;
58
+ try {
59
+ parsed = JSON.parse(raw);
60
+ } catch (error) {
61
+ throw new extensionFromStoreError('ExtractionFailed', 'manifest.json is not valid JSON', error);
62
+ }
63
+ if (!parsed || 'object' != typeof parsed || Array.isArray(parsed)) throw new extensionFromStoreError('ExtractionFailed', 'manifest.json must contain an object');
64
+ const manifest = parsed;
65
+ const manifestVersion = manifest.manifest_version;
66
+ const extensionVersion = manifest.version;
67
+ if (2 !== manifestVersion && 3 !== manifestVersion) throw new extensionFromStoreError('ExtractionFailed', 'manifest_version must be 2 or 3');
68
+ if (!extensionVersion || 'string' != typeof extensionVersion) throw new extensionFromStoreError('ExtractionFailed', 'manifest.json is missing a version');
69
+ return {
70
+ manifest,
71
+ manifestVersion,
72
+ extensionVersion
73
+ };
74
+ }
75
+ function normalizeChromePlatformInfo(platform) {
76
+ return {
77
+ os: platform.os,
78
+ arch: platform.arch,
79
+ naclArch: platform.naclArch || platform.arch
80
+ };
81
+ }
82
+ const DEFAULT_CHROME_PLATFORM = {
83
+ os: 'linux',
84
+ arch: 'x64'
85
+ };
86
+ function getChromeDownloadUrl(id, platformInfo = DEFAULT_CHROME_PLATFORM) {
87
+ const encoded = encodeURIComponent(id);
88
+ const platform = normalizeChromePlatformInfo(platformInfo);
89
+ const productId = 'chromiumcrx';
90
+ const productChannel = 'unknown';
91
+ const productVersion = '9999.0.9999.0';
92
+ return [
93
+ 'https://clients2.google.com/service/update2/crx',
94
+ '?response=redirect',
95
+ `&os=${platform.os}`,
96
+ `&arch=${platform.arch}`,
97
+ `&os_arch=${platform.arch}`,
98
+ `&nacl_arch=${platform.naclArch}`,
99
+ `&prod=${productId}`,
100
+ `&prodchannel=${productChannel}`,
101
+ `&prodversion=${productVersion}`,
102
+ '&acceptformat=crx2,crx3',
103
+ `&x=id%3D${encoded}%26uc`
104
+ ].join('');
105
+ }
106
+ function getEdgeDownloadUrl(id) {
107
+ const encoded = encodeURIComponent(id);
108
+ return [
109
+ 'https://edge.microsoft.com/extensionwebstorebase/v1/crx',
110
+ '?response=redirect',
111
+ '&prodversion=109.0.0.0',
112
+ `&x=id%3D${encoded}%26installsource%3Dondemand%26uc`
113
+ ].join('');
114
+ }
115
+ async function resolveFirefoxDownload(idOrSlug, versionHint, options) {
116
+ const baseUrl = `https://addons.mozilla.org/api/v5/addons/addon/${encodeURIComponent(idOrSlug)}/`;
117
+ const addon = await options.requestJson(baseUrl, options);
118
+ const slugOrId = addon.slug || idOrSlug;
119
+ if (versionHint) {
120
+ const versionUrl = `${baseUrl}versions/${encodeURIComponent(versionHint)}/`;
121
+ const version = await options.requestJson(versionUrl, options);
122
+ const downloadUrl = version.file?.url;
123
+ if (!downloadUrl) throw new extensionFromStoreError('NotPublic', `Version ${versionHint} is not publicly downloadable`);
124
+ return {
125
+ downloadUrl,
126
+ version: version.version || versionHint,
127
+ slugOrId
128
+ };
129
+ }
130
+ const downloadUrl = addon.current_version?.file?.url;
131
+ const version = addon.current_version?.version;
132
+ if (!downloadUrl || !version) throw new extensionFromStoreError('NotPublic', 'Extension is not publicly downloadable');
133
+ return {
134
+ downloadUrl,
135
+ version,
136
+ slugOrId
137
+ };
138
+ }
139
+ const chromePattern = /^https?:\/\/(?:chrome\.google\.com\/webstore|chromewebstore\.google\.com)\/.+?\/([a-p]{32})(?=[\/#?]|$)/i;
140
+ const chromeDownloadPattern = /^https?:\/\/clients2\.google\.com\/service\/update2\/crx\b.*?%3D([a-p]{32})%26uc/i;
141
+ const edgePattern = /^https?:\/\/microsoftedge\.microsoft\.com\/addons\/.+?\/([a-z]{32})(?=[\/#?]|$)/i;
142
+ const edgeDownloadPattern = /^https?:\/\/edge\.microsoft\.com\/extensionwebstorebase\/v1\/crx\b.*?%3D([a-z]{32})%26/i;
143
+ const firefoxPattern = /^https?:\/\/((?:reviewers\.)?(?:addons\.mozilla\.org|addons(?:-dev)?\.allizom\.org))\/.*?(?:addon|review)\/([^/<>"'?#]+)/i;
144
+ const firefoxDownloadPattern = /^https?:\/\/(addons\.mozilla\.org|addons(?:-dev)?\.allizom\.org)\/[^?#]*\/downloads\/latest\/([^/?#]+)/i;
145
+ function detectStoreFromUrl(url) {
146
+ if (chromePattern.test(url) || chromeDownloadPattern.test(url)) return 'chrome';
147
+ if (edgePattern.test(url) || edgeDownloadPattern.test(url)) return 'edge';
148
+ if (firefoxPattern.test(url) || firefoxDownloadPattern.test(url)) return 'firefox';
149
+ return null;
150
+ }
151
+ function extractChromeIdFromUrl(url) {
152
+ const match = chromePattern.exec(url) || chromeDownloadPattern.exec(url);
153
+ return match ? match[1] : null;
154
+ }
155
+ function extractEdgeIdFromUrl(url) {
156
+ const match = edgePattern.exec(url) || edgeDownloadPattern.exec(url);
157
+ return match ? match[1] : null;
158
+ }
159
+ function extractFirefoxSlugFromUrl(url) {
160
+ const match = firefoxPattern.exec(url) || firefoxDownloadPattern.exec(url);
161
+ return match ? match[2] : null;
162
+ }
163
+ function validateInput(url) {
164
+ if (!url || 'string' != typeof url) throw new extensionFromStoreError('InvalidInput', 'URL is required');
165
+ }
166
+ function sanitizeSegment(value, label) {
167
+ const sanitized = value.replace(/[\\/]/g, '-').trim();
168
+ if (!sanitized) throw new extensionFromStoreError('InvalidInput', `${label} is not a valid path segment`);
169
+ return sanitized;
170
+ }
171
+ async function resolveDownload(url, options) {
172
+ const store = detectStoreFromUrl(url);
173
+ if (!store) throw new extensionFromStoreError('UnsupportedStore', 'URL does not match a supported store');
174
+ if ('chrome' === store) {
175
+ const downloadId = extractChromeIdFromUrl(url);
176
+ if (!downloadId) throw new extensionFromStoreError('NotFound', 'Chrome extension id not found in URL');
177
+ return {
178
+ store,
179
+ downloadUrl: getChromeDownloadUrl(downloadId, options.platform),
180
+ archiveType: 'crx',
181
+ downloadId,
182
+ slugOrId: downloadId
183
+ };
184
+ }
185
+ if ('edge' === store) {
186
+ const downloadId = extractEdgeIdFromUrl(url);
187
+ if (!downloadId) throw new extensionFromStoreError('NotFound', 'Edge extension id not found in URL');
188
+ return {
189
+ store,
190
+ downloadUrl: getEdgeDownloadUrl(downloadId),
191
+ archiveType: 'crx',
192
+ downloadId,
193
+ slugOrId: downloadId
194
+ };
195
+ }
196
+ const slug = extractFirefoxSlugFromUrl(url);
197
+ if (!slug) throw new extensionFromStoreError('NotFound', 'Firefox extension slug not found in URL');
198
+ const firefox = await resolveFirefoxDownload(slug, options.version, {
199
+ userAgent: options.userAgent,
200
+ logger: options.logger,
201
+ requestJson: options.requestJson
202
+ });
203
+ return {
204
+ store,
205
+ downloadUrl: firefox.downloadUrl,
206
+ archiveType: 'xpi',
207
+ versionHint: firefox.version,
208
+ slugOrId: firefox.slugOrId
209
+ };
210
+ }
211
+ export { asExtensionFromStoreError, createLogger, detectStoreFromUrl, extensionFromStoreError, extractChromeIdFromUrl, extractEdgeIdFromUrl, extractFirefoxSlugFromUrl, getChromeDownloadUrl, getEdgeDownloadUrl, normalizeChromePlatformInfo, parseManifestInfo, resolveDownload, resolveFirefoxDownload, sanitizeSegment, stripCrxHeader, validateInput };