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/README.md
CHANGED
|
@@ -45,6 +45,33 @@ const options = {
|
|
|
45
45
|
await fetchExtensionFromStore(url, options)
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
**Via browser entrypoint:**
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import {fetchExtensionFromStoreBrowser} from 'extension-from-store/browser'
|
|
52
|
+
|
|
53
|
+
const result = await fetchExtensionFromStoreBrowser(
|
|
54
|
+
'https://chromewebstore.google.com/detail/adblock-plus-free-ad-bloc/cfhdojbkjhnklbpkdaibdccddilifddb',
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
console.log(result.meta)
|
|
58
|
+
console.log(result.files.find((file) => file.path === 'manifest.json')?.text)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The browser entry keeps everything in memory and returns archive bytes, extracted files, and parsed manifest metadata. It does not write to disk.
|
|
62
|
+
|
|
63
|
+
**Via core helpers:**
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import {
|
|
67
|
+
detectStoreFromUrl,
|
|
68
|
+
extractChromeIdFromUrl,
|
|
69
|
+
getChromeDownloadUrl,
|
|
70
|
+
parseManifestInfo,
|
|
71
|
+
stripCrxHeader,
|
|
72
|
+
} from 'extension-from-store/core'
|
|
73
|
+
```
|
|
74
|
+
|
|
48
75
|
**Via CLI (default command is `fetch`):**
|
|
49
76
|
|
|
50
77
|
```bash
|
package/bin.cjs
CHANGED
|
@@ -116,16 +116,13 @@ async function main() {
|
|
|
116
116
|
try {
|
|
117
117
|
args = parseArgs(process.argv.slice(2));
|
|
118
118
|
const logger = createCliLogger(args);
|
|
119
|
-
await fetchExtensionFromStore(
|
|
120
|
-
args.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
logger,
|
|
127
|
-
},
|
|
128
|
-
);
|
|
119
|
+
await fetchExtensionFromStore(args.url, {
|
|
120
|
+
outDir: args.out || undefined,
|
|
121
|
+
userAgent: args.userAgent || undefined,
|
|
122
|
+
version: args.version || undefined,
|
|
123
|
+
extract: args.extract,
|
|
124
|
+
logger,
|
|
125
|
+
});
|
|
129
126
|
|
|
130
127
|
process.exit(0);
|
|
131
128
|
} catch (error) {
|
package/bin.js
CHANGED
|
@@ -114,16 +114,13 @@ async function main() {
|
|
|
114
114
|
try {
|
|
115
115
|
args = parseArgs(process.argv.slice(2));
|
|
116
116
|
const logger = createCliLogger(args);
|
|
117
|
-
await fetchExtensionFromStore(
|
|
118
|
-
args.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
logger,
|
|
125
|
-
},
|
|
126
|
-
);
|
|
117
|
+
await fetchExtensionFromStore(args.url, {
|
|
118
|
+
outDir: args.out || undefined,
|
|
119
|
+
userAgent: args.userAgent || undefined,
|
|
120
|
+
version: args.version || undefined,
|
|
121
|
+
extract: args.extract,
|
|
122
|
+
logger,
|
|
123
|
+
});
|
|
127
124
|
process.exit(0);
|
|
128
125
|
} catch (error) {
|
|
129
126
|
if (error instanceof extensionFromStoreError) {
|
package/dist/browser.cjs
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
5
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: definition[key]
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
})();
|
|
11
|
+
(()=>{
|
|
12
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
13
|
+
})();
|
|
14
|
+
(()=>{
|
|
15
|
+
__webpack_require__.r = (exports1)=>{
|
|
16
|
+
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
17
|
+
value: 'Module'
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
20
|
+
value: true
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
})();
|
|
24
|
+
var __webpack_exports__ = {};
|
|
25
|
+
__webpack_require__.r(__webpack_exports__);
|
|
26
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
fetchExtensionFromStoreBrowser: ()=>fetchExtensionFromStoreBrowser
|
|
28
|
+
});
|
|
29
|
+
const external_fflate_namespaceObject = require("fflate");
|
|
30
|
+
function _define_property(obj, key, value) {
|
|
31
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
32
|
+
value: value,
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true
|
|
36
|
+
});
|
|
37
|
+
else obj[key] = value;
|
|
38
|
+
return obj;
|
|
39
|
+
}
|
|
40
|
+
class errors_extensionFromStoreError extends Error {
|
|
41
|
+
constructor(code, message, cause){
|
|
42
|
+
super(message), _define_property(this, "code", void 0), _define_property(this, "cause", void 0);
|
|
43
|
+
this.code = code;
|
|
44
|
+
this.cause = cause;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function readUInt32LE(bytes, offset) {
|
|
48
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset + offset, Uint32Array.BYTES_PER_ELEMENT);
|
|
49
|
+
return view.getUint32(0, true);
|
|
50
|
+
}
|
|
51
|
+
function readMagic(bytes) {
|
|
52
|
+
let out = '';
|
|
53
|
+
const slice = bytes.subarray(0, 4);
|
|
54
|
+
for(let index = 0; index < slice.length; index += 1)out += String.fromCharCode(slice[index]);
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
function stripCrxHeader(buffer) {
|
|
58
|
+
if (buffer.length < 16) throw new errors_extensionFromStoreError('ExtractionFailed', 'CRX file too small');
|
|
59
|
+
const magic = readMagic(buffer);
|
|
60
|
+
if ('Cr24' !== magic) throw new errors_extensionFromStoreError('ExtractionFailed', 'Invalid CRX header');
|
|
61
|
+
const version = readUInt32LE(buffer, 4);
|
|
62
|
+
if (2 === version) {
|
|
63
|
+
const publicKeyLength = readUInt32LE(buffer, 8);
|
|
64
|
+
const signatureLength = readUInt32LE(buffer, 12);
|
|
65
|
+
const headerSize = 16 + publicKeyLength + signatureLength;
|
|
66
|
+
return buffer.subarray(headerSize);
|
|
67
|
+
}
|
|
68
|
+
if (3 === version) {
|
|
69
|
+
const headerSize = readUInt32LE(buffer, 8);
|
|
70
|
+
return buffer.subarray(12 + headerSize);
|
|
71
|
+
}
|
|
72
|
+
throw new errors_extensionFromStoreError('ExtractionFailed', `Unsupported CRX version ${version}`);
|
|
73
|
+
}
|
|
74
|
+
function createLogger(logger) {
|
|
75
|
+
return {
|
|
76
|
+
info: (message)=>{
|
|
77
|
+
var _logger_onInfo;
|
|
78
|
+
return null == logger ? void 0 : null == (_logger_onInfo = logger.onInfo) ? void 0 : _logger_onInfo.call(logger, message);
|
|
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
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function parseManifestInfo(raw) {
|
|
91
|
+
let parsed;
|
|
92
|
+
try {
|
|
93
|
+
parsed = JSON.parse(raw);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
throw new errors_extensionFromStoreError('ExtractionFailed', 'manifest.json is not valid JSON', error);
|
|
96
|
+
}
|
|
97
|
+
if (!parsed || 'object' != typeof parsed || Array.isArray(parsed)) throw new errors_extensionFromStoreError('ExtractionFailed', 'manifest.json must contain an object');
|
|
98
|
+
const manifest = parsed;
|
|
99
|
+
const manifestVersion = manifest.manifest_version;
|
|
100
|
+
const extensionVersion = manifest.version;
|
|
101
|
+
if (2 !== manifestVersion && 3 !== manifestVersion) throw new errors_extensionFromStoreError('ExtractionFailed', 'manifest_version must be 2 or 3');
|
|
102
|
+
if (!extensionVersion || 'string' != typeof extensionVersion) throw new errors_extensionFromStoreError('ExtractionFailed', 'manifest.json is missing a version');
|
|
103
|
+
return {
|
|
104
|
+
manifest,
|
|
105
|
+
manifestVersion,
|
|
106
|
+
extensionVersion
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function normalizeChromePlatformInfo(platform) {
|
|
110
|
+
return {
|
|
111
|
+
os: platform.os,
|
|
112
|
+
arch: platform.arch,
|
|
113
|
+
naclArch: platform.naclArch || platform.arch
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const DEFAULT_CHROME_PLATFORM = {
|
|
117
|
+
os: 'linux',
|
|
118
|
+
arch: 'x64'
|
|
119
|
+
};
|
|
120
|
+
function getChromeDownloadUrl(id, platformInfo = DEFAULT_CHROME_PLATFORM) {
|
|
121
|
+
const encoded = encodeURIComponent(id);
|
|
122
|
+
const platform = normalizeChromePlatformInfo(platformInfo);
|
|
123
|
+
const productId = 'chromiumcrx';
|
|
124
|
+
const productChannel = 'unknown';
|
|
125
|
+
const productVersion = '9999.0.9999.0';
|
|
126
|
+
return [
|
|
127
|
+
'https://clients2.google.com/service/update2/crx',
|
|
128
|
+
'?response=redirect',
|
|
129
|
+
`&os=${platform.os}`,
|
|
130
|
+
`&arch=${platform.arch}`,
|
|
131
|
+
`&os_arch=${platform.arch}`,
|
|
132
|
+
`&nacl_arch=${platform.naclArch}`,
|
|
133
|
+
`&prod=${productId}`,
|
|
134
|
+
`&prodchannel=${productChannel}`,
|
|
135
|
+
`&prodversion=${productVersion}`,
|
|
136
|
+
'&acceptformat=crx2,crx3',
|
|
137
|
+
`&x=id%3D${encoded}%26uc`
|
|
138
|
+
].join('');
|
|
139
|
+
}
|
|
140
|
+
function getEdgeDownloadUrl(id) {
|
|
141
|
+
const encoded = encodeURIComponent(id);
|
|
142
|
+
return [
|
|
143
|
+
'https://edge.microsoft.com/extensionwebstorebase/v1/crx',
|
|
144
|
+
'?response=redirect',
|
|
145
|
+
'&prodversion=109.0.0.0',
|
|
146
|
+
`&x=id%3D${encoded}%26installsource%3Dondemand%26uc`
|
|
147
|
+
].join('');
|
|
148
|
+
}
|
|
149
|
+
async function resolveFirefoxDownload(idOrSlug, versionHint, options) {
|
|
150
|
+
var _addon_current_version_file, _addon_current_version, _addon_current_version1;
|
|
151
|
+
const baseUrl = `https://addons.mozilla.org/api/v5/addons/addon/${encodeURIComponent(idOrSlug)}/`;
|
|
152
|
+
const addon = await options.requestJson(baseUrl, options);
|
|
153
|
+
const slugOrId = addon.slug || idOrSlug;
|
|
154
|
+
if (versionHint) {
|
|
155
|
+
var _version_file;
|
|
156
|
+
const versionUrl = `${baseUrl}versions/${encodeURIComponent(versionHint)}/`;
|
|
157
|
+
const version = await options.requestJson(versionUrl, options);
|
|
158
|
+
const downloadUrl = null == (_version_file = version.file) ? void 0 : _version_file.url;
|
|
159
|
+
if (!downloadUrl) throw new errors_extensionFromStoreError('NotPublic', `Version ${versionHint} is not publicly downloadable`);
|
|
160
|
+
return {
|
|
161
|
+
downloadUrl,
|
|
162
|
+
version: version.version || versionHint,
|
|
163
|
+
slugOrId
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
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;
|
|
167
|
+
const version = null == (_addon_current_version1 = addon.current_version) ? void 0 : _addon_current_version1.version;
|
|
168
|
+
if (!downloadUrl || !version) throw new errors_extensionFromStoreError('NotPublic', 'Extension is not publicly downloadable');
|
|
169
|
+
return {
|
|
170
|
+
downloadUrl,
|
|
171
|
+
version,
|
|
172
|
+
slugOrId
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const chromePattern = /^https?:\/\/(?:chrome\.google\.com\/webstore|chromewebstore\.google\.com)\/.+?\/([a-p]{32})(?=[\/#?]|$)/i;
|
|
176
|
+
const chromeDownloadPattern = /^https?:\/\/clients2\.google\.com\/service\/update2\/crx\b.*?%3D([a-p]{32})%26uc/i;
|
|
177
|
+
const edgePattern = /^https?:\/\/microsoftedge\.microsoft\.com\/addons\/.+?\/([a-z]{32})(?=[\/#?]|$)/i;
|
|
178
|
+
const edgeDownloadPattern = /^https?:\/\/edge\.microsoft\.com\/extensionwebstorebase\/v1\/crx\b.*?%3D([a-z]{32})%26/i;
|
|
179
|
+
const firefoxPattern = /^https?:\/\/((?:reviewers\.)?(?:addons\.mozilla\.org|addons(?:-dev)?\.allizom\.org))\/.*?(?:addon|review)\/([^/<>"'?#]+)/i;
|
|
180
|
+
const firefoxDownloadPattern = /^https?:\/\/(addons\.mozilla\.org|addons(?:-dev)?\.allizom\.org)\/[^?#]*\/downloads\/latest\/([^/?#]+)/i;
|
|
181
|
+
function detectStoreFromUrl(url) {
|
|
182
|
+
if (chromePattern.test(url) || chromeDownloadPattern.test(url)) return 'chrome';
|
|
183
|
+
if (edgePattern.test(url) || edgeDownloadPattern.test(url)) return 'edge';
|
|
184
|
+
if (firefoxPattern.test(url) || firefoxDownloadPattern.test(url)) return 'firefox';
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
function extractChromeIdFromUrl(url) {
|
|
188
|
+
const match = chromePattern.exec(url) || chromeDownloadPattern.exec(url);
|
|
189
|
+
return match ? match[1] : null;
|
|
190
|
+
}
|
|
191
|
+
function extractEdgeIdFromUrl(url) {
|
|
192
|
+
const match = edgePattern.exec(url) || edgeDownloadPattern.exec(url);
|
|
193
|
+
return match ? match[1] : null;
|
|
194
|
+
}
|
|
195
|
+
function extractFirefoxSlugFromUrl(url) {
|
|
196
|
+
const match = firefoxPattern.exec(url) || firefoxDownloadPattern.exec(url);
|
|
197
|
+
return match ? match[2] : null;
|
|
198
|
+
}
|
|
199
|
+
function validateInput(url) {
|
|
200
|
+
if (!url || 'string' != typeof url) throw new errors_extensionFromStoreError('InvalidInput', 'URL is required');
|
|
201
|
+
}
|
|
202
|
+
async function resolveDownload(url, options) {
|
|
203
|
+
const store = detectStoreFromUrl(url);
|
|
204
|
+
if (!store) throw new errors_extensionFromStoreError('UnsupportedStore', 'URL does not match a supported store');
|
|
205
|
+
if ('chrome' === store) {
|
|
206
|
+
const downloadId = extractChromeIdFromUrl(url);
|
|
207
|
+
if (!downloadId) throw new errors_extensionFromStoreError('NotFound', 'Chrome extension id not found in URL');
|
|
208
|
+
return {
|
|
209
|
+
store,
|
|
210
|
+
downloadUrl: getChromeDownloadUrl(downloadId, options.platform),
|
|
211
|
+
archiveType: 'crx',
|
|
212
|
+
downloadId,
|
|
213
|
+
slugOrId: downloadId
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if ('edge' === store) {
|
|
217
|
+
const downloadId = extractEdgeIdFromUrl(url);
|
|
218
|
+
if (!downloadId) throw new errors_extensionFromStoreError('NotFound', 'Edge extension id not found in URL');
|
|
219
|
+
return {
|
|
220
|
+
store,
|
|
221
|
+
downloadUrl: getEdgeDownloadUrl(downloadId),
|
|
222
|
+
archiveType: 'crx',
|
|
223
|
+
downloadId,
|
|
224
|
+
slugOrId: downloadId
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const slug = extractFirefoxSlugFromUrl(url);
|
|
228
|
+
if (!slug) throw new errors_extensionFromStoreError('NotFound', 'Firefox extension slug not found in URL');
|
|
229
|
+
const firefox = await resolveFirefoxDownload(slug, options.version, {
|
|
230
|
+
userAgent: options.userAgent,
|
|
231
|
+
logger: options.logger,
|
|
232
|
+
requestJson: options.requestJson
|
|
233
|
+
});
|
|
234
|
+
return {
|
|
235
|
+
store,
|
|
236
|
+
downloadUrl: firefox.downloadUrl,
|
|
237
|
+
archiveType: 'xpi',
|
|
238
|
+
versionHint: firefox.version,
|
|
239
|
+
slugOrId: firefox.slugOrId
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
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;
|
|
243
|
+
function getDefaultFetch() {
|
|
244
|
+
if ('function' != typeof globalThis.fetch) throw new errors_extensionFromStoreError('DownloadFailed', 'No fetch implementation was provided');
|
|
245
|
+
return globalThis.fetch.bind(globalThis);
|
|
246
|
+
}
|
|
247
|
+
function inferBrowserChromePlatformInfo() {
|
|
248
|
+
var _navigatorLike_userAgentData;
|
|
249
|
+
const navigatorLike = globalThis.navigator;
|
|
250
|
+
const fingerprint = [
|
|
251
|
+
null == navigatorLike ? void 0 : navigatorLike.platform,
|
|
252
|
+
null == navigatorLike ? void 0 : navigatorLike.userAgent,
|
|
253
|
+
null == navigatorLike ? void 0 : null == (_navigatorLike_userAgentData = navigatorLike.userAgentData) ? void 0 : _navigatorLike_userAgentData.platform
|
|
254
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
255
|
+
const os = fingerprint.includes('mac') ? 'mac' : fingerprint.includes('win') ? 'win' : 'linux';
|
|
256
|
+
const arch = fingerprint.includes('arm') || fingerprint.includes('aarch64') ? 'arm64' : fingerprint.includes('i686') || fingerprint.includes('i386') || fingerprint.includes('x86') && !fingerprint.includes('x86_64') ? 'x86' : 'x64';
|
|
257
|
+
return {
|
|
258
|
+
os,
|
|
259
|
+
arch
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function isLikelyTextFile(path) {
|
|
263
|
+
return 'manifest.json' === path || TEXT_FILE_PATTERN.test(path);
|
|
264
|
+
}
|
|
265
|
+
function decodeText(bytes) {
|
|
266
|
+
return (0, external_fflate_namespaceObject.strFromU8)(bytes);
|
|
267
|
+
}
|
|
268
|
+
function mapHttpError(url, status) {
|
|
269
|
+
if (404 === status) return new errors_extensionFromStoreError('NotFound', `Extension not found at ${url}`);
|
|
270
|
+
if (401 === status || 403 === status) return new errors_extensionFromStoreError('NotPublic', 'Extension is not publicly downloadable');
|
|
271
|
+
return new errors_extensionFromStoreError('DownloadFailed', `Failed to request ${url} (HTTP ${status})`);
|
|
272
|
+
}
|
|
273
|
+
async function requestJsonWithFetch(url, options) {
|
|
274
|
+
if (options.userAgent) createLogger(options.logger).warn('Custom user agents are ignored in browser environments.');
|
|
275
|
+
const response = await options.fetchImpl(url);
|
|
276
|
+
if (!response.ok) throw mapHttpError(url, response.status);
|
|
277
|
+
const body = await response.text();
|
|
278
|
+
try {
|
|
279
|
+
return JSON.parse(body);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
throw new errors_extensionFromStoreError('StoreIncompatibility', `Invalid JSON response from ${url}`, error);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async function downloadBytes(url, options) {
|
|
285
|
+
if (options.userAgent) createLogger(options.logger).warn('Custom user agents are ignored in browser environments.');
|
|
286
|
+
const response = await options.fetchImpl(url);
|
|
287
|
+
if (!response.ok) throw mapHttpError(url, response.status);
|
|
288
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
289
|
+
return {
|
|
290
|
+
finalUrl: response.url || url,
|
|
291
|
+
bytes
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function buildBrowserFiles(entries) {
|
|
295
|
+
return Object.entries(entries).sort(([left], [right])=>left.localeCompare(right)).map(([path, bytes])=>({
|
|
296
|
+
path,
|
|
297
|
+
bytes,
|
|
298
|
+
text: isLikelyTextFile(path) ? decodeText(bytes) : void 0
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
async function fetchExtensionFromStoreBrowser(url, options = {}) {
|
|
302
|
+
validateInput(url);
|
|
303
|
+
const fetchImpl = options.fetch || getDefaultFetch();
|
|
304
|
+
const resolved = await resolveDownload(url, {
|
|
305
|
+
version: options.version,
|
|
306
|
+
userAgent: options.userAgent,
|
|
307
|
+
logger: options.logger,
|
|
308
|
+
platform: options.platform || inferBrowserChromePlatformInfo(),
|
|
309
|
+
requestJson: (requestUrl, requestOptions)=>requestJsonWithFetch(requestUrl, {
|
|
310
|
+
...requestOptions,
|
|
311
|
+
fetchImpl
|
|
312
|
+
})
|
|
313
|
+
});
|
|
314
|
+
const archive = await downloadBytes(resolved.downloadUrl, {
|
|
315
|
+
fetchImpl,
|
|
316
|
+
userAgent: options.userAgent,
|
|
317
|
+
logger: options.logger
|
|
318
|
+
});
|
|
319
|
+
const zipPayload = 'crx' === resolved.archiveType ? stripCrxHeader(archive.bytes) : archive.bytes;
|
|
320
|
+
let filesByPath;
|
|
321
|
+
try {
|
|
322
|
+
filesByPath = (0, external_fflate_namespaceObject.unzipSync)(zipPayload);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
throw new errors_extensionFromStoreError('ExtractionFailed', 'Failed to extract extension archive', error);
|
|
325
|
+
}
|
|
326
|
+
const manifestBytes = filesByPath['manifest.json'];
|
|
327
|
+
if (!manifestBytes) throw new errors_extensionFromStoreError('ExtractionFailed', 'manifest.json was not found after extraction');
|
|
328
|
+
const manifestInfo = parseManifestInfo(decodeText(manifestBytes));
|
|
329
|
+
const version = resolved.versionHint || manifestInfo.extensionVersion;
|
|
330
|
+
const files = buildBrowserFiles(filesByPath);
|
|
331
|
+
return {
|
|
332
|
+
store: resolved.store,
|
|
333
|
+
identifier: resolved.slugOrId,
|
|
334
|
+
version,
|
|
335
|
+
manifestVersion: manifestInfo.manifestVersion,
|
|
336
|
+
archiveType: resolved.archiveType,
|
|
337
|
+
downloadUrl: archive.finalUrl,
|
|
338
|
+
archiveBytes: archive.bytes,
|
|
339
|
+
manifest: manifestInfo.manifest,
|
|
340
|
+
files,
|
|
341
|
+
meta: {
|
|
342
|
+
store: resolved.store,
|
|
343
|
+
identifier: resolved.slugOrId,
|
|
344
|
+
version,
|
|
345
|
+
manifestVersion: manifestInfo.manifestVersion
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
exports.fetchExtensionFromStoreBrowser = __webpack_exports__.fetchExtensionFromStoreBrowser;
|
|
350
|
+
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
351
|
+
"fetchExtensionFromStoreBrowser"
|
|
352
|
+
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
353
|
+
Object.defineProperty(exports, '__esModule', {
|
|
354
|
+
value: true
|
|
355
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type Logger } from './logger';
|
|
2
|
+
import { type ExtensionManifest } from './manifest';
|
|
3
|
+
import type { ChromePlatformInfo } from './platform';
|
|
4
|
+
type FetchLikeResponse = {
|
|
5
|
+
ok: boolean;
|
|
6
|
+
status: number;
|
|
7
|
+
url?: string;
|
|
8
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
9
|
+
text(): Promise<string>;
|
|
10
|
+
};
|
|
11
|
+
export type FetchLike = (url: string, init?: {
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
}) => Promise<FetchLikeResponse>;
|
|
14
|
+
export type BrowserExtensionFile = {
|
|
15
|
+
path: string;
|
|
16
|
+
bytes: Uint8Array;
|
|
17
|
+
text?: string;
|
|
18
|
+
};
|
|
19
|
+
export type BrowserFetchOptions = {
|
|
20
|
+
version?: string;
|
|
21
|
+
userAgent?: string;
|
|
22
|
+
logger?: Logger;
|
|
23
|
+
platform?: ChromePlatformInfo;
|
|
24
|
+
fetch?: FetchLike;
|
|
25
|
+
};
|
|
26
|
+
export type BrowserFetchResult = {
|
|
27
|
+
store: 'chrome' | 'edge' | 'firefox';
|
|
28
|
+
identifier: string;
|
|
29
|
+
version: string;
|
|
30
|
+
manifestVersion: 2 | 3;
|
|
31
|
+
archiveType: 'crx' | 'xpi';
|
|
32
|
+
downloadUrl: string;
|
|
33
|
+
archiveBytes: Uint8Array;
|
|
34
|
+
manifest: ExtensionManifest;
|
|
35
|
+
files: BrowserExtensionFile[];
|
|
36
|
+
meta: {
|
|
37
|
+
store: 'chrome' | 'edge' | 'firefox';
|
|
38
|
+
identifier: string;
|
|
39
|
+
version: string;
|
|
40
|
+
manifestVersion: 2 | 3;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export declare function fetchExtensionFromStoreBrowser(url: string, options?: BrowserFetchOptions): Promise<BrowserFetchResult>;
|
|
44
|
+
export {};
|