findme-miniprogram-frontend/utils/nim-conversation-manager.js

988 lines
28 KiB
JavaScript
Raw Permalink Normal View History

2025-12-27 17:16:03 +08:00
/**
* NIM SDK 会话管理器
* 负责会话列表的获取监听和管理消息发送
* 基于网易云信 NIM SDK v10.9.50
*/
import { NIM, V2NIMConst } from '../dist/nim'
class NIMConversationManager {
constructor() {
this.nim = null
this.listeners = new Map() // 事件监听器集合
this.conversations = [] // 会话列表缓存
this.isInitialized = false
}
/**
* 初始化会话管理器
* @param {Object} nimInstance - NIM SDK 实例
*/
init(nimInstance) {
if (this.isInitialized) {
console.warn('⚠️ NIM会话管理器已经初始化')
return
}
this.nim = nimInstance
this.isInitialized = true
// 注册 NIM SDK 事件监听
this.registerNIMEvents()
console.log('✅ NIM会话管理器初始化成功')
}
/**
* 注册 NIM SDK 事件监听
*/
registerNIMEvents() {
if (!this.nim) {
console.error('❌ NIM实例不存在无法注册事件')
return
}
try {
console.log('📡 开始注册 NIM SDK 事件监听器...')
// 🔥 使用 addEventListener 方法注册会话监听器(官方推荐方式)
// 会话新增
this.nim.V2NIMConversationService.on('onConversationCreated', (conversation) => {
console.log('📝 NIM 会话新增:', conversation)
this.handleConversationCreated(conversation)
})
// 会话删除
this.nim.V2NIMConversationService.on('onConversationDeleted', (conversationIds) => {
console.log('🗑️ NIM 会话删除:', conversationIds)
this.handleConversationDeleted(conversationIds)
})
// 会话更新
this.nim.V2NIMConversationService.on('onConversationChanged', (conversations) => {
console.log('🔄 NIM 会话更新:', conversations)
this.handleConversationChanged(conversations)
})
// 🔥 使用 addEventListener 方法注册消息监听器(官方推荐方式)
// 接收新消息
this.nim.V2NIMMessageService.on('onReceiveMessages', (messages) => {
console.log('📨 NIM 收到新消息,数量:', messages?.length || 0, messages)
this.handleNewMessages(messages)
})
// 消息发送状态
this.nim.V2NIMMessageService.on('onSendMessage', (message) => {
console.log('📤 NIM 消息发送中:', message)
})
// 消息撤回
this.nim.V2NIMMessageService.on('onMessageRevokeNotifications', (revokeNotifications) => {
console.log('↩️ NIM 消息撤回通知:', revokeNotifications)
this.handleMessageRevoke(revokeNotifications)
})
console.log('✅ NIM事件监听注册成功')
} catch (error) {
console.error('❌ 注册NIM事件监听失败:', error)
}
}
/**
* 获取会话列表
* @param {Number} offset - 偏移
* @param {Number} limit - 分页
* @returns {Promise<Array>} 会话列表
*/
async getConversationList(offset = 0, limit = 10) {
if (!this.nim) {
throw new Error('NIM实例未初始化')
}
try {
console.log('📋 开始获取NIM云端会话列表...')
// 🔥 使用云端会话服务获取会话列表
const result = await this.nim.V2NIMConversationService.getConversationList(
offset, limit
// {
// offset: options.offset || 0,
// limit: options.limit || 100
// }
)
console.log('✅ 获取云端会话列表成功:', result)
// 转换为统一格式
const conversations = this.normalizeConversations(result?.conversationList || [])
// 更新缓存
this.conversations = conversations
return conversations
} catch (error) {
console.error('❌ 获取云端会话列表失败:', error)
throw error
}
}
/**
* NIM 会话数据转换为应用统一格式
* @param {Array} nimConversations - NIM会话数据
* @returns {Array} 标准化的会话列表
*/
normalizeConversations(nimConversations) {
if (!Array.isArray(nimConversations)) {
return []
}
return nimConversations.map(conv => this.normalizeConversation(conv))
}
/**
* 标准化单个会话
* @param {Object} conv - NIM会话对象
* @returns {Object} 标准化的会话对象
*/
normalizeConversation(conv) {
if (!conv) return null
// 解析会话类型
const conversationType = conv.conversationType || conv.type
const isGroupChat = conversationType === V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM
// 获取最后一条消息
const lastMessage = conv.lastMessage || {}
const lastMessageText = this.getMessagePreview(lastMessage)
// 🔥 正确获取时间V2NIMLastMessage.messageRefer.createTime
const messageRefer = lastMessage.messageRefer || {}
const messageTime = messageRefer.createTime || conv.updateTime || 0
return {
// 基础信息
id: conv.conversationId,
conversationId: conv.conversationId,
type: isGroupChat ? 'group' : 'single',
chatType: isGroupChat ? 1 : 0,
// 对方信息
targetId: this.extractTargetId(conv.conversationId, isGroupChat),
name: conv.name || conv.conversationName || '未知',
avatar: conv.avatar || '',
// 会话状态
isPinned: conv.stickTop || false,
isTop: conv.stickTop || false,
isMuted: conv.mute || false,
unreadCount: conv.unreadCount || 0,
// 🔥 最后消息信息 - 使用消息时间messageRefer.createTime
lastMessage: lastMessageText || '',
lastMessageType: this.getMessageType(lastMessage),
lastMessageTime: this.formatMessageTime(messageTime),
lastMsgTime: messageTime,
messageStatus: this.getMessageStatus(lastMessage),
// 在线状态(单聊)
isOnline: false, // 需要额外获取
// 原始 NIM 数据(用于调试)
_nimData: conv
}
}
/**
* 从会话ID中提取目标用户ID
* @param {String} conversationId - 会话ID
* @param {Boolean} isGroup - 是否是群聊
* @returns {String} 目标ID
*/
extractTargetId(conversationId, isGroup) {
if (!conversationId) return ''
// NIM会话ID格式: p2p-账号 或 team-群ID
if (isGroup) {
return conversationId.replace('team-', '')
} else {
return conversationId.replace('p2p-', '')
}
}
/**
* 获取消息预览文本
* @param {Object} message - 消息对象
* @returns {String} 预览文本
*/
getMessagePreview(message) {
if (!message) return ''
const messageType = message.messageType
// 兼容处理:如果 messageType 不存在,尝试从 text 字段获取文本
if (messageType === undefined || messageType === null) {
return message.text || ''
}
switch (messageType) {
case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_TEXT:
return message.text || ''
case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_IMAGE:
return '[图片]'
case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_AUDIO:
return '[语音]'
case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_VIDEO:
return '[视频]'
case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_FILE:
return '[文件]'
case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_LOCATION:
return '[位置]'
case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_NOTIFICATION:
return '[通知]'
case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_TIP:
return '[提示]'
case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CUSTOM:
return '[自定义消息]'
default:
return '[未知消息]'
}
}
/**
* 获取消息类型
* @param {Object} message - 消息对象
* @returns {String} 消息类型
*/
getMessageType(message) {
if (!message || !message.messageType) return 'text'
const typeMap = {
[V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_TEXT]: 'text',
[V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_IMAGE]: 'image',
[V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_AUDIO]: 'audio',
[V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_VIDEO]: 'video',
[V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_FILE]: 'file',
[V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_LOCATION]: 'location',
[V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_NOTIFICATION]: 'notification',
[V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_TIP]: 'tip',
[V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CUSTOM]: 'custom'
}
return typeMap[message.messageType] || 'text'
}
/**
* 获取消息状态
* @param {Object} message - 消息对象
* @returns {String} 消息状态
*/
getMessageStatus(message) {
if (!message) return 'sent'
if (message.sendingState === V2NIMConst.V2NIMMessageSendingState.V2NIM_MESSAGE_SENDING_STATE_SENDING) {
return 'sending'
} else if (message.sendingState === V2NIMConst.V2NIMMessageSendingState.V2NIM_MESSAGE_SENDING_STATE_FAILED) {
return 'failed'
} else {
return 'sent'
}
}
/**
* 格式化消息时间Telegram风格
* @param {Number} timestamp - 时间戳
* @returns {String} 格式化的时间
*/
formatMessageTime(timestamp) {
if (!timestamp) return ''
const now = new Date()
const msg = new Date(timestamp)
// 今天HH:mm
if (now.toDateString() === msg.toDateString()) {
const hh = msg.getHours().toString().padStart(2, '0')
const mm = msg.getMinutes().toString().padStart(2, '0')
return `${hh}:${mm}`
}
// 昨天
const yesterday = new Date(now)
yesterday.setDate(yesterday.getDate() - 1)
if (yesterday.toDateString() === msg.toDateString()) {
return '昨天'
}
// 更早YYYY/MM/DD
const year = msg.getFullYear()
const month = (msg.getMonth() + 1).toString().padStart(2, '0')
const day = msg.getDate().toString().padStart(2, '0')
return `${year}/${month}/${day}`
}
/**
* 处理会话新增
*/
handleConversationCreated(conversation) {
const normalized = this.normalizeConversation(conversation)
if (normalized) {
this.conversations.unshift(normalized)
this.emit('conversation_created', normalized)
}
}
/**
* 处理会话删除
*/
handleConversationDeleted(conversationIds) {
if (!Array.isArray(conversationIds)) return
this.conversations = this.conversations.filter(
conv => !conversationIds.includes(conv.conversationId)
)
this.emit('conversation_deleted', conversationIds)
}
/**
* 处理会话更新
*/
handleConversationChanged(conversations) {
if (!Array.isArray(conversations)) return
conversations.forEach(updatedConv => {
const normalized = this.normalizeConversation(updatedConv)
if (!normalized) return
const index = this.conversations.findIndex(
conv => conv.conversationId === normalized.conversationId
)
if (index >= 0) {
this.conversations[index] = normalized
} else {
this.conversations.unshift(normalized)
}
})
this.emit('conversation_changed', this.conversations)
}
/**
* 处理新消息更新会话
*/
handleNewMessages(messages) {
if (!Array.isArray(messages)) {
console.warn('⚠️ handleNewMessages 收到非数组消息:', messages)
return
}
console.log('📨 处理新消息,数量:', messages.length)
let hasUpdates = false
messages.forEach(message => {
const conversationId = message.conversationId
if (!conversationId) {
console.warn('⚠️ 消息缺少 conversationId:', message)
return
}
const index = this.conversations.findIndex(
conv => conv.conversationId === conversationId
)
if (index >= 0) {
// 更新现有会话
const conv = this.conversations[index]
conv.lastMessage = this.getMessagePreview(message)
conv.lastMessageType = this.getMessageType(message)
// 🔥 使用消息的 createTime如果没有则保持原时间
const messageTime = message.createTime || conv.lastMsgTime || 0
conv.lastMessageTime = this.formatMessageTime(messageTime)
conv.lastMsgTime = messageTime
conv.unreadCount = (conv.unreadCount || 0) + 1
// 移到列表顶部
this.conversations.splice(index, 1)
this.conversations.unshift(conv)
hasUpdates = true
console.log('✅ 已更新会话:', conv.name || conversationId, '未读数:', conv.unreadCount)
} else {
console.log('⚠️ 会话不在本地列表中conversationId:', conversationId)
}
})
if (hasUpdates) {
console.log('📤 触发 new_message 事件,通知页面刷新')
}
// 🔥 无论会话是否存在于本地列表,都触发事件让页面处理
// 页面会根据情况决定是更新现有会话还是重新加载会话列表
this.emit('new_message', messages)
}
/**
* 处理消息撤回
*/
handleMessageRevoke(revokeNotifications) {
if (!Array.isArray(revokeNotifications)) return
revokeNotifications.forEach(notification => {
const conversationId = notification.conversationId
if (!conversationId) return
const conv = this.conversations.find(
c => c.conversationId === conversationId
)
if (conv && notification.messageRefer) {
// 如果撤回的是最后一条消息,更新预览
conv.lastMessage = '已撤回'
conv.lastMessageType = 'system'
}
})
this.emit('message_revoked', revokeNotifications)
}
/**
* 置顶/取消置顶会话
* @param {String} conversationId - 会话ID
* @param {Boolean} stickTop - 是否置顶
*/
async setConversationStickTop(conversationId, stickTop) {
if (!this.nim) {
throw new Error('NIM实例未初始化')
}
try {
// 🔥 使用云端会话服务设置置顶
await this.nim.V2NIMConversationService.setConversationStickTop(
conversationId,
stickTop
)
// 更新本地缓存
const conv = this.conversations.find(c => c.conversationId === conversationId)
if (conv) {
conv.isPinned = stickTop
conv.isTop = stickTop
}
console.log(`${stickTop ? '置顶' : '取消置顶'}会话成功:`, conversationId)
return true
} catch (error) {
console.error('❌ 设置会话置顶失败:', error)
throw error
}
}
/**
* 删除会话
* @param {String} conversationId - 会话ID
*/
async deleteConversation(conversationId) {
if (!this.nim) {
throw new Error('NIM实例未初始化')
}
try {
// 🔥 使用云端会话服务删除会话
await this.nim.V2NIMConversationService.deleteConversation(conversationId)
// 从缓存中移除
this.conversations = this.conversations.filter(
conv => conv.conversationId !== conversationId
)
console.log('✅ 删除会话成功:', conversationId)
return true
} catch (error) {
console.error('❌ 删除会话失败:', error)
throw error
}
}
/**
* 清空会话未读数标记会话已读
* @param {String} conversationId - 会话ID
*/
async clearUnreadCount(conversationId) {
if (!this.nim) {
throw new Error('NIM实例未初始化')
}
if (!conversationId || !conversationId.trim()) {
throw new Error('会话ID不能为空')
}
try {
console.log('📖 开始清空会话未读数 (NIM SDK V2):', conversationId)
// 🔥 NIM SDK V2 使用 clearUnreadCountByIds 方法清除未读数
// 参数是会话ID数组
await this.nim.V2NIMConversationService.clearUnreadCountByIds([conversationId])
// 更新本地缓存
const conv = this.conversations.find(c => c.conversationId === conversationId)
if (conv) {
conv.unreadCount = 0
}
console.log('✅ 清空会话未读数成功:', conversationId)
return true
} catch (error) {
console.error('❌ 清空会话未读数失败:', error)
throw error
}
}
/**
* 标记会话已读别名方法方便调用
* @param {String} conversationId - 会话ID
*/
async markConversationRead(conversationId) {
return await this.clearUnreadCount(conversationId)
}
/**
* 获取总未读数
* @returns {Number} 总未读数
*/
getTotalUnreadCount() {
return this.conversations.reduce((sum, conv) => sum + (conv.unreadCount || 0), 0)
}
/**
* 注册事件监听器
* @param {String} event - 事件名称
* @param {Function} callback - 回调函数
*/
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, [])
}
this.listeners.get(event).push(callback)
}
/**
* 移除事件监听器
* @param {String} event - 事件名称
* @param {Function} callback - 回调函数
*/
off(event, callback) {
if (!this.listeners.has(event)) return
const callbacks = this.listeners.get(event)
const index = callbacks.indexOf(callback)
if (index > -1) {
callbacks.splice(index, 1)
}
}
/**
* 触发事件
* @param {String} event - 事件名称
* @param {*} data - 事件数据
*/
emit(event, data) {
if (!this.listeners.has(event)) return
const callbacks = this.listeners.get(event)
callbacks.forEach(callback => {
try {
callback(data)
} catch (error) {
console.error(`❌ 执行事件回调失败 [${event}]:`, error)
}
})
}
/**
* 销毁管理器
*/
destroy() {
this.listeners.clear()
this.conversations = []
this.nim = null
this.isInitialized = false
console.log('✅ NIM会话管理器已销毁')
}
// ==================== 🔥 消息发送功能 ====================
/**
* 发送文本消息
* @param {String} conversationId - 会话ID
* @param {String} text - 消息内容
* @param {Object} options - 可选参数
* @returns {Promise<Object>} 发送结果
*/
async sendTextMessage(conversationId, text, options = {}) {
if (!this.nim || !this.nim.V2NIMMessageService) {
throw new Error('NIM消息服务未初始化')
}
if (!conversationId || !conversationId.trim()) {
throw new Error('会话ID不能为空')
}
if (!text || !text.trim()) {
throw new Error('消息内容不能为空')
}
try {
console.log('📤 发送文本消息:', { conversationId, text })
// 创建文本消息
const message = this.nim.V2NIMMessageCreator.createTextMessage(text)
// 发送消息
const sendResult = await this.nim.V2NIMMessageService.sendMessage(
message,
conversationId
)
console.log('✅ 文本消息发送成功')
// NIM SDK 返回的是包装对象,真正的消息在 sendResult.message 中
const actualMessage = sendResult.message || sendResult
return {
success: true,
messageId: actualMessage.messageClientId,
serverId: actualMessage.messageServerId,
createTime: actualMessage.createTime,
conversationId: conversationId,
message: actualMessage
}
} catch (error) {
console.error('❌ 发送文本消息失败:', error)
throw error
}
}
/**
* 发送图片消息
* @param {String} conversationId - 会话ID
* @param {String} imagePath - 图片路径本地临时路径或URL
* @param {Object} options - 可选参数
* @returns {Promise<Object>} 发送结果
*/
async sendImageMessage(conversationId, imagePath, options = {}) {
if (!this.nim || !this.nim.V2NIMMessageService) {
throw new Error('NIM消息服务未初始化')
}
if (!conversationId || !conversationId.trim()) {
throw new Error('会话ID不能为空')
}
if (!imagePath) {
throw new Error('图片路径不能为空')
}
try {
console.log('📤 发送图片消息:', { conversationId, imagePath })
// 创建图片消息
const message = this.nim.V2NIMMessageCreator.createImageMessage(imagePath)
// 发送消息
const sendResult = await this.nim.V2NIMMessageService.sendMessage(
message,
conversationId
)
console.log('✅ 图片消息发送成功')
const actualMessage = sendResult.message || sendResult
return {
success: true,
conversationId,
messageId: actualMessage.messageClientId,
serverId: actualMessage.messageServerId,
createTime: actualMessage.createTime,
message: actualMessage
}
} catch (error) {
console.error('❌ 发送图片消息失败:', error)
throw error
}
}
/**
* 发送语音消息
* @param {String} conversationId - 会话ID直接使用当前会话ID
* @param {String} audioPath - 语音文件路径微信小程序临时文件路径
* @param {Number} duration - 语音时长毫秒
* @param {Object} options - 可选参数
* @returns {Promise<Object>} 发送结果
*/
async sendAudioMessage(conversationId, audioPath, duration, options = {}) {
if (!this.nim || !this.nim.V2NIMMessageService) {
throw new Error('NIM消息服务未初始化')
}
if (!conversationId || !conversationId.trim()) {
throw new Error('会话ID不能为空')
}
if (!audioPath || typeof audioPath !== 'string' || !audioPath.trim()) {
console.error('❌ 语音文件路径无效:', audioPath);
throw new Error('语音文件路径不能为空')
}
try {
console.log('📤 发送语音消息:', { conversationId, audioPath, duration })
// 🔥 小程序环境下创建语音消息需要使用特殊参数
// 参考: https://doc.yunxin.163.com/messaging2/guide/Dk0MTA5MDI?platform=miniapp
const message = this.nim.V2NIMMessageCreator.createAudioMessage(
audioPath // 微信小程序临时文件路径
)
console.log('✅ 创建语音消息对象成功')
// 发送消息
const sendResult = await this.nim.V2NIMMessageService.sendMessage(
message,
conversationId
)
console.log('✅ 语音消息发送成功')
const actualMessage = sendResult.message || sendResult
return {
success: true,
conversationId,
messageId: actualMessage.messageClientId,
serverId: actualMessage.messageServerId,
createTime: actualMessage.createTime,
message: actualMessage
}
} catch (error) {
console.error('❌ 发送语音消息失败:', error)
throw error
}
}
/**
* 发送视频消息
* @param {String} conversationId - 会话ID直接使用当前会话ID
* @param {String} videoPath - 视频文件路径
* @param {Number} duration - 视频时长毫秒
* @param {Object} options - 可选参数
* @returns {Promise<Object>} 发送结果
*/
async sendVideoMessage(conversationId, videoPath, duration, options = {}) {
if (!this.nim || !this.nim.V2NIMMessageService) {
throw new Error('NIM消息服务未初始化')
}
if (!conversationId || !conversationId.trim()) {
throw new Error('会话ID不能为空')
}
if (!videoPath) {
throw new Error('视频文件路径不能为空')
}
try {
console.log('📤 发送视频消息:', { conversationId, duration })
// 创建视频消息
const message = this.nim.V2NIMMessageCreator.createVideoMessage(videoPath)
// 发送消息
const sendResult = await this.nim.V2NIMMessageService.sendMessage(
message,
conversationId
)
console.log('✅ 视频消息发送成功')
const actualMessage = sendResult.message || sendResult
return {
success: true,
conversationId,
messageId: actualMessage.messageClientId,
serverId: actualMessage.messageServerId,
createTime: actualMessage.createTime,
message: actualMessage
}
} catch (error) {
console.error('❌ 发送视频消息失败:', error)
throw error
}
}
/**
* 发送文件消息
* @param {String} conversationId - 会话ID直接使用当前会话ID
* @param {String} filePath - 文件路径
* @param {Object} options - 可选参数
* @returns {Promise<Object>} 发送结果
*/
async sendFileMessage(conversationId, filePath, options = {}) {
if (!this.nim || !this.nim.V2NIMMessageService) {
throw new Error('NIM消息服务未初始化')
}
if (!conversationId || !conversationId.trim()) {
throw new Error('会话ID不能为空')
}
if (!filePath) {
throw new Error('文件路径不能为空')
}
try {
console.log('📤 发送文件消息:', { conversationId })
// 创建文件消息
const message = this.nim.V2NIMMessageCreator.createFileMessage(filePath)
// 发送消息
const sendResult = await this.nim.V2NIMMessageService.sendMessage(
message,
conversationId
)
console.log('✅ 文件消息发送成功')
const actualMessage = sendResult.message || sendResult
return {
success: true,
conversationId,
messageId: actualMessage.messageClientId,
serverId: actualMessage.messageServerId,
createTime: actualMessage.createTime,
message: actualMessage
}
} catch (error) {
console.error('❌ 发送文件消息失败:', error)
throw error
}
}
/**
* 发送位置消息
* @param {String} conversationId - 会话ID直接使用当前会话ID
* @param {Number} latitude - 纬度
* @param {Number} longitude - 经度
* @param {String} address - 地址描述
* @param {Object} options - 可选参数
* @returns {Promise<Object>} 发送结果
*/
async sendLocationMessage(conversationId, latitude, longitude, address, options = {}) {
if (!this.nim || !this.nim.V2NIMMessageService) {
throw new Error('NIM消息服务未初始化')
}
if (!conversationId || !conversationId.trim()) {
throw new Error('会话ID不能为空')
}
if (typeof latitude !== 'number' || typeof longitude !== 'number') {
throw new Error('经纬度必须是数字')
}
try {
console.log('📤 发送位置消息:', { conversationId, latitude, longitude })
// 创建位置消息
const message = this.nim.V2NIMMessageCreator.createLocationMessage(latitude, longitude, address || '')
// 发送消息
const sendResult = await this.nim.V2NIMMessageService.sendMessage(
message,
conversationId
)
console.log('✅ 位置消息发送成功')
const actualMessage = sendResult.message || sendResult
return {
success: true,
conversationId,
messageId: actualMessage.messageClientId,
serverId: actualMessage.messageServerId,
createTime: actualMessage.createTime,
message: actualMessage
}
} catch (error) {
console.error('❌ 发送位置消息失败:', error)
throw error
}
}
/**
* 撤回消息
* @param {String} conversationId - 会话ID
* @param {Object} message - 消息对象必须包含完整的消息信息
* @returns {Promise<Object>} 撤回结果
*/
async recallMessage(conversationId, message) {
if (!this.nim) {
throw new Error('NIM实例未初始化')
}
try {
console.log('↩️ 撤回消息:', { conversationId, messageId: message?.messageServerId || message?.messageClientId })
// 🔥 使用 NIM SDK V2 的 revokeMessage 方法
// 根据官方文档revokeMessage 需要传入完整的消息对象和撤回参数
// 参数1: message - 完整的消息对象
// 参数2: revokeParams - V2NIMMessageRevokeParams 对象(可选)
const revokeResult = await this.nim.V2NIMMessageService.revokeMessage(
message,
{
serverExtension: '', // 服务端扩展字段(可选)
postscript: '' // 撤回附言(可选)
}
)
console.log('✅ 消息撤回成功:', revokeResult)
return {
success: true,
conversationId,
messageId: message.messageServerId || message.messageClientId,
revokeTime: Date.now()
}
} catch (error) {
console.error('❌ 撤回消息失败:', error)
throw error
}
}
/**
* 检查管理器是否已初始化
* @returns {Boolean} 是否已初始化
*/
getInitStatus() {
return this.isInitialized && this.nim !== null
}
}
// 导出单例
const nimConversationManager = new NIMConversationManager()
module.exports = nimConversationManager