This commit is contained in:
Austin
2026-09-18 18:34:21 +08:00
3 changed files with 59 additions and 17 deletions
+34 -13
View File
@@ -12,6 +12,7 @@
// getParams -- so this always leaves first when already subscribed, then resubscribes.
import type { Contact, StoredMessage, Topic } from 'tinode-sdk';
import { getGroup } from '@/lib/api/groups';
import { MESSAGES_PAGE } from '@/lib/config';
import { getTinode, onReauthenticated } from './connection';
@@ -128,7 +129,22 @@ function notifyTopic(topicName: string) {
pendingAnyNotification = true;
if (!notifyFrameScheduled) {
notifyFrameScheduled = true;
requestAnimationFrame(flushTopicNotifications);
// Live-confirmed real bug (surfaced while testing an unrelated group-rename fix, in a webview whose
// frame compositing was paused, i.e. genuinely not painting -- not just an ordinary backgrounded
// browser tab): a `requestAnimationFrame` scheduled while the page isn't compositing frames can go
// uncalled indefinitely rather than merely deferred-until-visible. Since `notifyFrameScheduled` only
// resets to false inside the callback, one dropped frame permanently wedges this guard, silently
// freezing EVERY future topic-list update (not just the group name) until a full page reload -- data
// kept arriving and updating correctly underneath (confirmed live: `me.contacts()` had the right
// data the whole time), only the UI never found out. `document.hidden` costs nothing to check and
// `setTimeout` is never silently dropped the way this rAF was, so route through it whenever the page
// isn't visible; keep `requestAnimationFrame` for the visible case since that's what actually gives
// the intended one-flush-per-paint batching this function exists for.
if (typeof document !== 'undefined' && document.hidden) {
setTimeout(flushTopicNotifications, 0);
} else {
requestAnimationFrame(flushTopicNotifications);
}
}
}
@@ -327,19 +343,24 @@ function wireTopicCallbacks(topic: Topic) {
topic.getMeta(topic.startMetaQuery().withData(seq, seq + 1, 1).build()).catch(() => {});
}
}
// docs/IM-前后端对接文档.md:1542: `PATCH .../groups/{topic}` (rename/avatar/description) pushes a
// custom `{info what:"group", event:"group.profile.updated"}` to every other online member -- same
// undocumented-to-the-SDK shape as `group.dissolved` above, so nothing here ever refreshed the
// topic's native `public` fields on receipt. Live-confirmed the actual gap: the sidebar/header name
// docs/IM-前后端对接文档.md:1542 documents this push as `group.profile.updated`, but a real rename,
// live-instrumented (logging the raw `info` object as it arrived), showed the actual wire event is
// `{what:"group", event:"group.settings.updated"}` -- same doc-vs-wire drift already seen elsewhere
// in this backend. Matching the real event name fixes only half the bug: the sidebar/header name
// comes from `contact.public?.fn` (native SDK state, see ChatListColumn.tsx), which a REST-only
// mutation never touches for ANYONE -- not just other members, but the renamer's own other tabs/
// views too -- and only ever got refreshed as a side effect of the unrelated focus-triggered
// `reconcileMeSubs()`, which is why refocus "fixed" it for everyone. `withDesc()` is the SDK's own
// sanctioned way to re-pull `public`/description from the server (contrast the abandoned approach
// of manually patching `Topic._processMetaDesc` tried earlier for a different bug -- that one was
// reverted for inconclusive verification; this one goes through a real server round-trip instead).
if (groupEvent?.what === 'group' && groupEvent.event === 'group.profile.updated') {
topic.getMeta(topic.startMetaQuery().withDesc().build()).catch(() => {});
// mutation never touches for anyone, and the "obvious" fix -- `getMeta(startMetaQuery().withDesc()
// .build())`, the SDK's own sanctioned way to re-pull desc from the server -- was live-instrumented
// too and found to be genuinely broken against this backend: a consistent 504 timeout (~5.4s, every
// attempt, still failing 10+s after the write) on a brand-new topic, and on an established topic a
// fast-but-stale response whose `public.fn` still held the pre-rename name. Using the already-
// reliable REST `getGroup()` instead (confirmed live to return the fresh name immediately after a
// rename, every time) and applying it locally via `_processMetaDesc` -- the same internal method the
// SDK itself calls when a real `{meta desc}` response arrives, live-confirmed to correctly reach
// `me.contacts()` -- sidesteps the broken path entirely instead of routing through it.
if (groupEvent?.what === 'group' && groupEvent.event === 'group.settings.updated') {
getGroup(topic.name)
.then((data) => topic._processMetaDesc({ public: { fn: data.name, name: data.name } }))
.catch(() => {});
}
};
topic.onMetaDesc = notify;