@xcanwin/manyoyo 8.6.5 → 8.6.6
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/lib/download-verified.js +30 -7
- package/lib/web/index.html +73 -73
- package/package.json +1 -1
package/lib/download-verified.js
CHANGED
|
@@ -16,11 +16,16 @@ function sha256File(filePath) {
|
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
const RETRYABLE_STATUS = new Set([408, 429, 500, 502, 503, 504]);
|
|
20
|
+
|
|
21
|
+
class RetryableError extends Error {}
|
|
22
|
+
|
|
19
23
|
/**
|
|
20
24
|
* 下载并校验 SHA256。缓存命中(文件存在且哈希一致)直接复用;
|
|
21
25
|
* 哈希不符一律删除并报错,绝不保留不可信文件。
|
|
26
|
+
* 5xx / 429 / 网络错误(含下载中途断开)按 1s、2s、4s 退避,最多重试 retries 次;4xx 与哈希不符不重试。
|
|
22
27
|
*/
|
|
23
|
-
async function downloadVerified({ url, sha256, dest, fetchImpl = fetch, log = () => {} }) {
|
|
28
|
+
async function downloadVerified({ url, sha256, dest, fetchImpl = fetch, log = () => {}, retries = 3, retryDelayMs = 1000 }) {
|
|
24
29
|
if (!/^[0-9a-f]{64}$/.test(sha256 || '')) {
|
|
25
30
|
throw new Error(`缺少有效的 SHA256: ${url}`);
|
|
26
31
|
}
|
|
@@ -29,13 +34,20 @@ async function downloadVerified({ url, sha256, dest, fetchImpl = fetch, log = ()
|
|
|
29
34
|
log(`缓存命中: ${path.basename(dest)}`);
|
|
30
35
|
return dest;
|
|
31
36
|
}
|
|
32
|
-
log(`下载: ${url}`);
|
|
33
|
-
const response = await fetchImpl(url, { redirect: 'follow' });
|
|
34
|
-
if (!response.ok || !response.body) {
|
|
35
|
-
throw new Error(`下载失败 (${response.status}): ${url}`);
|
|
36
|
-
}
|
|
37
37
|
const partial = `${dest}.partial`;
|
|
38
|
-
|
|
38
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
39
|
+
try {
|
|
40
|
+
await fetchOnce({ url, partial, fetchImpl, log });
|
|
41
|
+
break;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
fs.rmSync(partial, { force: true });
|
|
44
|
+
const retryable = error instanceof RetryableError || !(error && error.status);
|
|
45
|
+
if (!retryable || attempt >= retries) throw error instanceof RetryableError ? error.cause : error;
|
|
46
|
+
const wait = retryDelayMs * (2 ** attempt);
|
|
47
|
+
log(`下载失败,${wait / 1000} 秒后重试(${attempt + 1}/${retries}): ${error.message}`);
|
|
48
|
+
await new Promise(resolve => setTimeout(resolve, wait));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
39
51
|
const actual = await sha256File(partial);
|
|
40
52
|
if (actual !== sha256) {
|
|
41
53
|
fs.rmSync(partial, { force: true });
|
|
@@ -45,4 +57,15 @@ async function downloadVerified({ url, sha256, dest, fetchImpl = fetch, log = ()
|
|
|
45
57
|
return dest;
|
|
46
58
|
}
|
|
47
59
|
|
|
60
|
+
async function fetchOnce({ url, partial, fetchImpl, log }) {
|
|
61
|
+
log(`下载: ${url}`);
|
|
62
|
+
const response = await fetchImpl(url, { redirect: 'follow' });
|
|
63
|
+
if (!response.ok || !response.body) {
|
|
64
|
+
const error = Object.assign(new Error(`下载失败 (${response.status}): ${url}`), { status: response.status });
|
|
65
|
+
if (RETRYABLE_STATUS.has(response.status)) throw Object.assign(new RetryableError(error.message), { cause: error });
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
await pipeline(Readable.fromWeb(response.body), fs.createWriteStream(partial));
|
|
69
|
+
}
|
|
70
|
+
|
|
48
71
|
module.exports = { sha256File, downloadVerified };
|