/** * P1-8:把「长连接」挂到签发它的认证会话下,认证一结束就立刻掐断。 * * 背景:H5 logout / 会话到期、legacy token rotate、平台解绑之后,短请求会立刻 * 401,但**已经建立的流**不会——`/events` SSE、Preview 的 SSE/长响应、Preview * WebSocket 都是登出前握好的手,握完就一直流,直到对端自己断开。这等于登出只 * 关了前门,后窗还开着:一台被借走的手机、一个已经交接掉的工作台页面,仍然能持 * 续看到会话板与预览内容,旧的 management SSE 甚至会收到**登出之后新产生**的 * `riffAccessUrl`(Riff 沙箱写凭据)。 * * 这里只做一件事:authSessionId → 一组「关闭器」。建流时登记,流自然结束时注销, * 身份结束时遍历关闭。它不认识 HTTP,也不认识 WebSocket——调用方给什么关闭动作 * 就执行什么(`res.destroy()` / `socket.destroy()`),因此同一份索引可以同时管 * SSE 响应和 WS 桥接 socket。 * * 与 `TerminalControlManager.registerReadSocket` 的关系:那份索引是终端读流专用 * 的(P1-5),语义与写租约耦合;本索引是通用的,覆盖终端之外的所有长连接。两者 * 都由 `dashboardSessions.onEnd` / rotate / unbind 同一批钩子驱动。 */ /** 关闭一条已建立的长连接。必须幂等:可能与对端自己断开竞争。 */ export type AuthSessionConnectionCloser = () => void; export declare class AuthSessionConnectionRegistry { private readonly byAuthSession; /** * P1-13:同一批连接的第二个索引——按被预览的**业务会话** id。 * * 身份还在、但预览目标没了(worker 换代 / 切 CLI / 会话关闭 / 端口被别的进程接管) * 时,authSession 那把钥匙开不了这扇门:登录用户完全合法,只是他正在看的那条流已经 * 指向一个不再属于这个会话的进程。必须能按 sessionId 单独把这批流掐掉,且不碰同一 * 认证会话名下其它会话的连接。 */ private readonly bySession; /** * 登记一条属于 `authSessionId` 的长连接,返回注销闭包。 * 调用方必须在连接自然关闭时调用注销闭包,否则 Map 会随连接数无限增长。 * * `sessionId` 可选:预览类长连接传它,好让预览目标失效时能定点断流。 */ register(authSessionId: string, close: AuthSessionConnectionCloser, sessionId?: string): () => void; private add; private drop; /** * 身份结束(logout / 到期 / rotate / 解绑)时调用:关闭该认证会话名下的全部 * 长连接,返回关闭条数。先把整组摘下来再逐个关,避免关闭回调同步触发注销时 * 改动正在遍历的集合。别的认证会话的连接一条都不动。 */ closeAuthSession(authSessionId: string): number; /** * P1-13:预览目标失效(换代 / 关闭 / 端口易主)时,关闭这个业务会话名下的全部 * 长连接。注销闭包会把它们同时从 authSession 索引里摘掉,两个索引不会漂移。 */ closeSessionStreams(sessionId: string): number; private closeAll; /** 当前登记的连接数:不传参是全量,传参是单个认证会话。仅用于测试与自检。 */ count(authSessionId?: string): number; /** 某个业务会话名下的连接数。仅用于测试与自检。 */ countSessionStreams(sessionId: string): number; } //# sourceMappingURL=auth-session-connections.d.ts.map