extension-from-store 0.2.1 → 0.2.3
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 +4 -4
- package/dist/588.js +211 -0
- package/dist/browser.cjs +21 -29
- package/dist/browser.js +14 -226
- package/dist/core.cjs +29 -36
- package/dist/core.js +1 -222
- package/dist/index.cjs +18 -25
- package/dist/index.js +41 -256
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
[npm-downloads-url]: https://www.npmjs.com/package/extension-from-store
|
|
5
5
|
[action-image]: https://github.com/cezaraugusto/extension-from-store/actions/workflows/ci.yml/badge.svg?branch=main
|
|
6
6
|
[action-url]: https://github.com/cezaraugusto/extension-from-store/actions
|
|
7
|
+
[provenance-image]: https://img.shields.io/badge/provenance-verified-0971fe?logo=npm&logoColor=white
|
|
8
|
+
[provenance-url]: https://www.npmjs.com/package/extension-from-store
|
|
7
9
|
|
|
8
10
|
> Download public browser extensions from official stores
|
|
9
11
|
|
|
10
|
-
# extension-from-store [![Version][npm-version-image]][npm-version-url] [![Downloads][npm-downloads-image]][npm-downloads-url] [![workflow][action-image]][action-url]
|
|
12
|
+
# extension-from-store [![Version][npm-version-image]][npm-version-url] [![Downloads][npm-downloads-image]][npm-downloads-url] [![workflow][action-image]][action-url] [![provenance][provenance-image]][provenance-url]
|
|
11
13
|
|
|
12
14
|
- Chrome Web Store, Microsoft Edge Add-ons, Firefox AMO
|
|
13
|
-
-
|
|
15
|
+
- Promise-based API
|
|
14
16
|
- Node.js + CLI support
|
|
15
17
|
|
|
16
18
|
## Install
|
|
@@ -21,8 +23,6 @@ npm i extension-from-store
|
|
|
21
23
|
|
|
22
24
|
## Usage
|
|
23
25
|
|
|
24
|
-
Designed for quiet reliability, extension-from-store keeps the interface simple and the output predictable.
|
|
25
|
-
|
|
26
26
|
**Via Node.js:**
|
|
27
27
|
|
|
28
28
|
```ts
|
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 };
|
package/dist/browser.cjs
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __webpack_require__ = {};
|
|
3
3
|
(()=>{
|
|
4
|
-
__webpack_require__.d = (exports1,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
__webpack_require__.d = (exports1, getters, values)=>{
|
|
5
|
+
var define = (defs, kind)=>{
|
|
6
|
+
for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
[kind]: defs[key]
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
define(getters, "get");
|
|
12
|
+
define(values, "value");
|
|
9
13
|
};
|
|
10
14
|
})();
|
|
11
15
|
(()=>{
|
|
@@ -13,7 +17,7 @@ var __webpack_require__ = {};
|
|
|
13
17
|
})();
|
|
14
18
|
(()=>{
|
|
15
19
|
__webpack_require__.r = (exports1)=>{
|
|
16
|
-
if (
|
|
20
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
17
21
|
value: 'Module'
|
|
18
22
|
});
|
|
19
23
|
Object.defineProperty(exports1, '__esModule', {
|
|
@@ -73,18 +77,9 @@ function stripCrxHeader(buffer) {
|
|
|
73
77
|
}
|
|
74
78
|
function createLogger(logger) {
|
|
75
79
|
return {
|
|
76
|
-
info: (message)=>
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
},
|
|
80
|
-
warn: (message)=>{
|
|
81
|
-
var _logger_onWarn;
|
|
82
|
-
return null == logger ? void 0 : null == (_logger_onWarn = logger.onWarn) ? void 0 : _logger_onWarn.call(logger, message);
|
|
83
|
-
},
|
|
84
|
-
error: (message, error)=>{
|
|
85
|
-
var _logger_onError;
|
|
86
|
-
return null == logger ? void 0 : null == (_logger_onError = logger.onError) ? void 0 : _logger_onError.call(logger, message, error);
|
|
87
|
-
}
|
|
80
|
+
info: (message)=>logger?.onInfo?.(message),
|
|
81
|
+
warn: (message)=>logger?.onWarn?.(message),
|
|
82
|
+
error: (message, error)=>logger?.onError?.(message, error)
|
|
88
83
|
};
|
|
89
84
|
}
|
|
90
85
|
function parseManifestInfo(raw) {
|
|
@@ -147,15 +142,13 @@ function getEdgeDownloadUrl(id) {
|
|
|
147
142
|
].join('');
|
|
148
143
|
}
|
|
149
144
|
async function resolveFirefoxDownload(idOrSlug, versionHint, options) {
|
|
150
|
-
var _addon_current_version_file, _addon_current_version, _addon_current_version1;
|
|
151
145
|
const baseUrl = `https://addons.mozilla.org/api/v5/addons/addon/${encodeURIComponent(idOrSlug)}/`;
|
|
152
146
|
const addon = await options.requestJson(baseUrl, options);
|
|
153
147
|
const slugOrId = addon.slug || idOrSlug;
|
|
154
148
|
if (versionHint) {
|
|
155
|
-
var _version_file;
|
|
156
149
|
const versionUrl = `${baseUrl}versions/${encodeURIComponent(versionHint)}/`;
|
|
157
150
|
const version = await options.requestJson(versionUrl, options);
|
|
158
|
-
const downloadUrl =
|
|
151
|
+
const downloadUrl = version.file?.url;
|
|
159
152
|
if (!downloadUrl) throw new errors_extensionFromStoreError('NotPublic', `Version ${versionHint} is not publicly downloadable`);
|
|
160
153
|
return {
|
|
161
154
|
downloadUrl,
|
|
@@ -163,8 +156,8 @@ async function resolveFirefoxDownload(idOrSlug, versionHint, options) {
|
|
|
163
156
|
slugOrId
|
|
164
157
|
};
|
|
165
158
|
}
|
|
166
|
-
const downloadUrl =
|
|
167
|
-
const version =
|
|
159
|
+
const downloadUrl = addon.current_version?.file?.url;
|
|
160
|
+
const version = addon.current_version?.version;
|
|
168
161
|
if (!downloadUrl || !version) throw new errors_extensionFromStoreError('NotPublic', 'Extension is not publicly downloadable');
|
|
169
162
|
return {
|
|
170
163
|
downloadUrl,
|
|
@@ -245,12 +238,11 @@ function getDefaultFetch() {
|
|
|
245
238
|
return globalThis.fetch.bind(globalThis);
|
|
246
239
|
}
|
|
247
240
|
function inferBrowserChromePlatformInfo() {
|
|
248
|
-
var _navigatorLike_userAgentData;
|
|
249
241
|
const navigatorLike = globalThis.navigator;
|
|
250
242
|
const fingerprint = [
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
243
|
+
navigatorLike?.platform,
|
|
244
|
+
navigatorLike?.userAgent,
|
|
245
|
+
navigatorLike?.userAgentData?.platform
|
|
254
246
|
].filter(Boolean).join(' ').toLowerCase();
|
|
255
247
|
const os = fingerprint.includes('mac') ? 'mac' : fingerprint.includes('win') ? 'win' : 'linux';
|
|
256
248
|
const arch = fingerprint.includes('arm') || fingerprint.includes('aarch64') ? 'arm64' : fingerprint.includes('i686') || fingerprint.includes('i386') || fingerprint.includes('x86') && !fingerprint.includes('x86_64') ? 'x86' : 'x64';
|
|
@@ -347,9 +339,9 @@ async function fetchExtensionFromStoreBrowser(url, options = {}) {
|
|
|
347
339
|
};
|
|
348
340
|
}
|
|
349
341
|
exports.fetchExtensionFromStoreBrowser = __webpack_exports__.fetchExtensionFromStoreBrowser;
|
|
350
|
-
for(var
|
|
342
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
351
343
|
"fetchExtensionFromStoreBrowser"
|
|
352
|
-
].indexOf(
|
|
344
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
353
345
|
Object.defineProperty(exports, '__esModule', {
|
|
354
346
|
value: true
|
|
355
347
|
});
|
package/dist/browser.js
CHANGED
|
@@ -1,228 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
if (key in obj) Object.defineProperty(obj, key, {
|
|
4
|
-
value: value,
|
|
5
|
-
enumerable: true,
|
|
6
|
-
configurable: true,
|
|
7
|
-
writable: true
|
|
8
|
-
});
|
|
9
|
-
else obj[key] = value;
|
|
10
|
-
return obj;
|
|
11
|
-
}
|
|
12
|
-
class errors_extensionFromStoreError extends Error {
|
|
13
|
-
constructor(code, message, cause){
|
|
14
|
-
super(message), _define_property(this, "code", void 0), _define_property(this, "cause", void 0);
|
|
15
|
-
this.code = code;
|
|
16
|
-
this.cause = cause;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function readUInt32LE(bytes, offset) {
|
|
20
|
-
const view = new DataView(bytes.buffer, bytes.byteOffset + offset, Uint32Array.BYTES_PER_ELEMENT);
|
|
21
|
-
return view.getUint32(0, true);
|
|
22
|
-
}
|
|
23
|
-
function readMagic(bytes) {
|
|
24
|
-
let out = '';
|
|
25
|
-
const slice = bytes.subarray(0, 4);
|
|
26
|
-
for(let index = 0; index < slice.length; index += 1)out += String.fromCharCode(slice[index]);
|
|
27
|
-
return out;
|
|
28
|
-
}
|
|
29
|
-
function stripCrxHeader(buffer) {
|
|
30
|
-
if (buffer.length < 16) throw new errors_extensionFromStoreError('ExtractionFailed', 'CRX file too small');
|
|
31
|
-
const magic = readMagic(buffer);
|
|
32
|
-
if ('Cr24' !== magic) throw new errors_extensionFromStoreError('ExtractionFailed', 'Invalid CRX header');
|
|
33
|
-
const version = readUInt32LE(buffer, 4);
|
|
34
|
-
if (2 === version) {
|
|
35
|
-
const publicKeyLength = readUInt32LE(buffer, 8);
|
|
36
|
-
const signatureLength = readUInt32LE(buffer, 12);
|
|
37
|
-
const headerSize = 16 + publicKeyLength + signatureLength;
|
|
38
|
-
return buffer.subarray(headerSize);
|
|
39
|
-
}
|
|
40
|
-
if (3 === version) {
|
|
41
|
-
const headerSize = readUInt32LE(buffer, 8);
|
|
42
|
-
return buffer.subarray(12 + headerSize);
|
|
43
|
-
}
|
|
44
|
-
throw new errors_extensionFromStoreError('ExtractionFailed', `Unsupported CRX version ${version}`);
|
|
45
|
-
}
|
|
46
|
-
function createLogger(logger) {
|
|
47
|
-
return {
|
|
48
|
-
info: (message)=>{
|
|
49
|
-
var _logger_onInfo;
|
|
50
|
-
return null == logger ? void 0 : null == (_logger_onInfo = logger.onInfo) ? void 0 : _logger_onInfo.call(logger, message);
|
|
51
|
-
},
|
|
52
|
-
warn: (message)=>{
|
|
53
|
-
var _logger_onWarn;
|
|
54
|
-
return null == logger ? void 0 : null == (_logger_onWarn = logger.onWarn) ? void 0 : _logger_onWarn.call(logger, message);
|
|
55
|
-
},
|
|
56
|
-
error: (message, error)=>{
|
|
57
|
-
var _logger_onError;
|
|
58
|
-
return null == logger ? void 0 : null == (_logger_onError = logger.onError) ? void 0 : _logger_onError.call(logger, message, error);
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
function parseManifestInfo(raw) {
|
|
63
|
-
let parsed;
|
|
64
|
-
try {
|
|
65
|
-
parsed = JSON.parse(raw);
|
|
66
|
-
} catch (error) {
|
|
67
|
-
throw new errors_extensionFromStoreError('ExtractionFailed', 'manifest.json is not valid JSON', error);
|
|
68
|
-
}
|
|
69
|
-
if (!parsed || 'object' != typeof parsed || Array.isArray(parsed)) throw new errors_extensionFromStoreError('ExtractionFailed', 'manifest.json must contain an object');
|
|
70
|
-
const manifest = parsed;
|
|
71
|
-
const manifestVersion = manifest.manifest_version;
|
|
72
|
-
const extensionVersion = manifest.version;
|
|
73
|
-
if (2 !== manifestVersion && 3 !== manifestVersion) throw new errors_extensionFromStoreError('ExtractionFailed', 'manifest_version must be 2 or 3');
|
|
74
|
-
if (!extensionVersion || 'string' != typeof extensionVersion) throw new errors_extensionFromStoreError('ExtractionFailed', 'manifest.json is missing a version');
|
|
75
|
-
return {
|
|
76
|
-
manifest,
|
|
77
|
-
manifestVersion,
|
|
78
|
-
extensionVersion
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
function normalizeChromePlatformInfo(platform) {
|
|
82
|
-
return {
|
|
83
|
-
os: platform.os,
|
|
84
|
-
arch: platform.arch,
|
|
85
|
-
naclArch: platform.naclArch || platform.arch
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
const DEFAULT_CHROME_PLATFORM = {
|
|
89
|
-
os: 'linux',
|
|
90
|
-
arch: 'x64'
|
|
91
|
-
};
|
|
92
|
-
function getChromeDownloadUrl(id, platformInfo = DEFAULT_CHROME_PLATFORM) {
|
|
93
|
-
const encoded = encodeURIComponent(id);
|
|
94
|
-
const platform = normalizeChromePlatformInfo(platformInfo);
|
|
95
|
-
const productId = 'chromiumcrx';
|
|
96
|
-
const productChannel = 'unknown';
|
|
97
|
-
const productVersion = '9999.0.9999.0';
|
|
98
|
-
return [
|
|
99
|
-
'https://clients2.google.com/service/update2/crx',
|
|
100
|
-
'?response=redirect',
|
|
101
|
-
`&os=${platform.os}`,
|
|
102
|
-
`&arch=${platform.arch}`,
|
|
103
|
-
`&os_arch=${platform.arch}`,
|
|
104
|
-
`&nacl_arch=${platform.naclArch}`,
|
|
105
|
-
`&prod=${productId}`,
|
|
106
|
-
`&prodchannel=${productChannel}`,
|
|
107
|
-
`&prodversion=${productVersion}`,
|
|
108
|
-
'&acceptformat=crx2,crx3',
|
|
109
|
-
`&x=id%3D${encoded}%26uc`
|
|
110
|
-
].join('');
|
|
111
|
-
}
|
|
112
|
-
function getEdgeDownloadUrl(id) {
|
|
113
|
-
const encoded = encodeURIComponent(id);
|
|
114
|
-
return [
|
|
115
|
-
'https://edge.microsoft.com/extensionwebstorebase/v1/crx',
|
|
116
|
-
'?response=redirect',
|
|
117
|
-
'&prodversion=109.0.0.0',
|
|
118
|
-
`&x=id%3D${encoded}%26installsource%3Dondemand%26uc`
|
|
119
|
-
].join('');
|
|
120
|
-
}
|
|
121
|
-
async function resolveFirefoxDownload(idOrSlug, versionHint, options) {
|
|
122
|
-
var _addon_current_version_file, _addon_current_version, _addon_current_version1;
|
|
123
|
-
const baseUrl = `https://addons.mozilla.org/api/v5/addons/addon/${encodeURIComponent(idOrSlug)}/`;
|
|
124
|
-
const addon = await options.requestJson(baseUrl, options);
|
|
125
|
-
const slugOrId = addon.slug || idOrSlug;
|
|
126
|
-
if (versionHint) {
|
|
127
|
-
var _version_file;
|
|
128
|
-
const versionUrl = `${baseUrl}versions/${encodeURIComponent(versionHint)}/`;
|
|
129
|
-
const version = await options.requestJson(versionUrl, options);
|
|
130
|
-
const downloadUrl = null == (_version_file = version.file) ? void 0 : _version_file.url;
|
|
131
|
-
if (!downloadUrl) throw new errors_extensionFromStoreError('NotPublic', `Version ${versionHint} is not publicly downloadable`);
|
|
132
|
-
return {
|
|
133
|
-
downloadUrl,
|
|
134
|
-
version: version.version || versionHint,
|
|
135
|
-
slugOrId
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
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;
|
|
139
|
-
const version = null == (_addon_current_version1 = addon.current_version) ? void 0 : _addon_current_version1.version;
|
|
140
|
-
if (!downloadUrl || !version) throw new errors_extensionFromStoreError('NotPublic', 'Extension is not publicly downloadable');
|
|
141
|
-
return {
|
|
142
|
-
downloadUrl,
|
|
143
|
-
version,
|
|
144
|
-
slugOrId
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
const chromePattern = /^https?:\/\/(?:chrome\.google\.com\/webstore|chromewebstore\.google\.com)\/.+?\/([a-p]{32})(?=[\/#?]|$)/i;
|
|
148
|
-
const chromeDownloadPattern = /^https?:\/\/clients2\.google\.com\/service\/update2\/crx\b.*?%3D([a-p]{32})%26uc/i;
|
|
149
|
-
const edgePattern = /^https?:\/\/microsoftedge\.microsoft\.com\/addons\/.+?\/([a-z]{32})(?=[\/#?]|$)/i;
|
|
150
|
-
const edgeDownloadPattern = /^https?:\/\/edge\.microsoft\.com\/extensionwebstorebase\/v1\/crx\b.*?%3D([a-z]{32})%26/i;
|
|
151
|
-
const firefoxPattern = /^https?:\/\/((?:reviewers\.)?(?:addons\.mozilla\.org|addons(?:-dev)?\.allizom\.org))\/.*?(?:addon|review)\/([^/<>"'?#]+)/i;
|
|
152
|
-
const firefoxDownloadPattern = /^https?:\/\/(addons\.mozilla\.org|addons(?:-dev)?\.allizom\.org)\/[^?#]*\/downloads\/latest\/([^/?#]+)/i;
|
|
153
|
-
function detectStoreFromUrl(url) {
|
|
154
|
-
if (chromePattern.test(url) || chromeDownloadPattern.test(url)) return 'chrome';
|
|
155
|
-
if (edgePattern.test(url) || edgeDownloadPattern.test(url)) return 'edge';
|
|
156
|
-
if (firefoxPattern.test(url) || firefoxDownloadPattern.test(url)) return 'firefox';
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
function extractChromeIdFromUrl(url) {
|
|
160
|
-
const match = chromePattern.exec(url) || chromeDownloadPattern.exec(url);
|
|
161
|
-
return match ? match[1] : null;
|
|
162
|
-
}
|
|
163
|
-
function extractEdgeIdFromUrl(url) {
|
|
164
|
-
const match = edgePattern.exec(url) || edgeDownloadPattern.exec(url);
|
|
165
|
-
return match ? match[1] : null;
|
|
166
|
-
}
|
|
167
|
-
function extractFirefoxSlugFromUrl(url) {
|
|
168
|
-
const match = firefoxPattern.exec(url) || firefoxDownloadPattern.exec(url);
|
|
169
|
-
return match ? match[2] : null;
|
|
170
|
-
}
|
|
171
|
-
function validateInput(url) {
|
|
172
|
-
if (!url || 'string' != typeof url) throw new errors_extensionFromStoreError('InvalidInput', 'URL is required');
|
|
173
|
-
}
|
|
174
|
-
async function resolveDownload(url, options) {
|
|
175
|
-
const store = detectStoreFromUrl(url);
|
|
176
|
-
if (!store) throw new errors_extensionFromStoreError('UnsupportedStore', 'URL does not match a supported store');
|
|
177
|
-
if ('chrome' === store) {
|
|
178
|
-
const downloadId = extractChromeIdFromUrl(url);
|
|
179
|
-
if (!downloadId) throw new errors_extensionFromStoreError('NotFound', 'Chrome extension id not found in URL');
|
|
180
|
-
return {
|
|
181
|
-
store,
|
|
182
|
-
downloadUrl: getChromeDownloadUrl(downloadId, options.platform),
|
|
183
|
-
archiveType: 'crx',
|
|
184
|
-
downloadId,
|
|
185
|
-
slugOrId: downloadId
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
if ('edge' === store) {
|
|
189
|
-
const downloadId = extractEdgeIdFromUrl(url);
|
|
190
|
-
if (!downloadId) throw new errors_extensionFromStoreError('NotFound', 'Edge extension id not found in URL');
|
|
191
|
-
return {
|
|
192
|
-
store,
|
|
193
|
-
downloadUrl: getEdgeDownloadUrl(downloadId),
|
|
194
|
-
archiveType: 'crx',
|
|
195
|
-
downloadId,
|
|
196
|
-
slugOrId: downloadId
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
const slug = extractFirefoxSlugFromUrl(url);
|
|
200
|
-
if (!slug) throw new errors_extensionFromStoreError('NotFound', 'Firefox extension slug not found in URL');
|
|
201
|
-
const firefox = await resolveFirefoxDownload(slug, options.version, {
|
|
202
|
-
userAgent: options.userAgent,
|
|
203
|
-
logger: options.logger,
|
|
204
|
-
requestJson: options.requestJson
|
|
205
|
-
});
|
|
206
|
-
return {
|
|
207
|
-
store,
|
|
208
|
-
downloadUrl: firefox.downloadUrl,
|
|
209
|
-
archiveType: 'xpi',
|
|
210
|
-
versionHint: firefox.version,
|
|
211
|
-
slugOrId: firefox.slugOrId
|
|
212
|
-
};
|
|
213
|
-
}
|
|
1
|
+
import { strFromU8, unzipSync } from "fflate";
|
|
2
|
+
import { createLogger, resolveDownload, extensionFromStoreError, stripCrxHeader, validateInput, parseManifestInfo } from "./588.js";
|
|
214
3
|
const TEXT_FILE_PATTERN = /(^|\/)(?:[^/]+\.(?:txt|md|mdx|json|js|jsx|mjs|cjs|ts|tsx|css|scss|sass|less|html|xml|svg|yml|yaml|toml|ini|conf|map)|\.(?:gitignore|npmrc|editorconfig|prettierrc|eslintrc))$/i;
|
|
215
4
|
function getDefaultFetch() {
|
|
216
|
-
if ('function' != typeof globalThis.fetch) throw new
|
|
5
|
+
if ('function' != typeof globalThis.fetch) throw new extensionFromStoreError('DownloadFailed', 'No fetch implementation was provided');
|
|
217
6
|
return globalThis.fetch.bind(globalThis);
|
|
218
7
|
}
|
|
219
8
|
function inferBrowserChromePlatformInfo() {
|
|
220
|
-
var _navigatorLike_userAgentData;
|
|
221
9
|
const navigatorLike = globalThis.navigator;
|
|
222
10
|
const fingerprint = [
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
11
|
+
navigatorLike?.platform,
|
|
12
|
+
navigatorLike?.userAgent,
|
|
13
|
+
navigatorLike?.userAgentData?.platform
|
|
226
14
|
].filter(Boolean).join(' ').toLowerCase();
|
|
227
15
|
const os = fingerprint.includes('mac') ? 'mac' : fingerprint.includes('win') ? 'win' : 'linux';
|
|
228
16
|
const arch = fingerprint.includes('arm') || fingerprint.includes('aarch64') ? 'arm64' : fingerprint.includes('i686') || fingerprint.includes('i386') || fingerprint.includes('x86') && !fingerprint.includes('x86_64') ? 'x86' : 'x64';
|
|
@@ -235,12 +23,12 @@ function isLikelyTextFile(path) {
|
|
|
235
23
|
return 'manifest.json' === path || TEXT_FILE_PATTERN.test(path);
|
|
236
24
|
}
|
|
237
25
|
function decodeText(bytes) {
|
|
238
|
-
return
|
|
26
|
+
return strFromU8(bytes);
|
|
239
27
|
}
|
|
240
28
|
function mapHttpError(url, status) {
|
|
241
|
-
if (404 === status) return new
|
|
242
|
-
if (401 === status || 403 === status) return new
|
|
243
|
-
return new
|
|
29
|
+
if (404 === status) return new extensionFromStoreError('NotFound', `Extension not found at ${url}`);
|
|
30
|
+
if (401 === status || 403 === status) return new extensionFromStoreError('NotPublic', 'Extension is not publicly downloadable');
|
|
31
|
+
return new extensionFromStoreError('DownloadFailed', `Failed to request ${url} (HTTP ${status})`);
|
|
244
32
|
}
|
|
245
33
|
async function requestJsonWithFetch(url, options) {
|
|
246
34
|
if (options.userAgent) createLogger(options.logger).warn('Custom user agents are ignored in browser environments.');
|
|
@@ -250,7 +38,7 @@ async function requestJsonWithFetch(url, options) {
|
|
|
250
38
|
try {
|
|
251
39
|
return JSON.parse(body);
|
|
252
40
|
} catch (error) {
|
|
253
|
-
throw new
|
|
41
|
+
throw new extensionFromStoreError('StoreIncompatibility', `Invalid JSON response from ${url}`, error);
|
|
254
42
|
}
|
|
255
43
|
}
|
|
256
44
|
async function downloadBytes(url, options) {
|
|
@@ -291,12 +79,12 @@ async function fetchExtensionFromStoreBrowser(url, options = {}) {
|
|
|
291
79
|
const zipPayload = 'crx' === resolved.archiveType ? stripCrxHeader(archive.bytes) : archive.bytes;
|
|
292
80
|
let filesByPath;
|
|
293
81
|
try {
|
|
294
|
-
filesByPath =
|
|
82
|
+
filesByPath = unzipSync(zipPayload);
|
|
295
83
|
} catch (error) {
|
|
296
|
-
throw new
|
|
84
|
+
throw new extensionFromStoreError('ExtractionFailed', 'Failed to extract extension archive', error);
|
|
297
85
|
}
|
|
298
86
|
const manifestBytes = filesByPath['manifest.json'];
|
|
299
|
-
if (!manifestBytes) throw new
|
|
87
|
+
if (!manifestBytes) throw new extensionFromStoreError('ExtractionFailed', 'manifest.json was not found after extraction');
|
|
300
88
|
const manifestInfo = parseManifestInfo(decodeText(manifestBytes));
|
|
301
89
|
const version = resolved.versionHint || manifestInfo.extensionVersion;
|
|
302
90
|
const files = buildBrowserFiles(filesByPath);
|