croc 网页客户端深度指南:croc-web 统一服务器、WASM 安全协议与 WebSocket 中继桥接
croc 的网页客户端(web client)是一个 React/Vite 应用,可与普通 croc CLI 对端完成文件收发和短文本消息直传;其生产构建与 WebAssembly 运行时被嵌入独立的 croc-web 二进制,由这一个 HTTP 地址同时承载网站、运行时配置、健康检查和 WebSocket-to-TCP 中继桥。本文基于 web/README.md 与对应源码展开,帮你掌握 croc-web 的本地开发、自托管中继池配置、生产拓扑与存储模式(stored mode)的完整运维细节,以及安全敏感协议操作如何被编译进 WASM 供浏览器执行。
一、croc web 客户端定位:浏览器与 CLI 对端之间保持加密
croc-web 的客户端能力包括:
- 与普通
crocCLI 对端发送和接收文件,以及短的直接文本消息; - 文件元数据与内容在浏览器和另一 croc 客户端之间保持加密;
- 生产构建与 WebAssembly 运行时嵌入
croc-web二进制,部署后的二进制不需要任何外部静态文件。
启动时如果带上 --store-dir,UI 会额外提供一个显式的 stored 模式,由发送方选择一个有限的生存期。该模式在浏览器内加密文件名、元数据和 4 MiB 分块,只上传密文,并同时产出一个浏览器链接和一个 CLI 令牌;传输会在“已验证下载次数额度”或“所选生存期”中先到者触发时被删除。常规直传(direct)模式仍是默认模式。
两种发送模式都可以为浏览器接收地址显示 QR 码:直传模式的二维码打开接收页并自动填好 croc 码开始连接;stored 模式的二维码包含完整的加密分享链接。直传模式还提供与 croc send --text 兼容的次级文本编辑器:文本在显示前被审查、在内存中校验,从不落盘到临时存储,且文本载荷限制为 1 MiB 的 UTF-8 内容。
进度方面,活跃的发送与接收会显示总量与单文件进度、实测字节/秒速率,以及用 arrival-time 库计算的 ETA(见 web/package.json 中的 arrival-time 依赖)。
二、安全敏感操作编译进 WASM:同一份 Go 协议代码跑在浏览器里
这是 croc web 最核心的设计:安全敏感的协议操作不是用 JavaScript 重写,而是直接由本仓库的 Go 包编译为 WebAssembly,从而与原生客户端共享同一份密码学实现。文档列出的 WASM 能力包括:
- croc PAKE,用于中继(relay)握手与对端(peer)握手;
- 与身份和会话绑定的 HKDF,加对端通道的相互确认(mutual confirmation);
- 用于兼容旧版本的中继单跳(legacy-compatible relay hop)的 PBKDF2,以及 AES-GCM 加密;
- 原始 DEFLATE 压缩;
- xxhash 校验;
- EFF 三词码生成与兼容性解析;
- 与原生客户端共享的 SHA-256 码到中继路由。
从源码看,WASM 入口在 web/wasm/main.go,它通过 syscall/js 把一组函数挂到全局 crocWasm 对象上,与上述文档条目一一对应:
pakeInit/pakeInitWithIdentities/pakeUpdate:分别基于pake库和仓库内 src/pakekey/pakekey.go 的带身份 PAKE;pakeUpdate完成握手并返回会话密钥;derivePeerKeys/confirmPeerKey:通过pakekey.Derive用目的、房间、曲线、发起方/响应方身份与盐派生加密密钥和两个确认标签,再校验对端确认,实现通道密钥与身份、会话的绑定;deriveKey:调用 src/crypt/crypt.go 的crypt.New(PBKDF2)派生中继跳密钥;encrypt/decrypt:AES-GCM 加解密;cipherInit/cipherRelease/encodeChunk/decodeChunk维护 AEAD 句柄并按分块加密(可选先压缩);compress/decompress:调用 src/compress/compress.go,解压时要求显式字节上限;hashInit/hashUpdate/hashFinal:流式 xxhash;codeComponents/relayIndex:基于 src/codephrase/codephrase.go 解析 croc 码并计算码对应的中继索引(SHA-256 路由),与原生客户端共享逻辑;sha256Init等:SHA-256 摘要;storeGenerateKey/storeRedeemCapability/storeSealManifest/storeOpenManifest/storeSealChunk/storeOpenChunk:stored 模式在浏览器内完成密钥生成、能力赎回、清单与 4 MiB 分块的密封/开启,底层是 src/storecrypto/storecrypto.go。
每个暴露函数都走 safeCall 包装(recover panic 后返回 {ok:false,error}),字节数据通过 js.CopyBytesToGo / js.CopyBytesToJS 以 Uint8Array 传递。构建脚本为 web/scripts/build-wasm.mjs(npm run wasm),这解释了“浏览器端只上传密文”的可信边界:加密路径不经过 JavaScript 生态的第三方依赖。
三、本地开发:一条命令跑起完整客户端 + croc-web
在 web 目录下:
npm install
npm run dev:stack
该流程会先构建并嵌入完整客户端,然后运行:
croc-web localhost:5173
这个本地快捷方式会直接把服务绑定到 localhost:5173,网站和 WebSocket 中继都在同一地址可用。这一行为由 src/webcli/webcli.go 中 resolveServeAddress 实现:当显式给出带端口的回环地址(如 localhost:5173)且未显式提供 --bind 时,bind 直接沿用该公网地址。
需要前端热重载时运行:
npm run dev:hot
dev:hot 会先执行 embed,再用 concurrently 并行跑 Vite dev server 与 croc-web --bind 127.0.0.1:9014 localhost:5173(见 web/package.json 的 scripts 定义)。
常用检查命令:
npm test # vitest 单元测试
npm run test:e2e # Playwright 端到端(先 embed)
npm run typecheck # tsc -b
npm run build
npm run embed
make build-web
go test ./...
npm run embed 构建 WASM 与 Vite 客户端,并把产物复制到被 git 忽略的 src/webassets/dist 目录,Go 的 embed 包只在 croc-web 中打包它;生成文件不提交仓库。部署后的 croc-web 二进制既不需要该目录,也不需要任何外部静态文件。从源码看:
- src/webassets/assets.go 使用
//go:embed all:dist,Files()返回dist子树; - web/scripts/embed-dist.mjs 把
web/dist拷入src/webassets/dist,同时把 CLI 安装脚本 src/install/default.txt 拷为default.txt——这正是/路径下 curl/wget 收到的安装脚本。
Playwright 套件(npm run test:e2e)会构建真实的 croc 与 croc-web 二进制,在临时端口上启动隔离的本地 croc 中继与统一嵌入式服务器,然后逐字节验证 CLI → Web、Web → CLI、Web → Web、CLI stored → Web、Web stored → CLI 五类传输(见 web/e2e/transfers.spec.ts)。浏览器安装一次即可:npx playwright install chromium。测试进程使用隔离的 CROC_CONFIG_DIR 与存储目录,不读取也不改动你已记住的 croc 配置。
四、自托管中继池:croc-web 作为受约束的 WebSocket 到 TCP 桥
服务器端固定一个有序的上游主机列表并放行所有 TCP 端口,使 /ws 不可能被当成任意网络代理使用。单主机自托管池示例:
croc-web --pass YOUR_RELAY_PASSWORD \
--bind 127.0.0.1:9014 \
--relays relay.example.com \
--ports 9009,9010,9011,9012,9013,9014,9015,9016,9017 \
files.example.com
files.example.com 是作为位置参数传入的公网网站地址(UsageText 为 croc-web [OPTIONS] [public-host[:port]])。服务器通过 /config.js 把权威有序中继地址与密码作为浏览器默认值注入:
window.__CROC_RUNTIME_CONFIG__ = {"gatewayURL":"/ws","relayAddresses":[...],"relayPassword":"...","store":{...}};
对应实现在 src/webrelay/webrelay.go 的 config handler(带 Cache-Control: no-store 与 X-Content-Type-Options: nosniff)。公共客户端使用 1.getcroc.com,2.getcroc.com,3.getcroc.com,4.getcroc.com(即 src/publicrelay/publicrelay.go 中固定的四台 9009 端口池,也是 --relays 的默认值);运维可以用 --relays 提供另一个逗号分隔的顺序。
关于“最佳中继”记忆:直传生成的发送会把胜出中继地址存进功能性 cookie croc-best-relay,有效期 30 天;之后的发送直接复用该精确地址,不做探测也不延长 cookie 有效期;无效池条目会被自动替换;中继连接失败会清除 cookie,让下一次发送重新竞速整个配置的池;手动清除站点 cookie 等价于强制同样的刷新。
/ws 端点的约束在源码中非常明确:src/webrelay/webrelay.go 的 websocket handler 只接受 relay=<零基索引>&port=<放行端口> 两个参数,索引必须落在配置的主机列表内、端口必须落在放行集合内,否则返回 403;连接建立后只是用 64 KiB 缓冲做双向 io.CopyBuffer 的透明字节流转发,不参与 croc 协议本身。
croc-web 的完整命令行参数(默认值取自 src/webcli/webcli.go):
| 参数 | 默认值 | 说明 |
|---|---|---|
--bind |
127.0.0.1:9014 |
本地 HTTP 绑定地址 |
--relays / --relay |
1.getcroc.com:9009,...(公共池) |
有序逗号分隔的上游 croc 中继主机 |
--ports |
9009,...,9017 |
放行的上游中继端口 |
--pass |
models.DEFAULT_PASSPHRASE |
中继密码,支持环境变量 CROC_PASS;若值是可读文件路径则读取文件内容 |
--debug |
false | 开启 debug 日志 |
--store-dir |
空(不启用) | 在此目录启用加密临时存储 |
--store-max-transfer |
1GiB |
单个 stored 传输的最大明文字节 |
--store-quota |
5GiB |
所有受管 stored 传输的总字节上限 |
--store-min-free |
512MiB |
磁盘需保留的空闲空间 |
--store-max-files |
100 |
单个 stored 传输的最大文件数 |
--store-downloads |
1 |
每个 stored 传输的最大已验证下载次数(CROC_STORE_DOWNLOADS) |
--store-max-expiration |
0(不限) |
最长 stored 生存期,单位 m/h/d/w(CROC_STORE_MAX_EXPIRATION) |
--store-create-rate |
5 |
每客户端 IP 每小时最多创建的 stored 传输数 |
--store-active-uploads |
2 |
每客户端 IP 的并发上传数 |
--store-trusted-proxy |
空 | 可重复;信任的反向代理 CIDR,用于客户端 IP 转发 |
注意 --pass 的 determinePass 逻辑:如果该值恰好是一个可读文件的路径,会读取文件内容作为实际密码,适合把密码放在磁盘文件里而非命令行。
五、统一服务器暴露的端点
从 Handler 的路由注册(src/webrelay/webrelay.go)看,croc-web 一个进程提供:
GET /与内嵌静态客户端资源;User-Agent 为curl/或wget/时返回安装脚本(default.txt),浏览器则收到 Web 客户端(响应带Vary: User-Agent);GET /config.js:运行时配置注入,见上一节;GET /healthz:返回ok;GET /ws?relay=<零基索引>&port=<放行端口>:升级为二进制 WebSocket,两个值都必须命中配置的白名单,单条 WebSocket 消息上限 65 MiB;- 当设置
--store-dir时,额外注册/api/v1/store/transfers及其子路径,由store.Service处理。
静态文件缓存策略也值得注意:assets/ 前缀资源是 public, max-age=31536000, immutable,HTML 页面 no-cache,Service Worker 文件 no-cache。
六、生产拓扑:HTTPS 反向代理 + 存储模式
生产环境把 croc-web 放在 HTTPS 反向代理之后:
croc-web --bind 127.0.0.1:9014 getcroc.com
完整回源(包括 WebSocket 升级)都指向 127.0.0.1:9014,并保留原始 Host 头。服务器在 / 返回站点、在 /ws 返回 WebSocket 桥,因此无需拆分路由或外部静态文件部署;TLS 证书留在反向代理。
可选的 Umami 分析只有在两个运行时变量都设置时才启用:
UMAMI_URL=https://umami.schollz.com \
UMAMI_WEBSITE_ID=website-uuid \
croc-web --bind 127.0.0.1:9014 getcroc.com
成功的浏览器传输会发出 send-direct、send-with-storage、receive 自定义事件。未配置 Umami 时事件追踪关闭、传输行为完全一致;已配置时,服务器把 Umami 的 defer 脚本直接注入每个页面的 <head>(见 injectUmamiScript,要求 URL 必须为 HTTPS)。Umami 记录常规页面浏览,自定义传输事件上报的 URL 会去掉查询串与 fragment。服务器在成功向 curl GET / 交付安装脚本后还会发出 installer-curl 事件(wget 下载与 HEAD 请求不计入),该服务端事件只上报站点主机名、/ 路径、事件名和 croc 版本。
临时存储在未显式配置目录前保持关闭:
croc-web \
--bind 127.0.0.1:9014 \
--store-dir /var/lib/croc/store \
--store-max-transfer 1GiB \
--store-quota 5GiB \
--store-min-free 512MiB \
--store-max-expiration 2w \
getcroc.com
运维要求:存储目录被锁给单个服务器进程,应是持久、私有、仅 croc 服务账号可写的目录,并排除出备份。若可信反向代理提供 X-Forwarded-For,用可重复的 --store-trusted-proxy CIDR 标识其网络;不受信任的转发头会被忽略。全部限制与运维细节见 src/docs/STORED_TRANSFERS.md。
--bind 默认为 127.0.0.1:9014;当显式使用带端口的回环网站地址(如 localhost:5173)时,本地快捷方式会绑定到该地址,除非显式提供 --bind。
七、当前边界与限制
文档明确列出的当前能力边界(适用于本仓库版本):
- 一次一个对端、一次一个传输;
- 文本消息仅限直传模式;stored 模式只接受常规文件;
- 可以一次发送多个选中的文件;发送文件夹与 ZIP 打包未实现;
- CLI 发送的嵌套文件夹与空文件夹,在浏览器支持目录访问时可以接收;逐文件下载的兜底方案会把文件名压平并在冲突时追加数字后缀;
- 本地组播(multicast)、浏览器到浏览器直传、非 xxhash 的实时传输未实现;
- stored 上传仅接受常规文件。CLI 端 stored 下载可续传已完成分块;浏览器 stored 下载在受支持时以流式方式工作,标签页关闭后不续传;
- 目标是当前主流常青(evergreen)桌面浏览器。
参考入口
- 文档主体:web/README.md
- 服务器与 WS 桥:src/webrelay/webrelay.go
- 命令行与默认参数:src/webcli/webcli.go
- WASM 桥接:web/wasm/main.go
- 嵌入与拷贝:src/webassets/assets.go、web/scripts/embed-dist.mjs
- 公共中继池与竞速选择:src/publicrelay/publicrelay.go
- stored 模式运维文档:src/docs/STORED_TRANSFERS.md
- 端到端传输验证:web/e2e/transfers.spec.ts
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00