You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Global-sales/src/stores/ConversationStore.js

300 lines
11 KiB
JavaScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { create } from 'zustand';
import { RealTimeAPI } from '@/lib/realTimeAPI';
import { olog, isEmpty } from '@/utils/utils';
import { receivedMsgTypeMapped } from '@/lib/msgUtils';
import { fetchConversationsList, fetchTemplates } from '@/actions/ConversationActions';
import { devtools } from 'zustand/middleware';
import { WS_URL } from '@/config';
// const WS_URL = 'ws://202.103.68.144:8888/whatever/';
// const WS_URL = 'ws://120.79.9.217:10022/whatever/';
const initialConversationState = {
// websocket: null,
// websocketOpened: null,
// websocketRetrying: null,
// websocketRetrytimes: null,
errors: [], // 错误信息
initialState: false,
// templates: [],
// conversationsList: [], // 对话列表
// currentConversation: {}, // 当前对话
// activeConversations: {}, // 激活的对话的消息列表: { [conversationId]: <messageItem>[] }
// referenceMsg: {},
};
const templatesSlice = (set) => ({
templates: [],
setTemplates: (templates) => set({ templates }),
});
const websocketSlice = (set, get) => ({
websocket: null,
websocketOpened: null,
websocketRetrying: null,
websocketRetrytimes: null,
setWebsocket: (websocket) => set({ websocket }),
setWebsocketOpened: (opened) => set({ websocketOpened: opened }),
setWebsocketRetrying: (retrying) => set({ websocketRetrying: retrying }),
setWebsocketRetrytimes: (retrytimes) => set({ websocketRetrytimes: retrytimes, websocketRetrying: retrytimes > 0 }),
connectWebsocket: (userId) => {
const { setWebsocket, setWebsocketOpened, setWebsocketRetrytimes, addError, handleMessage, activeConversations } = get();
const realtimeAPI = new RealTimeAPI(
{
url: `${WS_URL}?opisn=${userId || ''}&_spam=${Date.now().toString()}`,
protocol: 'WhatsApp',
},
() => {
setWebsocketOpened(true);
setWebsocketRetrytimes(0);
},
() => {
setWebsocketOpened(false)
const newMsgList = Object.keys(activeConversations).reduce((acc, key) => {
const newMsgList = activeConversations[key].slice(-10);
acc[key] = newMsgList;
return acc;
}, {});
set({ activeConversations: newMsgList });
},
(n) => setWebsocketRetrytimes(n)
);
realtimeAPI.onError(() => addError('Error'));
realtimeAPI.onMessage(handleMessage);
realtimeAPI.onCompletion(() => addError('Connection broken'));
olog('Connecting to websocket...', realtimeAPI)
setWebsocket(realtimeAPI);
},
disconnectWebsocket: () => {
const { websocket } = get();
if (websocket) websocket.disconnect();
return set({ websocket: null });
},
reconnectWebsocket: (userId) => {
const {disconnectWebsocket, connectWebsocket} = get();
disconnectWebsocket();
setTimeout(() => {
connectWebsocket(userId);
}, 500);
},
handleMessage: (data) => {
console.log('handleMessage------------------');
console.log(data);
const { updateMessageItem, sentOrReceivedNewMessage } = get();
const { errcode, errmsg, result } = data;
if (!result) {
return false;
}
let resultType = result?.action || result.type;
if (errcode !== 0) {
// addError('Error Connecting to Server');
resultType = 'error';
}
console.log(resultType, 'result.type');
const msgObj = receivedMsgTypeMapped[resultType].getMsg(result);
const msgRender = receivedMsgTypeMapped[resultType].contentToRender(msgObj);
const msgUpdate = receivedMsgTypeMapped[resultType].contentToUpdate(msgObj);
console.log('msgRender msgUpdate', msgRender, msgUpdate);
if (['whatsapp.message.updated', 'message', 'error'].includes(resultType)) {
updateMessageItem(msgUpdate);
// return false;
}
if (!isEmpty(msgRender)) {
sentOrReceivedNewMessage(msgRender.conversationid, msgRender);
window.Notification.requestPermission().then(function (permission) {
if (permission === 'granted') {
const notification = new Notification(`${msgRender.senderName}`, {
body: msgRender?.text || `[ ${msgRender.type} ]`,
requireInteraction: true, // 设置手动关闭
tag: 'global-sales-notification', // 通知ID同类通知建议设置相同ID避免通知过多遮挡桌面
...(msgRender.type === 'photo' ? { image: msgRender.data.uri } : {}),
});
notification.onclick = function () {
window.parent.parent.focus();
};
}
});
}
console.log('handleMessage*******************');
},
});
const referenceMsgSlice = (set) => ({
referenceMsg: {},
setReferenceMsg: (referenceMsg) => set({ referenceMsg }),
});
const complexMsgSlice = (set) => ({
complexMsg: {},
setComplexMsg: (complexMsg) => set({ complexMsg }),
});
const conversationSlice = (set, get) => ({
conversationsList: [],
currentConversation: {},
/**
* @deprecated
*/
setConversationsList: (conversationsList) => {
const conversationsMapped = conversationsList.reduce((r, v) => ({ ...r, [`${v.sn}`]: [] }), {});
return set({ conversationsList, activeConversations: conversationsMapped });
},
addToConversationList: (newList) => {
const { activeConversations } = get();
const conversationsIds = Object.keys(activeConversations);
const newConversations = newList.filter((conversation) => !conversationsIds.includes(`${conversation.sn}`));
const newConversationsMapped = newConversations.reduce((r, v) => ({ ...r, [`${v.sn}`]: [] }), {});
return set((state) => ({
conversationsList: [...newConversations, ...state.conversationsList],
activeConversations: { ...activeConversations, ...newConversationsMapped },
totalNotify: state.totalNotify + newConversations.map(ele => ele.unread_msg_count).reduce((acc, cur) => acc + (cur || 0), 0),
}));
},
delConversationitem: (conversation) => {
const { conversationsList, activeConversations } = get();
const targetId = conversation.sn;
const targetIndex = conversationsList.findIndex((ele) => String(ele.sn) === String(targetId));
conversationsList.splice(targetIndex, 1);
return set({
conversationsList: [...conversationsList],
activeConversations: { ...activeConversations, [`${targetId}`]: [] },
currentConversation: {},
});
},
setCurrentConversation: (conversation) => {
// 清空未读
const { conversationsList, totalNotify } = get();
const targetId = conversation.sn;
const targetIndex = conversationsList.findIndex((ele) => String(ele.sn) === String(targetId));
targetIndex !== -1 ? conversationsList.splice(targetIndex, 1, {
...conversationsList[targetIndex],
unread_msg_count: 0,
}) : null;
return set({ totalNotify: totalNotify - (conversation.unread_msg_count || 0), currentConversation: conversation, referenceMsg: {}, conversationsList: [...conversationsList] });
},
});
const messageSlice = (set, get) => ({
totalNotify: 0,
msgListLoading: false,
activeConversations: {},
setMsgLoading: (msgListLoading) => set({ msgListLoading }),
receivedMessageList: (conversationid, msgList) => set((state) => ({
msgListLoading: false,
activeConversations: { ...state.activeConversations, [String(conversationid)]: msgList }
})),
updateMessageItem: (message) => { // msgUpdate
console.log('UPDATE_SENT_MESSAGE_ITEM-----------------------------------------------------------------');
// 更新会话中的消息
const { activeConversations, conversationsList } = get();
const targetId = message.conversationid;
const targetMsgs = (activeConversations[String(targetId)] || []).map((ele) => {
// 更新状态
// * 已读的不再更新状态, 有时候投递结果在已读之后返回
if (ele.id === ele.actionId && ele.actionId === message.actionId) {
return { ...ele, id: message.id, status: ele.status === 'read' ? ele.status : message.status, dateString: message.dateString };
} else if (ele.id === message.id) {
return { ...ele, id: message.id, status: ele.status === 'read' ? ele.status : message.status, dateString: message.dateString };
}
return ele;
});
// 显示会话中其他客户端发送的消息
const targetMsgsIds = targetMsgs.map((ele) => ele.id);
if (!targetMsgsIds.includes(message.id)) {
targetMsgs.push(message);
}
// 更新列表的时间
// if (message.type !== 'error') {
// const targetIndex = conversationsList.findIndex((ele) => String(ele.sn) === String(targetId));
// conversationsList.splice(targetIndex, 1, {
// ...conversationsList[targetIndex],
// last_received_time: message.date,
// });
// }
return set({
activeConversations: { ...activeConversations, [String(targetId)]: targetMsgs },
// conversationsList: [...conversationsList],
});
},
sentOrReceivedNewMessage: (targetId, message) => { // msgRender:
const { activeConversations, conversationsList, currentConversation, totalNotify } = get();
const targetMsgs = activeConversations[String(targetId)] || [];
const targetIndex = conversationsList.findIndex((ele) => String(ele.sn) === String(targetId));
const lastReceivedTime = message.type !== 'system' && message.sender !== 'me' ? message.date : null;
const newConversation =
targetId !== -1
? {
...conversationsList[targetIndex],
last_received_time: lastReceivedTime || conversationsList[targetIndex].last_received_time,
unread_msg_count:
String(targetId) !== String(currentConversation.sn) && message.sender !== 'me'
? conversationsList[targetIndex].unread_msg_count + 1
: conversationsList[targetIndex].unread_msg_count,
}
: {
...message,
sn: targetId,
last_received_time: message.date,
unread_msg_count: message.sender === 'me' ? 0 : 1,
};
conversationsList.splice(targetIndex, 1);
conversationsList.unshift(newConversation);
return set({
totalNotify: totalNotify+newConversation.unread_msg_count,
activeConversations: { ...activeConversations, [String(targetId)]: [...targetMsgs, message] },
conversationsList: [...conversationsList],
currentConversation: {
...currentConversation,
...(String(targetId) === String(currentConversation.sn) ? { last_received_time: message.date } : {}),
},
});
},
});
export const useConversationStore = create(devtools((set, get) => ({
...initialConversationState,
...websocketSlice(set, get),
...conversationSlice(set, get),
...templatesSlice(set, get),
...messageSlice(set, get),
...referenceMsgSlice(set, get),
...complexMsgSlice(set, get),
// state actions
addError: (error) => set((state) => ({ errors: [...state.errors, error] })),
setInitial: (v) => set({ initialState: v }),
// side effects
fetchInitialData: async (userId) => {
const { addToConversationList, setTemplates, setInitial } = get();
const conversationsList = await fetchConversationsList({ opisn: userId });
addToConversationList(conversationsList);
const templates = await fetchTemplates();
setTemplates(templates);
setInitial(true);
},
})));
export default useConversationStore;