@ziuchen/wormhole-client
v0.0.0-b081b13
Published
Upload and download files via wormhole.app
Readme
@ziuchen/wormhole-client
通过 wormhole.app 上传和下载文件——端到端加密,零运行时依赖。
安装
npm install -g @ziuchen/wormhole-client
# 或
pnpm add -g @ziuchen/wormhole-clientCLI 用法
上传
wormhole upload <文件> [文件2 ...]| 参数 | 简写 | 说明 |
| ----------------- | ---- | ------------------------------------------ |
| --name <目录名> | -n | 多文件上传时展示给接收方的文件夹名称 |
| --expire <时间> | -e | 有效期:1h 2h 6h 12h 24h |
| --limit <n> | -l | 最大下载次数:1 5 10 20 50 100 |
# 上传单个文件
wormhole upload report.pdf
# 上传多个文件并指定文件夹名称
wormhole upload *.log --name server-logs
# 设置 2 小时有效期,限制下载 5 次
wormhole upload secret.zip --expire 2h --limit 5分享链接打印到 stdout;进度和状态信息输出到 stderr。
下载
wormhole download <url> [--output <目录>]| 参数 | 简写 | 说明 |
| ----------------- | ---- | --------------------- |
| --output <目录> | -o | 保存目录(默认:.) |
wormhole download "https://wormhole.app/abc123#key"
wormhole download "https://wormhole.app/abc123#key" --output ~/Downloads编程式调用
import { createClient, parseWormholeUrl } from "@ziuchen/wormhole-client";createClient(options?)
创建同时支持上传和下载的客户端实例。所有网络请求均使用配置的 fetch、超时时间和重试策略。
import { createClient } from "@ziuchen/wormhole-client";
const client = createClient({
timeout: 60_000, // 每次请求超时(毫秒),默认 60_000
fetch: globalThis.fetch, // 自定义 fetch(默认 globalThis.fetch)
retries: 3, // 最大重试次数(默认 3,0 表示不重试)
apiBase: "https://wormhole.app/api", // API 基地址
userAgent: "MyApp/2.0", // User-Agent 头(默认 "Wormhole-Client/1.0")
uploadConcurrency: 10, // 并发上传 B2 分块数(默认 10)
});| 选项 | 类型 | 默认值 | 说明 |
| ------------------- | --------------- | ---------------------------- | --------------------------------- |
| timeout | number? | 60_000 | 每次网络请求的超时时间(毫秒) |
| fetch | typeof fetch? | globalThis.fetch | 自定义 fetch 实现(mock、代理等) |
| retries | number? | 3 | 失败重试次数(0 禁用) |
| apiBase | string? | "https://wormhole.app/api" | API 基地址 |
| userAgent | string? | "Wormhole-Client/1.0" | User-Agent 请求头 |
| uploadConcurrency | number? | 10 | B2 分块上传最大并发数 |
client.upload(files, options?)
const result = await client.upload(
[
{ stream: fileStream, name: "report.pdf", size: 12345 },
{ buffer: pdfBytes, name: "data.csv" },
],
{
dirName: "quarterly-report", // 多文件上传时的文件夹名称
lifetime: 7200, // 2 小时(可选:3600 | 7200 | 21600 | 43200 | 86400)
maxDownloads: 10, // 可选:1 | 5 | 10 | 20 | 50 | 100
signal: ac.signal, // AbortSignal,用于取消上传
onProgress(uploaded, total) {
process.stdout.write(`\r${uploaded}/${total} 字节`);
},
},
);
console.log(result.url); // https://wormhole.app/<id>#<key>
console.log(result.roomId); // room ID
console.log(result.lifetime); // 服务器确认的有效期(秒)
console.log(result.maxDownloads); // 服务器确认的最大下载次数UploadFile
| 字段 | 类型 | 说明 |
| -------- | ---------------------------------------------- | --------------------------------------------------------------------------- |
| stream | ReadableStream<Uint8Array> + name + size | 流式输入(跨平台) |
| buffer | Uint8Array + name | 内存缓冲区输入(跨平台) |
| path | string + name? | 文件路径——仅 Node.js(请使用 @ziuchen/wormhole-client/node 便捷方法) |
UploadOptions
| 字段 | 类型 | 说明 |
| -------------- | ------------------------------------------- | ------------------------------------------------------ |
| dirName | string? | 多文件传输的文件夹名称 |
| lifetime | number? | 有效期(秒):3600 7200 21600 43200 86400 |
| maxDownloads | number? | 下载次数限制:1 5 10 20 50 100 |
| onProgress | (uploaded: number, total: number) => void | 周期性回调,参数为已上传的累计字节数和加密后的总字节数 |
| signal | AbortSignal? | 取消整个上传操作 |
UploadResult
| 字段 | 类型 | 说明 |
| -------------- | --------- | -------------------------------------------- |
| url | string | 包含加密密钥(在 URL fragment 中)的分享链接 |
| roomId | string | Room ID(URL 路径段) |
| lifetime | number? | 服务器确认的有效期(秒) |
| maxDownloads | number? | 服务器确认的最大下载次数 |
client.download(url, options?)
将文件下载到内存,返回解密后的 Uint8Array 数据。
const result = await client.download("https://wormhole.app/abc123#key", {
onInfo(torrent) {
console.log(
`共 ${torrent.files.length} 个文件:${torrent.files.map((f) => f.name).join(", ")}`,
);
},
onProgress(downloaded, total) {
process.stdout.write(`\r${downloaded}/${total} 字节`);
},
signal: ac.signal, // AbortSignal,用于取消下载
});
for (const f of result.files) {
console.log(f.name, f.data.byteLength); // 文件名 & 解密后的字节数
}DownloadOptions
| 字段 | 类型 | 说明 |
| ------------ | --------------------------------------------- | ---------------------------------------------- |
| onInfo | (torrent: ParsedTorrent) => void | 元数据解密完成后、开始下载前调用一次 |
| onProgress | (downloaded: number, total: number) => void | 周期性回调,参数为已下载的累计字节数和总字节数 |
| signal | AbortSignal? | 取消整个下载操作 |
DownloadResult
| 字段 | 类型 | 说明 |
| ------- | -------------------------------------- | -------------------------------------- |
| name | string | Torrent 名称(多文件时为文件夹名称) |
| files | { name: string; data: Uint8Array }[] | 已解密的文件列表,包含文件名和原始数据 |
parseWormholeUrl(url)
import { parseWormholeUrl } from "@ziuchen/wormhole-client";
const { roomId, masterKey } = parseWormholeUrl("https://wormhole.app/abc123#key");
// masterKey 为 Uint8Array按需导入子路径(Tree-shaking)
如果只需要上传或下载功能,可从对应子路径导入,避免打包未使用的代码:
// 仅上传 —— 不会引入下载代码
import { createUploadClient } from "@ziuchen/wormhole-client/upload";
const client = createUploadClient({ timeout: 30_000 });
await client.upload(files, opts);
// 仅下载 —— 不会引入上传代码
import { createDownloadClient, parseWormholeUrl } from "@ziuchen/wormhole-client/download";
const client = createDownloadClient({ timeout: 30_000 });
await client.download(url, opts);Node.js 文件系统辅助(@ziuchen/wormhole-client/node)
从磁盘读取文件或将下载内容写入目录时,使用 Node.js 专属子路径:
import { createUploadClient } from "@ziuchen/wormhole-client/upload";
import { uploadFromPaths } from "@ziuchen/wormhole-client/node";
const client = createUploadClient();
const result = await uploadFromPaths(
client,
[{ path: "/tmp/report.pdf" }, { path: "/tmp/data.csv", name: "export.csv" }],
{
dirName: "quarterly-report",
lifetime: 7200,
onProgress(uploaded, total) {
/* ... */
},
},
);import { createDownloadClient } from "@ziuchen/wormhole-client/download";
import { downloadToDir } from "@ziuchen/wormhole-client/node";
const client = createDownloadClient();
const result = await downloadToDir(client, "https://wormhole.app/abc123#key", "/tmp/out", {
onProgress(downloaded, total) {
/* ... */
},
});
console.log(result.outputDir); // "/tmp/out"
for (const f of result.files) {
console.log(f.name, f.size); // 已保存的文件名和明文大小(字节)
}工作原理
文件在本地使用 AES-128-GCM(RFC 8188 流式格式)加密,再通过 wormhole.app 上传至 Backblaze B2。加密密钥不会离开客户端——它仅存在于 URL fragment 中,不会发送给服务器。接收方的浏览器(或本 CLI)下载密文后在本地解密。
License
MIT
