The Lounge 4.5.2 REST API 与 socket.io 协议层详解
The Lounge 4.5.2 通信层详解:REST API 现状(官方未定稿)与 socket.io v4 完整事件协议(auth:start→auth:perform→init→network:new→input→msg 全事件流实测),端到端脚本 sock-test.js 验证,含安全建议与同类协议对比(2026-10-27 实测)。
The Lounge 4.5.2 REST API 与 socket.io 协议层详解
目标:梳理 The Lounge 与 Web 客户端之间的通信层——REST API 现状(官方明确标注「尚未定稿」)与 socket.io 实时协议(客户端 UI 的全部 IRC 事件都经此)。本文所有 socket.io 事件均来自端到端实测脚本
sock-test.js(auth:perform 登录 → network:new 连接 → 入频发言全流程),基线版本 4.5.2(2026-07-11 发布)。
一、环境信息
- 实测日期:2026-10-27
- The Lounge 版本:
4.5.2(npm 包thelounge@4.5.2,依赖socket.io@4.6.2) - socket.io 版本:服务端
4.6.2(dependencies.socket.io),客户端包socket.io-client@4.5.0(devDependencies) - 传输层:默认
["polling", "websocket"],生产环境建议只开 websocket - 官方 REST API 文档:
https://thelounge.chat/docs/api,首页声明「The Lounge API is not yet finalized and may change at any time.」
二、架构总览
浏览器(Vue 3 + socket.io-client)
│
HTTP polling / WebSocket(socket.io v4 协议)
│
The Lounge Express 服务器(同进程里跑 socket.io server)
│
irc-framework(kiwiirc/irc-framework,github commit 9578e59)
│
多个 IRC 网络(同时连 Libera.Chat / Freenode / 自建 ngIRCd …)
The Lounge 的 Web 服务是一个 Express 应用(见 npm 包 express@4.20.0),在同一个端口(默认 9000)上:
- REST 路由:Express 处理,主要是静态资源(
/、/fonts/*、/img/*)和配置 API; - socket.io namespace:默认
/(根命名空间),所有 IRC 实时事件走这个; - 没有独立的 REST API 网关——插件/自定义集成都往 socket.io 方向靠。
💡 技术拓展:socket.io 不是 HTTP 协议,是 WebSocket + 长轮询的上层封装。它在连接建立时先 HTTP 握手,协商传输方式,之后用二进制 frame 或 text frame 推送事件。The Lounge 用 socket.io 而非原生 WebSocket 的好处是:polling 回退(极端网络仍能用)、事件命名空间、自带心跳、自动重连——写 sock-test.js 时 reconnection 默认开着,断线自动试;脚本显式关掉
reconnection: false是因为实验场景要可控退出。
三、REST API 现状(仅供参考)
官方 API 文档列出的是 服务端插件开发 用的内部 API,不是 HTTP 对外接口:
| 模块 | 作用 | 适用对象 |
|---|---|---|
| Commands API | 注册 IRC 斜杠命令(/me、/away 之类) | 插件作者 |
| Config API | 读写 config.js 运行时配置 | 插件作者 |
| Logger API | 打到 The Lounge 统一日志器 | 插件作者 |
| Public Client API | 与客户端实例交互 | 插件作者 |
| Stylesheets API | 注入自定义 CSS | 主题作者 |
也就是说——没有稳定的公开 REST API。所有运维级操作(用户创建、密码重置、IRC 连接、频道发言)要么走 The Lounge 内置 CLI(thelounge add/reset/storage),要么走 socket.io 事件(见下)。
⚠️ 任何想直接调用 HTTP 做自动化的设想,先验证官方是否在你要的版本里暴露了 /api/* 路由——4.5.2 里没有。
四、socket.io 连接基础
4.1 握手与传输协商
客户端(浏览器或脚本)连 The Lounge 9000 端口,socket.io 先做 HTTP 握手:
GET /socket.io/?EIO=4&transport=polling&t=xxx HTTP/1.1
Upgrade: websocket ← 协商 websocket
Sec-WebSocket-Key: ...
服务器返回 101 Switching Protocols,升级到 WebSocket;不支持时回退 polling(长轮询,每 N 秒发一次 HTTP GET 取消息)。
sock-test.js 里显式要求 websocket:
const socket = io(BASE, { transports: ["websocket"], reconnection: false });
4.2 auth:start — 连接后第一个事件
只要 WebSocket 建起来,服务器立刻推 auth:start(server→client,无 payload),客户端必须在收到这个事件后发 auth:perform。sock-test.js 流程:
server → client: auth:start (无参数,示意客户端开始认证)
client → server: auth:perform {user, password} (发凭据)
server → client: auth:success / auth:failed (二选一)
private 模式必须认证;public 模式(public: true)跳过认证——服务器直接推 init。
4.3 auth:perform payload(client→server)
| 字段 | 类型 | 说明 |
|---|---|---|
user | string | The Lounge 用户名 |
password | string | 明文密码(传输层 HTTPS 时安全;否则建议前面套 TLS 反代) |
token | string? | LDAP 双因素或未来扩展用;4.5.2 基本不用 |
sock-test.js 实测:
socket.emit("auth:perform", { user: "root", password: "q1w2e3r4" });
4.4 auth:success / auth:failed
| 事件 | 方向 | payload |
|---|---|---|
auth:success | server→client | 通常带用户对象 + 客户端设置;脚本里没 dump,因为 init 紧跟 |
auth:failed | server→client | { reason: string },比如 "Invalid credentials"、"Account locked" |
五、init — 初始化同步(server→client)
认证通过后,服务器把用户当前的全部状态打包一次推过来——这是 The Lounge 重连后瞬间恢复 UI 的关键事件。
sock-test.js 实测 payload 结构(简化):
{
"networks": [
{
"uuid": "9c8a7b6e-...", // 这条 IRC 连接的唯一 ID
"name": "Local ngircd", // The Lounge UI 显示名
"nick": "lounge1", // 当前 nick
"username": "lounge1",
"realname": "The Lounge Test",
"host": "127.0.0.1",
"port": 6667,
"tls": false,
"channels": [
{
"id": "chan-001", // 频道唯一 ID,后续 input/msg 用它寻址
"name": "#lounge",
"topic": "...",
"users": [...],
"messages": [...]
}
]
}
],
"serverSettings": { // config.js 里客户端可见的部分
"public": false,
"prefetch": true,
...
},
"clientSettings": {...} // 这个用户的偏好(主题等)
}
脚本拿它做两件事:
- 打印
networks.length确认连上几条 IRC; - 如果
networks.length === 0说明用户还没配 IRC 网络 → 立刻发network:new加一条。
socket.on("init", (data) => {
console.log("== init: networks=", (data.networks || []).length);
if ((data.networks || []).length === 0 && !global.networkAdded) {
global.networkAdded = true;
socket.emit("network:new", { ... });
}
});
六、网络管理事件
6.1 network:new(client→server)
新增一条 IRC 连接。init 里没网络时用这个引导用户首次连接。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | ✅ | UI 显示名 |
host | string | ✅ | IRC 服务器地址 |
port | number | ✅ | IRC 端口(6667 / 6697) |
tls | boolean | ✅ | 是否 TLS |
rejectUnauthorized | boolean | TLS 时 | 验证证书 |
nick | string | ✅ | 初始 nick |
username | string | ✅ | IRC username |
realName | string | — | IRC realname |
join | string | — | 逗号分隔自动 join 的频道 |
password | string | — | IRC 网络密码 / SASL |
sock-test.js 实测:
socket.emit("network:new", {
name: "Local ngircd",
host: "127.0.0.1",
port: 6667,
tls: false,
rejectUnauthorized: false,
nick: "lounge1",
username: "lounge1",
realName: "The Lounge Test",
join: "#lounge",
});
6.2 network(server→client)
触发时机:network:new 完成、或 IRC 连接状态变化(断开重连、频道增减、nick 被改)。payload 里的 network 对象和 init 里的数组元素同结构——UI 用它增量更新频道树和消息。
脚本监听它来抓新频道:
socket.on("network", (data) => {
const chans = (data.network && data.network.channels) || [];
const lounge = chans.find((c) => c.name === "#lounge");
if (lounge && !joined) {
joined = true;
chanId = lounge.id; // 记住频道 ID
setTimeout(() => {
socket.emit("input", { // 5 秒后往频道发消息
target: lounge.id,
text: "hello from The Lounge 4.5.2",
});
}, 5000);
}
});
6.3 其他网络事件(方向 client→server)
| 事件 | 说明 |
|---|---|
network:edit | 改已有网络配置(host/port/nick 等) |
network:delete | 删一条网络 |
network:reconnect | 重连指定网络 |
七、频道与消息事件
7.1 input(client→server)— 向频道/私聊发消息
唯一的发送通道。所有用户输入(chat、/me、/topic 等)都塞 input,服务器看 text 前缀分发。
| 字段 | 类型 | 说明 |
|---|---|---|
target | string | 频道 id(从 init/network 事件里拿)或用户 ID(私聊) |
text | string | 完整输入文本,含 /command 前缀 |
socket.emit("input", {
target: "chan-001",
text: "/me waves hello",
});
7.2 msg(server→client)— 新消息到达
IRC 频道里任何人说话都会触发,UI 把它 append 到频道滚动区。payload(简化):
{
"target": "chan-001",
"from": "lounge1",
"text": "hello from The Lounge 4.5.2",
"time": "2026-10-27T06:42:11.123Z",
"type": "message",
"self": false // true 表示是自己刚发的回显
}
脚本 dump 它验证消息成功发出:
socket.onAny((ev, ...args) => {
if (["msg", "nick", "join", "part", "auth:success", "auth:failed"].includes(ev))
dump(ev, args);
});
7.3 join / part(server→client)
频道成员进出:
// join: {"target":"chan-001","channel":"#lounge","from":"lounge1","users":[...]}
// part: {"target":"chan-001","channel":"#lounge","from":"lounge1","reason":"leaving"}
7.4 nick / topic(server→client)
| 事件 | payload 关键字段 | 说明 |
|---|---|---|
nick | {from, to, network} | 某用户改了 nick |
topic | {target, channel, topic, from} | 频道 topic 变更 |
八、完整事件方向速查表
综合 sock-test.js 实测 + The Lounge 源码事件定义,核心事件分四类:
8.1 认证与会话
| 事件 | 方向 | 说明 |
|---|---|---|
auth:start | S→C | 连接后第一个事件,示意开始认证 |
auth:perform | C→S | 提交 user/password |
auth:success | S→C | 凭据正确(紧跟 init) |
auth:failed | S→C | 凭据错误,payload 含 reason |
init | S→C | 会话全量同步,含全部网络/频道/消息/设置 |
8.2 网络管理
| 事件 | 方向 | 说明 |
|---|---|---|
network | S→C | 网络状态变更(新增/重连/断开/频道变动/nick 改动) |
network:new | C→S | 新增 IRC 网络连接 |
network:edit | C→S | 修改已有网络 |
network:delete | C→S | 删除网络 |
network:reconnect | C→S | 重连 |
8.3 频道与消息
| 事件 | 方向 | 说明 |
|---|---|---|
input | C→S | 唯一输入通道:聊天、/command 全走这 |
msg | S→C | 新消息到达 |
join | S→C | 频道成员加入 |
part | S→C | 频道成员离开 |
quit | S→C | 网络层断开 |
nick | S→C | nick 变更 |
topic | S→C | topic 变更 |
mode | S→C | 频道/用户模式变更 |
kick / ban | S→C | 频道管控动作 |
8.4 客户端设置
| 事件 | 方向 | 说明 |
|---|---|---|
change:config | C→S | 改自己的客户端偏好(主题等) |
change:user:password | C→S | 改密码 |
open:query | C→S | 打开私聊窗口 |
close:query | C→S | 关闭私聊 |
九、端到端实测事件流
sock-test.js 跑完 20 秒超时退出,捕获事件序列大致如下(时间戳简化):
14:42:05 connect ← socket.io 连上
14:42:05 auth:start ← 服务器示意认证
14:42:05 auth:perform ← 脚本发 {user:"root", password:"q1w2e3r4"}
14:42:05 auth:success ← 认证通过
14:42:05 init ← payload.networks.length = 0(新用户没配过)
14:42:05 network:new ← 脚本自动 {name:"Local ngircd", host:"127.0.0.1", port:6667, join:"#lounge", ...}
14:42:06 network ← IRC 连接建立、频道信息回来,payload.network.channels[0].id = "chan-001"
14:42:11 input ← 延迟 5 秒,向 chan-001 发 "hello from The Lounge 4.5.2"
14:42:11 msg ← 服务器 echo 回来,{target:"chan-001", from:"lounge1", text:"hello...", self:true}
14:42:25 done ← 20 秒到点,脚本退出,events.json 落盘 /opt/siotest/sock-events.json
这个序列证明了:客户端 → The Lounge → IRCd → 回显 → 客户端 的完整闭环。
十、自己写客户端脚本的要点
sock-test.js 之所以能独立跑,靠三个关键约束:
- 先等
auth:start,再发auth:perform——时序错了服务器会踢; reconnection: false——实验脚本不想被 socket.io 自动重连无限挂住;transports: ["websocket"]——polling 回退在排障时会让事件乱序;- 频道寻址靠
id,不是#频道名——所有input.target都填 init/network 事件里给的 channel id; - 脚本必须 import
socket.io-client(require("socket.io-client"))——纯原生 WebSocket 连不上,握手协议不一样。
十一、安全与隐私
| 方面 | 说明 |
|---|---|
| 密码传输 | auth:perform.password 走明文 WebSocket——反代层套 HTTPS 是必须的 |
TLS 反代 + reverseProxy: true | The Lounge 能从 X-Forwarded-Proto 读到正确协议,不把会话标记成 insecure |
| socket.io 连接认证 | 有 session cookie;未认证的 socket 在 private 模式被强制发 auth:start,不响应就断开 |
| rate limit | sock-test.js 没测;IRC 层自己有 flood control(irc-framework 实现),频道发言太快会被服务器限速 |
十二、与 Kiwi IRC / Glowing Bear 的协议差异
三者都是 Web IRC,但 socket.io 的使用方式不一样:
| The Lounge | Kiwi IRC | Glowing Bear | |
|---|---|---|---|
| 协议层 | socket.io v4 + irc-framework | socket.io + 自研 IRC 代理 | WebSocket + 自研桥接 |
| 事件粒度 | 细(network/input/msg 独立事件) | 较粗(统一 irc 事件 + 子命令) | 消息 JSON 包 |
| 私有模式持久化 | ✅ SQLite + 持久 IRC 连接 | ❌ 纯浏览器态 | ✅ 但靠 Quassel core |
| 可脚本化 | 好(socket.io-client 直接 emit) | 好 | 中(需自己实现 WebSocket 帧) |
sock-test.js 只需 60 行就完成登录 + 连 IRC + 发言——The Lounge 的事件命名是三者里最清晰的。