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.
tailchat/client/shared/redux/hooks/useConverseMessage.ts

228 lines
6.3 KiB
TypeScript

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 { useEffect } from 'react';
import { ensureDMConverse } from '../../helper/converse-helper';
import { useAsync } from '../../hooks/useAsync';
import { showErrorToasts } from '../../manager/ui';
import {
fetchConverseMessage,
sendMessage,
SendMessagePayload,
} from '../../model/message';
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';
import { ChatConverseType } from '../../model/converse';
import { sharedEvent } from '../../event';
import { useUpdateRef } from '../../hooks/useUpdateRef';
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: 当先输入文本再选中消息回复时,直接发送无法带上回复信息
/**
* 发送消息
*/
const handleSendMessage = useEvent((payload: SendMessagePayload) => {
// 输入合法性检测
if (payload.content === '') {
showErrorToasts(t('无法发送空消息'));
return;
}
if (hasContext === true) {
// 如果有上下文, 则组装payload
const msgHelper = new MessageHelper(payload);
if (!_isNil(replyMsgRef.current)) {
msgHelper.setReplyMsg(replyMsgRef.current);
clearReplyMsg();
}
payload = msgHelper.generatePayload();
}
const localMessageId = genLocalMessageId();
dispatch(
chatActions.appendLocalMessage({
author: userId,
localMessageId,
payload,
})
);
sendMessage(payload)
.then((message) => {
dispatch(
chatActions.deleteMessageById({
converseId: payload.converseId,
messageId: localMessageId,
})
);
dispatch(
chatActions.appendConverseMessage({
converseId: payload.converseId,
messages: [message],
})
); // 确保删除消息后会立即显示消息适用于网络卡顿的情况同时远程的socket会接收到消息广播在redux中会自动去重
sharedEvent.emit('sendMessage', payload);
})
.catch((err) => {
showErrorToasts(err);
dispatch(
chatActions.updateMessageInfo({
messageId: localMessageId,
message: {
sendFailed: true,
},
})
);
});
});
return handleSendMessage;
}
/**
* 会话消息管理
*/
interface ConverseContext {
converseId: string;
isGroup: boolean;
}
export function useConverseMessage(context: ConverseContext) {
const { converseId, isGroup } = context;
const converse = useAppSelector<ChatConverseState | undefined>(
(state) => state.chat.converses[converseId]
);
const reconnectNum = useAppSelector((state) => state.global.reconnectNum);
const hasMoreMessage = converse?.hasMoreMessage ?? true;
const dispatch = useAppDispatch();
const messages = converse?.messages ?? [];
const currentUserId = useAppSelector((state) => state.user.info?._id);
useEffect(() => {
dispatch(chatActions.updateCurrentConverseId(converseId));
return () => {
dispatch(chatActions.updateCurrentConverseId(null));
};
}, [converseId]);
// NOTICE: 该hook只会在converseId变化和重新链接时执行
const { loading, error } = useAsync(async () => {
if (!currentUserId) {
// 如果当前用户不存在则跳过逻辑
return;
}
if (!converse) {
// 如果是一个新会话(或者当前会话列表中没有)
if (!isGroup) {
// 如果是私信会话
// Step 1. 创建会话 并确保私信列表中存在该会话
const converse = await ensureDMConverse(converseId, currentUserId);
dispatch(chatActions.setConverseInfo(converse));
} else {
// 如果是群组会话(文本频道)
// Step 1. 确保群组会话存在
dispatch(
chatActions.setConverseInfo({
_id: converseId,
name: '',
type: ChatConverseType.Group,
members: [],
})
);
}
// Step 2. 拉取消息
const historyMessages = await fetchConverseMessage(converseId);
dispatch(
chatActions.initialHistoryMessage({
converseId,
historyMessages,
})
);
} else {
// 已存在会话
if (!converse.hasFetchedHistory) {
// 没有获取过历史消息
// 拉取历史消息
const startId = _isNil(converse.messages[0])
? undefined
: converse.messages[0]._id;
const messages = await fetchConverseMessage(converseId, startId);
dispatch(
chatActions.initialHistoryMessage({
converseId,
historyMessages: messages,
})
);
}
}
}, [converseId, reconnectNum, currentUserId]);
// 加载更多消息
const [{ loading: isLoadingMore }, _handleFetchMoreMessage] =
useAsyncRequest(async () => {
const firstMessageId = _get(messages, [0, '_id']);
if (!isValidStr(firstMessageId)) {
return;
}
if (hasMoreMessage === false) {
return;
}
const olderMessages = await fetchConverseMessage(
converseId,
firstMessageId
);
dispatch(
chatActions.appendHistoryMessage({
converseId,
historyMessages: olderMessages,
})
);
}, [converseId, hasMoreMessage, _get(messages, [0, '_id'])]);
/**
* 加载更多
* 同一时间只能请求一次
*/
const handleFetchMoreMessage = useMemoizedFn(async () => {
if (isLoadingMore) {
return;
}
await _handleFetchMoreMessage();
});
const handleSendMessage = useHandleSendMessage();
return {
messages,
loading,
error,
isLoadingMore,
hasMoreMessage,
handleFetchMoreMessage,
handleSendMessage,
};
}