feat: add local message to avoid network error which occur repeat send same message

pull/105/merge
moonrailgun 2 years ago
parent c045475f62
commit 9bb931aa5d

@ -3,6 +3,17 @@ import type { ChatMessageReaction, ChatMessage } from 'tailchat-types';
export { ChatMessageReaction, ChatMessage };
export interface LocalChatMessage extends ChatMessage {
/**
*
*/
isLocal?: boolean;
/**
*
*/
sendFailed?: boolean;
}
export interface SimpleMessagePayload {
groupId?: string;
converseId: string;

@ -1,4 +1,4 @@
import { useCallback, useEffect } from 'react';
import { useEffect } from 'react';
import { ensureDMConverse } from '../../helper/converse-helper';
import { useAsync } from '../../hooks/useAsync';
import { showErrorToasts } from '../../manager/ui';
@ -11,12 +11,14 @@ import { chatActions } from '../slices';
import { useAppDispatch, useAppSelector } from './useAppSelector';
import _get from 'lodash/get';
import _isNil from 'lodash/isNil';
import _uniqueId from 'lodash/uniqueId';
import {
ChatConverseState,
isValidStr,
t,
useAsyncRequest,
useChatBoxContext,
useEvent,
useMemoizedFn,
} from '../..';
import { MessageHelper } from '../../utils/message-helper';
@ -24,8 +26,10 @@ import { ChatConverseType } from '../../model/converse';
import { sharedEvent } from '../../event';
import { useUpdateRef } from '../../hooks/useUpdateRef';
function useHandleSendMessage(context: ConverseContext) {
const { converseId } = context;
const genLocalMessageId = () => _uniqueId('localMessage_');
function useHandleSendMessage() {
const userId = useAppSelector((state) => state.user.info?._id);
const dispatch = useAppDispatch();
const { hasContext, replyMsg, clearReplyMsg } = useChatBoxContext();
const replyMsgRef = useUpdateRef(replyMsg); // NOTICE: 这个是为了修复一个边界case: 当先输入文本再选中消息回复时,直接发送无法带上回复信息
@ -33,43 +37,60 @@ function useHandleSendMessage(context: ConverseContext) {
/**
*
*/
const handleSendMessage = useCallback(
async (payload: SendMessagePayload) => {
// 输入合法性检测
if (payload.content === '') {
showErrorToasts(t('无法发送空消息'));
return;
}
try {
if (hasContext === true) {
// 如果有上下文, 则组装payload
const msgHelper = new MessageHelper(payload);
if (!_isNil(replyMsgRef.current)) {
msgHelper.setReplyMsg(replyMsgRef.current);
clearReplyMsg();
}
const handleSendMessage = useEvent((payload: SendMessagePayload) => {
// 输入合法性检测
if (payload.content === '') {
showErrorToasts(t('无法发送空消息'));
return;
}
payload = msgHelper.generatePayload();
}
if (hasContext === true) {
// 如果有上下文, 则组装payload
const msgHelper = new MessageHelper(payload);
if (!_isNil(replyMsgRef.current)) {
msgHelper.setReplyMsg(replyMsgRef.current);
clearReplyMsg();
}
// TODO: 增加临时消息, 对网络环境不佳的状态进行优化
payload = msgHelper.generatePayload();
}
const message = await sendMessage(payload);
const localMessageId = genLocalMessageId();
dispatch(
chatActions.appendLocalMessage({
author: userId,
localMessageId,
payload,
})
);
sendMessage(payload)
.then((message) => {
dispatch(
chatActions.appendConverseMessage({
converseId,
messages: [message],
chatActions.updateMessageInfo({
messageId: localMessageId,
message: {
...message,
isLocal: false,
sendFailed: false,
},
})
);
sharedEvent.emit('sendMessage', payload);
} catch (err) {
})
.catch((err) => {
showErrorToasts(err);
throw err;
}
},
[converseId, hasContext, clearReplyMsg]
);
dispatch(
chatActions.updateMessageInfo({
messageId: localMessageId,
message: {
sendFailed: true,
},
})
);
});
});
return handleSendMessage;
}
@ -190,7 +211,7 @@ export function useConverseMessage(context: ConverseContext) {
await _handleFetchMoreMessage();
});
const handleSendMessage = useHandleSendMessage(context);
const handleSendMessage = useHandleSendMessage();
return {
messages,

@ -166,9 +166,16 @@ function listenNotify(socket: AppSocket, store: AppStore) {
// 处理接受到的消息
const converseId = message.converseId;
const converse = store.getState().chat.converses[converseId];
const userId = store.getState().user.info?._id;
// 添加消息到会话中
const appendMessage = () => {
if (message.author === userId) {
// 如果是自己发送的消息,则忽略
// 因为存在local状态的消息应该由发送消息的地方处理
return;
}
store.dispatch(
chatActions.appendConverseMessage({
converseId,

@ -1,6 +1,11 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import type { ChatConverseInfo } from '../../model/converse';
import type { ChatMessage, ChatMessageReaction } from '../../model/message';
import type {
ChatMessage,
ChatMessageReaction,
LocalChatMessage,
SendMessagePayload,
} from '../../model/message';
import _uniqBy from 'lodash/uniqBy';
import _orderBy from 'lodash/orderBy';
import _last from 'lodash/last';
@ -8,7 +13,7 @@ import { isValidStr } from '../../utils/string-helper';
import type { InboxItem } from '../../model/inbox';
export interface ChatConverseState extends ChatConverseInfo {
messages: ChatMessage[];
messages: LocalChatMessage[];
hasFetchedHistory: boolean;
/**
*
@ -96,6 +101,45 @@ const chatSlice = createSlice({
}
},
/**
*
*/
appendLocalMessage(
state,
action: PayloadAction<{
author?: string;
localMessageId: string;
payload: SendMessagePayload;
}>
) {
const { author, localMessageId, payload } = action.payload;
const { converseId, groupId, content, meta } = payload;
if (!state.converses[converseId]) {
// 没有会话信息, 请先设置会话信息
console.error('没有会话信息, 请先设置会话信息');
return;
}
const message: LocalChatMessage = {
_id: localMessageId,
author,
groupId,
converseId,
content,
meta: meta as Record<string, unknown>,
isLocal: true,
};
const newMessages = _orderBy(
_uniqBy([...state.converses[converseId].messages, message], '_id'),
'_id',
'asc'
);
state.converses[converseId].messages = newMessages;
},
/**
*
*/
@ -186,18 +230,25 @@ const chatSlice = createSlice({
updateMessageInfo(
state,
action: PayloadAction<{
message: ChatMessage;
messageId?: string;
message: Partial<LocalChatMessage>;
}>
) {
const { message } = action.payload;
const messageId = action.payload.messageId ?? message._id;
const converseId = message.converseId;
if (!converseId) {
console.warn('Not found converse id,', message);
return;
}
const converse = state.converses[converseId];
if (!converse) {
console.warn('Not found converse,', converseId);
return;
}
const index = converse.messages.findIndex((m) => m._id === message._id);
const index = converse.messages.findIndex((m) => m._id === messageId);
if (index >= 0) {
converse.messages[index] = {
...converse.messages[index],

@ -25,6 +25,7 @@ import { AutoFolder, Avatar, Icon } from 'tailchat-design';
import { MessageAckContainer } from './MessageAckContainer';
import { UserPopover } from '@/components/popover/UserPopover';
import _isEmpty from 'lodash/isEmpty';
import type { LocalChatMessage } from 'tailchat-shared/model/message';
import './Item.less';
/**
@ -142,6 +143,13 @@ const NormalMessage: React.FC<ChatMessageItemProps> = React.memo((props) => {
<span>{getMessageRender(payload.content)}</span>
{payload.sendFailed === true && (
<Icon
className="inline-block ml-1"
icon="emojione:cross-mark-button"
/>
)}
{/* 解释器按钮 */}
{useRenderPluginMessageInterpreter(payload.content)}
</div>
@ -235,7 +243,7 @@ SystemMessageWithNickname.displayName = 'SystemMessageWithNickname';
interface ChatMessageItemProps {
showAvatar: boolean;
payload: ChatMessage;
payload: LocalChatMessage;
}
const ChatMessageItem: React.FC<ChatMessageItemProps> = React.memo((props) => {
const payload = props.payload;
@ -266,7 +274,10 @@ ChatMessageItem.displayName = 'ChatMessageItem';
/**
*
*/
export function buildMessageItemRow(messages: ChatMessage[], index: number) {
export function buildMessageItemRow(
messages: LocalChatMessage[],
index: number
) {
const message = messages[index];
if (!message) {
@ -305,12 +316,18 @@ export function buildMessageItemRow(messages: ChatMessage[], index: number) {
</Divider>
)}
<MessageAckContainer
converseId={message.converseId}
messageId={message._id}
>
<ChatMessageItem showAvatar={showAvatar} payload={message} />
</MessageAckContainer>
{message.isLocal === true ? (
<div className="opacity-50">
<ChatMessageItem showAvatar={showAvatar} payload={message} />
</div>
) : (
<MessageAckContainer
converseId={message.converseId}
messageId={message._id}
>
<ChatMessageItem showAvatar={showAvatar} payload={message} />
</MessageAckContainer>
)}
</div>
);
}

@ -1,6 +1,6 @@
import { Intersection } from '@/components/Intersection';
import React from 'react';
import { useConverseAck, useMemoizedFn } from 'tailchat-shared';
import { useConverseAck, useEvent } from 'tailchat-shared';
/**
*
@ -14,7 +14,7 @@ export const MessageAckContainer: React.FC<MessageAckContainerProps> =
React.memo((props) => {
const { updateConverseAck } = useConverseAck(props.converseId);
const handleIntersection = useMemoizedFn(() => {
const handleIntersection = useEvent(() => {
updateConverseAck(props.messageId);
});

Loading…
Cancel
Save