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/README.md +27 -0
- package/bin.cjs +7 -10
- package/bin.js +7 -10
- package/dist/browser.cjs +355 -0
- package/dist/browser.d.ts +44 -0
- package/dist/browser.js +321 -0
- package/dist/core.cjs +301 -0
- package/dist/core.d.ts +10 -0
- package/dist/core.js +222 -0
- package/dist/crx.d.ts +1 -0
- package/dist/extract.d.ts +1 -1
- package/dist/index.cjs +141 -101
- package/dist/index.js +131 -91
- package/dist/manifest.d.ts +7 -0
- package/dist/meta.d.ts +1 -4
- package/dist/node-platform.d.ts +2 -0
- package/dist/platform.d.ts +8 -0
- package/dist/resolve.d.ts +21 -0
- package/dist/stores/chrome.d.ts +2 -1
- package/dist/stores/firefox.d.ts +5 -0
- package/package.json +14 -2
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
|
|
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
|
|
46
|
-
var
|
|
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
|
-
|
|
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
|
-
|
|
200
|
-
const
|
|
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
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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:
|
|
216
|
-
naclArch:
|
|
237
|
+
os: platform.os,
|
|
238
|
+
arch: platform.arch,
|
|
239
|
+
naclArch: platform.naclArch || platform.arch
|
|
217
240
|
};
|
|
218
241
|
}
|
|
219
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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:
|
|
451
|
+
store: resolved.store,
|
|
412
452
|
identifier: resolved.slugOrId,
|
|
413
453
|
version: resolvedVersion,
|
|
414
454
|
manifestVersion: manifestInfo.manifestVersion
|