From a5d628b92d7841cf6ac44e4f32212cea820d736d Mon Sep 17 00:00:00 2001 From: Lei OT Date: Fri, 2 Feb 2024 12:34:18 +0800 Subject: [PATCH] =?UTF-8?q?websocket=20=E8=BF=9E=E6=8E=A5=E7=8A=B6?= =?UTF-8?q?=E6=80=81;=20=E9=87=8D=E8=BF=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/actions/ConversationActions.js | 9 ++- src/lib/realTimeAPI.js | 77 ++++++++++++++++--- src/records/ConversationState.js | 4 + src/reducers/ConversationReducer.js | 4 + src/views/Conversations/ChatWindow.jsx | 2 +- .../Components/InputComposer.jsx | 4 +- .../Conversations/Components/Messages.jsx | 21 ++--- .../Components/MessagesHeader.jsx | 9 ++- .../Conversations/ConversationProvider.jsx | 13 +++- 9 files changed, 114 insertions(+), 29 deletions(-) diff --git a/src/actions/ConversationActions.js b/src/actions/ConversationActions.js index b44fd84..ec0359c 100644 --- a/src/actions/ConversationActions.js +++ b/src/actions/ConversationActions.js @@ -14,7 +14,14 @@ export const closeWebsocket0 = () => ({ type: NAME_SPACE + 'CLOSE_WEBSOCKET', payload: null, }); -export const closeWebsocket = () => {}; +export const updateWebsocketState = (v) => ({ + type: NAME_SPACE + 'MODIFY_WEBSOCKET_STATE', + payload: v, +}); +export const updateWebsocketRetrytimes = (v) => ({ + type: NAME_SPACE + 'MODIFY_WEBSOCKET_RETRYTIMES', + payload: v, +}); export const receivedNewMessage = (targetId, message) => ({ type: NAME_SPACE + 'RECEIVED_NEW_MESSAGE', diff --git a/src/lib/realTimeAPI.js b/src/lib/realTimeAPI.js index 4a9f8f1..348ed21 100644 --- a/src/lib/realTimeAPI.js +++ b/src/lib/realTimeAPI.js @@ -1,20 +1,76 @@ import { webSocket } from 'rxjs/webSocket'; -import { of, timer, concatMap } from 'rxjs'; -import { filter, buffer, map, tap, retryWhen, retry, delay, take, } from 'rxjs/operators'; -import { v4 as uuid } from "uuid"; +import { of, timer, concatMap, EMPTY, takeWhile, concat } from 'rxjs'; +import { filter, buffer, map, tap, retryWhen, retry, delay, take, catchError } from 'rxjs/operators'; +import { v4 as uuid } from 'uuid'; export class RealTimeAPI { - constructor(param) { - this.webSocket = webSocket(param); + constructor(param, onOpenCallback, onCloseCallback, onRetryCallback) { + this.onOpenCallback = onOpenCallback; + this.onCloseCallback = onCloseCallback; + this.onErrorCallback = onRetryCallback; + this.webSocket = webSocket({ + ...param, + openObserver: { + next: () => { + this.onOpen(); + }, + }, + closeObserver: { + next: () => { + this.onClose(); + }, + }, + }); + this.retryCount = 0; + } + onOpen() { + console.log( + `%c WebSocket connection opened `, + 'background:#41b883 ; padding: 1px; border-radius: 3px; color: #fff', + ); + if (this.onOpenCallback) { + this.onOpenCallback(); + } + } + + onClose() { + console.log( + `%c WebSocket connection closed `, + 'background:#35495e ; padding: 1px; border-radius: 3px; color: #fff' + ); + if (this.onCloseCallback) { + this.onCloseCallback(this.retryCount); + } + } + + onRetry(i) { + this.retryCount = i; + if (this.onErrorCallback) { + this.onErrorCallback(i); + } } getObservable() { return this.webSocket.pipe( - retry(10) - // retry({ - // count: 10, - // delay: 3000, // () => timer(3000) - // }) + // retry(10) + retry({ + count: 10, + // delay: 3000, + delay: (errors, index) => { + this.onRetry(index); + return timer(3000); + }, + resetOnSuccess: true, + }), + catchError((error) => { + this.retryCount = 0; + this.onRetry(-1); + console.log( + `%c All retries exhausted `, + 'background:#fb923c ; padding: 1px; border-radius: 3px; color: #fff' + ); + return EMPTY; + }) ); } @@ -72,7 +128,6 @@ export class RealTimeAPI { return this.getObservableFilteredByMessageType('ping').pipe(tap(() => this.sendMessage({ msg: 'pong' }))); } - callMethod(method, ...params) { let id = 'uuid()'; this.sendMessage({ diff --git a/src/records/ConversationState.js b/src/records/ConversationState.js index fc3e06a..f16ef30 100644 --- a/src/records/ConversationState.js +++ b/src/records/ConversationState.js @@ -1,5 +1,9 @@ const initialState = { websocket: null, + websocketOpened: null, + websocketRetrying: null, + websocketRetrytimes: null, + errors: [], // 错误信息 conversationsList: [], // 对话列表 diff --git a/src/reducers/ConversationReducer.js b/src/reducers/ConversationReducer.js index a5c1595..5e12877 100644 --- a/src/reducers/ConversationReducer.js +++ b/src/reducers/ConversationReducer.js @@ -6,6 +6,10 @@ const ConversationReducer = (state = initialState, action) => { switch (action.type) { case NAME_SPACE + 'INIT_WEBSOCKET': return { ...state, websocket: action.payload }; + case NAME_SPACE + 'MODIFY_WEBSOCKET_STATE': + return { ...state, websocketOpened: action.payload, }; + case NAME_SPACE + 'MODIFY_WEBSOCKET_RETRYTIMES': + return { ...state, websocketRetrytimes: action.payload, websocketRetrying: action.payload > 0 }; case NAME_SPACE + 'SET_CONVERSATION_LIST':{ const conversationsMapped = action.payload.reduce((r, v) => ({ ...r, [`${v.sn}`]: [] }), {}); diff --git a/src/views/Conversations/ChatWindow.jsx b/src/views/Conversations/ChatWindow.jsx index 7b62c6f..8882ba2 100644 --- a/src/views/Conversations/ChatWindow.jsx +++ b/src/views/Conversations/ChatWindow.jsx @@ -31,7 +31,7 @@ const ChatWindow = () => { -
+
diff --git a/src/views/Conversations/Components/InputComposer.jsx b/src/views/Conversations/Components/InputComposer.jsx index a212257..a495133 100644 --- a/src/views/Conversations/Components/InputComposer.jsx +++ b/src/views/Conversations/Components/InputComposer.jsx @@ -13,11 +13,11 @@ import InputTemplate from './InputTemplate'; const InputBox = () => { const { loginUser } = useAuthContext(); const { userId } = loginUser; - const { websocket, currentConversation } = useConversationState(); + const { websocket, websocketOpened, currentConversation } = useConversationState(); const dispatch = useConversationDispatch(); const [textContent, setTextContent] = useState(''); - const talkabled = !isEmpty(currentConversation.sn); + const talkabled = !isEmpty(currentConversation.sn) && websocketOpened; const invokeSendMessage = (msgObj) => { console.log('sendMessage------------------', msgObj); diff --git a/src/views/Conversations/Components/Messages.jsx b/src/views/Conversations/Components/Messages.jsx index 365373c..8ce01ff 100644 --- a/src/views/Conversations/Components/Messages.jsx +++ b/src/views/Conversations/Components/Messages.jsx @@ -1,9 +1,9 @@ import { useEffect, useState, useRef } from 'react'; -import { List, Avatar, Timeline, Image } from 'antd'; +import { Image, Alert } from 'antd'; import { MessageBox } from 'react-chat-elements'; import { useConversationState } from '@/stores/ConversationContext'; -const Messages = (() => { +const Messages = () => { const { activeConversations, currentConversation } = useConversationState(); const messagesList = activeConversations[currentConversation.sn] || []; console.log('messagesList----------------------------------------------------', messagesList); @@ -29,6 +29,7 @@ const Messages = (() => { return (
+ {messagesList.map((message, index) => ( { onOpen={() => handlePreview(message)} // letterItem={{ id: 1, letter: 'AS' }} // 'waiting'| 'sent' | 'received' | 'read' - styles={{backgroundColor: message.sender === 'me' ? '#ccd5ae' : ''}} - {...(message.sender === 'me' ? { - style: { backgroundColor: '#ccd5ae' }, - notchStyle: {fill: '#ccd5ae'}, - className: 'whatsappme-container whitespace-pre-wrap', - } : {})} + styles={{ backgroundColor: message.sender === 'me' ? '#ccd5ae' : '' }} + {...(message.sender === 'me' + ? { + style: { backgroundColor: '#ccd5ae' }, + notchStyle: { fill: '#ccd5ae' }, + className: 'whatsappme-container whitespace-pre-wrap', + } + : {})} // notchStyle={{fill: '#ccd5ae'}} // copiableDate={false} /> @@ -52,6 +55,6 @@ const Messages = (() => {
); -}); +}; export default Messages; diff --git a/src/views/Conversations/Components/MessagesHeader.jsx b/src/views/Conversations/Components/MessagesHeader.jsx index 4eeccf7..0bd6462 100644 --- a/src/views/Conversations/Components/MessagesHeader.jsx +++ b/src/views/Conversations/Components/MessagesHeader.jsx @@ -1,14 +1,17 @@ import { createContext, useContext, useEffect, useState } from 'react'; import { useConversationState } from '@/stores/ConversationContext'; -import { Flex, Typography, Avatar } from 'antd'; +import { Flex, Typography, Avatar, Alert } from 'antd'; +import { LoadingOutlined } from '@ant-design/icons'; import LocalTimeClock from './LocalTimeClock'; const MessagesHeader = () => { - const { currentConversation } = useConversationState(); + const { websocketOpened, websocketRetrying, websocketRetrytimes, currentConversation } = useConversationState(); return ( <> - + {!websocketOpened && } + {websocketRetrying && websocketRetrytimes > 0 && } />} + {currentConversation.customer_name && } diff --git a/src/views/Conversations/ConversationProvider.jsx b/src/views/Conversations/ConversationProvider.jsx index e11abae..473c0b6 100644 --- a/src/views/Conversations/ConversationProvider.jsx +++ b/src/views/Conversations/ConversationProvider.jsx @@ -3,6 +3,8 @@ import { ConversationStateContext, ConversationDispatchContext } from '@/stores/ import ConversationReducer from '@/reducers/ConversationReducer'; import { initWebsocket, + updateWebsocketState, + updateWebsocketRetrytimes, addError, fetchConversationsList, fetchTemplates, @@ -26,15 +28,22 @@ const WS_URL = 'wss://p9axztuwd7x8a7.mycht.cn/whatsapp_callback'; // prod: const ConversationProvider = ({ children }) => { const { loginUser } = useContext(AuthContext); const { userId } = loginUser; - console.log('====', loginUser); - const realtimeAPI = new RealTimeAPI({ url: `${WS_URL}?opisn=${userId || ''}&_spam=${Date.now().toString()}`, protocol: 'WhatsApp' }); const [state, dispatch] = useReducer(ConversationReducer, { ...initialState, websocket: null }); console.log('ConversationProvider', state, dispatch); useEffect(() => { console.log('invoke provider'); + const realtimeAPI = new RealTimeAPI( + { + url: `${WS_URL}?opisn=${userId || ''}&_spam=${Date.now().toString()}`, + protocol: 'WhatsApp', + }, + () => {dispatch(updateWebsocketState(true)); dispatch(updateWebsocketRetrytimes(0));}, + () => dispatch(updateWebsocketState(false)), + (n) => dispatch(updateWebsocketRetrytimes(n)) + ); realtimeAPI.onError(() => dispatch(addError('Error'))); realtimeAPI.onMessage(handleMessage);