feat(Dependencies): add new libraries for enhanced functionality and update existing ones
- 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:
111
package-lock.json
generated
111
package-lock.json
generated
@@ -39,6 +39,9 @@
|
||||
"expo-task-manager": "~55.0.9",
|
||||
"expo-updates": "^55.0.12",
|
||||
"expo-video": "^55.0.10",
|
||||
"katex": "^0.16.42",
|
||||
"markdown-it": "^14.1.1",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-native": "0.83.2",
|
||||
@@ -50,6 +53,7 @@
|
||||
"react-native-screens": "~4.23.0",
|
||||
"react-native-sse": "^1.2.1",
|
||||
"react-native-web": "^0.21.0",
|
||||
"react-native-webview": "13.16.0",
|
||||
"react-native-worklets": "0.7.2",
|
||||
"zod": "^4.3.6",
|
||||
"zustand": "^5.0.11"
|
||||
@@ -5266,6 +5270,18 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/env-paths": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz",
|
||||
@@ -7611,6 +7627,31 @@
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/katex": {
|
||||
"version": "0.16.42",
|
||||
"resolved": "https://registry.npmmirror.com/katex/-/katex-0.16.42.tgz",
|
||||
"integrity": "sha512-sZ4jqyEXfHTLEFK+qsFYToa3UZ0rtFcPGwKpyiRYh2NJn8obPWOQ+/u7ux0F6CAU/y78+Mksh1YkxTPXTh47TQ==",
|
||||
"funding": [
|
||||
"https://opencollective.com/katex",
|
||||
"https://github.com/sponsors/katex"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "^8.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"katex": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/katex/node_modules/commander": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz",
|
||||
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/kleur": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/kleur/-/kleur-3.0.3.tgz",
|
||||
@@ -7930,6 +7971,15 @@
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uc.micro": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz",
|
||||
@@ -8170,6 +8220,23 @@
|
||||
"tmpl": "1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it": {
|
||||
"version": "14.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/markdown-it/-/markdown-it-14.1.1.tgz",
|
||||
"integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "^4.4.0",
|
||||
"linkify-it": "^5.0.0",
|
||||
"mdurl": "^2.0.0",
|
||||
"punycode.js": "^2.3.1",
|
||||
"uc.micro": "^2.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"markdown-it": "bin/markdown-it.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/marky": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/marky/-/marky-1.3.0.tgz",
|
||||
@@ -8185,6 +8252,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/mdurl/-/mdurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-1.1.0.tgz",
|
||||
@@ -9236,6 +9309,15 @@
|
||||
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/prismjs": {
|
||||
"version": "1.30.0",
|
||||
"resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz",
|
||||
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/proc-log": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/proc-log/-/proc-log-4.2.0.tgz",
|
||||
@@ -9282,6 +9364,15 @@
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode.js": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/punycode.js/-/punycode.js-2.3.1.tgz",
|
||||
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmmirror.com/qs/-/qs-6.15.0.tgz",
|
||||
@@ -9698,6 +9789,20 @@
|
||||
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-native-webview": {
|
||||
"version": "13.16.0",
|
||||
"resolved": "https://registry.npmmirror.com/react-native-webview/-/react-native-webview-13.16.0.tgz",
|
||||
"integrity": "sha512-Nh13xKZWW35C0dbOskD7OX01nQQavOzHbCw9XoZmar4eXCo7AvrYJ0jlUfRVVIJzqINxHlpECYLdmAdFsl9xDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"invariant": "2.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-worklets": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmmirror.com/react-native-worklets/-/react-native-worklets-0.7.2.tgz",
|
||||
@@ -11197,6 +11302,12 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/uc.micro": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz",
|
||||
|
||||
@@ -45,6 +45,9 @@
|
||||
"expo-task-manager": "~55.0.9",
|
||||
"expo-updates": "^55.0.12",
|
||||
"expo-video": "^55.0.10",
|
||||
"katex": "^0.16.42",
|
||||
"markdown-it": "^14.1.1",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-native": "0.83.2",
|
||||
@@ -56,6 +59,7 @@
|
||||
"react-native-screens": "~4.23.0",
|
||||
"react-native-sse": "^1.2.1",
|
||||
"react-native-web": "^0.21.0",
|
||||
"react-native-webview": "13.16.0",
|
||||
"react-native-worklets": "0.7.2",
|
||||
"zod": "^4.3.6",
|
||||
"zustand": "^5.0.11"
|
||||
|
||||
@@ -4,8 +4,6 @@ import EventSource from 'react-native-sse';
|
||||
import { api, SSE_URL } from './api';
|
||||
import { MessageCategory, SystemMessageType, SystemMessageExtraData, MessageSegment } from '../types/dto';
|
||||
import { systemNotificationService } from './systemNotificationService';
|
||||
import { getNotificationPreferencesSync } from './notificationPreferences';
|
||||
import { vibrateOnMessage } from './messageVibrationService';
|
||||
|
||||
export type WSMessageType =
|
||||
| 'chat'
|
||||
@@ -280,7 +278,6 @@ class SSEService {
|
||||
created_at: m.created_at || new Date().toISOString(),
|
||||
};
|
||||
this.emit('group_message', gm);
|
||||
vibrateOnMessage('group_message').catch(() => {});
|
||||
} else {
|
||||
const cm: WSChatMessage = {
|
||||
type: 'chat',
|
||||
@@ -292,7 +289,6 @@ class SSEService {
|
||||
created_at: m.created_at || new Date().toISOString(),
|
||||
};
|
||||
this.emit('chat', cm);
|
||||
vibrateOnMessage('chat').catch(() => {});
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -385,9 +381,6 @@ class SSEService {
|
||||
created_at: payload.created_at || new Date().toISOString(),
|
||||
};
|
||||
this.emit('notification', m);
|
||||
if (getNotificationPreferencesSync().pushEnabled) {
|
||||
vibrateOnMessage('notification').catch(() => {});
|
||||
}
|
||||
systemNotificationService.handleWSMessage(m as any).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Platform, AppState, AppStateStatus } from 'react-native';
|
||||
import type { WSChatMessage, WSNotificationMessage, WSAnnouncementMessage } from './sseService';
|
||||
import { extractTextFromSegments } from '../types/dto';
|
||||
import { getNotificationPreferencesSync } from './notificationPreferences';
|
||||
import { vibrateOnMessage } from './messageVibrationService';
|
||||
|
||||
// 通知渠道配置
|
||||
const CHANNEL_ID = 'default';
|
||||
@@ -185,6 +186,8 @@ class SystemNotificationService {
|
||||
} else {
|
||||
const notifMsg = message as WSNotificationMessage | WSAnnouncementMessage;
|
||||
void notifMsg;
|
||||
// 系统通知只在后台显示,因此震动也在该分支触发
|
||||
vibrateOnMessage('notification').catch(() => {});
|
||||
await this.showWSNotification(notifMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user