|
@@ -1,9 +1,12 @@
|
|
|
/**
|
|
/**
|
|
|
- * 消息 WebSocket:登录后连接,收到新消息时更新 chatStore(消息列表、本地未读、会话预览),不整表刷新
|
|
|
|
|
|
|
+ * 消息 WebSocket:登录后连接,收到新消息时更新 chatStore(消息列表、会话预览),不整表刷新
|
|
|
|
|
+ * 未读角标由 GET /messages/unread-count 刷新,不在此本地累加
|
|
|
* 断线后自动重连(指数退避);主动 disconnect 时不重连
|
|
* 断线后自动重连(指数退避);主动 disconnect 时不重连
|
|
|
*/
|
|
*/
|
|
|
-import { getToken, normalizeMessageContentType } from '../utils/api'
|
|
|
|
|
|
|
+import { getToken, markHistoryReadAll, normalizeMessageContentType } from '../utils/api'
|
|
|
import { chatStore } from '../store/chat'
|
|
import { chatStore } from '../store/chat'
|
|
|
|
|
+import { fetchContactsList } from './useContacts'
|
|
|
|
|
+import { fetchUnreadCountAndUpdateTabBar } from './useUnreadBadge'
|
|
|
|
|
|
|
|
const WS_BASE = 'wss://api.hnyunzhu.com/api/v1/ws/messages'
|
|
const WS_BASE = 'wss://api.hnyunzhu.com/api/v1/ws/messages'
|
|
|
const HEARTBEAT_INTERVAL = 30000
|
|
const HEARTBEAT_INTERVAL = 30000
|
|
@@ -11,7 +14,6 @@ const INITIAL_RECONNECT_DELAY = 1000
|
|
|
const MAX_RECONNECT_DELAY = 30000
|
|
const MAX_RECONNECT_DELAY = 30000
|
|
|
|
|
|
|
|
let socketTask = null
|
|
let socketTask = null
|
|
|
-let fetchContactsRef = null
|
|
|
|
|
let heartbeatTimer = null
|
|
let heartbeatTimer = null
|
|
|
/** 仅 uni.onSocket* 注册一次,避免每次 connect 叠加监听 */
|
|
/** 仅 uni.onSocket* 注册一次,避免每次 connect 叠加监听 */
|
|
|
let socketListenersAttached = false
|
|
let socketListenersAttached = false
|
|
@@ -26,6 +28,12 @@ function clearReconnectTimer() {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/** WS 心跳成功或收到推送后:拉总未读 + 会话列表 */
|
|
|
|
|
+function syncInboxFromServer() {
|
|
|
|
|
+ if (!getToken()) return
|
|
|
|
|
+ Promise.all([fetchUnreadCountAndUpdateTabBar(), fetchContactsList()]).catch(() => {})
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function scheduleReconnect() {
|
|
function scheduleReconnect() {
|
|
|
if (intentionalClose) return
|
|
if (intentionalClose) return
|
|
|
if (!getToken()) return
|
|
if (!getToken()) return
|
|
@@ -90,6 +98,7 @@ function attachSocketListenersOnce() {
|
|
|
reconnectAttempt = 0
|
|
reconnectAttempt = 0
|
|
|
clearReconnectTimer()
|
|
clearReconnectTimer()
|
|
|
console.log('[WS] messages connected')
|
|
console.log('[WS] messages connected')
|
|
|
|
|
+ syncInboxFromServer()
|
|
|
heartbeatTimer = setInterval(() => {
|
|
heartbeatTimer = setInterval(() => {
|
|
|
try {
|
|
try {
|
|
|
if (socketTask) uni.sendSocketMessage({ data: 'ping' })
|
|
if (socketTask) uni.sendSocketMessage({ data: 'ping' })
|
|
@@ -100,28 +109,38 @@ function attachSocketListenersOnce() {
|
|
|
uni.onSocketMessage((res) => {
|
|
uni.onSocketMessage((res) => {
|
|
|
try {
|
|
try {
|
|
|
// 心跳:服务端回复 pong
|
|
// 心跳:服务端回复 pong
|
|
|
- if (res.data === 'pong') return
|
|
|
|
|
|
|
+ if (res.data === 'pong') {
|
|
|
|
|
+ syncInboxFromServer()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
|
|
const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
|
|
|
|
|
+ console.log('[WS] recv', data)
|
|
|
|
|
+ syncInboxFromServer()
|
|
|
// 文档格式:{ type: 'NEW_MESSAGE', data: { id, sender_id, receiver_id, ... } }
|
|
// 文档格式:{ type: 'NEW_MESSAGE', data: { id, sender_id, receiver_id, ... } }
|
|
|
const msg = data.type === 'NEW_MESSAGE' ? data.data : (data.message ?? data)
|
|
const msg = data.type === 'NEW_MESSAGE' ? data.data : (data.message ?? data)
|
|
|
- if (!msg) return
|
|
|
|
|
|
|
+ if (!msg) {
|
|
|
|
|
+ console.log('[WS] recv (ignored: no message payload)')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
const normalized = normalizeWsMessage({ message: msg })
|
|
const normalized = normalizeWsMessage({ message: msg })
|
|
|
// 需要当前用户 id 判断会话方:若后端推送里带 current_user_id 用那个,否则用 receiver_id 判断
|
|
// 需要当前用户 id 判断会话方:若后端推送里带 current_user_id 用那个,否则用 receiver_id 判断
|
|
|
const currentUserId = data.current_user_id ?? normalized.receiverId
|
|
const currentUserId = data.current_user_id ?? normalized.receiverId
|
|
|
const contactId = getContactIdFromMessage(msg, currentUserId)
|
|
const contactId = getContactIdFromMessage(msg, currentUserId)
|
|
|
- if (contactId != null) {
|
|
|
|
|
|
|
+ if (contactId == null) {
|
|
|
|
|
+ console.log('[WS] recv (ignored: no contactId)', { msg, currentUserId })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ {
|
|
|
const cid = String(contactId)
|
|
const cid = String(contactId)
|
|
|
const list = chatStore.messages[cid] || []
|
|
const list = chatStore.messages[cid] || []
|
|
|
const hasId = normalized.id && list.some((m) => String(m.id) === String(normalized.id))
|
|
const hasId = normalized.id && list.some((m) => String(m.id) === String(normalized.id))
|
|
|
- if (hasId) return
|
|
|
|
|
|
|
+ if (hasId) {
|
|
|
|
|
+ console.log('[WS] recv (ignored: duplicate id)', String(normalized.id))
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
chatStore.appendMessage(cid, normalized)
|
|
chatStore.appendMessage(cid, normalized)
|
|
|
// 前台 & 后台消息通知:若当前不在该会话,则给出提示
|
|
// 前台 & 后台消息通知:若当前不在该会话,则给出提示
|
|
|
const isActive = String(chatStore.activeContactId || '') === String(contactId)
|
|
const isActive = String(chatStore.activeContactId || '') === String(contactId)
|
|
|
- // 别人发给我且不在当前会话:本地未读 +1,并更新底部消息 Tab 角标
|
|
|
|
|
- if (!normalized.isMe && !isActive) {
|
|
|
|
|
- chatStore.incrementUnread(cid)
|
|
|
|
|
- chatStore.updateTabBarUnreadBadge()
|
|
|
|
|
- }
|
|
|
|
|
if (!isActive) {
|
|
if (!isActive) {
|
|
|
const contact = (chatStore.contacts || []).find(
|
|
const contact = (chatStore.contacts || []).find(
|
|
|
(c) => String(c.user_id || c.id) === String(contactId)
|
|
(c) => String(c.user_id || c.id) === String(contactId)
|
|
@@ -154,6 +173,16 @@ function attachSocketListenersOnce() {
|
|
|
// #ifndef APP-PLUS
|
|
// #ifndef APP-PLUS
|
|
|
uni.showToast({ title: `${title}: ${body || '新消息'}`, icon: 'none' })
|
|
uni.showToast({ title: `${title}: ${body || '新消息'}`, icon: 'none' })
|
|
|
// #endif
|
|
// #endif
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 正在该会话对话框内收到新消息:标记本会话全部已读,并刷新未读与列表
|
|
|
|
|
+ const t = getToken()
|
|
|
|
|
+ if (t) {
|
|
|
|
|
+ Promise.resolve(markHistoryReadAll(t, cid))
|
|
|
|
|
+ .then(() =>
|
|
|
|
|
+ Promise.all([fetchUnreadCountAndUpdateTabBar(), fetchContactsList()])
|
|
|
|
|
+ )
|
|
|
|
|
+ .catch(() => {})
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
// 只更新该会话在列表中的最后一条预览与时间,不整表刷新
|
|
// 只更新该会话在列表中的最后一条预览与时间,不整表刷新
|
|
|
let preview = ''
|
|
let preview = ''
|
|
@@ -171,7 +200,7 @@ function attachSocketListenersOnce() {
|
|
|
chatStore.updateContactPreview(cid, { lastMessage: preview, time: normalized.createdAt })
|
|
chatStore.updateContactPreview(cid, { lastMessage: preview, time: normalized.createdAt })
|
|
|
}
|
|
}
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
- console.warn('[WS] parse message error', e)
|
|
|
|
|
|
|
+ console.warn('[WS] parse message error', e, res && res.data)
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -224,9 +253,12 @@ export function disconnectWebSocket() {
|
|
|
performDisconnect()
|
|
performDisconnect()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-export function useWebSocket(fetchContacts) {
|
|
|
|
|
- fetchContactsRef = fetchContacts
|
|
|
|
|
|
|
+/** 启动已登录 / 登录成功后显式建连(与 useWebSocket().connect 相同) */
|
|
|
|
|
+export function connectWebSocket() {
|
|
|
|
|
+ tryConnect()
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
|
|
+export function useWebSocket() {
|
|
|
function connect() {
|
|
function connect() {
|
|
|
tryConnect()
|
|
tryConnect()
|
|
|
}
|
|
}
|