import { encodeWithPassword } from "@/app/workers/crypto/crypto"; import { MessageReply } from "../DialogProvider/useReplyMessages"; import { Attachment, AttachmentType } from "../ProtocolProvider/protocol/packets/packet.message"; import { base64ImageToBlurhash } from "@/app/workers/image/image"; import { MESSAGE_MAX_TIME_TO_DELEVERED_S } from "@/app/constants"; import { useContext, useRef } from "react"; import { useTransport } from "../TransportProvider/useTransport"; import { useDialogsList } from "../DialogListProvider/useDialogsList"; import { useDatabase } from "../DatabaseProvider/useDatabase"; import { useConsoleLogger } from "@/app/hooks/useConsoleLogger"; import { useDialogsCache } from "../DialogProvider/useDialogsCache"; import { DialogContext } from "../DialogProvider/DialogProvider"; import { useTransportServer } from "../TransportProvider/useTransportServer"; import { usePublicKey } from "../AccountProvider/usePublicKey"; export function usePrepareAttachment() { const intervalsRef = useRef(null); const {uploadFile} = useTransport(); const {updateDialog} = useDialogsList(); const {runQuery} = useDatabase(); const {info} = useConsoleLogger('usePrepareAttachment'); const {getDialogCache} = useDialogsCache(); const context = useContext(DialogContext); const transportServer = useTransportServer(); const publicKey = usePublicKey(); const updateTimestampInDialogCache = (dialog : string, message_id: string) => { const dialogCache = getDialogCache(dialog); if(dialogCache == null){ return; } for(let i = 0; i < dialogCache.length; i++){ if(dialogCache[i].message_id == message_id){ dialogCache[i].timestamp = Date.now(); break; } } } /** * Обновляет временную метку в сообщении, пока вложения отправляются, * потому что если этого не делать, то сообщение может быть помечено как * не доставленное из-за таймаута доставки * @param attachments Вложения */ const doTimestampUpdateImMessageWhileAttachmentsSend = (message_id: string, dialog: string) => { if(intervalsRef.current){ clearInterval(intervalsRef.current); } intervalsRef.current = setInterval(async () => { /** * Обновляем время в левом меню */ await runQuery("UPDATE messages SET timestamp = ? WHERE message_id = ?", [Date.now(), message_id]); updateDialog(dialog); /** * Обновляем состояние в кэше диалогов */ updateTimestampInDialogCache(dialog, message_id); if(context == null || !context){ /** * Если этот диалог сейчас не открыт */ return; } context.setMessages((prev) => { return prev.map((value) => { if(value.message_id != message_id){ return value; } return { ...value, timestamp: Date.now() }; }) }); }, (MESSAGE_MAX_TIME_TO_DELEVERED_S / 2) * 1000); } /** * Подготавливает вложения для отправки. Подготовка * состоит в загрузке файлов на транспортный сервер, мы не делаем * это через WebSocket из-за ограничений по размеру сообщений, * а так же из-за надежности доставки файлов через HTTP * @param attachments Attachments to prepare for sending */ const prepareAttachmentsToSend = async (message_id: string, dialog: string, password: string, attachments : Attachment[], rePrepared : boolean = false) : Promise => { if(attachments.length <= 0){ return []; } let prepared : Attachment[] = []; try{ for(let i = 0; i < attachments.length; i++){ const attachment : Attachment = attachments[i]; if(attachment.type == AttachmentType.CALL){ /** * Звонк загружать не надо, по этому просто отправляем его как есть, там нет blob */ prepared.push(attachment); continue; } if(attachment.type == AttachmentType.MESSAGES){ let reply : MessageReply[] = JSON.parse(attachment.blob) for(let j = 0; j < reply.length; j++){ reply[j].attachments = await prepareAttachmentsToSend(message_id, dialog, password, reply[j].attachments, true); } prepared.push({ ...attachment, blob: await encodeWithPassword(password, JSON.stringify(reply)) }); continue; } if((attachment.type == AttachmentType.IMAGE || attachment.type == AttachmentType.AVATAR) && attachment.preview == ""){ /** * Загружаем превью blurhash для изображения */ const blurhash = await base64ImageToBlurhash(attachment.blob); attachment.preview = blurhash; } if(rePrepared && (attachment.encoding.encoded_for == dialog || attachment.encoding.encoder == dialog)){ /** * Это пересланное сообщение и оно уже закодировано для этого диалога, или закодировано отправителем, значит не нужно его кодировать и загружать заново */ prepared.push({ ...attachment, blob: "" }); continue; } doTimestampUpdateImMessageWhileAttachmentsSend(message_id, dialog); const content = await encodeWithPassword(password, attachment.blob); const upid = attachment.id; info(`Uploading attachment with upid: ${upid}`); info(`Attachment content length: ${content.length}`); let tag = await uploadFile(upid, content); info(`Uploaded attachment with upid: ${upid}, received tag: ${tag}`); if(intervalsRef.current != null){ clearInterval(intervalsRef.current); } prepared.push({ ...attachment, transport_server: transportServer || "", transport_tag: tag, encoding: { encoded_for: dialog, encoder: publicKey }, preview: attachment.preview, blob: "" }); } return prepared; }catch(e){ return prepared; } } return { prepareAttachmentsToSend } }