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 LoungeThe LoungeIRC即时通讯Node.js2026/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)

字段类型说明
userstringThe Lounge 用户名
passwordstring明文密码(传输层 HTTPS 时安全;否则建议前面套 TLS 反代)
tokenstring?LDAP 双因素或未来扩展用;4.5.2 基本不用

sock-test.js 实测:

socket.emit("auth:perform", { user: "root", password: "q1w2e3r4" });

4.4 auth:success / auth:failed

事件方向payload
auth:successserver→client通常带用户对象 + 客户端设置;脚本里没 dump,因为 init 紧跟
auth:failedserver→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": {...}              // 这个用户的偏好(主题等)
}

脚本拿它做两件事:

  1. 打印 networks.length 确认连上几条 IRC;
  2. 如果 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 里没网络时用这个引导用户首次连接。

字段类型必填说明
namestring✅UI 显示名
hoststring✅IRC 服务器地址
portnumber✅IRC 端口(6667 / 6697)
tlsboolean✅是否 TLS
rejectUnauthorizedbooleanTLS 时验证证书
nickstring✅初始 nick
usernamestring✅IRC username
realNamestring—IRC realname
joinstring—逗号分隔自动 join 的频道
passwordstring—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 前缀分发。

字段类型说明
targetstring频道 id(从 init/network 事件里拿)或用户 ID(私聊)
textstring完整输入文本,含 /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:startS→C连接后第一个事件,示意开始认证
auth:performC→S提交 user/password
auth:successS→C凭据正确(紧跟 init)
auth:failedS→C凭据错误,payload 含 reason
initS→C会话全量同步,含全部网络/频道/消息/设置

8.2 网络管理

事件方向说明
networkS→C网络状态变更(新增/重连/断开/频道变动/nick 改动)
network:newC→S新增 IRC 网络连接
network:editC→S修改已有网络
network:deleteC→S删除网络
network:reconnectC→S重连

8.3 频道与消息

事件方向说明
inputC→S唯一输入通道:聊天、/command 全走这
msgS→C新消息到达
joinS→C频道成员加入
partS→C频道成员离开
quitS→C网络层断开
nickS→Cnick 变更
topicS→Ctopic 变更
modeS→C频道/用户模式变更
kick / banS→C频道管控动作

8.4 客户端设置

事件方向说明
change:configC→S改自己的客户端偏好(主题等)
change:user:passwordC→S改密码
open:queryC→S打开私聊窗口
close:queryC→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 之所以能独立跑,靠三个关键约束:

  1. 先等 auth:start,再发 auth:perform——时序错了服务器会踢;
  2. reconnection: false——实验脚本不想被 socket.io 自动重连无限挂住;
  3. transports: ["websocket"]——polling 回退在排障时会让事件乱序;
  4. 频道寻址靠 id,不是 #频道名——所有 input.target 都填 init/network 事件里给的 channel id;
  5. 脚本必须 import socket.io-client(require("socket.io-client"))——纯原生 WebSocket 连不上,握手协议不一样。

十一、安全与隐私

方面说明
密码传输auth:perform.password 走明文 WebSocket——反代层套 HTTPS 是必须的
TLS 反代 + reverseProxy: trueThe Lounge 能从 X-Forwarded-Proto 读到正确协议,不把会话标记成 insecure
socket.io 连接认证有 session cookie;未认证的 socket 在 private 模式被强制发 auth:start,不响应就断开
rate limitsock-test.js 没测;IRC 层自己有 flood control(irc-framework 实现),频道发言太快会被服务器限速

十二、与 Kiwi IRC / Glowing Bear 的协议差异

三者都是 Web IRC,但 socket.io 的使用方式不一样:

The LoungeKiwi IRCGlowing Bear
协议层socket.io v4 + irc-frameworksocket.io + 自研 IRC 代理WebSocket + 自研桥接
事件粒度细(network/input/msg 独立事件)较粗(统一 irc 事件 + 子命令)消息 JSON 包
私有模式持久化✅ SQLite + 持久 IRC 连接❌ 纯浏览器态✅ 但靠 Quassel core
可脚本化好(socket.io-client 直接 emit)好中(需自己实现 WebSocket 帧)

sock-test.js 只需 60 行就完成登录 + 连 IRC + 发言——The Lounge 的事件命名是三者里最清晰的。