feat(im): 优化【消息引用】的功能,来自第二波 code review,解决安全性问题

im
YunaiV 2026-05-01 18:20:04 +08:00
parent cfeee7bbb7
commit ef901b5381
1 changed files with 43 additions and 10 deletions

View File

@ -555,6 +555,22 @@ function consumeReply(): QuoteMessage | undefined {
return quote return quote
} }
/** 抓当前激活会话的 key媒体上传开始前调无激活会话返回 undefined */
function getActiveConversationKey(): string | undefined {
const conversation = conversationStore.activeConversation
return conversation ? getConversationKey(conversation) : undefined
}
/** 校验当前激活会话仍是 startKey切走了记日志 + 返回 false调用方放弃发送 */
function isStillSameConversation(startKey: string, kind: string): boolean {
const conversation = conversationStore.activeConversation
if (!conversation || getConversationKey(conversation) !== startKey) {
console.warn(`[IM] ${kind}上传期间切换了会话,放弃发送`, { startKey })
return false
}
return true
}
// ==================== ==================== // ==================== ====================
const emojiVisible = ref(false) const emojiVisible = ref(false)
/** 切换表情面板;打开时互斥关掉语音面板 */ /** 切换表情面板;打开时互斥关掉语音面板 */
@ -776,6 +792,10 @@ function onKeydown(e: KeyboardEvent) {
// ==================== / ==================== // ==================== / ====================
/** 上传并发送 IMAGE 消息;quote 抓取后立即清 draft.reply 让顶部引用条同步消失 */ /** 上传并发送 IMAGE 消息;quote 抓取后立即清 draft.reply 让顶部引用条同步消失 */
async function uploadAndSendImage(file: File) { async function uploadAndSendImage(file: File) {
const startKey = getActiveConversationKey()
if (!startKey) {
return
}
const replyQuote = consumeReply() const replyQuote = consumeReply()
const form = new FormData() const form = new FormData()
form.append('file', file) form.append('file', file)
@ -783,12 +803,19 @@ async function uploadAndSendImage(file: File) {
if (!url) { if (!url) {
return return
} }
if (!isStillSameConversation(startKey, '图片')) {
return
}
const payload = withQuotePayload<ImageMessage>({ url }, replyQuote) const payload = withQuotePayload<ImageMessage>({ url }, replyQuote)
await sendRaw(ImMessageType.IMAGE, serializeMessage(payload)) await sendRaw(ImMessageType.IMAGE, serializeMessage(payload))
} }
/** 上传并发送 FILE 消息;附原始 name / size 让接收端展示文件名和体积 */ /** 上传并发送 FILE 消息;附原始 name / size 让接收端展示文件名和体积 */
async function uploadAndSendFile(file: File) { async function uploadAndSendFile(file: File) {
const startKey = getActiveConversationKey()
if (!startKey) {
return
}
const replyQuote = consumeReply() const replyQuote = consumeReply()
const form = new FormData() const form = new FormData()
form.append('file', file) form.append('file', file)
@ -796,6 +823,9 @@ async function uploadAndSendFile(file: File) {
if (!url) { if (!url) {
return return
} }
if (!isStillSameConversation(startKey, '文件')) {
return
}
const payload = withQuotePayload<FileMessage>( const payload = withQuotePayload<FileMessage>(
{ url, name: file.name, size: file.size }, { url, name: file.name, size: file.size },
replyQuote replyQuote
@ -832,6 +862,10 @@ function openVoice() {
} }
/** VoiceRecorder 录完后回传 blob包成 webm 文件上传,发送 VOICE 消息 */ /** VoiceRecorder 录完后回传 blob包成 webm 文件上传,发送 VOICE 消息 */
async function onVoiceSend(payload: { blob: Blob; duration: number }) { async function onVoiceSend(payload: { blob: Blob; duration: number }) {
const startKey = getActiveConversationKey()
if (!startKey) {
return
}
const replyQuote = consumeReply() const replyQuote = consumeReply()
const file = new File([payload.blob], `voice-${Date.now()}.webm`, { type: payload.blob.type }) const file = new File([payload.blob], `voice-${Date.now()}.webm`, { type: payload.blob.type })
const form = new FormData() const form = new FormData()
@ -841,6 +875,9 @@ async function onVoiceSend(payload: { blob: Blob; duration: number }) {
if (!url) { if (!url) {
return return
} }
if (!isStillSameConversation(startKey, '语音')) {
return
}
const audioPayload = withQuotePayload<AudioMessage>( const audioPayload = withQuotePayload<AudioMessage>(
{ url, duration: payload.duration }, { url, duration: payload.duration },
replyQuote replyQuote
@ -953,14 +990,12 @@ async function probeVideoFile(file: File): Promise<VideoProbe> {
* 否则会落到错误的会话里切走再切回来不算变化key 仍相等 * 否则会落到错误的会话里切走再切回来不算变化key 仍相等
*/ */
async function uploadAndSendVideo(file: File) { async function uploadAndSendVideo(file: File) {
// 1. key // 1. key key
// 1.1 key const startKey = getActiveConversationKey()
const startConversation = conversationStore.activeConversation if (!startKey) {
if (!startConversation) {
return return
} }
const startKey = getConversationKey(startConversation) // quote draft.reply / /
// 1.2 quote draft.reply / /
const replyQuote = consumeReply() const replyQuote = consumeReply()
// 2. probe probe cover // 2. probe probe cover
@ -1006,10 +1041,8 @@ async function uploadAndSendVideo(file: File) {
if (!url) { if (!url) {
return return
} }
// 3.3 // 3.3
const currentConversation = conversationStore.activeConversation if (!isStillSameConversation(startKey, '视频')) {
if (!currentConversation || getConversationKey(currentConversation) !== startKey) {
console.warn('[IM] 视频上传期间切换了会话,放弃发送', { startKey })
return return
} }