extension-from-store 0.1.1 → 0.2.0

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/dist/core.js ADDED
@@ -0,0 +1,222 @@
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 createLogger(logger) {
23
+ return {
24
+ info: (message)=>{
25
+ var _logger_onInfo;
26
+ return null == logger ? void 0 : null == (_logger_onInfo = logger.onInfo) ? void 0 : _logger_onInfo.call(logger, message);
27
+ },
28
+ warn: (message)=>{
29
+ var _logger_onWarn;
30
+ return null == logger ? void 0 : null == (_logger_onWarn = logger.onWarn) ? void 0 : _logger_onWarn.call(logger, message);
31
+ },
32
+ error: (message, error)=>{
33
+ var _logger_onError;
34
+ return null == logger ? void 0 : null == (_logger_onError = logger.onError) ? void 0 : _logger_onError.call(logger, message, error);
35
+ }
36
+ };
37
+ }
38
+ function parseManifestInfo(raw) {
39
+ let parsed;
40
+ try {
41
+ parsed = JSON.parse(raw);
42
+ } catch (error) {
43
+ throw new extensionFromStoreError('ExtractionFailed', 'manifest.json is not valid JSON', error);
44
+ }
45
+ if (!parsed || 'object' != typeof parsed || Array.isArray(parsed)) throw new extensionFromStoreError('ExtractionFailed', 'manifest.json must contain an object');
46
+ const manifest = parsed;
47
+ const manifestVersion = manifest.manifest_version;
48
+ const extensionVersion = manifest.version;
49
+ if (2 !== manifestVersion && 3 !== manifestVersion) throw new extensionFromStoreError('ExtractionFailed', 'manifest_version must be 2 or 3');
50
+ if (!extensionVersion || 'string' != typeof extensionVersion) throw new extensionFromStoreError('ExtractionFailed', 'manifest.json is missing a version');
51
+ return {
52
+ manifest,
53
+ manifestVersion,
54
+ extensionVersion
55
+ };
56
+ }
57
+ function normalizeChromePlatformInfo(platform) {
58
+ return {
59
+ os: platform.os,
60
+ arch: platform.arch,
61
+ naclArch: platform.naclArch || platform.arch
62
+ };
63
+ }
64
+ const DEFAULT_CHROME_PLATFORM = {
65
+ os: 'linux',
66
+ arch: 'x64'
67
+ };
68
+ function getChromeDownloadUrl(id, platformInfo = DEFAULT_CHROME_PLATFORM) {
69
+ const encoded = encodeURIComponent(id);
70
+ const platform = normalizeChromePlatformInfo(platformInfo);
71
+ const productId = 'chromiumcrx';
72
+ const productChannel = 'unknown';
73
+ const productVersion = '9999.0.9999.0';
74
+ return [
75
+ 'https://clients2.google.com/service/update2/crx',
76
+ '?response=redirect',
77
+ `&os=${platform.os}`,
78
+ `&arch=${platform.arch}`,
79
+ `&os_arch=${platform.arch}`,
80
+ `&nacl_arch=${platform.naclArch}`,
81
+ `&prod=${productId}`,
82
+ `&prodchannel=${productChannel}`,
83
+ `&prodversion=${productVersion}`,
84
+ '&acceptformat=crx2,crx3',
85
+ `&x=id%3D${encoded}%26uc`
86
+ ].join('');
87
+ }
88
+ function getEdgeDownloadUrl(id) {
89
+ const encoded = encodeURIComponent(id);
90
+ return [
91
+ 'https://edge.microsoft.com/extensionwebstorebase/v1/crx',
92
+ '?response=redirect',
93
+ '&prodversion=109.0.0.0',
94
+ `&x=id%3D${encoded}%26installsource%3Dondemand%26uc`
95
+ ].join('');
96
+ }
97
+ async function resolveFirefoxDownload(idOrSlug, versionHint, options) {
98
+ var _addon_current_version_file, _addon_current_version, _addon_current_version1;
99
+ const baseUrl = `https://addons.mozilla.org/api/v5/addons/addon/${encodeURIComponent(idOrSlug)}/`;
100
+ const addon = await options.requestJson(baseUrl, options);
101
+ const slugOrId = addon.slug || idOrSlug;
102
+ if (versionHint) {
103
+ var _version_file;
104
+ const versionUrl = `${baseUrl}versions/${encodeURIComponent(versionHint)}/`;
105
+ const version = await options.requestJson(versionUrl, options);
106
+ const downloadUrl = null == (_version_file = version.file) ? void 0 : _version_file.url;
107
+ if (!downloadUrl) throw new extensionFromStoreError('NotPublic', `Version ${versionHint} is not publicly downloadable`);
108
+ return {
109
+ downloadUrl,
110
+ version: version.version || versionHint,
111
+ slugOrId
112
+ };
113
+ }
114
+ const downloadUrl = null == (_addon_current_version = addon.current_version) ? void 0 : null == (_addon_current_version_file = _addon_current_version.file) ? void 0 : _addon_current_version_file.url;
115
+ const version = null == (_addon_current_version1 = addon.current_version) ? void 0 : _addon_current_version1.version;
116
+ if (!downloadUrl || !version) throw new extensionFromStoreError('NotPublic', 'Extension is not publicly downloadable');
117
+ return {
118
+ downloadUrl,
119
+ version,
120
+ slugOrId
121
+ };
122
+ }
123
+ const chromePattern = /^https?:\/\/(?:chrome\.google\.com\/webstore|chromewebstore\.google\.com)\/.+?\/([a-p]{32})(?=[\/#?]|$)/i;
124
+ const chromeDownloadPattern = /^https?:\/\/clients2\.google\.com\/service\/update2\/crx\b.*?%3D([a-p]{32})%26uc/i;
125
+ const edgePattern = /^https?:\/\/microsoftedge\.microsoft\.com\/addons\/.+?\/([a-z]{32})(?=[\/#?]|$)/i;
126
+ const edgeDownloadPattern = /^https?:\/\/edge\.microsoft\.com\/extensionwebstorebase\/v1\/crx\b.*?%3D([a-z]{32})%26/i;
127
+ const firefoxPattern = /^https?:\/\/((?:reviewers\.)?(?:addons\.mozilla\.org|addons(?:-dev)?\.allizom\.org))\/.*?(?:addon|review)\/([^/<>"'?#]+)/i;
128
+ const firefoxDownloadPattern = /^https?:\/\/(addons\.mozilla\.org|addons(?:-dev)?\.allizom\.org)\/[^?#]*\/downloads\/latest\/([^/?#]+)/i;
129
+ function detectStoreFromUrl(url) {
130
+ if (chromePattern.test(url) || chromeDownloadPattern.test(url)) return 'chrome';
131
+ if (edgePattern.test(url) || edgeDownloadPattern.test(url)) return 'edge';
132
+ if (firefoxPattern.test(url) || firefoxDownloadPattern.test(url)) return 'firefox';
133
+ return null;
134
+ }
135
+ function extractChromeIdFromUrl(url) {
136
+ const match = chromePattern.exec(url) || chromeDownloadPattern.exec(url);
137
+ return match ? match[1] : null;
138
+ }
139
+ function extractEdgeIdFromUrl(url) {
140
+ const match = edgePattern.exec(url) || edgeDownloadPattern.exec(url);
141
+ return match ? match[1] : null;
142
+ }
143
+ function extractFirefoxSlugFromUrl(url) {
144
+ const match = firefoxPattern.exec(url) || firefoxDownloadPattern.exec(url);
145
+ return match ? match[2] : null;
146
+ }
147
+ function validateInput(url) {
148
+ if (!url || 'string' != typeof url) throw new extensionFromStoreError('InvalidInput', 'URL is required');
149
+ }
150
+ function sanitizeSegment(value, label) {
151
+ const sanitized = value.replace(/[\\/]/g, '-').trim();
152
+ if (!sanitized) throw new extensionFromStoreError('InvalidInput', `${label} is not a valid path segment`);
153
+ return sanitized;
154
+ }
155
+ async function resolveDownload(url, options) {
156
+ const store = detectStoreFromUrl(url);
157
+ if (!store) throw new extensionFromStoreError('UnsupportedStore', 'URL does not match a supported store');
158
+ if ('chrome' === store) {
159
+ const downloadId = extractChromeIdFromUrl(url);
160
+ if (!downloadId) throw new extensionFromStoreError('NotFound', 'Chrome extension id not found in URL');
161
+ return {
162
+ store,
163
+ downloadUrl: getChromeDownloadUrl(downloadId, options.platform),
164
+ archiveType: 'crx',
165
+ downloadId,
166
+ slugOrId: downloadId
167
+ };
168
+ }
169
+ if ('edge' === store) {
170
+ const downloadId = extractEdgeIdFromUrl(url);
171
+ if (!downloadId) throw new extensionFromStoreError('NotFound', 'Edge extension id not found in URL');
172
+ return {
173
+ store,
174
+ downloadUrl: getEdgeDownloadUrl(downloadId),
175
+ archiveType: 'crx',
176
+ downloadId,
177
+ slugOrId: downloadId
178
+ };
179
+ }
180
+ const slug = extractFirefoxSlugFromUrl(url);
181
+ if (!slug) throw new extensionFromStoreError('NotFound', 'Firefox extension slug not found in URL');
182
+ const firefox = await resolveFirefoxDownload(slug, options.version, {
183
+ userAgent: options.userAgent,
184
+ logger: options.logger,
185
+ requestJson: options.requestJson
186
+ });
187
+ return {
188
+ store,
189
+ downloadUrl: firefox.downloadUrl,
190
+ archiveType: 'xpi',
191
+ versionHint: firefox.version,
192
+ slugOrId: firefox.slugOrId
193
+ };
194
+ }
195
+ function readUInt32LE(bytes, offset) {
196
+ const view = new DataView(bytes.buffer, bytes.byteOffset + offset, Uint32Array.BYTES_PER_ELEMENT);
197
+ return view.getUint32(0, true);
198
+ }
199
+ function readMagic(bytes) {
200
+ let out = '';
201
+ const slice = bytes.subarray(0, 4);
202
+ for(let index = 0; index < slice.length; index += 1)out += String.fromCharCode(slice[index]);
203
+ return out;
204
+ }
205
+ function stripCrxHeader(buffer) {
206
+ if (buffer.length < 16) throw new extensionFromStoreError('ExtractionFailed', 'CRX file too small');
207
+ const magic = readMagic(buffer);
208
+ if ('Cr24' !== magic) throw new extensionFromStoreError('ExtractionFailed', 'Invalid CRX header');
209
+ const version = readUInt32LE(buffer, 4);
210
+ if (2 === version) {
211
+ const publicKeyLength = readUInt32LE(buffer, 8);
212
+ const signatureLength = readUInt32LE(buffer, 12);
213
+ const headerSize = 16 + publicKeyLength + signatureLength;
214
+ return buffer.subarray(headerSize);
215
+ }
216
+ if (3 === version) {
217
+ const headerSize = readUInt32LE(buffer, 8);
218
+ return buffer.subarray(12 + headerSize);
219
+ }
220
+ throw new extensionFromStoreError('ExtractionFailed', `Unsupported CRX version ${version}`);
221
+ }
222
+ export { asExtensionFromStoreError, createLogger, detectStoreFromUrl, extensionFromStoreError, extractChromeIdFromUrl, extractEdgeIdFromUrl, extractFirefoxSlugFromUrl, getChromeDownloadUrl, getEdgeDownloadUrl, normalizeChromePlatformInfo, parseManifestInfo, resolveDownload, resolveFirefoxDownload, sanitizeSegment, stripCrxHeader, validateInput };
package/dist/crx.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function stripCrxHeader(buffer: Uint8Array): Uint8Array;
package/dist/extract.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export declare function stripCrxHeader(buffer: Buffer): Buffer;
1
+ export { stripCrxHeader } from './crx';
2
2
  export declare function extractCrx(crxPath: string, extractDir: string, workDir: string): Promise<void>;
3
3
  export declare function extractZipArchive(zipPath: string, extractDir: string): Promise<void>;
package/dist/index.cjs CHANGED
@@ -38,18 +38,10 @@ __webpack_require__.d(__webpack_exports__, {
38
38
  });
39
39
  const promises_namespaceObject = require("node:fs/promises");
40
40
  var promises_default = /*#__PURE__*/ __webpack_require__.n(promises_namespaceObject);
41
- const external_node_path_namespaceObject = require("node:path");
42
- var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
43
41
  const external_node_os_namespaceObject = require("node:os");
44
42
  var external_node_os_default = /*#__PURE__*/ __webpack_require__.n(external_node_os_namespaceObject);
45
- const external_node_fs_namespaceObject = require("node:fs");
46
- var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
47
- const external_node_http_namespaceObject = require("node:http");
48
- var external_node_http_default = /*#__PURE__*/ __webpack_require__.n(external_node_http_namespaceObject);
49
- const external_node_https_namespaceObject = require("node:https");
50
- var external_node_https_default = /*#__PURE__*/ __webpack_require__.n(external_node_https_namespaceObject);
51
- const external_node_stream_promises_namespaceObject = require("node:stream/promises");
52
- const external_node_url_namespaceObject = require("node:url");
43
+ const external_node_path_namespaceObject = require("node:path");
44
+ var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
53
45
  function _define_property(obj, key, value) {
54
46
  if (key in obj) Object.defineProperty(obj, key, {
55
47
  value: value,
@@ -67,6 +59,58 @@ class errors_extensionFromStoreError extends Error {
67
59
  this.cause = cause;
68
60
  }
69
61
  }
62
+ const external_extract_zip_namespaceObject = require("extract-zip");
63
+ var external_extract_zip_default = /*#__PURE__*/ __webpack_require__.n(external_extract_zip_namespaceObject);
64
+ function readUInt32LE(bytes, offset) {
65
+ const view = new DataView(bytes.buffer, bytes.byteOffset + offset, Uint32Array.BYTES_PER_ELEMENT);
66
+ return view.getUint32(0, true);
67
+ }
68
+ function readMagic(bytes) {
69
+ let out = '';
70
+ const slice = bytes.subarray(0, 4);
71
+ for(let index = 0; index < slice.length; index += 1)out += String.fromCharCode(slice[index]);
72
+ return out;
73
+ }
74
+ function stripCrxHeader(buffer) {
75
+ if (buffer.length < 16) throw new errors_extensionFromStoreError('ExtractionFailed', 'CRX file too small');
76
+ const magic = readMagic(buffer);
77
+ if ('Cr24' !== magic) throw new errors_extensionFromStoreError('ExtractionFailed', 'Invalid CRX header');
78
+ const version = readUInt32LE(buffer, 4);
79
+ if (2 === version) {
80
+ const publicKeyLength = readUInt32LE(buffer, 8);
81
+ const signatureLength = readUInt32LE(buffer, 12);
82
+ const headerSize = 16 + publicKeyLength + signatureLength;
83
+ return buffer.subarray(headerSize);
84
+ }
85
+ if (3 === version) {
86
+ const headerSize = readUInt32LE(buffer, 8);
87
+ return buffer.subarray(12 + headerSize);
88
+ }
89
+ throw new errors_extensionFromStoreError('ExtractionFailed', `Unsupported CRX version ${version}`);
90
+ }
91
+ async function extractCrx(crxPath, extractDir, workDir) {
92
+ const crxBuffer = await promises_default().readFile(crxPath);
93
+ const zipBuffer = Buffer.from(stripCrxHeader(crxBuffer));
94
+ const zipPath = external_node_path_default().join(workDir, 'payload.zip');
95
+ await promises_default().writeFile(zipPath, zipBuffer);
96
+ await external_extract_zip_default()(zipPath, {
97
+ dir: extractDir
98
+ });
99
+ await promises_default().unlink(zipPath).catch(()=>void 0);
100
+ }
101
+ async function extractZipArchive(zipPath, extractDir) {
102
+ await external_extract_zip_default()(zipPath, {
103
+ dir: extractDir
104
+ });
105
+ }
106
+ const external_node_fs_namespaceObject = require("node:fs");
107
+ var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
108
+ const external_node_http_namespaceObject = require("node:http");
109
+ var external_node_http_default = /*#__PURE__*/ __webpack_require__.n(external_node_http_namespaceObject);
110
+ const external_node_https_namespaceObject = require("node:https");
111
+ var external_node_https_default = /*#__PURE__*/ __webpack_require__.n(external_node_https_namespaceObject);
112
+ const external_node_stream_promises_namespaceObject = require("node:stream/promises");
113
+ const external_node_url_namespaceObject = require("node:url");
70
114
  function createLogger(logger) {
71
115
  return {
72
116
  info: (message)=>{
@@ -97,7 +141,8 @@ function request(url, options) {
97
141
  hostname: target.hostname,
98
142
  port: target.port,
99
143
  path: `${target.pathname}${target.search}`,
100
- headers
144
+ headers,
145
+ agent: false
101
146
  }, (res)=>{
102
147
  resolve({
103
148
  statusCode: res.statusCode || 0,
@@ -148,77 +193,59 @@ async function requestJson(url, options, maxRedirects = 5) {
148
193
  throw new errors_extensionFromStoreError('StoreIncompatibility', `Invalid JSON response from ${url}`, error);
149
194
  }
150
195
  }
151
- const external_extract_zip_namespaceObject = require("extract-zip");
152
- var external_extract_zip_default = /*#__PURE__*/ __webpack_require__.n(external_extract_zip_namespaceObject);
153
- function stripCrxHeader(buffer) {
154
- if (buffer.length < 16) throw new errors_extensionFromStoreError('ExtractionFailed', 'CRX file too small');
155
- const magic = buffer.subarray(0, 4).toString('ascii');
156
- if ('Cr24' !== magic) throw new errors_extensionFromStoreError('ExtractionFailed', 'Invalid CRX header');
157
- const version = buffer.readUInt32LE(4);
158
- if (2 === version) {
159
- const publicKeyLength = buffer.readUInt32LE(8);
160
- const signatureLength = buffer.readUInt32LE(12);
161
- const headerSize = 16 + publicKeyLength + signatureLength;
162
- return buffer.subarray(headerSize);
163
- }
164
- if (3 === version) {
165
- const headerSize = buffer.readUInt32LE(8);
166
- const offset = 12 + headerSize;
167
- return buffer.subarray(offset);
168
- }
169
- throw new errors_extensionFromStoreError('ExtractionFailed', `Unsupported CRX version ${version}`);
170
- }
171
- async function extractCrx(crxPath, extractDir, workDir) {
172
- const crxBuffer = await promises_default().readFile(crxPath);
173
- const zipBuffer = stripCrxHeader(crxBuffer);
174
- const zipPath = external_node_path_default().join(workDir, 'payload.zip');
175
- await promises_default().writeFile(zipPath, zipBuffer);
176
- await external_extract_zip_default()(zipPath, {
177
- dir: extractDir
178
- });
179
- await promises_default().unlink(zipPath).catch(()=>void 0);
180
- }
181
- async function extractZipArchive(zipPath, extractDir) {
182
- await external_extract_zip_default()(zipPath, {
183
- dir: extractDir
184
- });
185
- }
186
- async function readManifestInfo(manifestPath) {
187
- let raw = '';
188
- try {
189
- raw = await promises_default().readFile(manifestPath, 'utf8');
190
- } catch (error) {
191
- throw new errors_extensionFromStoreError('ExtractionFailed', 'manifest.json was not found after extraction', error);
192
- }
196
+ function parseManifestInfo(raw) {
193
197
  let parsed;
194
198
  try {
195
199
  parsed = JSON.parse(raw);
196
200
  } catch (error) {
197
201
  throw new errors_extensionFromStoreError('ExtractionFailed', 'manifest.json is not valid JSON', error);
198
202
  }
199
- const manifestVersion = null == parsed ? void 0 : parsed.manifest_version;
200
- const extensionVersion = null == parsed ? void 0 : parsed.version;
203
+ if (!parsed || 'object' != typeof parsed || Array.isArray(parsed)) throw new errors_extensionFromStoreError('ExtractionFailed', 'manifest.json must contain an object');
204
+ const manifest = parsed;
205
+ const manifestVersion = manifest.manifest_version;
206
+ const extensionVersion = manifest.version;
201
207
  if (2 !== manifestVersion && 3 !== manifestVersion) throw new errors_extensionFromStoreError('ExtractionFailed', 'manifest_version must be 2 or 3');
202
208
  if (!extensionVersion || 'string' != typeof extensionVersion) throw new errors_extensionFromStoreError('ExtractionFailed', 'manifest.json is missing a version');
203
209
  return {
210
+ manifest,
204
211
  manifestVersion,
205
212
  extensionVersion
206
213
  };
207
214
  }
208
- function getPlatformInfo() {
209
- const platform = process.platform;
210
- const arch = process.arch;
211
- const os = 'darwin' === platform ? 'mac' : 'win32' === platform ? 'win' : 'linux';
212
- const archName = 'x64' === arch ? 'x64' : 'arm64' === arch ? 'arm64' : 'ia32' === arch ? 'x86' : 'x64';
215
+ async function readManifestInfo(manifestPath) {
216
+ let raw = '';
217
+ try {
218
+ raw = await promises_default().readFile(manifestPath, 'utf8');
219
+ } catch (error) {
220
+ throw new errors_extensionFromStoreError('ExtractionFailed', 'manifest.json was not found after extraction', error);
221
+ }
222
+ return parseManifestInfo(raw);
223
+ }
224
+ function getNodeArch() {
225
+ if ('arm64' === process.arch) return 'arm64';
226
+ if ('ia32' === process.arch) return 'x86';
227
+ return 'x64';
228
+ }
229
+ function getNodeChromePlatformInfo() {
230
+ return {
231
+ os: 'darwin' === process.platform ? 'mac' : 'win32' === process.platform ? 'win' : 'linux',
232
+ arch: getNodeArch()
233
+ };
234
+ }
235
+ function normalizeChromePlatformInfo(platform) {
213
236
  return {
214
- os,
215
- arch: archName,
216
- naclArch: archName
237
+ os: platform.os,
238
+ arch: platform.arch,
239
+ naclArch: platform.naclArch || platform.arch
217
240
  };
218
241
  }
219
- function getChromeDownloadUrl(id) {
242
+ const DEFAULT_CHROME_PLATFORM = {
243
+ os: 'linux',
244
+ arch: 'x64'
245
+ };
246
+ function getChromeDownloadUrl(id, platformInfo = DEFAULT_CHROME_PLATFORM) {
220
247
  const encoded = encodeURIComponent(id);
221
- const platform = getPlatformInfo();
248
+ const platform = normalizeChromePlatformInfo(platformInfo);
222
249
  const productId = 'chromiumcrx';
223
250
  const productChannel = 'unknown';
224
251
  const productVersion = '9999.0.9999.0';
@@ -248,12 +275,12 @@ function getEdgeDownloadUrl(id) {
248
275
  async function resolveFirefoxDownload(idOrSlug, versionHint, options) {
249
276
  var _addon_current_version_file, _addon_current_version, _addon_current_version1;
250
277
  const baseUrl = `https://addons.mozilla.org/api/v5/addons/addon/${encodeURIComponent(idOrSlug)}/`;
251
- const addon = await requestJson(baseUrl, options);
278
+ const addon = await options.requestJson(baseUrl, options);
252
279
  const slugOrId = addon.slug || idOrSlug;
253
280
  if (versionHint) {
254
281
  var _version_file;
255
282
  const versionUrl = `${baseUrl}versions/${encodeURIComponent(versionHint)}/`;
256
- const version = await requestJson(versionUrl, options);
283
+ const version = await options.requestJson(versionUrl, options);
257
284
  const downloadUrl = null == (_version_file = version.file) ? void 0 : _version_file.url;
258
285
  if (!downloadUrl) throw new errors_extensionFromStoreError('NotPublic', `Version ${versionHint} is not publicly downloadable`);
259
286
  return {
@@ -298,48 +325,20 @@ function extractFirefoxSlugFromUrl(url) {
298
325
  function validateInput(url) {
299
326
  if (!url || 'string' != typeof url) throw new errors_extensionFromStoreError('InvalidInput', 'URL is required');
300
327
  }
301
- function defaultOutputDir() {
302
- return external_node_path_default().resolve(process.cwd(), 'extensions');
303
- }
304
328
  function sanitizeSegment(value, label) {
305
329
  const sanitized = value.replace(/[\\/]/g, '-').trim();
306
330
  if (!sanitized) throw new errors_extensionFromStoreError('InvalidInput', `${label} is not a valid path segment`);
307
331
  return sanitized;
308
332
  }
309
- async function ensureDirExists(dir) {
310
- await promises_default().mkdir(dir, {
311
- recursive: true
312
- });
313
- }
314
- async function pathExists(target) {
315
- try {
316
- await promises_default().access(target);
317
- return true;
318
- } catch {
319
- return false;
320
- }
321
- }
322
- async function moveDir(source, destination) {
323
- try {
324
- await promises_default().rename(source, destination);
325
- } catch {
326
- await promises_default().cp(source, destination, {
327
- recursive: true
328
- });
329
- await promises_default().rm(source, {
330
- recursive: true,
331
- force: true
332
- });
333
- }
334
- }
335
- async function resolveDownload(url, version, options) {
333
+ async function resolveDownload(url, options) {
336
334
  const store = detectStoreFromUrl(url);
337
335
  if (!store) throw new errors_extensionFromStoreError('UnsupportedStore', 'URL does not match a supported store');
338
336
  if ('chrome' === store) {
339
337
  const downloadId = extractChromeIdFromUrl(url);
340
338
  if (!downloadId) throw new errors_extensionFromStoreError('NotFound', 'Chrome extension id not found in URL');
341
339
  return {
342
- downloadUrl: getChromeDownloadUrl(downloadId),
340
+ store,
341
+ downloadUrl: getChromeDownloadUrl(downloadId, options.platform),
343
342
  archiveType: 'crx',
344
343
  downloadId,
345
344
  slugOrId: downloadId
@@ -349,6 +348,7 @@ async function resolveDownload(url, version, options) {
349
348
  const downloadId = extractEdgeIdFromUrl(url);
350
349
  if (!downloadId) throw new errors_extensionFromStoreError('NotFound', 'Edge extension id not found in URL');
351
350
  return {
351
+ store,
352
352
  downloadUrl: getEdgeDownloadUrl(downloadId),
353
353
  archiveType: 'crx',
354
354
  downloadId,
@@ -357,14 +357,48 @@ async function resolveDownload(url, version, options) {
357
357
  }
358
358
  const slug = extractFirefoxSlugFromUrl(url);
359
359
  if (!slug) throw new errors_extensionFromStoreError('NotFound', 'Firefox extension slug not found in URL');
360
- const firefox = await resolveFirefoxDownload(slug, version, options);
360
+ const firefox = await resolveFirefoxDownload(slug, options.version, {
361
+ userAgent: options.userAgent,
362
+ logger: options.logger,
363
+ requestJson: options.requestJson
364
+ });
361
365
  return {
366
+ store,
362
367
  downloadUrl: firefox.downloadUrl,
363
368
  archiveType: 'xpi',
364
369
  versionHint: firefox.version,
365
370
  slugOrId: firefox.slugOrId
366
371
  };
367
372
  }
373
+ function defaultOutputDir() {
374
+ return external_node_path_default().resolve(process.cwd(), 'extensions');
375
+ }
376
+ async function ensureDirExists(dir) {
377
+ await promises_default().mkdir(dir, {
378
+ recursive: true
379
+ });
380
+ }
381
+ async function pathExists(target) {
382
+ try {
383
+ await promises_default().access(target);
384
+ return true;
385
+ } catch {
386
+ return false;
387
+ }
388
+ }
389
+ async function moveDir(source, destination) {
390
+ try {
391
+ await promises_default().rename(source, destination);
392
+ } catch {
393
+ await promises_default().cp(source, destination, {
394
+ recursive: true
395
+ });
396
+ await promises_default().rm(source, {
397
+ recursive: true,
398
+ force: true
399
+ });
400
+ }
401
+ }
368
402
  function errorWithCode(code, message, cause) {
369
403
  return new errors_extensionFromStoreError(code, message, cause);
370
404
  }
@@ -373,7 +407,13 @@ async function fetchExtensionFromStore(url, options = {}) {
373
407
  const log = createLogger(options.logger);
374
408
  const outDir = options.outDir ? external_node_path_default().resolve(options.outDir) : defaultOutputDir();
375
409
  await ensureDirExists(outDir);
376
- const resolved = await resolveDownload(url, options.version, options);
410
+ const resolved = await resolveDownload(url, {
411
+ version: options.version,
412
+ userAgent: options.userAgent,
413
+ logger: options.logger,
414
+ platform: getNodeChromePlatformInfo(),
415
+ requestJson: requestJson
416
+ });
377
417
  if (options.version && !url.includes('addons.mozilla.org')) log.warn('Version hints are best-effort and only supported on Firefox at the moment.');
378
418
  const workDir = await promises_default().mkdtemp(external_node_path_default().join(external_node_os_default().tmpdir(), 'extension-from-store-'));
379
419
  const archivePath = external_node_path_default().join(workDir, `archive.${resolved.archiveType}`);
@@ -408,7 +448,7 @@ async function fetchExtensionFromStore(url, options = {}) {
408
448
  await moveDir(extractDir, finalDir);
409
449
  const metaPath = external_node_path_default().join(finalDir, 'extension.meta.json');
410
450
  const meta = {
411
- store: detectStoreFromUrl(url) || 'chrome',
451
+ store: resolved.store,
412
452
  identifier: resolved.slugOrId,
413
453
  version: resolvedVersion,
414
454
  manifestVersion: manifestInfo.manifestVersion