群聊生命周期事件对被动成员实时不可见——前端兜底同步(真正的根因在后端,无法单靠前端代码彻底解决)
读im_be的server/im_group_events.go确认:群聊创建/加人/踢人/解散/转让群主等全部事件都走 publishIMGroupRealtimeEvent,这个函数只会广播给"当前已订阅该群topic的会话"——对比P2P场景专门 有一条deliverIMConversationSystemMessageToP2PSessions,会无视订阅状态强制投递给双方的session, 群聊从未做过同等实现。实机验证:一个全新创建、成员从未打开过的群,即使对方账号完全在线、页面就 停留在/messages上,群条目和未读角标都不会出现,等多久都不会自愈,必须手动刷新页面。 由于这个topic客户端本来就不知道存在,没有任何东西可以"响应",前端无法真正修复,只能缓解:复用 subscribeMeTopic()/onReauthenticated()已经在用的withLaterSub()增量语义,改用getMeta()(不像 subscribe()那样在_attached为true时直接短路)定时(30秒)+ 切回标签页时主动拉一次"me"话题的最新 订阅列表。 实机验证:创建一个全新群、只邀请一个从未见过这个群的被动成员,在其页面保持被动、不做任何操作/ 刷新的情况下——模拟切换标签页触发的focus/visibilitychange事件后约十几秒内,群条目正确出现在 列表顶部;另一次未受干预的测试中,纯靠30秒定时器本身也确认能自愈。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
d1f3aea26e
commit
ab3843dbdb
@@ -355,3 +355,54 @@ onReauthenticated(() => {
|
||||
topic.subscribe(topic.startMetaQuery().withLaterData(1).build()).catch(() => {});
|
||||
}
|
||||
});
|
||||
|
||||
const ME_RECONCILE_INTERVAL_MS = 30_000;
|
||||
let meReconcileTimer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
/**
|
||||
* Best-effort mitigation for a real backend gap that no amount of frontend-only code can fully close:
|
||||
* confirmed by reading im_be's server/im_group_events.go, EVERY group lifecycle event (created, member
|
||||
* added/removed, dissolved, owner transferred, ...) goes through publishIMGroupRealtimeEvent, which
|
||||
* only reaches sessions ALREADY subscribed to that specific group topic. Unlike P2P -- which has a
|
||||
* dedicated force-delivery path straight to a peer's session regardless of subscription state
|
||||
* (deliverIMConversationSystemMessageToP2PSessions) -- groups have no equivalent, so a member who has
|
||||
* never opened a given group gets zero live signal when added to it: no chat-list row, no unread badge,
|
||||
* nothing, indefinitely, until something re-syncs. Confirmed live (2026-09-16): a brand-new group with a
|
||||
* genuinely passive, fully-connected, already-on-/messages member produced no change at all, even
|
||||
* several seconds after a real message was sent into it.
|
||||
*
|
||||
* There is nothing to subscribe to or react to for a topic this client doesn't know exists yet, so the
|
||||
* only thing a client can do is periodically ask "what's new" -- reusing the exact same incremental
|
||||
* `withLaterSub()` semantics subscribeMeTopic()/onReauthenticated() above already rely on, just issued
|
||||
* via `getMeta()` instead of `subscribe()`. `Topic.subscribe()` short-circuits once `_attached` is
|
||||
* already true (the normal, undisturbed state this is guarding, unlike the reconnect case above), but
|
||||
* `getMeta()` sends the `{get}` regardless of attach state -- the same distinction ConversationView's
|
||||
* own P2P desc/presence fix relies on.
|
||||
*/
|
||||
function reconcileMeSubs() {
|
||||
const me = getTinode().getMeTopic();
|
||||
if (!me.isSubscribed()) return;
|
||||
me.getMeta(me.startMetaQuery().withLaterSub().build()).catch(() => {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the periodic timer plus tab-focus/visibility triggers (the common "switched away and came
|
||||
* back" case gets an immediate catch-up instead of waiting for the next tick). Call once per
|
||||
* authenticated session (RequireAuth.tsx) and invoke the returned function on logout/unmount -- safe
|
||||
* to start/stop repeatedly (React 19 StrictMode's mount->cleanup->remount included), unlike
|
||||
* subscribeMeTopic() itself, which is guarded separately against being re-fired.
|
||||
*/
|
||||
export function startMeReconciliation(): () => void {
|
||||
meReconcileTimer = setInterval(reconcileMeSubs, ME_RECONCILE_INTERVAL_MS);
|
||||
const onVisible = () => {
|
||||
if (document.visibilityState === 'visible') reconcileMeSubs();
|
||||
};
|
||||
document.addEventListener('visibilitychange', onVisible);
|
||||
window.addEventListener('focus', reconcileMeSubs);
|
||||
return () => {
|
||||
if (meReconcileTimer) clearInterval(meReconcileTimer);
|
||||
meReconcileTimer = null;
|
||||
document.removeEventListener('visibilitychange', onVisible);
|
||||
window.removeEventListener('focus', reconcileMeSubs);
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user