// utils/chat-core.js — 全局 IM 连接、监听、角标(稳定版) import GoEasy from '../static/lib/goeasy-2.13.24.esm.min'; import { resolveAvatarUrl } from './avatar.js'; import { getLocalImUserId, getLocalImIdentity } from './im-user.js'; let _convListener = null; let _privateListener = null; let _groupListener = null; let _notificationHandler = null; let _heartbeatTimer = null; let _listenersBoundForUser = null; let _lastGroupSubscribeAt = 0; const _notifiedMessageIds = new Map(); const NOTIFY_MAX_AGE_MS = 15000; function shouldShowNotification(message) { if (!message) return false; const id = message.messageId || `${message.senderId}_${message.timestamp}`; if (_notifiedMessageIds.has(id)) return false; if (message.timestamp && Date.now() - message.timestamp > NOTIFY_MAX_AGE_MS) return false; _notifiedMessageIds.set(id, Date.now()); if (_notifiedMessageIds.size > 400) { const cutoff = Date.now() - 60000; _notifiedMessageIds.forEach((t, k) => { if (t < cutoff) _notifiedMessageIds.delete(k); }); } return true; } function bindGlobalListenersIfNeeded(app, userId) { if (_listenersBoundForUser === userId && _convListener) return; registerGlobalListeners(app); registerNotification(app); _listenersBoundForUser = userId; } function initGlobalMessageSystem(app) { app.ensureConnection = () => ensureConnection(app); app.connectForCurrentRole = () => ensureConnection(app); app.loadConversations = () => loadConversations(app); app.disconnectGoEasy = () => disconnectGoEasy(app); app.switchRoleAndReconnect = (newRole) => switchRoleAndReconnect(app, newRole); app.updateUnreadCount = (count) => updateTabBarBadge(app, count || 0); app.reregisterNotification = () => registerNotification(app); app.startImWhenReady = () => startImWhenReady(app); if (!app.globalData.messageManager) { app.globalData.messageManager = {}; } try { const muted = wx.getStorageSync('notificationMuted'); if (muted !== undefined) app.globalData.messageManager.notificationMuted = muted; } catch (e) {} initAppStateListener(app); initNetworkListener(app); startHeartbeat(app); startImWhenReady(app); } /** GoEasy 未就绪时轮询,登录后尽早建立监听 */ function startImWhenReady(app, maxAttempts = 40) { let attempts = 0; const tick = () => { if (!wx.getStorageSync('uid')) return; attempts += 1; if (!wx.goEasy) { if (attempts < maxAttempts) setTimeout(tick, 250); return; } ensureConnection(app); }; tick(); } function getCurrentUserId() { return (wx.goEasy && wx.goEasy.im && wx.goEasy.im.userId) || null; } function getConnectionStatus() { if (!wx.goEasy || !wx.goEasy.getConnectionStatus) return 'disconnected'; return wx.goEasy.getConnectionStatus(); } function isConnected() { const s = getConnectionStatus(); return s === 'connected' || s === 'reconnected'; } /** 统一会话数据结构,避免不同回调格式导致列表空白 */ function normalizeConversationsPayload(result) { if (!result) return { conversations: [], unreadTotal: 0 }; const content = result.content || result; const conversations = content.conversations || result.conversations || []; const unreadTotal = content.unreadTotal ?? result.unreadTotal ?? 0; return { conversations, unreadTotal }; } function emitConversationsUpdated(app, result) { const payload = normalizeConversationsPayload(result); updateTabBarBadge(app, payload.unreadTotal); app.emitEvent('conversationsUpdated', { content: payload, unreadTotal: payload.unreadTotal, raw: result, }); } async function ensureConnection(app) { const uid = wx.getStorageSync('uid'); if (!uid || !wx.goEasy) return; const targetUserId = getLocalImUserId(app); const identityType = getLocalImIdentity(app); if (!targetUserId) return; if (isConnected()) { const currentUserId = getCurrentUserId(); if (currentUserId === targetUserId) { bindGlobalListenersIfNeeded(app, targetUserId); loadConversations(app); const now = Date.now(); if (now - _lastGroupSubscribeAt > 60000) { _lastGroupSubscribeAt = now; await subscribeAllGroups(app); } return; } try { wx.goEasy.disconnect(); } catch (e) {} } connectGoEasy(app, targetUserId, identityType); } function connectGoEasy(app, userId, identityType) { const defAvatar = resolveAvatarUrl('', app); const tx = wx.getStorageSync('touxiang') || ''; const uid = wx.getStorageSync('uid') || ''; const nick = wx.getStorageSync('nicheng') || app.globalData.nicheng || app.globalData.dashouNicheng || ''; const name = nick || ('用户' + (uid || '').substring(0, 6)); const avatar = resolveAvatarUrl(tx, app) || defAvatar; wx.goEasy.connect({ id: userId, data: { name, avatar, identityType: identityType === 'normal' ? 'boss' : identityType, }, onSuccess: async () => { try { wx.setStorageSync('savedGoEasyConnection', JSON.stringify({ userId, identityType, lastConnectTime: Date.now(), status: 'connected', })); } catch (e) {} bindGlobalListenersIfNeeded(app, userId); _lastGroupSubscribeAt = Date.now(); await subscribeAllGroups(app); loadConversations(app); }, onFailed: (error) => { console.error('GoEasy连接失败', error); if (error.code === 408) { bindGlobalListenersIfNeeded(app, userId); loadConversations(app); } else { wx.showToast({ title: '连接失败,请重试', icon: 'none' }); } }, }); } function subscribeAllGroups(app) { return new Promise((resolve) => { if (!wx.goEasy || !wx.goEasy.im) return resolve(); wx.goEasy.im.latestConversations({ onSuccess: (result) => { const payload = normalizeConversationsPayload(result); const groupIds = payload.conversations .filter((c) => c.type === 'group' && c.groupId) .map((c) => c.groupId); if (groupIds.length && wx.goEasy.im.subscribeGroup) { wx.goEasy.im.subscribeGroup({ groupIds, onSuccess: () => resolve(), onFailed: () => resolve(), }); } else { resolve(); } }, onFailed: () => resolve(), }); }); } function registerGlobalListeners(app) { if (!wx.goEasy || !wx.goEasy.im) return; if (_convListener) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, _convListener); } if (_privateListener) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _privateListener); } if (_groupListener) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _groupListener); } _convListener = (result) => { emitConversationsUpdated(app, result); }; wx.goEasy.im.on(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, _convListener); _privateListener = () => { loadConversations(app); }; wx.goEasy.im.on(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _privateListener); _groupListener = () => { loadConversations(app); }; wx.goEasy.im.on(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _groupListener); } function loadConversations(app) { if (!wx.goEasy || !wx.goEasy.im) return; if (!getCurrentUserId()) return; wx.goEasy.im.latestConversations({ onSuccess: (result) => { emitConversationsUpdated(app, result); subscribeAllGroups(app); }, onFailed: (err) => { console.error('加载会话失败', err); }, }); } function updateTabBarBadge(app, unreadCount) { const badgeText = unreadCount > 0 ? (unreadCount > 99 ? '99+' : String(unreadCount)) : ''; if (!app.globalData.messageManager) app.globalData.messageManager = {}; app.globalData.messageManager.unreadTotal = unreadCount; app.globalData.messageManager.tabBarBadgeText = badgeText; app.emitEvent('tabBarBadgeChanged', { badgeText, showRedDot: unreadCount > 0 }); try { const pages = getCurrentPages(); if (pages.length) { const page = pages[pages.length - 1]; const tabBar = page.selectComponent('#tab-bar'); if (tabBar && tabBar.setData) tabBar.setData({ badgeText }); if (page.getTabBar) { const tb = page.getTabBar(); if (tb && tb.setData) tb.setData({ badgeText }); } } } catch (e) {} wx.setStorageSync('messageUnreadTotal', unreadCount); } function registerNotification(app) { if (!wx.goEasy || !wx.goEasy.im) return; if (_notificationHandler) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _notificationHandler); wx.goEasy.im.off(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _notificationHandler); } _notificationHandler = (message) => { if (app.globalData.messageManager && app.globalData.messageManager.notificationMuted) return; if (!shouldShowNotification(message)) return; const pages = getCurrentPages(); if (pages.length) { const curPage = pages[pages.length - 1]; const route = curPage.route || ''; if (route === 'pages/chat/chat' || route === 'pages/group-chat/group-chat') return; } const senderName = message.senderData?.name || (message.senderId || '用户'); let content = ''; if (message.type === 'text') content = message.payload?.text || '[文本]'; else if (message.type === 'image') content = '[图片]'; else if (message.type === 'audio') content = '[语音]'; else content = '[新消息]'; const avatar = resolveAvatarUrl(message.senderData?.avatar, app); if (app.globalData.globalNotification && app.globalData.globalNotification.show) { app.globalData.globalNotification.show({ senderName, content, avatar, message, notificationType: message.groupId ? 'group' : 'private', groupId: message.groupId, orderId: message.orderId, }); } else { wx.showToast({ title: `${senderName}: ${content}`, icon: 'none', duration: 2000 }); } }; wx.goEasy.im.on(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _notificationHandler); wx.goEasy.im.on(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _notificationHandler); } function detachGlobalListeners() { if (!wx.goEasy || !wx.goEasy.im) return; if (_convListener) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, _convListener); _convListener = null; } if (_privateListener) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _privateListener); _privateListener = null; } if (_groupListener) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _groupListener); _groupListener = null; } if (_notificationHandler) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _notificationHandler); wx.goEasy.im.off(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _notificationHandler); _notificationHandler = null; } _listenersBoundForUser = null; } function disconnectGoEasy(app) { stopHeartbeat(app); detachGlobalListeners(); if (wx.goEasy && wx.goEasy.disconnect) { wx.goEasy.disconnect({ onSuccess: () => {}, onFailed: () => {} }); } wx.removeStorageSync('savedGoEasyConnection'); } async function switchRoleAndReconnect(app, newRole) { disconnectGoEasy(app); app.globalData.currentRole = newRole; wx.setStorageSync('currentRole', newRole); if (app.globalData.primaryRole !== newRole && ['normal', 'dashou', 'shangjia'].includes(newRole)) { app.globalData.primaryRole = newRole; wx.setStorageSync('primaryRole', newRole); } startHeartbeat(app); await ensureConnection(app); return true; } function startHeartbeat(app) { stopHeartbeat(app); _heartbeatTimer = setInterval(() => { if (!wx.getStorageSync('uid')) return; if (isConnected()) { loadConversations(app); } else { ensureConnection(app); } }, 45000); } function stopHeartbeat(app) { if (_heartbeatTimer) { clearInterval(_heartbeatTimer); _heartbeatTimer = null; } } function initAppStateListener(app) { wx.onAppShow(() => { setTimeout(() => { if (!wx.getStorageSync('uid')) return; if (!isConnected()) { ensureConnection(app); } else { loadConversations(app); setTimeout(() => { const unread = (app.globalData.messageManager && app.globalData.messageManager.unreadTotal) || 0; updateTabBarBadge(app, unread); }, 300); } }, 200); }); } function initNetworkListener(app) { wx.onNetworkStatusChange((res) => { if (res.isConnected) { setTimeout(() => ensureConnection(app), 1000); } }); } module.exports = { initGlobalMessageSystem };