npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-client

CLI 用法

上传

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