import ConnectionStatus from "./wfc/client/connectionStatus"; import Vue from "vue"; import wfc from "./wfc/client/wfc"; import EventType from "./wfc/client/wfcEvent"; import ConversationType from "./wfc/model/conversationType"; import { eq, gt, numberValue } from "./wfc/util/longUtil"; import helper from "./ui/util/helper"; import convert from "./vendor/pinyin"; import GroupType from "./wfc/model/groupType"; import { imageThumbnail, videoDuration, videoThumbnail } from "./ui/util/imageUtil"; import MessageContentMediaType from "./wfc/messages/messageContentMediaType"; import Conversation from "./wfc/model/conversation"; import MessageContentType from "./wfc/messages/messageContentType"; import MessageStatus from "./wfc/messages/messageStatus"; import Message from "./wfc/messages/message"; import ImageMessageContent from "./wfc/messages/imageMessageContent"; import VideoMessageContent from "./wfc/messages/videoMessageContent"; import FileMessageContent from "./wfc/messages/fileMessageContent"; import Push from "push.js"; import MessageConfig from "./wfc/client/messageConfig"; import PersistFlag from "./wfc/messages/persistFlag"; import ForwardType from "./ui/main/conversation/message/forward/ForwardType"; import TextMessageContent from "./wfc/messages/textMessageContent"; import { currentWindow, ipcRenderer, isElectron /*remote*/ } from "./platform"; import SearchType from "./wfc/model/searchType"; import Config from "./config"; import { getItem, setItem } from "./ui/util/storageHelper"; import CompositeMessageContent from "./wfc/messages/compositeMessageContent"; import { stringValue, longValue } from "./wfc/util/longUtil"; import DismissGroupNotification from "./wfc/messages/notification/dismissGroupNotification"; import KickoffGroupMemberNotification from "./wfc/messages/notification/kickoffGroupMemberNotification"; import QuitGroupNotification from "./wfc/messages/notification/quitGroupNotification"; import avenginekitproxy from "./wfc/av/engine/avenginekitproxy"; import MediaMessageContent from "./wfc/messages/mediaMessageContent"; import UnreadCount from "./wfc/model/unreadCount"; import LeaveChannelChatMessageContent from "./wfc/messages/leaveChannelChatMessageContent"; import EnterChannelChatMessageContent from "./wfc/messages/enterChannelChatMessageContent"; import ArticlesMessageContent from "./wfc/messages/articlesMessageContent"; import NullUserInfo from "./wfc/model/nullUserInfo"; import NullGroupInfo from "./wfc/model/nullGroupInfo"; // import { genGroupPortrait } from "./ui/util/imageUtil"; import IPCEventType from "@/wfc/ipcEventType"; import NullChannelInfo from "./wfc/model/NullChannelInfo"; import ModifyGroupSettingNotification from "./wfc/messages/notification/modifyGroupSettingNotification"; /** * 一些说明 * _开头的字段,是为了UI层展示方便,而打补丁上出去的 * __开头的字段,仅内部使用 * _开头的函数,是内部函数 * 外部不直接更新字段,而是通过提供各种action方法更新 */ let store = { debug: true, state: { conversation: { _wfc: wfc, currentConversationInfo: null, conversationInfoList: [], currentConversationMessageList: [], currentConversationOldestMessageId: 0, currentConversationOldestMessageUid: 0, currentConversationRead: null, // TODO 调用setUserEnableReceipt时,需要更新 isMessageReceiptEnable: false, inputtingUser: null, inputClearHandler: null, shouldAutoScrollToBottom: true, previewMediaItems: [], previewMediaIndex: null, enableMessageMultiSelection: false, showChannelMenu: false, quotedMessage: null, // 为什么不用 map? // map 里面的元素并不是 reactive 的 downloadingMessages: [], sendingMessages: [], floatingConversations: [], currentVoiceMessage: null, contextMenuConversationInfo: null, _reset() { this.currentConversationInfo = null; this.conversationInfoList = []; this.currentConversationMessageList = []; this.currentConversationOldestMessageId = 0; this.currentConversationOldestMessageUid = 0; this.currentConversationRead = null; this.isMessageReceiptEnable = false; this.inputtingUser = null; this.inputClearHandler = null; this.shouldAutoScrollToBottom = true; this.previewMediaItems = []; this.previewMediaIndex = null; this.enableMessageMultiSelection = false; this.showChannelMenu = false; this.quotedMessage = null; this.downloadingMessages = []; this.sendingMessages = []; this.floatingConversations = []; this.currentVoiceMessage = null; this.contextMenuConversationInfo = null; }, }, contact: { currentFriendRequest: null, currentGroup: null, currentChannel: null, currentFriend: null, currentOrganization: null, currentUser: null, expandFriendRequestList: false, expandFriendList: true, expandGroup: false, expandChanel: false, expandOrganization: false, unreadFriendRequestCount: 0, friendList: [], friendRequestList: [], favGroupList: [], channelList: [], organizationList: [], favContactList: [], selfUserInfo: null, contextMenuUserInfo: null, _reset() { this.currentFriendRequest = null; this.currentGroup = null; this.currentChannel = null; this.currentFriend = null; this.currentOrganization = null; this.currentUser = null; this.expandFriendRequestList = false; this.expandFriendList = true; this.expandGroup = false; this.expandChanel = false; this.unreadFriendRequestCount = 0; this.friendList = []; this.friendRequestList = []; this.favGroupList = []; this.channelList = []; this.organizationList = []; this.favContactList = []; this.selfUserInfo = null; this.contextMenuUserInfo = null; }, }, search: { query: null, show: false, userSearchResult: [], channelSearchResult: [], contactSearchResult: [], groupSearchResult: [], conversationSearchResult: [], messageSearchResult: [], _reset() { this.query = null; this.show = false; this.userSearchResult = []; this.channelSearchResult = []; this.contactSearchResult = []; this.groupSearchResult = []; this.conversationSearchResult = []; this.messageSearchResult = []; }, }, pick: { users: [], organizations: [], conversations: [], messages: [], _reset() { this.users = []; this.organizations = []; this.conversations = []; this.messages = []; }, }, misc: { connectionStatus: ConnectionStatus.ConnectionStatusUnconnected, isPageHidden: false, enableNotification: true, enableMinimize: getItem("minimizable") === "1", enableNotificationMessageDetail: true, enableCloseWindowToExit: false, enableAutoLogin: true, // 自动登录 isElectron: isElectron(), isElectronWindowsOrLinux: process && (process.platform === "win32" || process.platform === "linux"), isMainWindow: false, linuxUpdateTitleInterval: 0, wfc: wfc, config: Config, userOnlineStateMap: new Map(), enableOpenWorkSpace: !!Config.OPEN_PLATFORM_WORK_SPACE_URL, _reset() { this.connectionStatus = ConnectionStatus.ConnectionStatusUnconnected; this.isPageHidden = false; this.enableNotification = true; this.enableMinimize = getItem("minimizable") === "1"; this.enableNotificationMessageDetail = true; this.enableCloseWindowToExit = false; this.enableAutoLogin = false; this.isElectron = isElectron(); this.isElectronWindowsOrLinux = process && (process.platform === "win32" || process.platform === "linux"); // this.isMainWindow = false; this.linuxUpdateTitleInterval = 0; this.wfc = wfc; this.config = Config; this.userOnlineStateMap = new Map(); }, }, }, init(isMainWindow, subWindowLoadDataOptions) { console.log("init store"); miscState.connectionStatus = wfc.getConnectionStatus(); wfc.eventEmitter.on(EventType.ConnectionStatusChanged, (status) => { console.log("store ConnectionStatusChanged", status); miscState.connectionStatus = status; try { if (status === ConnectionStatus.ConnectionStatusConnected) { this._loadDefaultData(); this.updateTray(); } else if ( status === ConnectionStatus.ConnectionStatusLogout || status === ConnectionStatus.ConnectionStatusRejected || status === ConnectionStatus.ConnectionStatusSecretKeyMismatch || status === ConnectionStatus.ConnectionStatusKickedOff || status === ConnectionStatus.ConnectionStatusTokenIncorrect ) { _reset(); this.updateTray(); } } catch (e) { // do nothing } }); wfc.eventEmitter.on(EventType.UserInfosUpdate, (userInfos) => { console.log("store UserInfosUpdate", userInfos, miscState.connectionStatus); this._reloadSingleConversationIfExist(userInfos); // TODO optimize this._patchCurrentConversationMessages(); this._loadFriendList(); this._loadFriendRequest(); this._loadSelfUserInfo(); // TODO 其他相关逻辑 }); wfc.eventEmitter.on(EventType.SettingUpdate, () => { console.log("store SettingUpdate"); this._loadDefaultConversationList(); this._loadFavContactList(); this._loadFavGroupList(); this._loadChannelList(); this.updateTray(); // 清除远程消息时,WEB SDK会同时触发ConversationInfoUpdate 和 setting更新,但PC SDK不会,只会触发setting更新 // if (isElectron()) { // this._loadCurrentConversationMessages(); // } }); wfc.eventEmitter.on(EventType.FriendRequestUpdate, (newFrs) => { console.log(newFrs); this._loadFriendRequest(); }); wfc.eventEmitter.on(EventType.FriendListUpdate, (updatedFriendIds) => { console.log("FriendListUpdate", updatedFriendIds); this._loadFriendList(); this._loadFriendRequest(); this._loadFavContactList(); this._loadDefaultConversationList(); this._patchCurrentConversationMessages(); }); wfc.eventEmitter.on(EventType.GroupInfosUpdate, (groupInfos) => { // TODO optimize console.log("store GroupInfosUpdate", groupInfos); this._reloadGroupConversationIfExist(groupInfos); this._loadFavGroupList(); // TODO 其他相关逻辑 }); wfc.eventEmitter.on(EventType.GroupMembersUpdate, (groupId, members) => { // TODO optimize console.log("store GroupMembersUpdate", groupId, members); this._reloadGroupConversationIfExist([new NullGroupInfo(groupId)]); // this._loadFavGroupList(); // TODO 其他相关逻辑 }); wfc.eventEmitter.on(EventType.ChannelInfosUpdate, (groupInfos) => { console.log(groupInfos); this._loadDefaultConversationList(); this._loadChannelList(); }); wfc.eventEmitter.on(EventType.ConversationInfoUpdate, (conversationInfo) => { this._reloadConversation(conversationInfo.conversation); // if (conversationState.currentConversationInfo && conversationState.currentConversationInfo.conversation.equal(conversationInfo.conversation)) { // this._loadCurrentConversationMessages(); // } // 标记已读未读 this.updateTray(); }); wfc.eventEmitter.on(EventType.ReceiveMessage, (msg, hasMore) => { if (miscState.connectionStatus === ConnectionStatus.ConnectionStatusReceiveing) { return; } if (miscState.isMainWindow && !this.isConversationInCurrentWindow(msg.conversation)) { return; } if (!hasMore) { this._reloadConversation(msg.conversation); } if ( conversationState.currentConversationInfo && msg.conversation.equal(conversationState.currentConversationInfo.conversation) ) { if ( msg.messageContent instanceof DismissGroupNotification || (msg.messageContent instanceof KickoffGroupMemberNotification && msg.messageContent.kickedMembers.indexOf(wfc.getUserId()) >= 0) || (msg.messageContent instanceof QuitGroupNotification && msg.messageContent.operator === wfc.getUserId()) ) { this.setCurrentConversationInfo(null); return; } if (msg.messageContent.type === MessageContentType.Typing) { let groupId = msg.conversation.type === 1 ? msg.conversation.target : ""; let userInfo = wfc.getUserInfo(msg.from, false, groupId); userInfo = Object.assign({}, userInfo); userInfo._displayName = wfc.getGroupMemberDisplayNameEx(userInfo); conversationState.inputtingUser = userInfo; if (!conversationState.inputClearHandler) { conversationState.inputClearHandler = () => { conversationState.inputtingUser = null; }; } clearTimeout(conversationState.inputClearHandler); setTimeout(conversationState.inputClearHandler, 3000); } else { clearTimeout(conversationState.inputClearHandler); conversationState.inputtingUser = null; } if (!this._isDisplayMessage(msg) || msg.messageContent.type === MessageContentType.RecallMessage_Notification) { return; } // 会把下来加载更多加载的历史消息给清理了 let lastTimestamp = 0; let msgListLength = conversationState.currentConversationMessageList.length; if (msgListLength > 0) { lastTimestamp = conversationState.currentConversationMessageList[msgListLength - 1].timestamp; } this._patchMessage(msg, lastTimestamp); let msgIndex = conversationState.currentConversationMessageList.findIndex((m) => { return m.messageId === msg.messageId || (gt(m.messageUid, 0) && eq(m.messageUid, msg.messageUid)); }); if (msgIndex > -1) { // FYI: https://v2.vuejs.org/v2/guide/reactivity#Change-Detection-Caveats conversationState.currentConversationMessageList.splice(msgIndex, 1, msg); console.log("msg duplicate"); return; } conversationState.currentConversationMessageList.push(msg); } if (miscState.isMainWindow && this.isConversationInCurrentWindow(msg.conversation)) { if ( msg.conversation.type !== 2 && miscState.isPageHidden && (miscState.enableNotification || msg.status === MessageStatus.AllMentioned || msg.status === MessageStatus.Mentioned) ) { this.notify(msg); } this.updateTray(); } if (msg.messageContent instanceof ModifyGroupSettingNotification) { wfc.getGroupInfo(msg.messageContent.groupId, true); } }); wfc.eventEmitter.on(EventType.RecallMessage, (operator, messageUid) => { this._reloadConversationByMessageUidIfExist(messageUid); if (conversationState.currentConversationInfo) { let msg = wfc.getMessageByUid(messageUid); if (msg && msg.conversation.equal(conversationState.currentConversationInfo.conversation)) { if (conversationState.currentConversationMessageList) { let lastTimestamp = 0; conversationState.currentConversationMessageList = conversationState.currentConversationMessageList.map((msg) => { if (eq(msg.messageUid, messageUid)) { let newMsg = wfc.getMessageByUid(messageUid); this._patchMessage(newMsg, lastTimestamp); return newMsg; } lastTimestamp = msg.timestamp; return msg; }); } } } this.updateTray(); }); wfc.eventEmitter.on(EventType.UserOnlineEvent, (userOnlineStatus) => { userOnlineStatus.forEach((e) => { miscState.userOnlineStateMap.set(e.userId, e); }); // 更新在线状态 contactState.friendList = this._patchAndSortUserInfos(contactState.friendList, ""); }); // 服务端删除 wfc.eventEmitter.on(EventType.MessageDeleted, (messageUid) => { this._reloadConversationByMessageUidIfExist(messageUid); if (conversationState.currentConversationInfo) { if (conversationState.currentConversationMessageList) { conversationState.currentConversationMessageList = conversationState.currentConversationMessageList.filter( (msg) => !eq(msg.messageUid, messageUid) ); } } this.updateTray(); }); // 本地删除 wfc.eventEmitter.on(EventType.DeleteMessage, (messageId) => { this._reloadConversationByMessageIdIfExist(messageId); if (conversationState.currentConversationInfo) { if (conversationState.currentConversationMessageList) { conversationState.currentConversationMessageList = conversationState.currentConversationMessageList.filter( (msg) => msg.messageId !== messageId ); } } }); wfc.eventEmitter.on(EventType.SecretChatMessageBurned, (target, playedMessageId) => { // todo 倒计时等 console.log(target, playedMessageId); }); wfc.eventEmitter.on(EventType.SecretChatMessageBurned, (messageIds) => { this._loadDefaultConversationList(); if (conversationState.currentConversationInfo) { if (conversationState.currentConversationMessageList) { conversationState.currentConversationMessageList = conversationState.currentConversationMessageList.filter( (msg) => messageIds.indexOf(msg.messageId) < 0 ); } } }); wfc.eventEmitter.on(EventType.SecretChatStateChange, (targetId) => { console.log(targetId); this._loadDefaultConversationList(); }); wfc.eventEmitter.on(EventType.SendMessage, (message) => { // 删除频道,或者从频道会话切到其他会话时,会发送一条离开频道的消息 if (message.messageContent instanceof LeaveChannelChatMessageContent) { return; } this._reloadConversation(message.conversation); if (!this._isDisplayMessage(message)) { return; } if ( !conversationState.currentConversationInfo || !message.conversation.equal(conversationState.currentConversationInfo.conversation) ) { console.log("not current conv"); return; } let index = conversationState.currentConversationMessageList.findIndex((m) => m.messageId === message.messageId); if (index !== -1) { return; } let length = conversationState.currentConversationMessageList.length; let lastTimestamp = 0; if (length > 0) { let lastMessage = conversationState.currentConversationMessageList[length - 1]; lastTimestamp = lastMessage.timestamp; } this._patchMessage(message, lastTimestamp); conversationState.currentConversationMessageList.push(message); const defaultRenderMessageCount = 50; if (conversationState.currentConversationMessageList.length > defaultRenderMessageCount) { conversationState.currentConversationMessageList = conversationState.currentConversationMessageList.slice( conversationState.currentConversationMessageList.length - defaultRenderMessageCount ); } }); wfc.eventEmitter.on(EventType.MessageStatusUpdate, (message) => { console.log("message status update", message); if (!this._isDisplayMessage(message)) { return; } if ( !conversationState.currentConversationInfo || !message.conversation.equal(conversationState.currentConversationInfo.conversation) ) { console.log("not current conv"); return; } let index = conversationState.currentConversationMessageList.findIndex((m) => m.messageId === message.messageId); if (index < 0) { return; } let msg = conversationState.currentConversationMessageList[index]; Object.assign(msg, message); if ( conversationState.currentConversationInfo.lastMessage && conversationState.currentConversationInfo.lastMessage.messageId === message.messageId ) { Object.assign(conversationState.currentConversationInfo.lastMessage, message); } }); wfc.eventEmitter.on(EventType.MessageRead, (readEntries) => { console.log(readEntries); // optimization if (conversationState.currentConversationInfo) { // wfc.getConversationRead 每次返回同一个对象,只是该对象的值不一样。 // 由于 VUE 不能检测到初始化时,不存在的属性的变更,故此处重新 new 一个新的对象,并赋值。 // FYI:https://vuejs.org/v2/guide/reactivity.html conversationState.currentConversationRead = new Map( wfc.getConversationRead(conversationState.currentConversationInfo.conversation) ); } }); avenginekitproxy.onVoipCallStatusCallback = this.updateVoipStatus; if (isElectron()) { if (isMainWindow) { ipcRenderer.on("deep-link", (event, args) => { console.log("deep-link", args); // 下面是示例 // 可以根据 pathname 和 query parameter 进行相应的逻辑处理,这儿是跳转到对应的会话 let url = new URL(args); let pathname = url.pathname; let searchParams = url.searchParams; if ("//conversation" === pathname) { let target = searchParams.get("target"); let line = Number(searchParams.get("line")); let type = Number(searchParams.get("type")); let conversation = new Conversation(type, target, line); this.setCurrentConversation(conversation); } }); ipcRenderer.on("floating-conversation-window-closed", (event, args) => { let type = args.type; let target = args.target; let line = args.line; let conv = new Conversation(type, target, line); this.removeFloatingConversation(conv); this._reloadConversation(conv); }); } ipcRenderer.on("file-downloaded", (event, args) => { let messageId = args.messageId; let localPath = args.filePath; console.log("file-downloaded", args); conversationState.downloadingMessages = conversationState.downloadingMessages.filter((v) => v.messageId !== messageId); let msg = wfc.getMessageById(messageId); if (msg) { msg.messageContent.localPath = localPath; wfc.updateMessageContent(messageId, msg.messageContent); conversationState.currentConversationMessageList.forEach((m) => { if (m.messageId === messageId) { m.messageContent = msg.messageContent; } }); } }); ipcRenderer.on("file-download-failed", (event, args) => { let messageId = args.messageId; conversationState.downloadingMessages = conversationState.downloadingMessages.filter((v) => v.messageId !== messageId); // TODO 其他下载失败处理 }); ipcRenderer.on("file-download-progress", (event, args) => { let messageId = args.messageId; let receivedBytes = args.receivedBytes; let totalBytes = args.totalBytes; let dm = conversationState.downloadingMessages.find((dm) => dm.messageId === messageId); if (dm) { dm.progress = receivedBytes; dm.total = totalBytes; } // console.log('file download progress', messageId, receivedBytes, totalBytes); }); miscState.subWindowLoadDataOptions = subWindowLoadDataOptions ? subWindowLoadDataOptions : {}; if (!isMainWindow && wfc.getConnectionStatus() === ConnectionStatus.ConnectionStatusConnected) { // 根据 subWindowLoadDataOptions 配置去加载 this._loadDefaultData(); } window.__wfc = wfc; } miscState.isMainWindow = isMainWindow; window.__cw = currentWindow; }, _loadDefaultData() { this._loadFavGroupList(); this._loadChannelList(); this._loadFriendList(); this._loadFavContactList(); this._loadFriendRequest(); this._loadSelfUserInfo(); this._loadDefaultConversationList(); this._loadUserLocalSettings(); conversationState.isMessageReceiptEnable = wfc.isReceiptEnabled() && wfc.isUserReceiptEnabled(); // if (conversationState.currentConversationInfo) { // this._loadCurrentConversationMessages(); // } }, // conversation actions _isDisplayMessage(message) { // return [PersistFlag.Persist, PersistFlag.Persist_And_Count].indexOf(MessageConfig.getMessageContentPersitFlag(message.messageContent.type)) > -1; return message.messageId !== 0; }, _loadDefaultConversationList() { console.log("store _loadDefaultConversationList"); let conversationTypes = isElectron() ? [0, 1, 3, 5] : [0, 1, 3]; this._loadConversationList(conversationTypes, [0]); }, _loadConversationList(conversationType = [0, 1, 3], lines = [0]) { let conversationList = wfc.getConversationList(conversationType, lines); console.log("conversationList", conversationList); let toLoadUserIdSet = new Set(); let toLoadGroupIds = []; conversationList.forEach((info) => { if (info.conversation.type === ConversationType.Single) { toLoadUserIdSet.add(info.conversation.target); if (info.lastMessage && info.lastMessage.from) { toLoadUserIdSet.add(info.lastMessage.from); } } else if (info.conversation.type === ConversationType.Group) { toLoadGroupIds.push(info.conversation.target); if (info.lastMessage && info.lastMessage.from) { toLoadUserIdSet.add(info.lastMessage.from); } } }); let userInfoMap = new Map(); let groupInfoMap = new Map(); toLoadUserIdSet.forEach((uid) => { userInfoMap.set(uid, new NullUserInfo(uid)); }); toLoadGroupIds.forEach((gid) => { groupInfoMap.set(gid, new NullGroupInfo(gid)); }); console.log("to load userIds", [...toLoadUserIdSet]); wfc.getUserInfos([...toLoadUserIdSet]).forEach((u) => { userInfoMap.set(u.uid, u); }); console.log("to load groupIds", toLoadGroupIds); wfc.getGroupInfos(toLoadGroupIds).forEach((g) => { groupInfoMap.set(g.target, g); }); conversationList.forEach((info) => { this._patchConversationInfo(info, true, userInfoMap, groupInfoMap); // side affect if ( conversationState.currentConversationInfo && conversationState.currentConversationInfo.conversation.equal(info.conversation) ) { conversationState.currentConversationInfo = info; this._patchCurrentConversationOnlineStatus(); } }); conversationState.conversationInfoList = conversationList; }, _reloadConversation(conversation, insertIfNoExist = true) { let conversationInfo = wfc.getConversationInfo(conversation); if (conversationInfo) { conversationInfo = this._patchConversationInfo(conversationInfo); } let index = conversationState.conversationInfoList.findIndex((info) => info.conversation.equal(conversation)); if (index >= 0) { Object.assign(conversationState.conversationInfoList[index], conversationInfo); } else { if (insertIfNoExist && conversation.type !== ConversationType.ChatRoom) { conversationState.conversationInfoList.push(conversationInfo); } else { return conversationInfo; } } if (conversationState.currentConversationInfo && conversationState.currentConversationInfo.conversation.equal(conversation)) { conversationState.currentConversationInfo = conversationInfo; // 清除聊天记录 if (!conversationInfo.lastMessage) { conversationState.currentConversationMessageList = []; } } // sort conversationState.conversationInfoList.sort((a, b) => { if ((a.top && b.top) || (!a.top && !b.top)) { return gt(a.timestamp, b.timestamp) ? -1 : 1; } else { if (a.top) { return -1; } else { return 1; } } }); return conversationInfo; }, _reloadSingleConversationIfExist(userInfos) { if (userInfos.length > 10) { this._loadDefaultConversationList(); } else { userInfos.forEach((ui) => { let conv = new Conversation(ConversationType.Single, ui.uid, 0); this._reloadConversation(conv, false); }); } }, _reloadGroupConversationIfExist(groupInfos) { if (groupInfos.length > 10) { this._loadDefaultConversationList(); } else { groupInfos.forEach((gi) => { let conv = new Conversation(ConversationType.Group, gi.target, 0); this._reloadConversation(conv, false); }); } }, _reloadConversationByMessageIdIfExist(messageId) { if (messageId === 0) { return; } let toLoadConversationInfo = null; for (let i = 0; i < conversationState.conversationInfoList.length; i++) { let info = conversationState.conversationInfoList[i]; if (info.lastMessage && info.lastMessage.messageId === messageId) { toLoadConversationInfo = info; break; } } if (toLoadConversationInfo) { this._reloadConversation(toLoadConversationInfo.conversation); } }, _reloadConversationByMessageUidIfExist(messageUid) { let toLoadConversationInfo = null; for (let i = 0; i < conversationState.conversationInfoList.length; i++) { let info = conversationState.conversationInfoList[i]; if (info.lastMessage && eq(info.lastMessage.messageUid, messageUid)) { toLoadConversationInfo = info; break; } } if (toLoadConversationInfo) { this._reloadConversation(toLoadConversationInfo.conversation); } }, setCurrentConversation(conversation) { if (!conversation) { this.setCurrentConversationInfo(null); return; } if (conversationState.currentConversationInfo && conversation.equal(conversationState.currentConversationInfo.conversation)) { return; } let convs = conversationState.conversationInfoList.filter((info) => info.conversation.equal(conversation)); let info; if (convs && convs.length > 0) { info = convs[0]; } else { wfc.setConversationTimestamp(conversation, new Date().getTime()); info = this._reloadConversation(conversation); } this.setCurrentConversationInfo(info); }, setCurrentConversationInfo(conversationInfo) { if (!conversationInfo) { if (conversationState.currentConversationInfo) { let conversation = conversationState.currentConversationInfo.conversation; wfc.unwatchOnlineState(conversation.type, [conversation.target]); if (conversation.type === ConversationType.Channel) { let content = new LeaveChannelChatMessageContent(); wfc.sendConversationMessage(conversation, content); } } conversationState.currentConversationInfo = null; conversationState.shouldAutoScrollToBottom = false; conversationState.currentConversationMessageList.length = 0; conversationState.currentConversationOldestMessageId = 0; conversationState.currentConversationOldestMessageUid = 0; conversationState.currentConversationRead = null; conversationState.enableMessageMultiSelection = false; conversationState.showChannelMenu = false; return; } if ( conversationState.currentConversationInfo && conversationState.currentConversationInfo.conversation.equal(conversationInfo.conversation) ) { return; } let conversation = conversationInfo.conversation; if ( conversation.type === ConversationType.Group || (conversation.type === ConversationType.Single && !wfc.isMyFriend(conversation.target)) ) { wfc.watchOnlineState( conversation.type, [conversation.target], 1000, (states) => { states.forEach((e) => { miscState.userOnlineStateMap.set(e.userId, e); }); this._patchCurrentConversationOnlineStatus(); }, (err) => { console.log("watchOnlineState error", err); } ); } if (conversation.type === ConversationType.Channel) { let content = new EnterChannelChatMessageContent(); wfc.sendConversationMessage(conversation, content); } conversationState.currentConversationInfo = conversationInfo; conversationState.shouldAutoScrollToBottom = true; conversationState.currentConversationMessageList.length = 0; conversationState.currentConversationOldestMessageId = 0; conversationState.currentConversationOldestMessageUid = 0; // 会话页面会触发调用 loadConversationHistoryMessages,这儿不用提前加载消息 // this._loadCurrentConversationMessages(); this._patchCurrentConversationOnlineStatus(); conversationState.currentConversationRead = wfc.getConversationRead(conversationInfo.conversation); conversationState.enableMessageMultiSelection = false; conversationState.showChannelMenu = false; if (conversation.type === ConversationType.Channel) { let channelInfo = wfc.getChannelInfo(conversation.target, true); if (channelInfo.menus && channelInfo.menus.length > 0) { conversationState.showChannelMenu = true; } } else if (conversation.type === ConversationType.Group) { wfc.getGroupInfo(conversation.target, true); } else if (conversation.type === ConversationType.Single) { wfc.getUserInfo(conversation.target, true); } conversationState.quotedMessage = null; conversationState.currentVoiceMessage = null; clearTimeout(conversationState.inputClearHandler); conversationState.inputtingUser = null; pickState.messages.length = 0; }, quitGroup(groupId) { wfc.quitGroup( groupId, [0], null, () => { this.setCurrentConversationInfo(null); }, (err) => { console.log("quit group error", err); } ); }, dismissGroup(groupId) { wfc.dismissGroup( groupId, [0], null, () => { this.setCurrentConversationInfo(null); }, (err) => { console.log("dismiss group error", err); } ); }, subscribeChannel(channelId, subscribe) { wfc.listenChannel( channelId, subscribe, () => { //this.setCurrentConversationInfo(null) }, (err) => { console.log("unsubscribe channel error", err); } ); }, toggleMessageMultiSelection(message) { conversationState.enableMessageMultiSelection = !conversationState.enableMessageMultiSelection; pickState.messages.length = 0; if (conversationState.enableMessageMultiSelection && message) { pickState.messages.push(message); } }, toggleChannelMenu(toggle) { conversationState.showChannelMenu = toggle; }, selectOrDeselectMessage(message) { let index = pickState.messages.findIndex((m) => m.messageId === message.messageId); if (index >= 0) { pickState.messages.splice(index, 1); } else { pickState.messages.push(message); } }, deleteSelectedMessages() { conversationState.enableMessageMultiSelection = false; if (pickState.messages.length < 1) { return; } pickState.messages.sort((m1, m2) => m1.messageId - m2.messageId); pickState.messages.forEach((m) => { wfc.deleteMessage(m.messageId); }); pickState.messages.length = 0; }, forwardMessage(forwardType, targetConversations, messages, extraMessageText, successCB) { // web 端,避免撤回消息等操作,影响组合消息 if (!isElectron()) { messages = messages.map((m) => Object.assign({}, m)); } targetConversations.forEach((conversation) => { // let msg =new Message(conversation, message.messageContent) // wfc.sendMessage(msg) // 或者下面这种 if (forwardType === ForwardType.NORMAL || forwardType === ForwardType.ONE_BY_ONE) { messages.forEach((message) => { if (message.messageContent instanceof ArticlesMessageContent) { let linkContents = message.messageContent.toLinkMessageContent(); linkContents.forEach((lm) => { wfc.sendConversationMessage(conversation, lm); }); } else { wfc.sendConversationMessage(conversation, message.messageContent); } }); } else { // 合并转发 let compositeMessageContent = new CompositeMessageContent(); let title = ""; let msgConversation = messages[0].conversation; if (msgConversation.type === ConversationType.Single) { let users = store.getUserInfos([wfc.getUserId(), msgConversation.target], ""); title = users[0]._displayName + "和" + users[1]._displayName + "的聊天记录"; } else { title = "群的聊天记录"; } compositeMessageContent.title = title; compositeMessageContent.setMessages(messages); wfc.sendConversationMessage(conversation, compositeMessageContent, [], null, null, successCB, null); } if (extraMessageText) { let textMessage = new TextMessageContent(extraMessageText); wfc.sendConversationMessage(conversation, textMessage); } }); }, forwardByCreateConversation(forwardType, users, messages, extraMessageText, userGroupName, successCB) { this.createConversation( users, (conversation) => { this.forwardMessage(forwardType, [conversation], messages, extraMessageText, successCB); }, (err) => { console.error("createConversation error", err); }, userGroupName ); }, setShouldAutoScrollToBottom(scroll) { conversationState.shouldAutoScrollToBottom = scroll; }, /** * * @param src {String} 媒体url * @param thumbUrl {String} 缩略图url * @param thumb {String} base64格式的缩略图,但不包含'data:image/png;base64,' * @param autoplay */ previewMedia(src, thumbUrl, thumb, autoplay = true) { conversationState.previewMediaItems.length = 0; conversationState.previewMediaItems.push({ src: src, thumb: thumbUrl ? thumbUrl : "data:image/png;base64," + thumb, autoplay: autoplay, }); conversationState.previewMediaIndex = 0; console.log("preview media", conversationState.previewMediaItems, conversationState.previewMediaIndex); }, previewMedias(mediaItems, index) { conversationState.previewMediaItems.length = 0; conversationState.previewMediaItems.push(...mediaItems); conversationState.previewMediaIndex = index; console.log("preview medias", conversationState.previewMediaItems, conversationState.previewMediaIndex); }, playVoice(message) { if (conversationState.currentVoiceMessage) { conversationState.currentVoiceMessage._isPlaying = false; } conversationState.currentVoiceMessage = message; }, /** * * @param message * @param {Boolean} continuous true,预览周围的媒体消息;false,只预览第一个参数传入的那条媒体消息 */ previewMessage(message, continuous) { conversationState.previewMediaItems.length = 0; conversationState.previewMediaIndex = 0; if (continuous && conversationState.currentConversationMessageList.length > 0) { let mediaMsgs = conversationState.currentConversationMessageList.filter( (m) => [MessageContentType.Image, MessageContentType.Video].indexOf(m.messageContent.type) > -1 ); let msg; for (let i = 0; i < mediaMsgs.length; i++) { msg = mediaMsgs[i]; if (msg.messageId === message.messageId) { conversationState.previewMediaIndex = i; } let mediaUrl = msg.messageContent.remotePath; if (!mediaUrl) { if (msg.messageContent.file) { mediaUrl = URL.createObjectURL(msg.messageContent.file); } } conversationState.previewMediaItems.push({ src: mediaUrl, thumb: "data:image/png;base64," + msg.messageContent.thumbnail, autoplay: true, }); } } else { conversationState.previewMediaIndex = 0; let mediaUrl = message.messageContent.remotePath; if (!mediaUrl) { if (message.messageContent.file) { mediaUrl = URL.createObjectURL(message.messageContent.file); } } conversationState.previewMediaItems.push({ src: mediaUrl, thumb: "data:image/png;base64," + message.messageContent.thumbnail, autoplay: true, }); } }, previewCompositeMessage(compositeMessage, focusMessageUid) { conversationState.previewMediaItems.length = 0; conversationState.previewMediaIndex = 0; let mediaMsgs = compositeMessage.messageContent.messages.filter( (m) => [MessageContentType.Image, MessageContentType.Video].indexOf(m.messageContent.type) > -1 ); let msg; for (let i = 0; i < mediaMsgs.length; i++) { msg = mediaMsgs[i]; if (eq(msg.messageUid, focusMessageUid)) { conversationState.previewMediaIndex = i; } conversationState.previewMediaItems.push({ src: msg.messageContent.remotePath, thumb: "data:image/png;base64," + msg.messageContent.thumbnail, autoplay: true, }); } }, /** * * @param conversation * @param {File | string} file html File 类型或者url,绝对路径只在electron里面生效 * @return {Promise} */ async sendFile(conversation, file) { console.log("send file", file); if (file.size && file.size > 100 * 1024 * 1024) { if (!wfc.isSupportBigFilesUpload() || conversation.type === ConversationType.SecretChat) { console.log("file too big, and not support upload big file"); return true; } } let fileOrLocalPath = null; let remotePath = null; if (typeof file === "string") { if (!file.startsWith("http")) { fileOrLocalPath = file; } else { remotePath = file; } file = { path: file, name: file.substring(file.lastIndexOf("/") + 1), }; } else { fileOrLocalPath = file; } let msg = new Message(); msg.conversation = conversation; let mediaType = helper.getMediaType(file.name.split(".").slice(-1).pop()); // todo other file type let messageContentmediaType = { pic: MessageContentMediaType.Image, video: MessageContentMediaType.Video, doc: MessageContentMediaType.File, }[mediaType]; let messageContent; switch (messageContentmediaType) { case MessageContentMediaType.Image: let { thumbnail: it, width: iw, height: ih } = await imageThumbnail(file); it = it ? it : Config.DEFAULT_THUMBNAIL_URL; console.log("image file", file); if (it.length > 15 * 1024) { console.warn("generated thumbnail is too large, use default thumbnail", it.length); it = Config.DEFAULT_THUMBNAIL_URL; } messageContent = new ImageMessageContent(fileOrLocalPath, remotePath, it.split(",")[1]); messageContent.imageWidth = iw; messageContent.imageHeight = ih; break; case MessageContentMediaType.Video: let { thumbnail: vt, width: vw, height: vh } = await videoThumbnail(file); let duration = await videoDuration(file); duration = Math.ceil(duration * 1000); if (vt === null) { return false; } if (vt.length > 15 * 1024) { console.warn("generated thumbnail is too large, use default thumbnail", vt.length); vt = Config.DEFAULT_THUMBNAIL_URL; } messageContent = new VideoMessageContent(fileOrLocalPath, remotePath, vt.split(",")[1]); // TODO width and height break; case MessageContentMediaType.File: messageContent = new FileMessageContent(fileOrLocalPath, remotePath); break; default: return false; } msg.messageContent = messageContent; wfc.sendMessage( msg, (messageId) => { msg.messageId = messageId; console.log("sf, pr", messageId); }, (progress, total) => { // console.log('sf p', msg.messageId, Math.ceil(progress / total * 100)) let sm = conversationState.sendingMessages.find((e) => e.messageId === msg.messageId); if (sm) { sm.progress = progress; sm.total = total; } else { conversationState.sendingMessages.push({ messageId: msg.messageId, progress, total, }); } }, (messageUid) => { console.log("sf s", messageUid); conversationState.sendingMessages = conversationState.sendingMessages.filter((e) => e.messageId !== msg.messageId); }, (error) => { console.log("sf e", error); conversationState.sendingMessages = conversationState.sendingMessages.filter((e) => e.messageId !== msg.messageId); } ); }, quoteMessage(message) { conversationState.quotedMessage = message; conversationState.currentConversationInfo._quotedMessage = message; }, getConversationInfo(conversation) { let info = wfc.getConversationInfo(conversation); return this._patchConversationInfo(info, false); }, /** * 获取会话消息 * @param {Conversation} conversation 会话 * @param {number} fromIndex 其实消息的 messageId * @param {boolean} before 获取其实消息之前,还是之后的消息 * @param {string} withUser 过滤该用户发送或接收的消息 * @param {function (Message[]) } callback 消息列表会回调 */ getMessages(conversation, fromIndex = 0, before = true, withUser = "", callback) { wfc.getMessagesV2( conversation, fromIndex, before, 20, withUser, (msgs) => { msgs = msgs.map((m) => this._patchMessage(m, 0)); //callback && callback(msgs); setTimeout(() => callback && callback(msgs), 200); }, (err) => { console.error("getMessageV2 error", err); callback && callback([]); } ); }, getMessageInTypes(conversation, contentTypes, timestamp, before = true, withUser = "", callback) { wfc.getMessagesByTimestampV2( conversation, contentTypes, timestamp, before, 20, withUser, (msgs) => { msgs = msgs.map((m) => this._patchMessage(m, 0)); callback && callback(msgs); }, (err) => { console.log(err); callback && callback([]); } ); }, _loadCurrentConversationMessages() { console.log("_loadCurrentConversationMessages"); if (!conversationState.currentConversationInfo) { return; } // TODO 可以在这儿加载所有未读消息,以实现滚动到一条未读消息的地方 let conversation = conversationState.currentConversationInfo.conversation; wfc.getMessagesV2( conversation, 0, true, 20, "", (msgs) => { conversationState.currentConversationMessageList = msgs; this._patchCurrentConversationMessages(); if (msgs.length) { conversationState.currentConversationOldestMessageId = msgs[0].messageId; } for (let i = 0; i < msgs.length; i++) { if (gt(msgs[i].messageUid, 0)) { conversationState.currentConversationOldestMessageUid = msgs[0].messageUid; break; } } }, (err) => { console.error("_loadCurrentConversationMessages error", err); } ); }, _patchCurrentConversationMessages() { let lastTimestamp = 0; let msgs = conversationState.currentConversationMessageList; msgs.forEach((m) => { this._patchMessage(m, lastTimestamp); lastTimestamp = m.timestamp; }); }, _onloadConversationMessages(conversation, messages) { if (!messages || messages.length === 0) { return false; } let loadNewMsg = false; let lastTimestamp = 0; let newMsgs = []; messages.forEach((m) => { let index = conversationState.currentConversationMessageList.findIndex((cm) => cm.messageId === m.messageId); if (index === -1) { this._patchMessage(m, lastTimestamp); lastTimestamp = m.timestamp; newMsgs.push(m); loadNewMsg = true; } }); conversationState.currentConversationMessageList = newMsgs.concat(conversationState.currentConversationMessageList); return loadNewMsg; }, loadConversationHistoryMessages(loadedCB, completeCB) { if (!conversationState.currentConversationInfo) { return; } let conversation = conversationState.currentConversationInfo.conversation; console.log( "loadConversationHistoryMessage", conversation, conversationState.currentConversationOldestMessageId, stringValue(conversationState.currentConversationOldestMessageUid) ); let loadRemoteHistoryMessageFunc = () => { wfc.loadRemoteConversationMessages( conversation, [], conversationState.currentConversationOldestMessageUid, 20, (msgs, hasMore) => { console.log("loadRemoteConversationMessages response", msgs.length); if (msgs.length === 0) { // 拉回来的消息,本地全都有时,会走到这儿 if (hasMore) { loadedCB(); } else { completeCB(); } } else { // 可能拉回来的时候,本地已经切换会话了 if (conversation.equal(conversationState.currentConversationInfo.conversation)) { conversationState.currentConversationOldestMessageUid = msgs[0].messageUid; msgs = msgs.filter((m) => m.messageId !== 0); this._onloadConversationMessages(conversation, msgs); } this._reloadConversation(conversation); loadedCB(); } }, (error) => { completeCB(error); } ); }; wfc.getMessagesV2( conversation, conversationState.currentConversationOldestMessageId, true, 20, "", (lmsgs) => { if (lmsgs.length > 0) { if (!conversation.equal(conversationState.currentConversationInfo.conversation)) { return; } conversationState.currentConversationOldestMessageId = lmsgs[0].messageId; if (gt(lmsgs[0].messageUid, 0)) { conversationState.currentConversationOldestMessageUid = lmsgs[0].messageUid; } this._onloadConversationMessages(conversation, lmsgs); if (lmsgs.length === 0) { loadRemoteHistoryMessageFunc(); } else { // loadedCB(); setTimeout(() => loadedCB(), 200); } } else { loadRemoteHistoryMessageFunc(); } }, (err) => { completeCB(err); } ); }, setConversationTop(conversation, top) { wfc.setConversationTop( conversation, top, () => { this._reloadConversation(conversation); }, (err) => { console.log("setConversationTop error", err); } ); }, setConversationSilent(conversation, silent) { wfc.setConversationSlient( conversation, silent, () => { this._reloadConversation(conversation); }, (err) => { console.log("setConversationSilent error", err); } ); }, removeConversation(conversation) { wfc.removeConversation(conversation, false); if (conversationState.currentConversationInfo && conversationState.currentConversationInfo.conversation.equal(conversation)) { this.setCurrentConversationInfo(null); } conversationState.conversationInfoList = conversationState.conversationInfoList.filter( (info) => !info.conversation.equal(conversation) ); this.updateTray(); }, getMessageById(messageId) { let msg = wfc.getMessageById(messageId); if (msg) { this._patchMessage(msg, 0); } return msg; }, getMessageByUid(messageUid) { let msg = wfc.getMessageByUid(messageUid); if (msg) { this._patchMessage(msg, 0); } return msg; }, _patchMessage(m, lastTimestamp = 0, userInfoMap) { // TODO // _from // _showTime if (m.conversation.type === ConversationType.Single) { m._from = userInfoMap ? userInfoMap.get(m.from) : wfc.getUserInfo(m.from, false, ""); } if (!m._from) { let u = wfc.getUserInfo(m.from, false, m.conversation.type === ConversationType.Group ? m.conversation.target : ""); // clone for modify // TODO sdk 返回的时候,直接返回 clone copy,而不是直接返回底层的数据,防止上层修改,影响到底层数据模型 m._from = Object.assign({}, u); } if (m.conversation.type === ConversationType.Group) { m._from._displayName = wfc.getGroupMemberDisplayNameEx(m._from); } else { m._from._displayName = wfc.getUserDisplayNameEx(m._from); } if (m.conversation.type === ConversationType.SecretChat) { if ( m.messageContent instanceof MediaMessageContent && m.messageContent.remotePath && m.messageContent.remotePath.startsWith("http") ) { m.messageContent.remotePath = `http://localhost:${Config.SECRET_CHAT_MEDIA_DECODE_SERVER_PORT}?target=${m.conversation.target}&url=${m.messageContent.remotePath}`; } } if (numberValue(lastTimestamp) > 0 && numberValue(m.timestamp) - numberValue(lastTimestamp) > 5 * 60 * 1000) { m._showTime = true; } m._timeStr = helper.timeFormat(m.timestamp); if (m.messageContent instanceof CompositeMessageContent) { this._patchCompositeMessageContent(m.messageContent); } // TODO 如果Im server支持备选网络,需要根据当前的网络情况,判断当前是处于主网络,还是备选网络,并动态修改媒体类消息的remotePath,不然可能会出现不能正常加载的情况 // 如何判断是主网络,还是备选网络,这儿提供一种思路:分别通过主网络和备选网络测试访问im server的/api/version接口 // 判断是主网络,还是备选网络,一般启动的时候,检测到网络网络变化的时候,在判断一次。 // if(m.messageContent instanceof MediaMessageContent){ // TODO 动态修改remotePath // } return m; }, _patchCompositeMessageContent(compositeMessageContent) { let messages = compositeMessageContent.messages; messages.forEach((m) => { this._patchMessage(m, 0); }); }, _patchConversationInfo(info, patchLastMessage = true, userInfoMap, groupInfoMap) { if (info.conversation.type === ConversationType.Single) { info.conversation._target = userInfoMap ? userInfoMap.get(info.conversation.target) : wfc.getUserInfo(info.conversation.target, false); if (info.conversation._target) { info.conversation._target._displayName = wfc.getUserDisplayNameEx(info.conversation._target); } } else if (info.conversation.type === ConversationType.Group) { info.conversation._target = groupInfoMap ? groupInfoMap.get(info.conversation.target) : wfc.getGroupInfo(info.conversation.target, false); if (info.conversation._target) { info.conversation._target._isFav = wfc.isFavGroup(info.conversation.target); info.conversation._target._displayName = info.conversation._target.remark ? info.conversation._target.remark : info.conversation._target.name; } } else if (info.conversation.type === ConversationType.Channel) { info.conversation._target = wfc.getChannelInfo(info.conversation.target, false); info.conversation._target._displayName = info.conversation._target.name; } else if (info.conversation.type === ConversationType.SecretChat) { let secretChatInfo = wfc.getSecretChatInfo(info.conversation.target); if (secretChatInfo) { let userId = secretChatInfo.userId; let userInfo = wfc.getUserInfo(userId, false); info.conversation._target = userInfo; info.conversation._target._displayName = wfc.getUserDisplayNameEx(userInfo); } else { info.conversation._target = {}; } } else if (info.conversation.type === ConversationType.ChatRoom) { wfc.getChatroomInfo( info.conversation.target, 0, (chatRoomInfo) => { info.conversation._target = chatRoomInfo; }, (err) => { console.log("get chatRoomInfo error", err); info.conversation._target = {}; } ); } if (gt(info.timestamp, 0)) { info._timeStr = helper.dateFormat(info.timestamp); } else { info._timeStr = ""; } // 显示的时候,再 patch if (info.lastMessage && info.lastMessage.conversation !== undefined && patchLastMessage) { //this._patchMessage(info.lastMessage, 0, userInfoMap) if (!info.lastMessage._from) { info.lastMessage._from = undefined; } } if (info.unreadCount) { info._unread = info.unreadCount.unread + info.unreadCount.unreadMention + info.unreadCount.unreadMentionAll; } if (info.conversation.equal(avenginekitproxy.conversation)) { info._isVoipOngoing = true; } else { info._isVoipOngoing = false; } return info; }, addDownloadingMessage(messageId) { conversationState.downloadingMessages.push({ messageId: messageId, progress: 0, total: Number.MAX_SAFE_INTEGER, }); console.log("add downloading"); }, isDownloadingMessage(messageId) { // web端尚未测试,先屏蔽 if (!isElectron()) { return false; } return conversationState.downloadingMessages.findIndex((dm) => dm.messageId === messageId) >= 0; }, isSendingMessage(messageId) { return conversationState.sendingMessages.has(messageId); }, getDownloadingMessageStatus(messageId) { return conversationState.downloadingMessages.find((dm) => dm.messageId === messageId); }, getSendingStatus(messageId) { return conversationState.sendingMessages.find((e) => e.messageId === messageId); }, addFloatingConversation(conversation) { conversationState.floatingConversations.push(conversation); }, removeFloatingConversation(conversation) { conversationState.floatingConversations = conversationState.floatingConversations.filter((c) => !c.equal(conversation)); }, isConversationInCurrentWindow(conversation) { if (miscState.isMainWindow) { let index = conversationState.floatingConversations.findIndex((fc) => fc.equal(conversation)); return index === -1; } else { return ( conversationState.currentConversationInfo && conversationState.currentConversationInfo.conversation.equal(conversation) ); } }, // contact actions _loadSelfUserInfo() { contactState.selfUserInfo = wfc.getUserInfo(wfc.getUserId(), false); }, _loadFriendList() { let friends = wfc.getMyFriendList(false); let fileHelperIndex = friends.indexOf(Config.FILE_HELPER_ID); if (fileHelperIndex < 0) { friends.push(Config.FILE_HELPER_ID); } if (friends && friends.length > 0) { let friendList = wfc.getUserInfos(friends, ""); contactState.friendList = this._patchAndSortUserInfos(friendList, ""); } }, getUserOnlineState(userId) { let userOnlineState = miscState.userOnlineStateMap.get(userId); if (userOnlineState) { return userOnlineState.desc(); } return ""; }, _patchCurrentConversationOnlineStatus() { let convInfo = conversationState.currentConversationInfo; if (convInfo && convInfo.conversation.type === ConversationType.Single) { // 在 将 object 和 ui 绑定之前, 向 object 中新增的属性是 reactive 的,但绑定之后,才新增的属性,不是 reactive 的, // 故需要通过下面这种方法,让其成为 reactive 的属性 // conversationState.currentConversationInfo.conversation._targetOnlineStateDesc = userOnlineStatus.desc(); Vue.set( conversationState.currentConversationInfo.conversation, "_targetOnlineStateDesc", this.getUserOnlineState(convInfo.conversation.target) ); } else { //TODO } }, _loadFriendRequest() { let requests = wfc.getIncommingFriendRequest(); requests.sort((a, b) => numberValue(b.timestamp) - numberValue(a.timestamp)); requests = requests.length >= 20 ? requests.slice(0, 20) : requests; let uids = []; requests.forEach((fr) => { uids.push(fr.target); }); let userInfos = wfc.getUserInfos(uids, ""); requests.forEach((fr) => { let userInfo = userInfos.find((u) => u.uid === fr.target); fr._target = userInfo; }); contactState.friendRequestList = requests; contactState.unreadFriendRequestCount = wfc.getUnreadFriendRequestCount(); }, _patchAndSortUserInfos(userInfos, groupId = "", compareFn) { userInfos = userInfos.map((u) => { if (groupId) { u._displayName = wfc.getGroupMemberDisplayNameEx(u); } else { u._displayName = wfc.getUserDisplayNameEx(u); } u._pinyin = convert(u._displayName, { style: 0 }).join("").trim().toLowerCase(); let firstLetter = u._pinyin[0]; if (firstLetter >= "a" && firstLetter <= "z") { u.__sortPinyin = "a" + u._pinyin; } else { u.__sortPinyin = "z" + u._pinyin; } u._firstLetters = convert(u._displayName, { style: 4 }).join("").trim().toLowerCase(); return u; }); if (compareFn) { userInfos = userInfos.sort(compareFn); } else { userInfos = userInfos.sort((a, b) => a.__sortPinyin.localeCompare(b.__sortPinyin)); } userInfos.forEach((u) => { let uFirstLetter = u.__sortPinyin[1]; if (uFirstLetter >= "a" && uFirstLetter <= "z") { u._category = uFirstLetter; } else { u._category = "#"; } u._userOnlineStatusDesc = this.getUserOnlineState(u.uid); }); return userInfos; }, _loadFavGroupList() { contactState.favGroupList = wfc.getFavGroupList(); }, _loadChannelList() { wfc.getRemoteListenedChannels( (channelIds) => { if (channelIds) { contactState.channelList = channelIds.map((channelId) => wfc.getChannelInfo(channelId, false)); contactState.channelList = contactState.channelList.filter((ch) => { return !(ch instanceof NullChannelInfo); }); } }, (err) => { console.error("getRemoteListenedChannels error", err); } ); }, _loadFavContactList() { let favUserIds = wfc.getFavUsers(); if (favUserIds.length > 0) { contactState.favContactList = this.getUserInfos(favUserIds, ""); contactState.favContactList.forEach((u) => { u._category = "☆ 星标朋友"; }); } else { contactState.favContactList = []; } }, reloadFavGroupList() { this._loadFavGroupList(); }, setCurrentFriendRequest(friendRequest) { contactState.currentFriendRequest = friendRequest; contactState.currentFriend = null; contactState.currentOrganization = null; contactState.currentGroup = null; contactState.currentChannel = null; }, setCurrentFriend(friend) { contactState.currentFriendRequest = null; contactState.currentFriend = friend; contactState.currentOrganization = null; contactState.currentGroup = null; contactState.currentChannel = null; }, setCurrentGroup(group) { contactState.currentFriendRequest = null; contactState.currentFriend = null; contactState.currentOrganization = null; contactState.currentGroup = group; contactState.currentChannel = null; }, setCurrentChannel(channel) { contactState.currentFriendRequest = null; contactState.currentFriend = null; contactState.currentOrganization = null; contactState.currentGroup = null; contactState.currentChannel = channel; }, setCurrentOrganization(organization) { contactState.currentFriendRequest = null; contactState.currentFriend = null; contactState.currentGroup = null; contactState.currentChannel = null; contactState.currentOrganization = organization; }, toggleGroupList() { contactState.expandGroup = !contactState.expandGroup; }, toggleChannelList() { contactState.expandChanel = !contactState.expandChanel; }, toggleFriendRequestList() { contactState.expandFriendRequestList = !contactState.expandFriendRequestList; }, toggleFriendList() { contactState.expandFriendList = !contactState.expandFriendList; }, toggleOrganizationList() { // TEST DATA contactState.organizationList = [ { name: "测试公司", portrait: Config.DEFAULT_ORGANIZATION_PORTRAIT_URL, }, ]; contactState.expandOrganization = !contactState.expandOrganization; }, // search actions toggleSearchView(show) { console.log("ts", show, searchState.show); searchState.show = show; }, setSearchQuery(query) { searchState.query = query; if (query) { console.log("search", query); searchState.contactSearchResult = this.filterContact(query); searchState.groupSearchResult = this.filterGroupConversation(query); searchState.conversationSearchResult = this.filterConversation(query); // searchState.messageSearchResult = this.searchMessage(query); this.searchUser(query); this.searchChannel(query); } else { searchState._reset(); } }, searchUser(query) { console.log("search user", query); wfc.searchUser( query, SearchType.General, 0, (keyword, userInfos) => { console.log("search user result", query, userInfos); if (searchState.query === keyword) { searchState.userSearchResult = userInfos.filter((u) => !wfc.isMyFriend(u.uid)); } }, (err) => { console.log("search user error", query, err); if (searchState.query === query) { searchState.userSearchResult = []; } } ); }, searchChannel(query) { console.log("search channel"); wfc.searchChannel( query, true, (keyword, channelInfos) => { console.log("search channel result", channelInfos); if (searchState.query === keyword) { console.log("search channel result", channelInfos); searchState.channelSearchResult = channelInfos; } }, (err) => { console.log("search channel error", query, err); if (searchState.query === query) { searchState.channelSearchResult = []; } } ); }, // TODO 到底是什么匹配了 filterContact(query) { let result = contactState.friendList.filter((u) => { return ( u._displayName.indexOf(query) > -1 || u._firstLetters.indexOf(query.toLowerCase()) > -1 || u._pinyin.indexOf(query.toLowerCase()) > -1 ); }); console.log("friend searchResult", result); return result; }, searchFiles(keyword, beforeMessageUid, successCB, failCB) { if (!keyword) { return; } wfc.searchFiles( keyword, null, "", beforeMessageUid, 0, 20, (files) => { this._patchFileRecords(files); successCB && successCB(files); }, (errorCode) => { console.log("search file error", errorCode); failCB && failCB(errorCode); } ); }, filterUsers(users, filter) { if (!users || !filter || !filter.trim()) { return users; } let queryPinyin = convert(filter, { style: 0 }).join("").trim().toLowerCase(); let result = users.filter((u) => { return ( u._displayName.indexOf(filter) > -1 || u._displayName.indexOf(queryPinyin) > -1 || u._pinyin.indexOf(filter) > -1 || u._pinyin.indexOf(queryPinyin) > -1 || u._firstLetters.indexOf(filter) > -1 || u._firstLetters.indexOf(queryPinyin) > -1 ); }); return result; }, // TODO 匹配类型,是群名称匹配上了,还是群成员的名称匹配上了? // 目前只搜索群名称 filterFavGroup(query) { console.log("to search group", contactState.favGroupList); let queryPinyin = convert(query, { style: 0 }).join("").trim().toLowerCase(); let result = contactState.favGroupList.filter((g) => { let groupNamePinyin = convert(g.name, { style: 0 }).join("").trim().toLowerCase(); return ( g.name.indexOf(query) > -1 || g.name.indexOf(queryPinyin) > -1 || groupNamePinyin.indexOf(query) > -1 || groupNamePinyin.indexOf(queryPinyin) > -1 ); }); console.log("group searchResult", result); return result; }, // TODO filterConversation(query) { return conversationState.conversationInfoList.filter((info) => { let displayNamePinyin = convert(info.conversation._target._displayName, { style: 0 }).join("").trim().toLowerCase(); let firstLetters = convert(info.conversation._target._displayName, { style: 4, }) .join("") .trim() .toLowerCase(); return ( info.conversation._target._displayName.indexOf(query) > -1 || displayNamePinyin.indexOf(query.toLowerCase()) > -1 || firstLetters.indexOf(query) > -1 ); }); }, filterGroupConversation(query) { // query = query.toLowerCase(); // let groups = conversationState.conversationInfoList.filter(info => info.conversation.type === ConversationType.Group).map(info => info.conversation._target); // return groups.filter(groupInfo => { // let namePinyin = convert(groupInfo.name, {style: 0}).join('').trim().toLowerCase(); // let firstLetters = convert(groupInfo.name, {style: 4}).join('').trim().toLowerCase(); // return groupInfo.name.indexOf(query) > -1 || namePinyin.indexOf(query) > -1 || firstLetters.indexOf(query) > -1 // }) let gsr = wfc.searchGroups(query); return gsr.map((r) => r.groupInfo); }, searchMessage(conversation, query) { let msgs = wfc.searchMessage(conversation, query); msgs = msgs.reverse(); return msgs.map((m) => this._patchMessage(m, 0)); }, searchMessageInTypes(conversation, contentTypes, query, offset) { let msgs = wfc.searchMessageByTypes(conversation, query, contentTypes, true, 20, offset); return msgs.map((m) => this._patchMessage(m, 0)); }, searchConversation(query, types = [0, 1, 2], lines = [0, 1, 2]) { let results = wfc.searchConversation(query, types, lines); return results.map((r) => { let info = wfc.getConversationInfo(r.conversation); r._conversationInfo = this._patchConversationInfo(info, false); return r; }); }, // pick actions pickOrUnpickUser(user) { let index = pickState.users.findIndex((u) => u.uid === user.uid); if (index >= 0) { pickState.users = pickState.users.filter((u) => user.uid !== u.uid); } else { pickState.users.push(user); } }, isUserPicked(user) { let index = pickState.users.findIndex((u) => u.uid === user.uid); return index >= 0; }, // pick actions pickOrUnpickOrganization(org) { let index = pickState.organizations.findIndex((o) => o.id === org.id); if (index >= 0) { pickState.organizations = pickState.organizations.filter((o) => o.id !== org.id); } else { pickState.organizations.push(org); } }, isOrganizationPicked(org) { let index = pickState.organizations.findIndex((o) => o.id === org.id); return index >= 0; }, pickOrUnpickConversation(conversation) { let index = pickState.conversations.findIndex( (c) => conversation.target === c.target && conversation.line === c.line && conversation.type === c.type ); if (index >= 0) { pickState.conversations = pickState.conversations.filter( (c) => !(conversation.target === c.target && conversation.line === c.line && conversation.type === c.type) ); } else { pickState.conversations.push(conversation); } }, /** * misc actions * @param users {array} users * @callback successCB - success callback * @callback failCB - fail callback * @param successCB {successCB} - success callback * @param failCB {failCB} - success callback * @param userGroupName {string} */ createConversation(users, successCB, failCB, userGroupName) { if (users.length === 1 && !userGroupName) { let conversation = new Conversation(ConversationType.Single, users[0].uid, 0); this.setCurrentConversation(conversation); successCB && successCB(conversation); return; } let contactName = contactState.selfUserInfo.displayName; let groupMemberIds = []; for (let i = 0; i < users.length; i++) { groupMemberIds.push(users[i].uid); if (i <= 3) { contactName += "、" + users[i].displayName; } } const groupName = userGroupName || contactName.substr(0, contactName.length - 1); wfc.createGroup( null, GroupType.Restricted, groupName, null, null, groupMemberIds, null, [0], null, (groupId) => { let conversation = new Conversation(ConversationType.Group, groupId, 0); this.setCurrentConversation(conversation); successCB && successCB(conversation); }, (error) => { console.log("create group error", error); failCB && failCB(error); } ); }, _loadUserLocalSettings() { let userId = wfc.getUserId(); // 默认允许通知 let setting = getItem(userId + "-" + "notification"); miscState.enableNotification = setting === null || setting === "1"; setting = getItem(userId + "-" + "notificationDetail"); miscState.enableNotificationMessageDetail = setting === null || setting === "1"; miscState.enableCloseWindowToExit = getItem(userId + "-" + "closeWindowToExit") === "1"; miscState.enableAutoLogin = getItem(userId + "-" + "autoLogin") === "1"; setting = getItem("minimizable"); miscState.enableMinimize = setting === null || setting === "1"; }, setEnableNotification(enable) { miscState.enableNotification = enable; setItem(contactState.selfUserInfo.uid + "-" + "notification", enable ? "1" : "0"); }, setEnableMinimize(enable) { miscState.enableMinimize = enable; setItem("minimizable", enable ? "1" : "0"); currentWindow.minimizable = enable; }, setEnableNotificationDetail(enable) { miscState.enableNotificationMessageDetail = enable; setItem(contactState.selfUserInfo.uid + "-" + "notificationDetail", enable ? "1" : "0"); }, setEnableCloseWindowToExit(enable) { miscState.enableCloseWindowToExit = enable; setItem(contactState.selfUserInfo.uid + "-" + "closeWindowToExit", enable ? "1" : "0"); ipcRenderer.send(IPCEventType.ENABLE_CLOSE_WINDOW_TO_EXIT, enable); }, setEnableAutoLogin(enable) { miscState.enableAutoLogin = enable; setItem(contactState.selfUserInfo.uid + "-" + "autoLogin", enable ? "1" : "0"); }, // clone一下,别影响到好友列表 getUserInfos(userIds, groupId) { let userInfos = wfc.getUserInfos(userIds, groupId); let userInfosCloneCopy = userInfos.map((u) => Object.assign({}, u)); return this._patchAndSortUserInfos(userInfosCloneCopy, groupId); }, // clone一下,别影响到好友列表 getGroupMemberUserInfos(groupId, includeSelf = true, sortByPinyin = false) { let memberIds = wfc.getGroupMemberIds(groupId); let userInfos = wfc.getUserInfos(memberIds, groupId); if (!includeSelf) { userInfos = userInfos.filter((u) => u.uid !== wfc.getUserId()); } let userInfosCloneCopy = userInfos.map((u) => Object.assign({}, u)); if (sortByPinyin) { return this._patchAndSortUserInfos(userInfosCloneCopy, groupId); } else { let compareFn = (u1, u2) => { let index1 = memberIds.findIndex((id) => id === u1.uid); let index2 = memberIds.findIndex((id) => id === u2.uid); return index1 - index2; }; return this._patchAndSortUserInfos(userInfosCloneCopy, groupId, compareFn); } }, // clone一下,别影响到好友列表 getConversationMemberUsrInfos(conversation) { let userInfos = []; if (conversation.type === 0) { if (conversation.target !== contactState.selfUserInfo.uid) { userInfos.push(wfc.getUserInfo(wfc.getUserId(), false)); } userInfos.push(wfc.getUserInfo(conversation.target, false)); let userInfosCloneCopy = userInfos.map((u) => Object.assign({}, u)); userInfos = this._patchAndSortUserInfos(userInfosCloneCopy, ""); } else if (conversation.type === 1) { userInfos = this.getGroupMemberUserInfos(conversation.target, true); } return userInfos; }, getMyFileRecords(beforeUid, count, successCB, failCB) { if (!successCB) { return; } wfc.getMyFileRecords( beforeUid, 0, count, (fileRecords) => { this._patchFileRecords(fileRecords); successCB(fileRecords); }, failCB ); }, getConversationFileRecords(conversation, fromUser, beforeMessageUid, count, successCB, failCB) { wfc.getConversationFileRecords( conversation, fromUser, beforeMessageUid, 0, count, (fileRecords) => { this._patchFileRecords(fileRecords); successCB(fileRecords); }, failCB ); }, deleteFriend(target) { wfc.deleteFriend( target, () => { let conv = new Conversation(ConversationType.Single, target, 0); wfc.removeConversation(conv, true); conversationState.conversationInfoList = conversationState.conversationInfoList.filter( (info) => !info.conversation.equal(conv) ); }, (err) => { console.log("deleteFriend error", err); } ); }, _patchFileRecords(fileRecords) { fileRecords.forEach((fileRecord) => { let groupId = fileRecord.conversation.type === 1 ? fileRecord.conversation.target : ""; if (groupId) { fileRecord._userDisplayName = wfc.getGroupMemberDisplayName(groupId, fileRecord.userId); } else { fileRecord._userDisplayName = wfc.getUserDisplayName(fileRecord.userId); } let conversationInfo = wfc.getConversationInfo(fileRecord.conversation); this._patchConversationInfo(conversationInfo, false); if (fileRecord.conversation.type === 0) { fileRecord._conversationDisplayName = "与" + conversationInfo.conversation._target._displayName + "的聊天"; } else { fileRecord._conversationDisplayName = conversationInfo.conversation._target._displayName; } if (fileRecord.name.indexOf(FileMessageContent.FILE_NAME_PREFIX) === 0) { fileRecord.name = fileRecord.name.substring( fileRecord.name.indexOf(FileMessageContent.FILE_NAME_PREFIX) + FileMessageContent.FILE_NAME_PREFIX.length ); } fileRecord._timeStr = helper.dateFormat(fileRecord.timestamp); fileRecord._sizeStr = helper.humanSize(fileRecord.size); fileRecord._fileIconName = helper.getFiletypeIcon(fileRecord.name.substring(fileRecord.name.lastIndexOf("."))); }); }, setPageVisibility(visible) { miscState.isPageHidden = !visible; if (!visible) { conversationState.shouldAutoScrollToBottom = false; } else if (conversationState.currentConversationInfo) { conversationState.shouldAutoScrollToBottom = true; } }, clearConversationUnreadStatus(conversation) { let info = wfc.getConversationInfo(conversation); if (info && info.unreadCount.unread + info.unreadCount.unreadMention + info.unreadCount.unreadMentionAll > 0) { wfc.clearConversationUnreadStatus(conversation); this.updateTray(); } }, clearAllUnreadStatus() { wfc.clearAllUnreadStatus(); conversationState.conversationInfoList.forEach((info) => { info.unreadCount = new UnreadCount(); }); this.updateTray(); }, notify(msg) { let content = msg.messageContent; let icon = require("@/assets/images/icon.png"); let tip; //Todo if (msg.direction === 0 /* && !(type===0 && target===file_transfer_id)*/) { return; } if (MessageConfig.getMessageContentPersitFlag(content.type) === PersistFlag.Persist_And_Count) { if (msg.status !== MessageStatus.AllMentioned && msg.status !== MessageStatus.Mentioned) { let silent = false; for (const info of conversationState.conversationInfoList) { if (info.conversation.equal(msg.conversation)) { silent = info.isSilent; break; } } if (silent) { return; } tip = "新消息来了"; } else { tip = "有人@你"; } Push.create(tip, { body: miscState.enableNotificationMessageDetail ? content.digest(msg) : "", // TODO 下面好像不生效,更新成图片链接 icon: icon, timeout: 4000, onClick: () => { if (isElectron()) { ipcRenderer.send(IPCEventType.CLICK_NOTIFICATION, currentWindow.getMediaSourceId()); } else { window.focus(); this.close(); } this.setCurrentConversation(msg.conversation); }, }); } }, updateTray() { if (!isElectron() || !miscState.isMainWindow) { return; } let count = 0; conversationState.conversationInfoList.forEach((info) => { if (info.isSilent) { return; } let unreadCount = info.unreadCount; count += unreadCount.unread; }); if (process.platform === "linux") { this.updateLinuxTitle(count); } else { ipcRenderer.send(IPCEventType.UPDATE_BADGE, count); } }, updateLinuxTitle(unreadCount) { this.updateLinuxTitle.title = "野火IM"; this.updateLinuxTitle.unreadCount = unreadCount; this.updateLinuxTitle.showTitle = true; if (!miscState.linuxUpdateTitleInterval) { miscState.linuxUpdateTitleInterval = setInterval(() => { if (this.updateLinuxTitle.showTitle || this.updateLinuxTitle.unreadCount < 1) { document.title = this.updateLinuxTitle.title; } else { document.title = this.updateLinuxTitle.title + " " + this.updateLinuxTitle.unreadCount; } this.updateLinuxTitle.showTitle = !this.updateLinuxTitle.showTitle; }, 1000); } }, updateVoipStatus(conversation, isOngoing) { conversationState.conversationInfoList.forEach((ci) => { if (ci.conversation.equal(conversation)) { ci._isVoipOngoing = isOngoing; } else { ci._isVoipOngoing = false; } }); }, }; let conversationState = store.state.conversation; let contactState = store.state.contact; let searchState = store.state.search; let pickState = store.state.pick; let miscState = store.state.misc; function _reset() { conversationState._reset(); contactState._reset(); searchState._reset(); pickState._reset(); miscState._reset(); } window.__store = store; window.stringValue = stringValue; window.longValue = longValue; export default store;