✨ feat(im): 优化【消息引用】的功能,来自第二波 code review,解决安全性问题
parent
cfeee7bbb7
commit
ef901b5381
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue