feat(Dependencies): add new libraries for enhanced functionality and update existing ones
Some checks failed
Frontend CI / ota-android (push) Successful in 11m11s
Frontend CI / build-and-push-web (push) Successful in 11m41s
Frontend CI / build-android-apk (push) Has been cancelled

- Added `katex`, `markdown-it`, and `prismjs` for improved rendering of mathematical expressions and markdown content.
- Included `react-native-webview` to support web content within the app.
- Updated package versions in `package-lock.json` and `package.json` for better compatibility and performance.
- Refactored notification handling in `sseService` and `systemNotificationService` to improve user experience with vibration feedback.
- Introduced buffering for SSE messages in `messageManager` to prevent race conditions during initialization.
This commit is contained in:
lafay
2026-03-25 16:09:43 +08:00
parent 4ee3079b9f
commit f875b417c8
5 changed files with 225 additions and 15 deletions

View File

@@ -48,6 +48,7 @@ import {
saveGroupsCache,
} from '../services/database';
import { api } from '../services/api';
import { vibrateOnMessage } from '../services/messageVibrationService';
import { useAuthStore } from './authStore';
import {
type IConversationListPagedSource,
@@ -186,6 +187,14 @@ class MessageManager {
*/
private readStateVersion: number = 0;
// 初始化阶段缓冲来自 SSE 的消息事件:在已拉取服务器权威状态(会话/未读)前
// 不把 SSE 事件直接用于震动/未读增量,避免竞态导致“闪一下又消失”
private bufferedChatMessages: Array<(WSChatMessage | WSGroupChatMessage) & { _isAck?: boolean }> = [];
private bufferedReadReceipts: Array<WSReadMessage | WSGroupReadMessage> = [];
// 用于覆盖“初始化完成但仍在刷新服务器权威状态”的过渡窗口
private isBootstrapping: boolean = false;
constructor(deps?: MessageManagerConversationListDeps) {
const pageSize = deps?.remoteListPageSize ?? CONVERSATION_LIST_PAGE_SIZE;
this.remoteConversationListSource =
@@ -467,6 +476,8 @@ class MessageManager {
return;
}
this.isBootstrapping = true;
// 2. 初始化SSE监听
this.initSSEListeners();
@@ -478,6 +489,11 @@ class MessageManager {
this.state.isInitialized = true;
// 初始化窗口期内缓冲的 SSE 事件:刷新为服务器权威,避免“未读闪一下又消失”
await this.flushBufferedSSEEvents();
this.isBootstrapping = false;
this.notifySubscribers({
type: 'conversations_updated',
payload: { conversations: this.state.conversationList },
@@ -490,6 +506,7 @@ class MessageManager {
}
} catch (error) {
console.error('[MessageManager] 初始化失败:', error);
this.isBootstrapping = false;
this.notifySubscribers({
type: 'error',
payload: { error, context: 'initialize' },
@@ -505,6 +522,52 @@ class MessageManager {
}
}
/**
* 初始化完成后,处理在初始化阶段被缓冲的 SSE 事件。
*
* 架构目标:
* 1) 初始化阶段不把 SSE 用于未读增量/震动(避免竞态“先加后清”)
* 2) 初始化完成后,用服务器权威状态刷新会话/未读,再把事件用于消息内容展示
*/
private async flushBufferedSSEEvents(): Promise<void> {
// 为了覆盖“刷新服务器权威状态过程中又到了一些 SSE”这种窗口这里做一个有上限的循环清空缓冲。
// 如果在线流持续不断,会产生更多轮次;因此使用上限避免长时间阻塞初始化。
const MAX_FLUSH_ITERATIONS = 3;
for (let i = 0; i < MAX_FLUSH_ITERATIONS; i++) {
const hasBuffered = this.bufferedChatMessages.length > 0 || this.bufferedReadReceipts.length > 0;
if (!hasBuffered) return;
const chatEvents = this.bufferedChatMessages;
const readEvents = this.bufferedReadReceipts;
this.bufferedChatMessages = [];
this.bufferedReadReceipts = [];
// 先把消息内容灌进内存(用于 ChatScreen 如果正好激活能立刻看到)
// 不在这里修改 unread_count / 不触发震动 / 不做自动标记已读。
for (const m of chatEvents) {
await this.handleNewMessage(m, {
suppressUnreadIncrement: true,
suppressVibration: true,
suppressConversationUpdates: true,
suppressAutoMarkAsRead: true,
});
}
// 已读回执目前在本仓库实现为空handleReadReceipt / handleGroupReadReceipt 是 no-op保留扩展位
for (const r of readEvents) {
if (r.type === 'read') {
this.handleReadReceipt(r);
} else {
this.handleGroupReadReceipt(r as WSGroupReadMessage);
}
}
// 使用服务器权威刷新会话与未读,避免初始化窗口期内漏算/重复算
await this.fetchConversations(true, 'sse-buffer-flush');
await this.fetchUnreadCount();
}
}
destroy(): void {
if (this.sseUnsubscribe) {
this.sseUnsubscribe();
@@ -529,21 +592,37 @@ class MessageManager {
// 监听私聊消息
sseService.on('chat', (message: WSChatMessage) => {
if (!this.state.isInitialized || this.isBootstrapping) {
this.bufferedChatMessages.push(message);
return;
}
this.handleNewMessage(message);
});
// 监听群聊消息
sseService.on('group_message', (message: WSGroupChatMessage) => {
if (!this.state.isInitialized || this.isBootstrapping) {
this.bufferedChatMessages.push(message);
return;
}
this.handleNewMessage(message);
});
// 监听私聊已读回执
sseService.on('read', (message: WSReadMessage) => {
if (!this.state.isInitialized || this.isBootstrapping) {
this.bufferedReadReceipts.push(message);
return;
}
this.handleReadReceipt(message);
});
// 监听群聊已读回执
sseService.on('group_read', (message: WSGroupReadMessage) => {
if (!this.state.isInitialized || this.isBootstrapping) {
this.bufferedReadReceipts.push(message);
return;
}
this.handleGroupReadReceipt(message);
});
@@ -784,10 +863,22 @@ class MessageManager {
* 处理新消息SSE推送
* 这是核心方法,必须立即处理并同步到所有订阅者
*/
private async handleNewMessage(message: (WSChatMessage | WSGroupChatMessage) & { _isAck?: boolean }): Promise<void> {
private async handleNewMessage(
message: (WSChatMessage | WSGroupChatMessage) & { _isAck?: boolean },
options?: {
suppressUnreadIncrement?: boolean;
suppressVibration?: boolean;
suppressConversationUpdates?: boolean;
suppressAutoMarkAsRead?: boolean;
}
): Promise<void> {
const { conversation_id, id, sender_id, seq, segments, created_at, _isAck } = message;
const normalizedConversationId = this.normalizeConversationId(conversation_id);
const currentUserId = this.getCurrentUserId();
const suppressUnreadIncrement = options?.suppressUnreadIncrement ?? false;
const suppressVibration = options?.suppressVibration ?? false;
const suppressConversationUpdates = options?.suppressConversationUpdates ?? false;
const suppressAutoMarkAsRead = options?.suppressAutoMarkAsRead ?? false;
// 1. 消息去重检查 - 防止ACK消息和正常消息重复处理
if (this.isMessageProcessed(id)) {
@@ -855,24 +946,32 @@ class MessageManager {
});
}
// 4. 更新会话信息
this.updateConversationWithNewMessage(normalizedConversationId, newMessage, created_at);
// 4. 更新会话信息(初始化缓冲刷新阶段可选择跳过,避免会话未读/排序竞态)
if (!suppressConversationUpdates) {
this.updateConversationWithNewMessage(normalizedConversationId, newMessage, created_at);
}
// 5. 更新未读数(如果不是当前用户发送且不是当前活动会话)
const isCurrentUserMessage = sender_id === currentUserId;
const isActiveConversation = normalizedConversationId === this.state.currentConversationId;
// 确保当前用户发送的消息不会增加未读数
// 同时确保currentUserId有效避免undefined === undefined的情况
// ACK消息发送确认也不应该增加未读数
const shouldIncrementUnread = !isCurrentUserMessage && !isActiveConversation && !!currentUserId && !_isAck;
const shouldIncrementUnread =
!suppressUnreadIncrement &&
!isCurrentUserMessage &&
!isActiveConversation &&
!!currentUserId &&
!_isAck;
if (shouldIncrementUnread) {
if (!suppressVibration) {
const vibrationType = message.type === 'group_message' ? 'group_message' : 'chat';
vibrateOnMessage(vibrationType).catch(() => {});
}
this.incrementUnreadCount(normalizedConversationId);
}
// 6. 如果是当前活动会话,自动标记已读
if (isActiveConversation && !isCurrentUserMessage) {
if (isActiveConversation && !isCurrentUserMessage && !suppressAutoMarkAsRead) {
this.markAsRead(normalizedConversationId, seq);
}