Romzo SDK

TypeScript client for the romzo/v1 protocol. Depends only on WebCrypto and WebSocket.

romzo/v1 协议的 TypeScript 客户端,只依赖 WebCrypto 与 WebSocket。

npm
npm i romzo

Build from source

从源码构建

The SDK lives in packages/romzo-client. From the repository root:

SDK 位于 packages/romzo-client。在仓库根目录执行:

The build is ESM with strict TypeScript types.

构建产物为 ESM,带严格模式 TypeScript 类型。

SH
npm ci
npm run build -w romzo

Concepts

概念

  • Room — a 10-character room code. The host signs the room descriptor with the host key pair.
  • 房间——10 位房间码,房主持 host 密钥对 descriptor 签名。
  • Tickets — admission tickets authenticate the WebSocket (60 seconds, single handshake). Recovery tickets restore a room after a client crash or relay restart.
  • 票据——admission 票据用于 WebSocket 认证 (60 秒、单次握手);recovery 票据用于客户端崩溃或中继重启后恢复房间。
  • Channels — declared per room in options.channels (name plus per-channel frame cap, at most 8); there are no predefined channels. Omission means no channels. Subscribers receive join / leave / receive frames.
  • 通道——按房间在 options.channels 中声明(名字 + 逐频道帧上限,最多 8 个); 中继不预置频道,未声明时没有频道。订阅后收到 join / leave / receive 帧。

Host

房主

Create the room, confirm it on the relay, then open a WebSocket session and subscribe to channels.

创建房间、在中继上确认,然后建立 WebSocket 会话并订阅频道。

The public relay enforces Turnstile on room creation: pass { turnstileToken } as the third argument of createRoom. Relays with CREATE_AUTH=password take { password } instead.

公共中继在建房时强制 Turnstile:createRoom 第三个参数传 { turnstileToken };开了 CREATE_AUTH=password 的中继则传 { password }。

TS
import { createRoom, RelaySession } from 'romzo';

const roomOptions = {
  app: 'my-game',
  channels: [
    { name: 'table', maxFrameBytes: 262144 },
    { name: 'chat', maxFrameBytes: 16384 },
  ],
};
const room = await createRoom('romzo.net', roomOptions);

const url = room.descriptor.origin.replace(/^http/, 'ws') + '/ws';
const session = await RelaySession.connect(
  url, room.admission, room.transport
);

session.on('join', (_p, { channel, peer, key }) => {
  // peer key for E2E crypto
});
session.on('receive', (value, { channel, peer }) => {
  // a peer's payload
});
session.on('close', () => {
  // reconnect with the recovery ticket
});

await session.subscribe('table');
TS
import { createRoom, RelaySession } from 'romzo';

const roomOptions = {
  app: 'my-game',
  channels: [
    { name: 'table', maxFrameBytes: 262144 },
    { name: 'chat', maxFrameBytes: 16384 },
  ],
};
const room = await createRoom('romzo.net', roomOptions);

const url = room.descriptor.origin.replace(/^http/, 'ws') + '/ws';
const session = await RelaySession.connect(
  url, room.admission, room.transport
);

session.on('join', (_p, { channel, peer, key }) => {
  // peer 公钥 key 可用于端侧加密
});
session.on('receive', (value, { channel, peer }) => {
  // 他人 send 的负载
});
session.on('close', () => {
  // 断开后用 recovery 票据重连
});

await session.subscribe('table');

Guest

访客

joinRoom verifies the descriptor, config hash and host signature before requesting admission.

joinRoom 在申请 admission 前核对 descriptor、 configHash 与宿主签名。

TS
import { joinRoom, RelaySession } from 'romzo';

const joined = await joinRoom('romzo.net', 'ABCD2345EF');
const url = joined.signedDescriptor.descriptor.origin
  .replace(/^http/, 'ws') + '/ws';
const session = await RelaySession.connect(
  url, joined.admission, joined.transport
);
await session.subscribe('table');

Crash recovery

崩溃恢复

Keepalive responses update session.currentRecovery() automatically. Persist three values — signedDescriptor, transport, recovery — to restore the room after a restart.

keepalive 应答自动更新 session.currentRecovery()。 持久化三个值——signedDescriptor、transport、 recovery——即可在中继重启后恢复房间。

TS
import { restoreSession, RelaySession } from 'romzo';

const restored = await restoreSession(
  saved.signedDescriptor, saved.recovery, saved.transport);
const session = await RelaySession.connect(wsUrl,
  restored.admission, restored.transport);
// resubscribe and sync history in the envelope layer

Modules

模块

modulecontents
模块内容
canonical canonical(), hash(domain, v), mac(key, domain, v), hex utilities — byte-compatible with the Go implementation canonical()、hash(domain, v)、 mac(key, domain, v)、hex 工具,与 Go 实现逐字节一致
crypto Ed25519 key generation / import / export, sign / verify, PBKDF2 password derivation (600000 iterations), passwordProof Ed25519 密钥生成 / 导入导出、sign / verify、PBKDF2 口令派生(600000 迭代)、passwordProof
http discover, createChallenge, allocate, confirmRoom, requestAdmission, fetchDescriptor, restoreRoom — injected fetch, errors throw RelayHttpError(code, status) discover、createChallenge、 allocate、confirmRoom、requestAdmission、 fetchDescriptor、restoreRoom,支持注入 fetch,错误抛 RelayHttpError(code, status)
session RelaySession.connect(url, admission, identity, opts) — four-frame handshake, subscribe / unsubscribe / send / keepalive, events, automatic keepalive, currentRecovery(), injectable WebSocket factory RelaySession.connect(url, admission, identity, opts)—— 四帧握手、subscribe / unsubscribe / send / keepalive、事件订阅、自动保活、 currentRecovery(),支持注入 WebSocket 工厂
facade 门面 createRoom() / joinRoom() / restoreSession() wrap the full HTTP sequence createRoom() / joinRoom() / restoreSession() 封装完整 HTTP 序列

The SDK leaves these to you

以下部分由调用方负责

  • Reconnection. After a drop, reconnect with the recovery ticket, resubscribe and sync history.
  • 重连。断线后凭 recovery 票据重连、重新订阅并同步历史。
  • Key storage. Generated keys are extractable for export and recovery. Pick a protected storage and a clearing policy.
  • 密钥存储。SDK 生成的密钥均为 extractable,存储位置与清除时机由应用决定。
  • Rules. The SDK does not interpret options. Validate them with the validateOptions callback.
  • 规则。SDK 不解读 options 内容,由应用通过 validateOptions 回调校验。