extension-from-store 0.2.3 → 0.2.4
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 +17 -1
- package/dist/browser.js +1 -1
- package/dist/core.js +1 -1
- package/dist/index.cjs +22 -0
- package/dist/index.js +24 -2
- package/package.json +6 -6
- /package/dist/{588.js → 614.js} +0 -0
package/README.md
CHANGED
|
@@ -107,7 +107,23 @@ When extraction is disabled, the archive is saved as:
|
|
|
107
107
|
|
|
108
108
|
- `.crx`: strip CRX header, extract ZIP payload
|
|
109
109
|
- `.xpi`: treat as ZIP
|
|
110
|
-
- No normalization, no rewriting, no formatting
|
|
110
|
+
- No content normalization, no rewriting, no formatting
|
|
111
|
+
- Unix permission bits from the archive are not preserved: directories come
|
|
112
|
+
out `0755` and files come out `0644` (`0755` when the archive marked them
|
|
113
|
+
executable). Some store packages ship entries with modes like `0204` that
|
|
114
|
+
the owner cannot read back after extraction; browsers ignore archive modes
|
|
115
|
+
when installing, so this package does too.
|
|
116
|
+
|
|
117
|
+
## When the store refuses to serve an extension
|
|
118
|
+
|
|
119
|
+
Stores sometimes decline to serve a listed extension for download. The Chrome
|
|
120
|
+
Web Store update endpoint answers `HTTP 204 No Content` instead of a CRX when
|
|
121
|
+
an extension is blocked, for example when its update check reports
|
|
122
|
+
`_malware="true"` (observed live with ModHeader,
|
|
123
|
+
`idgpnmonknjnojddfkpgkljpfnnfcklj`, on 2026-08-02). This is a store-side
|
|
124
|
+
refusal, not a truncated download: real Chrome clients get the same empty
|
|
125
|
+
response. extension-from-store surfaces it as a `NotPublic` error that names
|
|
126
|
+
the URL that returned no content.
|
|
111
127
|
|
|
112
128
|
## `extension.meta.json`
|
|
113
129
|
|
package/dist/browser.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { strFromU8, unzipSync } from "fflate";
|
|
2
|
-
import { createLogger, resolveDownload, extensionFromStoreError, stripCrxHeader, validateInput, parseManifestInfo } from "./
|
|
2
|
+
import { createLogger, resolveDownload, extensionFromStoreError, stripCrxHeader, validateInput, parseManifestInfo } from "./614.js";
|
|
3
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;
|
|
4
4
|
function getDefaultFetch() {
|
|
5
5
|
if ('function' != typeof globalThis.fetch) throw new extensionFromStoreError('DownloadFailed', 'No fetch implementation was provided');
|
package/dist/core.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { asExtensionFromStoreError, createLogger, detectStoreFromUrl, extensionFromStoreError, extractChromeIdFromUrl, extractEdgeIdFromUrl, extractFirefoxSlugFromUrl, getChromeDownloadUrl, getEdgeDownloadUrl, normalizeChromePlatformInfo, parseManifestInfo, resolveDownload, resolveFirefoxDownload, sanitizeSegment, stripCrxHeader, validateInput } from "./
|
|
1
|
+
export { asExtensionFromStoreError, createLogger, detectStoreFromUrl, extensionFromStoreError, extractChromeIdFromUrl, extractEdgeIdFromUrl, extractFirefoxSlugFromUrl, getChromeDownloadUrl, getEdgeDownloadUrl, normalizeChromePlatformInfo, parseManifestInfo, resolveDownload, resolveFirefoxDownload, sanitizeSegment, stripCrxHeader, validateInput } from "./614.js";
|
package/dist/index.cjs
CHANGED
|
@@ -92,6 +92,20 @@ function stripCrxHeader(buffer) {
|
|
|
92
92
|
}
|
|
93
93
|
throw new errors_extensionFromStoreError('ExtractionFailed', `Unsupported CRX version ${version}`);
|
|
94
94
|
}
|
|
95
|
+
async function normalizeExtractedModes(dir) {
|
|
96
|
+
await promises_default().chmod(dir, 493);
|
|
97
|
+
const entries = await promises_default().readdir(dir, {
|
|
98
|
+
withFileTypes: true
|
|
99
|
+
});
|
|
100
|
+
for (const entry of entries){
|
|
101
|
+
const target = external_node_path_default().join(dir, entry.name);
|
|
102
|
+
if (entry.isDirectory()) await normalizeExtractedModes(target);
|
|
103
|
+
else if (entry.isFile()) {
|
|
104
|
+
const { mode } = await promises_default().stat(target);
|
|
105
|
+
await promises_default().chmod(target, 73 & mode ? 493 : 420);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
95
109
|
async function extractCrx(crxPath, extractDir, workDir) {
|
|
96
110
|
const crxBuffer = await promises_default().readFile(crxPath);
|
|
97
111
|
const zipBuffer = Buffer.from(stripCrxHeader(crxBuffer));
|
|
@@ -100,12 +114,14 @@ async function extractCrx(crxPath, extractDir, workDir) {
|
|
|
100
114
|
await external_extract_zip_default()(zipPath, {
|
|
101
115
|
dir: extractDir
|
|
102
116
|
});
|
|
117
|
+
await normalizeExtractedModes(extractDir);
|
|
103
118
|
await promises_default().unlink(zipPath).catch(()=>void 0);
|
|
104
119
|
}
|
|
105
120
|
async function extractZipArchive(zipPath, extractDir) {
|
|
106
121
|
await external_extract_zip_default()(zipPath, {
|
|
107
122
|
dir: extractDir
|
|
108
123
|
});
|
|
124
|
+
await normalizeExtractedModes(extractDir);
|
|
109
125
|
}
|
|
110
126
|
const external_node_fs_namespaceObject = require("node:fs");
|
|
111
127
|
var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
|
|
@@ -166,7 +182,13 @@ async function downloadToFile(url, filePath, options, maxRedirects = 5) {
|
|
|
166
182
|
if (401 === statusCode || 403 === statusCode) throw new errors_extensionFromStoreError('NotPublic', 'Extension is not publicly downloadable');
|
|
167
183
|
throw new errors_extensionFromStoreError('DownloadFailed', `Failed to download ${url} (HTTP ${statusCode})`);
|
|
168
184
|
}
|
|
185
|
+
if (204 === statusCode) {
|
|
186
|
+
stream.resume();
|
|
187
|
+
throw new errors_extensionFromStoreError('NotPublic', `Store returned no content for ${url}; the extension is not being served for download`);
|
|
188
|
+
}
|
|
169
189
|
await (0, external_node_stream_promises_namespaceObject.pipeline)(stream, external_node_fs_default().createWriteStream(filePath));
|
|
190
|
+
const { size } = await external_node_fs_default().promises.stat(filePath);
|
|
191
|
+
if (0 === size) throw new errors_extensionFromStoreError('DownloadFailed', `Downloaded file from ${url} is empty`);
|
|
170
192
|
}
|
|
171
193
|
async function requestJson(url, options, maxRedirects = 5) {
|
|
172
194
|
const { statusCode, headers, stream } = await request(url, options);
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,21 @@ import node_http from "node:http";
|
|
|
7
7
|
import node_https from "node:https";
|
|
8
8
|
import { pipeline } from "node:stream/promises";
|
|
9
9
|
import { URL } from "node:url";
|
|
10
|
-
import { createLogger, stripCrxHeader, extensionFromStoreError as errors_extensionFromStoreError, resolveDownload, sanitizeSegment, validateInput, parseManifestInfo } from "./
|
|
10
|
+
import { createLogger, stripCrxHeader, extensionFromStoreError as errors_extensionFromStoreError, resolveDownload, sanitizeSegment, validateInput, parseManifestInfo } from "./614.js";
|
|
11
|
+
async function normalizeExtractedModes(dir) {
|
|
12
|
+
await promises.chmod(dir, 493);
|
|
13
|
+
const entries = await promises.readdir(dir, {
|
|
14
|
+
withFileTypes: true
|
|
15
|
+
});
|
|
16
|
+
for (const entry of entries){
|
|
17
|
+
const target = node_path.join(dir, entry.name);
|
|
18
|
+
if (entry.isDirectory()) await normalizeExtractedModes(target);
|
|
19
|
+
else if (entry.isFile()) {
|
|
20
|
+
const { mode } = await promises.stat(target);
|
|
21
|
+
await promises.chmod(target, 73 & mode ? 493 : 420);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
11
25
|
async function extractCrx(crxPath, extractDir, workDir) {
|
|
12
26
|
const crxBuffer = await promises.readFile(crxPath);
|
|
13
27
|
const zipBuffer = Buffer.from(stripCrxHeader(crxBuffer));
|
|
@@ -16,12 +30,14 @@ async function extractCrx(crxPath, extractDir, workDir) {
|
|
|
16
30
|
await extract_zip(zipPath, {
|
|
17
31
|
dir: extractDir
|
|
18
32
|
});
|
|
33
|
+
await normalizeExtractedModes(extractDir);
|
|
19
34
|
await promises.unlink(zipPath).catch(()=>void 0);
|
|
20
35
|
}
|
|
21
36
|
async function extractZipArchive(zipPath, extractDir) {
|
|
22
37
|
await extract_zip(zipPath, {
|
|
23
38
|
dir: extractDir
|
|
24
39
|
});
|
|
40
|
+
await normalizeExtractedModes(extractDir);
|
|
25
41
|
}
|
|
26
42
|
const DEFAULT_USER_AGENT = 'extension-from-store';
|
|
27
43
|
function request(url, options) {
|
|
@@ -67,7 +83,13 @@ async function downloadToFile(url, filePath, options, maxRedirects = 5) {
|
|
|
67
83
|
if (401 === statusCode || 403 === statusCode) throw new errors_extensionFromStoreError('NotPublic', 'Extension is not publicly downloadable');
|
|
68
84
|
throw new errors_extensionFromStoreError('DownloadFailed', `Failed to download ${url} (HTTP ${statusCode})`);
|
|
69
85
|
}
|
|
86
|
+
if (204 === statusCode) {
|
|
87
|
+
stream.resume();
|
|
88
|
+
throw new errors_extensionFromStoreError('NotPublic', `Store returned no content for ${url}; the extension is not being served for download`);
|
|
89
|
+
}
|
|
70
90
|
await pipeline(stream, node_fs.createWriteStream(filePath));
|
|
91
|
+
const { size } = await node_fs.promises.stat(filePath);
|
|
92
|
+
if (0 === size) throw new errors_extensionFromStoreError('DownloadFailed', `Downloaded file from ${url} is empty`);
|
|
71
93
|
}
|
|
72
94
|
async function requestJson(url, options, maxRedirects = 5) {
|
|
73
95
|
const { statusCode, headers, stream } = await request(url, options);
|
|
@@ -203,5 +225,5 @@ async function fetchExtensionFromStore(url, options = {}) {
|
|
|
203
225
|
}).catch(()=>void 0);
|
|
204
226
|
}
|
|
205
227
|
}
|
|
206
|
-
export { extensionFromStoreError } from "./
|
|
228
|
+
export { extensionFromStoreError } from "./614.js";
|
|
207
229
|
export { fetchExtensionFromStore };
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"engineStrict": false,
|
|
11
11
|
"name": "extension-from-store",
|
|
12
|
-
"version": "0.2.
|
|
12
|
+
"version": "0.2.4",
|
|
13
13
|
"description": "Download public browser extensions from official stores",
|
|
14
14
|
"homepage": "https://www.npmjs.com/package/extension-from-store",
|
|
15
15
|
"type": "module",
|
|
@@ -97,12 +97,12 @@
|
|
|
97
97
|
}
|
|
98
98
|
},
|
|
99
99
|
"devDependencies": {
|
|
100
|
-
"@rslib/core": "^0.23.
|
|
101
|
-
"@types/node": "^
|
|
102
|
-
"eslint": "^10.
|
|
103
|
-
"eslint-config-auditor": "^
|
|
100
|
+
"@rslib/core": "^0.23.2",
|
|
101
|
+
"@types/node": "^26.1.1",
|
|
102
|
+
"eslint": "^10.8.0",
|
|
103
|
+
"eslint-config-auditor": "^3.0.0",
|
|
104
104
|
"typescript": "^6.0.3",
|
|
105
|
-
"vitest": "^4.1.
|
|
105
|
+
"vitest": "^4.1.10"
|
|
106
106
|
},
|
|
107
107
|
"packageManager": "pnpm@9.9.0+sha512.60c18acd138bff695d339be6ad13f7e936eea6745660d4cc4a776d5247c540d0edee1a563695c183a66eb917ef88f2b4feb1fc25f32a7adcadc7aaf3438e99c1",
|
|
108
108
|
"bugs": "https://github.com/cezaraugusto/extension-from-store/issues",
|
/package/dist/{588.js → 614.js}
RENAMED
|
File without changes
|