websocket 连接状态; 重连

dev/chat
Lei OT 2 years ago
parent 5631ba4997
commit a5d628b92d

@ -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',

@ -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({

@ -1,5 +1,9 @@
const initialState = {
websocket: null,
websocketOpened: null,
websocketRetrying: null,
websocketRetrytimes: null,
errors: [], // 错误信息
conversationsList: [], // 对话列表

@ -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}`]: [] }), {});

@ -31,7 +31,7 @@ const ChatWindow = () => {
<Content style={{ maxHeight: 'calc(100vh - 198px)', height: 'calc(100vh - 198px)' }}>
<Layout className='h-full'>
<Header className='ant-layout-sider-light ant-card p-1 h-auto'>
<Header className='ant-layout-sider-light ant-card h-auto'>
<MessagesHeader />
</Header>
<Content style={{ maxHeight: '74vh', height: '74vh' }}>

@ -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);

@ -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 (
<div>
{messagesList.map((message, index) => (
<MessageBox
// className={message.sender === 'me' ? 'whatsappme-container' : ''}
@ -39,11 +40,13 @@ const Messages = (() => {
// letterItem={{ id: 1, letter: 'AS' }}
// 'waiting'| 'sent' | 'received' | 'read'
styles={{ backgroundColor: message.sender === 'me' ? '#ccd5ae' : '' }}
{...(message.sender === 'me' ? {
{...(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 = (() => {
<div ref={messagesEndRef}></div>
</div>
);
});
};
export default Messages;

@ -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 (
<>
<Flex gap={16}>
{!websocketOpened && <Alert type='error' message='断开连接' banner />}
{websocketRetrying && websocketRetrytimes > 0 && <Alert type={'warning'} message={`正在重连, ${websocketRetrytimes}次...`} banner icon={<LoadingOutlined />} />}
<Flex gap={16} className='p-1'>
{currentConversation.customer_name && <Avatar src={`https://api.dicebear.com/7.x/avataaars/svg?seed=${currentConversation.customer_name}`} />}
<Flex flex={'1'} justify='space-between'>
<Flex vertical={true} justify='space-between'>

@ -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);

Loading…
Cancel
Save