fix(message): improve message synchronization and reliability
Refactor the messaging subsystem to enhance data consistency, prevent race conditions during WebSocket reconnection, and ensure accurate unread counts.
- **WebSocket Reliability**: Implement `client_msg_id` for precise message ACK matching, preventing incorrect pending message resolution in high-frequency scenarios.
- **Sync Logic**: Update `WSMessageHandler` and `MessageManager` to use a bootstrapping state, ensuring buffered SSE events are flushed only after the initial synchronization is complete.
- **State Consistency**:
- Introduce atomic unread count increments to prevent lost updates.
- Implement conditional rollback for optimistic read receipts, ensuring that failed API calls do not overwrite newer, valid read states.
- **Resource Management**: Add reference counting to `useMessages` hook to prevent premature clearing of loading states during rapid conversation switching or React StrictMode double-invocations.
- **Data Integrity**: Update `UserCacheService` to re-read the latest message list before applying sender info enrichment, ensuring new messages arriving during the async process are correctly processed.
This commit is contained in:
@@ -100,9 +100,14 @@ interface UseMessagesReturn {
|
||||
*
|
||||
* 核心修复:
|
||||
* 1. useFocusEffect:屏幕重新获得焦点时强制同步消息,解决"需要退出重进才能看到新消息"的问题
|
||||
* 2. 清理 isLoadingMessages:组件卸载时清除残留的 loading 锁,防止下次进入被阻断
|
||||
* 2. 引用计数清理 isLoadingMessages:仅在最后一个订阅者卸载时清除 loading 锁,
|
||||
* 避免 StrictMode 双调用或快速切换会话时,第一次 fetch 还没完成就被清锁
|
||||
* 3. 重入时 forceSync:相同 conversationId 重复进入时强制拉取最新消息
|
||||
*/
|
||||
|
||||
// 记录每个 conversationId 上的活跃订阅数,仅在归零时清锁
|
||||
const loadingLockRefCount: Map<string, number> = new Map();
|
||||
|
||||
export function useMessages(conversationId: string | null): UseMessagesReturn {
|
||||
const normalizedConversationId = conversationId ? String(conversationId) : null;
|
||||
|
||||
@@ -128,6 +133,10 @@ export function useMessages(conversationId: string | null): UseMessagesReturn {
|
||||
return;
|
||||
}
|
||||
|
||||
// 引用计数 +1
|
||||
const prevCount = loadingLockRefCount.get(normalizedConversationId) ?? 0;
|
||||
loadingLockRefCount.set(normalizedConversationId, prevCount + 1);
|
||||
|
||||
lastActivatedAtRef.current = Date.now();
|
||||
|
||||
const isReentry = lastActivatedIdRef.current === normalizedConversationId;
|
||||
@@ -141,11 +150,17 @@ export function useMessages(conversationId: string | null): UseMessagesReturn {
|
||||
lastActivatedIdRef.current = normalizedConversationId;
|
||||
|
||||
return () => {
|
||||
// 清理活动会话 + 清除残留的 loading 锁
|
||||
if (messageManager.getActiveConversation() === normalizedConversationId) {
|
||||
messageManager.setActiveConversation(null);
|
||||
// 引用计数 -1,仅在最后一个订阅者卸载时清活动会话 + 清 loading 锁
|
||||
const nextCount = (loadingLockRefCount.get(normalizedConversationId) ?? 1) - 1;
|
||||
if (nextCount <= 0) {
|
||||
loadingLockRefCount.delete(normalizedConversationId);
|
||||
if (messageManager.getActiveConversation() === normalizedConversationId) {
|
||||
messageManager.setActiveConversation(null);
|
||||
}
|
||||
useMessageStore.getState().setLoadingMessages(normalizedConversationId, false);
|
||||
} else {
|
||||
loadingLockRefCount.set(normalizedConversationId, nextCount);
|
||||
}
|
||||
useMessageStore.getState().setLoadingMessages(normalizedConversationId, false);
|
||||
};
|
||||
}, [normalizedConversationId]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user