240 lines
10 KiB
TypeScript
240 lines
10 KiB
TypeScript
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 { AttachmentMeta, DialogContext } from "../DialogProvider/DialogProvider";
|
||
import { useTransportServer } from "../TransportProvider/useTransportServer";
|
||
|
||
export function usePrepareAttachment() {
|
||
const intervalsRef = useRef<NodeJS.Timeout>(null);
|
||
const {uploadFile} = useTransport();
|
||
const {updateDialog} = useDialogsList();
|
||
const {runQuery, getQuery} = useDatabase();
|
||
const {info, error} = useConsoleLogger('usePrepareAttachment');
|
||
const {getDialogCache} = useDialogsCache();
|
||
const context = useContext(DialogContext);
|
||
const transportServer = useTransportServer();
|
||
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Обновляет транспортный сервер в кэше, чтобы поддерживать его в актуальном состоянии после загрузки
|
||
*/
|
||
const updateAttachmentTransportInCache = (dialog: string, message_id : string, attachment: Attachment) => {
|
||
const dialogCache = getDialogCache(dialog);
|
||
if(dialogCache == null){
|
||
return;
|
||
}
|
||
for(let i = 0; i < dialogCache.length; i++){
|
||
if(dialogCache[i].message_id == message_id){
|
||
for(let j = 0; j < dialogCache[i].attachments.length; j++){
|
||
if(dialogCache[i].attachments[j].id == attachment.id){
|
||
dialogCache[i].attachments[j].transport = attachment.transport;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Обновляет транспорт в базе после загрузки вложения (нам нужно сохранить транспорт)
|
||
*/
|
||
const updateAttachmentTransportInDatabase = async (message_id : string, attachment: Attachment) => {
|
||
let message = await getQuery(`SELECT attachments FROM messages WHERE message_id = ?`, [message_id]);
|
||
console.info(message)
|
||
if(!message){
|
||
return;
|
||
}
|
||
if(message.attachments == '[]'){
|
||
return;
|
||
}
|
||
let meta : AttachmentMeta[] = JSON.parse(message.attachments);
|
||
for(let i = 0; i < meta.length; i++){
|
||
if(meta[i].id == attachment.id){
|
||
meta[i].transport = attachment.transport;
|
||
}
|
||
}
|
||
await runQuery(`UPDATE messages SET attachments = ? WHERE message_id = ?`, [JSON.stringify(meta), message_id]);
|
||
}
|
||
|
||
/**
|
||
* Обновляет вложение в стейте сообщений
|
||
*/
|
||
const updateAttachmentTransportInContext = (message_id: string, attachment : Attachment) => {
|
||
if(context == null || !context){
|
||
/**
|
||
* Если этот диалог сейчас не открыт
|
||
*/
|
||
return;
|
||
}
|
||
context.setMessages((prev) => {
|
||
return prev.map((value) => {
|
||
if(value.message_id != message_id){
|
||
return value;
|
||
}
|
||
for(let i = 0; i < value.attachments.length; i++){
|
||
if(value.attachments[i].id != attachment.id){
|
||
return value;
|
||
}
|
||
value.attachments[i].transport = attachment.transport;
|
||
return value;
|
||
}
|
||
return value;
|
||
})
|
||
});
|
||
}
|
||
|
||
const updateTransportAfterUploading = async (dialog: string, message_id : string, attachment: Attachment) => {
|
||
updateAttachmentTransportInCache(dialog, message_id, attachment);
|
||
updateAttachmentTransportInDatabase(message_id, attachment);
|
||
updateAttachmentTransportInContext(message_id, attachment);
|
||
}
|
||
|
||
/**
|
||
* Обновляет временную метку в сообщении, пока вложения отправляются,
|
||
* потому что если этого не делать, то сообщение может быть помечено как
|
||
* не доставленное из-за таймаута доставки
|
||
* @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[]) : Promise<Attachment[]> => {
|
||
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++){
|
||
for(let k = 0; k < reply[j].attachments.length; k++){
|
||
reply[j].attachments[k].blob = "";
|
||
}
|
||
}
|
||
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;
|
||
}
|
||
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).catch(() => {
|
||
error(`Network error while uploading attachment ${upid}`);
|
||
});
|
||
if(!tag){
|
||
/**
|
||
* При ошибке загрузки по сети
|
||
*/
|
||
stopUpdateTimeInUpAttachment();
|
||
console.info("stop upd")
|
||
continue;
|
||
}
|
||
info(`Uploaded attachment with upid: ${upid}, received tag: ${tag}`);
|
||
stopUpdateTimeInUpAttachment();
|
||
const preparedAttachment : Attachment = {
|
||
...attachment,
|
||
transport: {
|
||
transport_server: transportServer || "",
|
||
transport_tag: tag
|
||
},
|
||
preview: attachment.preview,
|
||
blob: ""
|
||
};
|
||
await updateTransportAfterUploading(dialog, message_id, preparedAttachment);
|
||
prepared.push(preparedAttachment);
|
||
}
|
||
return prepared;
|
||
}catch(e){
|
||
return prepared;
|
||
}
|
||
}
|
||
|
||
const stopUpdateTimeInUpAttachment = () => {
|
||
if(intervalsRef.current != null){
|
||
clearInterval(intervalsRef.current);
|
||
}
|
||
}
|
||
|
||
return {
|
||
prepareAttachmentsToSend
|
||
}
|
||
} |