Skip to content

Latest commit

 

History

History
227 lines (187 loc) · 7.8 KB

File metadata and controls

227 lines (187 loc) · 7.8 KB

justget/browser — 浏览器端竞速下载

justget/browser 是 justget 的浏览器端版本,通过 import { download } from 'justget/browser' 使用。

设计思路

核心差异(与 Node.js 版)

特性 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 其余

学习型缓存(localStorage)

第1次:主站(慢) → 启动 mirror-1(成功) → 记到 localStorage
第2次:查 localStorage → mirror-1 提为主站 → 直接下载,秒开
  • key: justget:cache:{资源URL的FNV哈希}
  • value: { bestUrl, timestamp, successCount }
  • 7 天自动过期

快速开始

npm install justget
import { 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`);
    },
  },
});

获取 Uint8Array(直接喂给 face-api / WebAssembly)

const result = await download({
  url: 'https://example.com/model.bin',
  options: { responseType: 'uint8array' },
});
// result.data 是 Uint8Array
const bytes = result.data as Uint8Array;

校验 checksum

const result = await download({
  url: 'https://example.com/model.bin',
  options: {
    checksum: 'b413e420d6840b2775fba32008db6f3cddb07d485967fb42cfcf379c16a8c589',
    checksumAlgorithm: 'sha256',
  },
});

与 face-api 集成示例

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() 校验后一并拦截即可。

API

download(options: DownloadOptions): Promise<DownloadResult>

参数 类型 默认 说明
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 | — | 完成回调 |

返回 DownloadResult

interface DownloadResult {
  data: Blob | Uint8Array;  // 下载的数据
  bytes: number;            // 总字节数
  duration: number;         // 耗时 ms
  averageSpeed: number;     // 平均速度 B/s
  sources: SourceResult[];  // 各源结果
  mimeType: string;         // MIME 类型
}

缓存 API(独立使用)

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               # 测试