fix(collab): room_closed 无条件归位、断线重连补状态、学生 socket 重连迁移

Critical:room_closed 不再按 reason 挑着重置 helpStatus——学生自己的 socket
发送失败时服务端删了请求却发不出任何纠正帧,store 会卡在陈旧的 active/pending
上出不来。现在一律先归 idle,老师掉线那条路径服务端会紧跟着补一条
help_status:pending,同一条连接消息严格按序到达,不会被这次重置抢跑。

Critical:断线重连没有补状态。前端 CollabWebSocket 加 onConnected 钩子,
每次连接建立(含重连)都清空本地 requests/helpStatus/room,等服务端补发;
后端 handleCollabOpen 对非老师的重连方,如果这个账号名下还有请求,
补发对应的 help_status(pending 带重算的 queueAhead,active 带 teacherName)。

Critical:重连后请求仍绑在旧 socket 上,导致 handleHelpCancel 的归属校验
认不出新连接、后续通知也写进死连接。handleCollabOpen 里把请求和(如果有)
房间迁移到新 socket,并清掉旧 socket 的 roomOwnerId,防止它稍后的 close
反过来拆掉刚迁移出去的房间。

Minor:disconnect() 补齐 queueAhead/teacherName/notice 的重置,不留陈旧值。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1d8B3f4SXJwDvUY625eQd
This commit is contained in:
2026-08-28 06:01:41 -06:00
parent 17aa69f8a7
commit ab8fcc2d42
3 changed files with 65 additions and 2 deletions

View File

@@ -736,6 +736,7 @@ export interface CollabMessage extends WebSocketMessage {
*/
export class CollabWebSocket extends BaseWebSocket<CollabMessage> {
private binaryHandler: ((data: ArrayBuffer) => void) | null = null
private connectHandler: (() => void) | null = null
constructor() {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"
@@ -746,7 +747,21 @@ export class CollabWebSocket extends BaseWebSocket<CollabMessage> {
this.binaryHandler = handler
}
/**
* 每次连接**建立**都触发,含重连 —— 不止首次 connect()。用来在重连瞬间
* 清掉本地缓存的求助/房间状态:旧连接期间的 pending/active 可能早就过时了,
* 服务端会在 handleCollabOpen 里紧接着补发 requests老师或 help_status
* (还在排队/协作中的学生),补发落地前先归零,好过让过时状态活过一次重连。
*/
setConnectHandler(handler: (() => void) | null) {
this.connectHandler = handler
}
protected override onBinary(data: ArrayBuffer) {
this.binaryHandler?.(data)
}
protected override onConnected() {
this.connectHandler?.()
}
}