feat: 登录设备管理与编辑器图片上传

- 新增登录设备列表与会话下线(ListLoginDevices/RevokeLoginDevice),按设备指纹识别当前会话
- Markdown 编辑器新增图片插入弹窗与图片上传(ImageInsertModal)
- 实时总线、账号守卫与用户事件适配
This commit is contained in:
2026-09-26 01:10:10 +08:00
parent 5270fee2b7
commit 31f5360c26
12 changed files with 706 additions and 165 deletions

View File

@@ -3,12 +3,15 @@
// - 单条 WebSocket(浏览器握手自动携带 HttpOnly cookie 鉴权),后续群聊/通知/
// 审核角标都复用本连接,按 message type 路由。
// - 连接状态机 + 指数退避重连;应用层心跳(协议层 Ping 由浏览器自动应答)。
// - 鉴权失效关闭码 4401 不自动重连(封禁/强制下线由 AccountGuard 统一处理)。
// - 鉴权失效关闭码 4401 不自动重连;转一次 /api/me 探测(封禁走现有弹窗链路,
// 被顶下线由 AccountGuard 弹"登录已失效",见 lib/api 的统一判定)。
// - 在线集合:hello 帧给 staff 全量快照,之后 presence:update 增量维护。
// - 群聊:进入群详情页调用 joinRoom(roomID) 订阅 chat:{id} 房间,
// 重连后会自动重订当前已订阅集合;离开页面/退出群调用 leaveRoom。
// - HTTP 仅作回前台 / 进页校准;实时靠 WS(通知、待审、主题、群聊未读)。
import { apiMe } from "./api";
export const RT_HELLO = "hello";
export const RT_PONG = "pong";
export const RT_SETTINGS_CHANGED = "settings:changed";
@@ -20,6 +23,8 @@ export const RT_CHAT_UNREAD = "chat:unread";
export const RT_NOTIFICATION_NEW = "notification:new";
export const RT_FEED_CHANGED = "feed:changed";
export const RT_MODERATION_CHANGED = "moderation:changed";
// 登录态被顶/被剔除(其他浏览器登录、安全设置剔除设备),推 user:{id}
export const RT_SESSION_REPLACED = "session:replaced";
export interface RtEnvelope<T = unknown> {
type: string;
@@ -323,10 +328,13 @@ class RealtimeClient {
this.clearHeartbeat();
if (this.ws === ws) this.ws = null;
this.state = "closed";
// 鉴权失效:账号封禁/角色变更导致 token_version 失效,
// 不重连(强制下线/重新登录由现有 HTTP 拦截链路处理)
// 鉴权失效:账号封禁/角色变更/会话被顶导致 claims 校验失效,
// 不重连;探测一次登录态(封禁→现有弹窗链路;被顶→"登录已失效"提示)
if (!this.wanted || ev.code === CLOSE_AUTH_INVALID) {
this.wanted = false;
if (ev.code === CLOSE_AUTH_INVALID) {
void apiMe().catch(() => {});
}
return;
}
this.scheduleReconnect();