feat(web): 实现强制下线通知与顶号互踢的客户端感知(Ctrl 205 evicted)
按docs/强制下线通知与顶号互踢-后端接口文档.md §2实现。该文档§2.4声称
"SDK侧已内置:收到后关闭autoreconnect并回调onKick(reason, ctrl)"——读
实际安装的tinode-sdk(0.25.3)真实源码确认这个说法不成立:SDK对
code:205/text:"evicted"只有topic级的内部处理(topic._resetSub()),账
号级(topic为空)完全没有特殊逻辑,也没有onKick这个回调。
改为用SDK真实存在、此前项目里完全没用过的onCtrlMessage钩子(每条
{ctrl}包都会触发,含账号级的)在connection.ts里自行判定
"code===205 && text==='evicted' && !topic"。检测到后立即:清空
sessionStorage的token、清空myUserId、调用resetTinode()——其内部的
disconnect()会把SDK自己的#boffClosed标记置真,这是唯一能真正阻止
backoff计时器安排下一次重连的方式(读真实bundle的Connection#init_ws
确认,不存在另外一个"关闭autoreconnect"的方法)。只有面向用户的弹窗
和跳转延后到用户点确定,逻辑上是"会话立刻终止,通知是补充说明",不
是"通知完才终止"。
新增KickedNoticeRoot(挂载在App.tsx顶层,和ConfirmDialogRoot/
GlobalToastRoot同一层级,保证任意页面被踢都能弹出),文案原样照抄文
档§2.4的表格(reason→标题/正文),单一"确定"按钮(不是取消/确定二
选一——已经被踢了,没有"取消"这个选项,给出会误导)。确认后用
window.location.href硬跳转/login,和client.ts的forceReauth()同一个
理由:保证内存里的每一处状态(TanStack Query缓存等)都从头重置,不用
指望每个组件各自正确响应"你已经被登出了"。
topic非空的205(群内移出等topic级事件)不受影响,走原有的SDK内部
处理,不弹这个全局提示。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
c3e8f91364
commit
9adadaa57d
@@ -0,0 +1,42 @@
|
||||
import { clearKickedInfo } from '@/lib/tinode/connection';
|
||||
import { kickedNoticeText } from '@/lib/tinode/errors';
|
||||
import { useKickedInfo } from '@/lib/tinode/hooks';
|
||||
|
||||
/**
|
||||
* Mounted once at the app root (App.tsx), same placement as ConfirmDialogRoot/GlobalToastRoot --
|
||||
* an eviction can land on any authenticated page, not just ones that happen to render their own
|
||||
* connection-state UI. By the time this renders, connection.ts's onCtrlMessage handler has already
|
||||
* torn down the session (token cleared, Tinode singleton disconnected+reset) -- this component's
|
||||
* only job is showing the user WHY before sending them to /login, per 强制下线通知与顶号互踢-后端
|
||||
* 接口文档.md §2.4's "弹出中文提示后跳转登录页".
|
||||
*
|
||||
* A single acknowledgment button, not ConfirmDialogRoot's 取消/确定 pair -- there is no "cancel" for
|
||||
* an eviction that already happened, offering one would be misleading. Hard `window.location.href`
|
||||
* navigation (not an in-SPA `navigate()`), matching client.ts's forceReauth() for the identical
|
||||
* reason: every piece of in-memory state (TanStack Query cache, any component's local state) should
|
||||
* reset from scratch, not rely on each one separately noticing "you're logged out now".
|
||||
*/
|
||||
export function KickedNoticeRoot() {
|
||||
const kicked = useKickedInfo();
|
||||
if (!kicked) return null;
|
||||
const { title, message } = kickedNoticeText(kicked.reason);
|
||||
|
||||
const acknowledge = () => {
|
||||
clearKickedInfo();
|
||||
window.location.href = '/login';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-[#22262d]/25">
|
||||
<div className="w-[340px] rounded-xl bg-white p-5 shadow-modal">
|
||||
<strong className="mb-2 block text-base font-semibold text-text-heading">{title}</strong>
|
||||
<p className="m-0 text-sm leading-relaxed text-text-body">{message}</p>
|
||||
<div className="mt-5 flex justify-end">
|
||||
<button type="button" onClick={acknowledge} className="grid h-9 place-items-center rounded-lg bg-btn px-4 text-sm font-medium text-white">
|
||||
确定
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user