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'; // const WS_URL = 'ws://202.103.68.144:8888/whatever/'; // const WS_URL = 'ws://120.79.9.217:10022/whatever/'; const WS_URL = 'wss://p9axztuwd7x8a7.mycht.cn/whatsapp_callback'; // prod: const initialConversationState = { // websocket: null, // websocketOpened: null, // websocketRetrying: null, // websocketRetrytimes: null, errors: [], // 错误信息 // templates: [], // conversationsList: [], // 对话列表 // currentConversation: {}, // 当前对话 // activeConversations: {}, // 激活的对话的消息列表: { [conversationId]: [] } // referenceMsg: {}, }; olog('initialConversationState'); export const templatesSlice = (set) => ({ templates: [], setTemplates: (templates) => set({ templates }), }); export 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 } = get(); const realtimeAPI = new RealTimeAPI( { url: `${WS_URL}?opisn=${userId || ''}&_spam=${Date.now().toString()}`, protocol: 'WhatsApp', }, () => { setWebsocketOpened(true); setWebsocketRetrytimes(0); }, () => setWebsocketOpened(false), (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(); websocket.disconnect(); return set({ websocket: null }); }, 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} ]`, ...(msgRender.type === 'photo' ? { image: msgRender.data.uri } : {}), }); notification.onclick = function () { window.parent.parent.focus(); }; } }); } console.log('handleMessage*******************'); }, }); export const referenceMsgSlice = (set) => ({ referenceMsg: [], setReferenceMsg: (referenceMsg) => set({ referenceMsg }), }); export const conversationSlice = (set, get) => ({ conversationsList: [], currentConversation: {}, 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 } })); }, 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((state) => ({ conversationsList: [...conversationsList], activeConversations: { ...activeConversations, [`${targetId}`]: [] }, currentConversation: {}, })); }, setCurrentConversation: (conversation) => { // 清空未读 const { conversationsList } = 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({ currentConversation: conversation, referenceMsg: {}, conversationsList: [...conversationsList] }); }, }); export const messageSlice = (set, get) => ({ activeConversations: {}, receivedMessageList: (conversationid, msgList) => set((state) => ({ 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((state) => ({ activeConversations: { ...activeConversations, [String(targetId)]: targetMsgs }, conversationsList: [...conversationsList], })); }, sentOrReceivedNewMessage: (targetId, message) => { // msgRender: const { activeConversations, conversationsList, currentConversation } = get(); const targetMsgs = activeConversations[String(targetId)] || []; const targetIndex = conversationsList.findIndex((ele) => String(ele.sn) === String(targetId)); const newConversation = targetId !== -1 ? { ...conversationsList[targetIndex], last_received_time: message.type !== 'system' && message.sender !== 'me' ? message.date : 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((state) => ({ activeConversations: { ...activeConversations, [String(targetId)]: [...targetMsgs, message] }, conversationsList: [...conversationsList], currentConversation: { ...currentConversation, last_received_time: String(targetId) === String(currentConversation.sn) ? message.date : currentConversation.last_received_time, }, })); }, }); export const useConversationStore = create(devtools((set, get) => ({ ...initialConversationState, ...websocketSlice(set, get), ...conversationSlice(set, get), ...templatesSlice(set, get), ...messageSlice(set, get), ...referenceMsgSlice(set, get), // state actions addError: (error) => set((state) => ({ errors: [...state.errors, error] })), // side effects fetchInitialData: async (userId) => { olog('fetch init'); const { setConversationsList, setTemplates } = get(); const conversationsList = await fetchConversationsList({ opisn: userId }); setConversationsList(conversationsList); const templates = await fetchTemplates(); setTemplates(templates); }, }))); // window.store = useConversationStore; // debug: export default useConversationStore;