401 lines
13 KiB
JavaScript
401 lines
13 KiB
JavaScript
// 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 };
|