Skip to content

feat: 房间上限 50 人(写死),并让流量随人数合理增长 - #8

Merged
Freecode100Year merged 13 commits into
mainfrom
feat/fifty-members
Oct 30, 2026
Merged

Freecode100Year merged 13 commits into
mainfrom
feat/fifty-members

Conversation

@Freecode100Year

Copy link
Copy Markdown
Owner

概要

一个房间最多 50 人,统一写死在代码里(src/limits.js),所有中继和页面执行同一个数,没有任何配置能改。

⚠️ 本分支建立在 feat/single-file(#7)之上,包含此前全部提交。把 feat/fifty-members 作为新 main,即可一次得到全部改动。

为什么要优化

用 50 个真实客户端(完整签名、混合加密、ML-KEM)同时进房实测,原有逻辑在约 5 秒内产生约 600 MB 流量,所有人都被中继限流断开。25 人时同样存在这个问题,只是原有测试只有两三个人,没测出来。根因:

  • 每对成员互发钥匙的帧(含约 1.5 KB 的 ML-KEM 密文)被广播给全房间;
  • 每次有人进房,所有人都重发完整 hello;
  • 每次定时 hello 都会让所有接收者重发整条钥匙链。

改动

  1. 人数统一为 50:MAX_MEMBERS = 50 是唯一来源。经典中继(Node、Worker、文件内置服务器)入房、/bc/ 每个 topic 的连接上限(50+14,留给重连)、页面校验、去中心化编号认领都从这里取。
  2. 定向投递:只含钥匙包的 key 帧带 to,中继只转给这些成员;hello 仍发给所有人。旧中继会忽略 to、照常广播,仍然兼容。
  3. 只向缺钥匙的人补问:进房时广播一次 hello;之后只向链还没到的成员定向补问。钥匙齐全后不再定时发送。
  4. 合并发送:欠下的钥匙包合并 300 ms 后一次发出。去中心化房间里,编号先保持 1.5 s 无冲突才生成密钥;人多时从最小的几个空号里随机认领。
  5. 修复 lib/group.js 的一个协议 bug:第一次得知某成员的 ML-KEM 公钥时,不再清空已建立的配对密钥。定向投递下,旧逻辑会让部分成员对永远等待。
  6. 中继限流:每连接 10 秒 100 帧(原 60)、钥匙帧 60(原 40)、每地址每分钟入房 120(原 60)、/bc/ 10 秒 100 帧。

实测(test/fifty.test.mjs,所有客户端在同一进程,结果偏保守)

  • 经典中继,50 人同时进房:约 7 秒全部就绪,平均每人约 265 KB;50 人安全码一致,第 51 人被拒,消息不重复,有人离开后照常聊天。
  • 去中心化 /bc/(只能广播):
    • 20 人:约 12 秒就绪,每人约 450 KB(CI 默认跑这一档);
    • 30 人:约 18 秒,每人约 2 MB;
    • 50 人:可以完成,但成本按 n³ 增长,建议去中心化房间控制在约 30 人。HR_MESH_N=50 可以跑满员版本。

测试

单元测试 78 项、Worker 探针 10 项、9 个浏览器测试和 Pages 测试全部通过;Go 中继通过 go vet。


Generated by Claude Code

claude added 13 commits October 29, 2026 15:43
Room files gain a 更换中继 panel that re-emits the same room (master or
wrapped key untouched, no password needed) with a new relay list and exact
CSP connect-src. The hosted maker converts existing files, and the CLI gains
a retarget command. All three paths produce byte-identical output that
passes verify-room-file.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PCUwNSXQdqfDSugmFo4qmM
1. Relay-agnostic transport (version 3 rooms)
   - Relay entries bc+wss://, nostr+wss://, mqtt+wss:// select version 3;
     classic wss:// rooms stay version 2, unchanged. Retarget converts
     between them with the same key, password and roomId.
   - html-room/mesh.js: members admit (AES-GCM frames under a master-derived
     key), number (announce, claim, earliest claim wins), list and attribute
     each other; relays only forward. Every frame goes to every relay and is
     de-duplicated by IV, so one surviving relay keeps the room together.
   - Node server and Cloudflare Worker serve /bc/<topic> plain broadcast.

2. Easier relays
   - relay/go: dependency-free Go broadcast relay, one ~6 MB binary for
     Windows, macOS, Linux, BSD, Android and OpenWrt routers; -tunnel prints
     a public bc+wss:// address via a Cloudflare quick tunnel (no domain,
     open port or root).
   - scripts/relay-quickstart.sh downloads it (SHA-256 enforced) and
     cloudflared; Relay binaries workflow publishes 13 platform builds.

