justget/browser 是 justget 的浏览器端版本,通过 import { download } from 'justget/browser' 使用。
| 特性 | Node.js justget |
浏览器 justget/browser |
|---|---|---|
| 分块 Range | 分块并行下载 | ❌ 不分块,整文件下载 |
| 浏览器缓存 | 不利用 | ✅ 整文件请求 → 浏览器 HTTP 缓存自然生效 |
| 镜像策略 | 预热后全部启动 | ✅ 逐步释放,一次一个,按需启动 |
| 镜像记忆 | 无 | ✅ localStorage 学习型缓存,下次提为主站 |
| 存储 | 临时文件(fs) | ✅ 内存(Uint8Array[]) |
| 返回 | 文件路径 | ✅ Blob / Uint8Array |
| 哈希 | Node crypto | ✅ Web Crypto API |
| 依赖 | node-fetch, commander, progress | ✅ 零依赖 |
| 竞速 | 分块竞速 | ✅ 整文件竞速,先完成胜出 |
| 慢源替换 | 有 | ✅ 保留,慢于最快源 1/3 则 abort |
主源启动 → 预热(5s)
├─ 速度达标(>2MB/s) → 单源完成,浏览器缓存
└─ 速度不达标(<500KB/s) → 启动 mirror-1
├─ 现在最快源达标 → 等它完成
└─ 仍不达标 → 启动 mirror-2
└─ 仍不达标 → 启动 mirror-3 ...
└─ 任一完成 → 胜出,abort 其余
第1次:主站(慢) → 启动 mirror-1(成功) → 记到 localStorage
第2次:查 localStorage → mirror-1 提为主站 → 直接下载,秒开
- key:
justget:cache:{资源URL的FNV哈希} - value:
{ bestUrl, timestamp, successCount } - 7 天自动过期
npm install justgetimport { download } from 'justget/browser';
// 基本下载 → 返回 Blob
const result = await download({
url: 'https://example.com/models/face-detection.bin',
});
// result.data 是 Blob,可直接用于 createObjectURL
const url = URL.createObjectURL(result.data as Blob);const result = await download({
url: 'https://github.com/xxx/models/face.bin',
mirrors: [
'https://gh-proxy.com/...',
'https://mirror.example.com/...',
],
});
// 如果 mirror 成功,下次它自动当主站const result = await download({
url: 'https://example.com/models/large-model.onnx',
options: {
onProgress: (p) => {
console.log(`${p.percentage.toFixed(1)}% | ${(p.speed/1024).toFixed(0)} KB/s`);
},
},
});const result = await download({
url: 'https://example.com/model.bin',
options: { responseType: 'uint8array' },
});
// result.data 是 Uint8Array
const bytes = result.data as Uint8Array;const result = await download({
url: 'https://example.com/model.bin',
options: {
checksum: 'b413e420d6840b2775fba32008db6f3cddb07d485967fb42cfcf379c16a8c589',
checksumAlgorithm: 'sha256',
},
});import { download } from 'justget/browser';
import * as faceapi from '@vladmandic/face-api';
// 下载模型文件(竞速 + SHA-256 校验;不匹配自动剔除该源重跑竞速)
const modelResult = await download({
url: 'https://cdn.jsdelivr.net/npm/@vladmandic/face-api@1.7.15/model/face_recognition_model.bin',
mirrors: [
'https://unpkg.com/@vladmandic/face-api@1.7.15/model/face_recognition_model.bin',
'https://gitee.com/justdb-mirrors/face-models/releases/download/face-api-1.7.15/face_recognition_model.bin',
],
options: {
checksum: 'b413e420d6840b2775fba32008db6f3cddb07d485967fb42cfcf379c16a8c589',
responseType: 'uint8array',
onProgress: (p) => updateProgressBar(p.percentage),
},
});
const modelBytes = modelResult.data as Uint8Array;
// 喂给 face-api:face-api 的 load() 只收 URI 或 Float32Array,没有
// loadFromBuffer —— 用 fetch 拦截把已下载(已校验)的字节喂给
// loadFromUri,避免它再从网络拉一遍:
const MODEL_URL = 'https://cdn.jsdelivr.net/npm/@vladmandic/face-api@1.7.15/model/';
const origFetch = window.fetch;
window.fetch = async (input, init) => {
const u = typeof input === 'string' ? input : (input as Request).url;
if (u.startsWith(MODEL_URL)) {
return new Response(modelBytes, {
headers: { 'Content-Type': 'application/octet-stream' },
});
}
return origFetch(input, init);
};
try {
await faceapi.nets.faceRecognitionNet.loadFromUri(MODEL_URL);
} finally {
window.fetch = origFetch; // 用完即还原,不污染全局 fetch
}注:需要一并拦截/替换 manifest JSON(
loadFromUri会先拉*-weights_manifest.json再拉.bin),逐文件下载时把 manifest 也走 download() 校验后一并拦截即可。
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
url |
string |
— | 主站 URL(必填) |
mirrors |
string[] |
[] |
镜像 URL 列表 |
options.primaryWarmupTime |
number |
5000 |
主站预热 ms |
options.speedThreshold |
number |
512000 |
速度阈值 B/s,低于此值启动镜像 |
options.fastPrimaryThreshold |
number |
2048000 |
快速阈值 B/s,高于此值不启动镜像 |
options.sizeThreshold |
number |
52428800 |
文件大小阈值 B |
options.minReplaceTime |
number |
30000 |
慢源替换最短等待 ms |
options.replaceCheckInterval |
number |
10000 |
检查间隔 ms |
options.retries |
number |
3 |
重试次数 |
options.timeout |
number |
300000 |
下载超时 ms |
options.connectTimeout |
number |
10000 |
连接超时 ms |
options.checksum |
string |
— | 预期校验和 |
options.checksumAlgorithm |
'md5'|'sha1'|'sha256'|'sha512' |
'sha256' |
校验算法(md5 不受 Web Crypto 支持,会抛错) |
checksum 降级语义:胜出源校验不匹配不会直接失败——库会在本次调用内剔除该源并
用剩余源重跑竞速,直至校验通过或源穷尽(穷尽时抛最后一个
ChecksumMismatchError,含 expected / actual / badUrl,调用方可据此
自行处理污染源)。镜像清单是调用方意图,库不跨调用持久化“坏源”。
注意:重跑竞速会重新计耗时与进度回调,onComplete 只在最终成功后触发。
| options.responseType | 'blob'\|'uint8array' | 'blob' | 返回类型 |
| options.onProgress | (p: ProgressInfo) => void | — | 进度回调 |
| options.onComplete | (r: DownloadResult) => void | — | 完成回调 |
interface DownloadResult {
data: Blob | Uint8Array; // 下载的数据
bytes: number; // 总字节数
duration: number; // 耗时 ms
averageSpeed: number; // 平均速度 B/s
sources: SourceResult[]; // 各源结果
mimeType: string; // MIME 类型
}import { saveBestSource, loadBestSource, prioritizeMirrors, recordSuccess } from 'justget/browser';
// 手动记录
saveBestSource('https://example.com/model.bin', 'https://mirror.example.com/model.bin');
// 手动读取
const best = loadBestSource('https://example.com/model.bin');
// → 'https://mirror.example.com/model.bin'
// 重排镜像列表(把缓存的最优源提到主站)
const { primaryUrl, mirrors } = prioritizeMirrors(
'https://example.com/model.bin',
['https://mirror1.com/...', 'https://mirror2.com/...'],
'https://example.com/model.bin'
);npm run build # 构建 Node.js + 浏览器版
npm run build:browser # 仅构建浏览器版
npm run build:node # 仅构建 Node.js 版
npm test # 测试