3. External protocols
   - Nostr: ephemeral kind 20111 events tagged with the topic, signed with
     a per-session BIP-340 key (@noble/curves).
   - MQTT 3.1.1 over WebSocket, QoS 0, topic htmlroom/<topic>.

Tests: unit (parsing, sealing, packets, membership clashes), real relays
(Node, Go, signature-checking Nostr relay, aedes MQTT, Worker /bc/ under
wrangler dev), browser end-to-end over four relays with relay loss.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PCUwNSXQdqfDSugmFo4qmM
Rewrites every user-facing string in the room creator and the generated
index.html in everyday words, moves relay settings into a collapsed
section, highlights the main buttons, and links the repository
(https://github.com/Freecode100Year/htmlroom.git) from both pages.
Tests updated for the new wording.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PCUwNSXQdqfDSugmFo4qmM
Files carry an optional custom: true (on by default in the creation page
and CLI room; untick or --strict for exact relays only). Such files allow
connect-src wss: plus loopback ws, and show a relay box on the entry
screen; typed relays apply to that visit only and pick classic or
decentralized mode, with the same key, password and roomId. Web links
never show the box. Converter and CLI retarget keep or change the setting.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PCUwNSXQdqfDSugmFo4qmM
- Room files fold a creator: a new room with a new key and optional new
  password, built offline from the page's own script and style bytes
  (htmlFor), byte-identical to a release build.
- A blank file ({} or {relays}) is the creator: the offline generator and
  the hosted site. It also opens #hr1= links (CSP wss: + loopback ws) and
  converts old files; hosted, it offers itself as an offline download.
- The separate web entry (web.js, hr.js, hr.css) is gone: hr.html and the
  Pages index.html are the creator file; _headers is generated with the
  same script and style hashes.

Tests: offline chain blank -> A -> B (new password) -> C with no network
requests and release-identical output, chat in B, site self-download;
existing suites updated for links on the site reaching any relay.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PCUwNSXQdqfDSugmFo4qmM
Every file carries html-room/serve.js in an HTML comment before the
doctype; to Node the page is a block comment, so `node index.html` runs a
zero-dependency relay (/hr/ with the shared rules, /bc/, /healthz,
--tunnel, --tls-cert). A blank creator also serves itself, making one file
a complete site; a room file never serves itself (its key). New files
created in the browser carry the server part from document.firstChild.

Tests: the shared /hr/ and /bc/ relay probes run against node index.html;
browser test serves the site from the file, chats through it, and runs the
downloaded room file as a relay.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PCUwNSXQdqfDSugmFo4qmM
- src/limits.js: MAX_MEMBERS = 50, the single source for every relay (Node,
  Worker, the file's own server, /bc/) and every page; not configurable.
- Key wraps go only to their members: key frames carry `to`, relays hand
  them to those members (hellos still go to all); old relays ignore it.
- A page asks again (hello) only the members whose chains are missing,
  never the whole room on a timer: every hello makes all receivers re-send.
- Owed keys are batched for 300 ms; decentralized numbers settle 1.5 s
  before keys are made, and claims spread over the lowest free numbers.
- lib/group.js: learning a member's ML-KEM key for the first time no longer
  resets a pair secret already received (stalled pairs under targeting).
- Relay limits sized for a newcomer's key burst in a full room.

Measured with 50 real clients in one process: classic relay, all ready in
~7 s, ~265 KB each (was: relay disconnects at ~600 MB); decentralized /bc/
20 members ~12 s, ~450 KB each.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PCUwNSXQdqfDSugmFo4qmM
The single-file site no longer ships hr.js/hr.css; the installer's source
check and its CI test now use dist/hr.html.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PCUwNSXQdqfDSugmFo4qmM
…cleanup

The Windows unit job has stayed in progress since the decentralized relay
commit. Background intervals (mesh sweep, relay heartbeat) kept test
processes alive when a timing assertion failed before cleanup was wired.
Timers are now unref'd (no-op in browsers), test cleanup tolerates early
failures, awaited closes time out, and npm test sets --test-timeout.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PCUwNSXQdqfDSugmFo4qmM
URL.pathname gives /D:/... on Windows, so the relay fixtures never started
(and the Go build ran in a missing directory). Use fileURLToPath/join and
os.tmpdir(); skip the POSIX fake-cloudflared test on Windows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PCUwNSXQdqfDSugmFo4qmM

This branch was previously deployed

1 inactive deployment
github-pages — b934a039 Deployed Oct 30, 2026 by Freecode100Year via deploy #9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants