Новая система вложений

This commit is contained in:
RoyceDa
2026-03-26 22:29:03 +02:00
parent fd3fac54f6
commit 8d6090e632
12 changed files with 192 additions and 145 deletions

View File

@@ -89,7 +89,10 @@ export function DialogInput() {
blob: fileContent, blob: fileContent,
id: generateRandomKey(8), id: generateRandomKey(8),
type: AttachmentType.FILE, type: AttachmentType.FILE,
preview: files[0].size + "::" + files[0].name preview: files[0].size + "::" + files[0].name,
transport_server: "",
transport_tag: "",
encoded_for: dialog
}]); }]);
} }
}); });
@@ -116,7 +119,10 @@ export function DialogInput() {
type: AttachmentType.MESSAGES, type: AttachmentType.MESSAGES,
id: generateRandomKey(8), id: generateRandomKey(8),
blob: JSON.stringify([...replyMessages.messages]), blob: JSON.stringify([...replyMessages.messages]),
preview: "" preview: "",
transport_server: "",
transport_tag: "",
encoded_for: dialog
}]); }]);
if(editableDivRef.current){ if(editableDivRef.current){
editableDivRef.current.focus(); editableDivRef.current.focus();
@@ -230,7 +236,10 @@ export function DialogInput() {
blob: avatars[0].avatar, blob: avatars[0].avatar,
id: generateRandomKey(8), id: generateRandomKey(8),
type: AttachmentType.AVATAR, type: AttachmentType.AVATAR,
preview: "" preview: "",
transport_server: "",
transport_tag: "",
encoded_for: dialog
}]); }]);
if(editableDivRef.current){ if(editableDivRef.current){
editableDivRef.current.focus(); editableDivRef.current.focus();
@@ -270,7 +279,10 @@ export function DialogInput() {
blob: base64Image, blob: base64Image,
id: attachmentId, id: attachmentId,
type: AttachmentType.IMAGE, type: AttachmentType.IMAGE,
preview: "" preview: "",
transport_server: "",
transport_tag: "",
encoded_for: dialog
}]); }]);
} }
if(editableDivRef.current){ if(editableDivRef.current){
@@ -304,7 +316,10 @@ export function DialogInput() {
blob: fileContent, blob: fileContent,
id: attachmentId, id: attachmentId,
type: AttachmentType.FILE, type: AttachmentType.FILE,
preview: files[0].size + "::" + files[0].name preview: files[0].size + "::" + files[0].name,
transport_server: "",
transport_tag: "",
encoded_for: dialog
}]); }]);
} }

View File

@@ -29,6 +29,7 @@ export function MessageImage(props: AttachmentProps) {
const [blurhashPreview, setBlurhashPreview] = useState(""); const [blurhashPreview, setBlurhashPreview] = useState("");
useEffect(() => { useEffect(() => {
console.info(props.attachment);
console.info("Consturcting image, download status: " + downloadStatus); console.info("Consturcting image, download status: " + downloadStatus);
constructBlob(); constructBlob();
constructFromBlurhash(); constructFromBlurhash();

View File

@@ -27,11 +27,10 @@ export enum DownloadStatus {
} }
export function useAttachment(attachment: Attachment, parentMessage: MessageProps) { export function useAttachment(attachment: Attachment, parentMessage: MessageProps) {
const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
const uploadedPercentage = useUploadStatus(attachment.id); const uploadedPercentage = useUploadStatus(attachment.id);
const downloadPercentage = useDownloadStatus(attachment.id); const downloadPercentage = useDownloadStatus(attachment.id);
const [downloadStatus, setDownloadStatus] = useMemory("attachment-downloaded-status-" + attachment.id, DownloadStatus.PENDING, true); const [downloadStatus, setDownloadStatus] = useMemory("attachment-downloaded-status-" + attachment.id, DownloadStatus.PENDING, true);
const [downloadTag, setDownloadTag] = useState(""); const [downloadTag, setDownloadTag] = useState(attachment.transport_tag || "");
const {readFile, writeFile, fileExists, size} = useFileStorage(); const {readFile, writeFile, fileExists, size} = useFileStorage();
const { downloadFile } = useTransport(); const { downloadFile } = useTransport();
const publicKey = usePublicKey(); const publicKey = usePublicKey();
@@ -50,30 +49,18 @@ export function useAttachment(attachment: Attachment, parentMessage: MessageProp
}, []); }, []);
const getPreview = () => { const getPreview = () => {
if(attachment.preview.split("::")[0].match(uuidRegex)){
/**
* Это тег загрузки
*/
return attachment.preview.split("::").splice(1).join("::");
}
return attachment.preview; return attachment.preview;
} }
const calcDownloadStatus = async () => { const calcDownloadStatus = async () => {
if(attachment.preview.split("::")[0].match(uuidRegex)){ if (downloadStatus == DownloadStatus.DOWNLOADED) {
/**
* Это тег загрузки
*/
setDownloadTag(attachment.preview.split("::")[0]);
}
if(!attachment.preview.split("::")[0].match(uuidRegex)){
/**
* Там не тег загрузки, значит это наш файл
*/
setDownloadStatus(DownloadStatus.DOWNLOADED);
return; return;
} }
if (downloadStatus == DownloadStatus.DOWNLOADED) { if(attachment.transport_tag == ""){
/**
* Транспортного тега нет только у сообщений отправленных нами, значит он точно наш
*/
setDownloadStatus(DownloadStatus.DOWNLOADED);
return; return;
} }
if(attachment.type == AttachmentType.FILE){ if(attachment.type == AttachmentType.FILE){
@@ -143,7 +130,7 @@ export function useAttachment(attachment: Attachment, parentMessage: MessageProp
let downloadedBlob = ''; let downloadedBlob = '';
try { try {
downloadedBlob = await downloadFile(attachment.id, downloadedBlob = await downloadFile(attachment.id,
downloadTag); downloadTag, attachment.transport_server);
} catch (e) { } catch (e) {
console.info(e); console.info(e);
info("Error downloading attachment: " + attachment.id); info("Error downloading attachment: " + attachment.id);

View File

@@ -10,6 +10,7 @@ import { useDatabase } from "../DatabaseProvider/useDatabase";
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger"; import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
import { useDialogsCache } from "../DialogProvider/useDialogsCache"; import { useDialogsCache } from "../DialogProvider/useDialogsCache";
import { DialogContext } from "../DialogProvider/DialogProvider"; import { DialogContext } from "../DialogProvider/DialogProvider";
import { useTransportServer } from "../TransportProvider/useTransportServer";
export function usePrepareAttachment() { export function usePrepareAttachment() {
const intervalsRef = useRef<NodeJS.Timeout>(null); const intervalsRef = useRef<NodeJS.Timeout>(null);
@@ -19,6 +20,7 @@ export function usePrepareAttachment() {
const {info} = useConsoleLogger('usePrepareAttachment'); const {info} = useConsoleLogger('usePrepareAttachment');
const {getDialogCache} = useDialogsCache(); const {getDialogCache} = useDialogsCache();
const context = useContext(DialogContext); const context = useContext(DialogContext);
const transportServer = useTransportServer();
const updateTimestampInDialogCache = (dialog : string, message_id: string) => { const updateTimestampInDialogCache = (dialog : string, message_id: string) => {
const dialogCache = getDialogCache(dialog); const dialogCache = getDialogCache(dialog);
@@ -74,18 +76,6 @@ export function usePrepareAttachment() {
}, (MESSAGE_MAX_TIME_TO_DELEVERED_S / 2) * 1000); }, (MESSAGE_MAX_TIME_TO_DELEVERED_S / 2) * 1000);
} }
/**
* Удаляет старый тег если вложения были подготовлены заново
* например при пересылке сообщений
*/
const removeOldTagIfAttachemtnsRePreapred = (preview : string) => {
if(preview.indexOf("::") == -1){
return preview;
}
let parts = preview.split("::");
return parts.slice(1).join("::");
}
/** /**
* Подготавливает вложения для отправки. Подготовка * Подготавливает вложения для отправки. Подготовка
* состоит в загрузке файлов на транспортный сервер, мы не делаем * состоит в загрузке файлов на транспортный сервер, мы не делаем
@@ -127,6 +117,16 @@ export function usePrepareAttachment() {
const blurhash = await base64ImageToBlurhash(attachment.blob); const blurhash = await base64ImageToBlurhash(attachment.blob);
attachment.preview = blurhash; attachment.preview = blurhash;
} }
if(rePrepared && attachment.encoded_for == dialog){
/**
* Это пересланное сообщение и оно уже закодировано для этого диалога, значит не нужно его кодировать и загружать заново
*/
prepared.push({
...attachment,
blob: ""
});
continue;
}
doTimestampUpdateImMessageWhileAttachmentsSend(message_id, dialog); doTimestampUpdateImMessageWhileAttachmentsSend(message_id, dialog);
const content = await encodeWithPassword(password, attachment.blob); const content = await encodeWithPassword(password, attachment.blob);
const upid = attachment.id; const upid = attachment.id;
@@ -139,7 +139,10 @@ export function usePrepareAttachment() {
} }
prepared.push({ prepared.push({
...attachment, ...attachment,
preview: tag + "::" + (rePrepared ? removeOldTagIfAttachemtnsRePreapred(attachment.preview) : attachment.preview), transport_server: transportServer || "",
transport_tag: tag,
encoded_for: dialog,
preview: attachment.preview,
blob: "" blob: ""
}); });
} }

View File

@@ -472,6 +472,9 @@ export function CallProvider(props : CallProviderProps) {
id: generateRandomKey(16), id: generateRandomKey(16),
preview: duration.toString(), preview: duration.toString(),
type: AttachmentType.CALL, type: AttachmentType.CALL,
transport_server: "",
transport_tag: "",
encoded_for: "",
blob: "" blob: ""
}], true); }], true);
} }

View File

@@ -46,6 +46,9 @@ export interface AttachmentMeta {
id: string; id: string;
type: AttachmentType; type: AttachmentType;
preview: string; preview: string;
transport_tag: string;
encoded_for: string;
transport_server: string;
} }
export interface Message { export interface Message {
@@ -469,9 +472,7 @@ export function DialogProvider(props: DialogProviderProps) {
for(let i = 0; i < packet.getAttachments().length; i++) { for(let i = 0; i < packet.getAttachments().length; i++) {
const attachment = packet.getAttachments()[i]; const attachment = packet.getAttachments()[i];
attachments.push({ attachments.push({
id: attachment.id, ...attachment,
preview: attachment.preview,
type: attachment.type,
blob: attachment.type == AttachmentType.MESSAGES ? await decodeWithPassword(chachaDecryptedKey.toString('utf-8'), attachment.blob) : "" blob: attachment.type == AttachmentType.MESSAGES ? await decodeWithPassword(chachaDecryptedKey.toString('utf-8'), attachment.blob) : ""
}); });
} }
@@ -549,9 +550,7 @@ export function DialogProvider(props: DialogProviderProps) {
for(let i = 0; i < packet.getAttachments().length; i++) { for(let i = 0; i < packet.getAttachments().length; i++) {
const attachment = packet.getAttachments()[i]; const attachment = packet.getAttachments()[i];
attachments.push({ attachments.push({
id: attachment.id, ...attachment,
preview: attachment.preview,
type: attachment.type,
blob: attachment.type == AttachmentType.MESSAGES ? await decodeWithPassword(groupKey, attachment.blob) : "" blob: attachment.type == AttachmentType.MESSAGES ? await decodeWithPassword(groupKey, attachment.blob) : ""
}); });
} }
@@ -627,9 +626,7 @@ export function DialogProvider(props: DialogProviderProps) {
for(let i = 0; i < packet.getAttachments().length; i++) { for(let i = 0; i < packet.getAttachments().length; i++) {
const attachment = packet.getAttachments()[i]; const attachment = packet.getAttachments()[i];
attachments.push({ attachments.push({
id: attachment.id, ...attachment,
preview: attachment.preview,
type: attachment.type,
blob: attachment.type == AttachmentType.MESSAGES ? await decodeWithPassword(chachaDecryptedKey.toString('utf-8'), attachment.blob) : "" blob: attachment.type == AttachmentType.MESSAGES ? await decodeWithPassword(chachaDecryptedKey.toString('utf-8'), attachment.blob) : ""
}); });
} }
@@ -707,9 +704,7 @@ export function DialogProvider(props: DialogProviderProps) {
for(let i = 0; i < packet.getAttachments().length; i++) { for(let i = 0; i < packet.getAttachments().length; i++) {
const attachment = packet.getAttachments()[i]; const attachment = packet.getAttachments()[i];
attachments.push({ attachments.push({
id: attachment.id, ...attachment,
preview: attachment.preview,
type: attachment.type,
blob: attachment.type == AttachmentType.MESSAGES ? await decodeWithPassword(groupKey, attachment.blob) : "" blob: attachment.type == AttachmentType.MESSAGES ? await decodeWithPassword(groupKey, attachment.blob) : ""
}); });
} }
@@ -964,7 +959,10 @@ export function DialogProvider(props: DialogProviderProps) {
id: meta.id, id: meta.id,
blob: blob, blob: blob,
type: meta.type, type: meta.type,
preview: meta.preview preview: meta.preview,
transport_server: meta.transport_server,
transport_tag: meta.transport_tag,
encoded_for: meta.encoded_for
}); });
} }
return attachments; return attachments;

View File

@@ -118,7 +118,10 @@ export function useDialog() : {
attachmentsMeta.push({ attachmentsMeta.push({
id: attachment.id, id: attachment.id,
type: attachment.type, type: attachment.type,
preview: attachment.preview preview: attachment.preview,
transport_server: attachment.transport_server,
transport_tag: attachment.transport_tag,
encoded_for: dialog
}); });
if(attachment.type == AttachmentType.FILE){ if(attachment.type == AttachmentType.FILE){
/** /**

View File

@@ -20,7 +20,7 @@ import { useGroupInviteStatus } from "./useGroupInviteStatus";
import { Attachment, AttachmentType, PacketMessage } from "../ProtocolProvider/protocol/packets/packet.message"; import { Attachment, AttachmentType, PacketMessage } from "../ProtocolProvider/protocol/packets/packet.message";
import { useUpdateSyncTime } from "./useUpdateSyncTime"; import { useUpdateSyncTime } from "./useUpdateSyncTime";
import { useFileStorage } from "@/app/hooks/useFileStorage"; import { useFileStorage } from "@/app/hooks/useFileStorage";
import { DeliveredMessageState, Message } from "./DialogProvider"; import { AttachmentMeta, DeliveredMessageState, Message } from "./DialogProvider";
import { MESSAGE_MAX_LOADED, TIME_TO_INACTIVE_FOR_MESSAGES_UNREAD } from "@/app/constants"; import { MESSAGE_MAX_LOADED, TIME_TO_INACTIVE_FOR_MESSAGES_UNREAD } from "@/app/constants";
import { useMemory } from "../MemoryProvider/useMemory"; import { useMemory } from "../MemoryProvider/useMemory";
import { useDialogsCache } from "./useDialogsCache"; import { useDialogsCache } from "./useDialogsCache";
@@ -165,7 +165,7 @@ export function useSynchronize() {
const nonce = chachaDecryptedKey.slice(32); const nonce = chachaDecryptedKey.slice(32);
const decryptedContent = await chacha20Decrypt(content, nonce.toString('hex'), key.toString('hex')); const decryptedContent = await chacha20Decrypt(content, nonce.toString('hex'), key.toString('hex'));
await updateSyncTime(timestamp); await updateSyncTime(timestamp);
let attachmentsMeta: any[] = []; let attachmentsMeta: AttachmentMeta[] = [];
let messageAttachments: Attachment[] = []; let messageAttachments: Attachment[] = [];
for (let i = 0; i < packet.getAttachments().length; i++) { for (let i = 0; i < packet.getAttachments().length; i++) {
const attachment = packet.getAttachments()[i]; const attachment = packet.getAttachments()[i];
@@ -190,7 +190,10 @@ export function useSynchronize() {
attachmentsMeta.push({ attachmentsMeta.push({
id: attachment.id, id: attachment.id,
type: attachment.type, type: attachment.type,
preview: attachment.preview preview: attachment.preview,
encoded_for: attachment.encoded_for,
transport_server: attachment.transport_server,
transport_tag: attachment.transport_tag
}); });
} }
@@ -347,7 +350,7 @@ export function useSynchronize() {
decryptedContent = ''; decryptedContent = '';
} }
let attachmentsMeta: any[] = []; let attachmentsMeta: AttachmentMeta[] = [];
let messageAttachments: Attachment[] = []; let messageAttachments: Attachment[] = [];
for (let i = 0; i < packet.getAttachments().length; i++) { for (let i = 0; i < packet.getAttachments().length; i++) {
const attachment = packet.getAttachments()[i]; const attachment = packet.getAttachments()[i];
@@ -372,7 +375,10 @@ export function useSynchronize() {
attachmentsMeta.push({ attachmentsMeta.push({
id: attachment.id, id: attachment.id,
type: attachment.type, type: attachment.type,
preview: attachment.preview preview: attachment.preview,
encoded_for: attachment.encoded_for,
transport_server: attachment.transport_server,
transport_tag: attachment.transport_tag
}); });
} }

View File

@@ -7,7 +7,7 @@ import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
interface TransportContextValue { interface TransportContextValue {
transportServer: string | null; transportServer: string | null;
uploadFile: (id: string, content: string) => Promise<any>; uploadFile: (id: string, content: string) => Promise<any>;
downloadFile: (id: string, tag: string) => Promise<string>; downloadFile: (id: string, tag: string, transportServer: string) => Promise<string>;
uploading: TransportState[]; uploading: TransportState[];
downloading: TransportState[]; downloading: TransportState[];
} }
@@ -86,14 +86,14 @@ export function TransportProvider(props: TransportProviderProps) {
* @param tag тег файла * @param tag тег файла
* @param chachaDecryptedKey ключ для расшифровки файла * @param chachaDecryptedKey ключ для расшифровки файла
*/ */
const downloadFile = (id: string, tag : string) : Promise<string> => { const downloadFile = (id: string, tag : string, transportServer: string) : Promise<string> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!transportServerRef.current) { if (!transportServer) {
throw new Error("Transport server is not set"); throw new Error("Transport server is not set");
} }
setDownloading(prev => [...prev, { id: id, progress: 0 }]); setDownloading(prev => [...prev, { id: id, progress: 0 }]);
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('GET', `${transportServerRef.current}/d/${tag}`); xhr.open('GET', `${transportServer}/d/${tag}`);
xhr.responseType = 'text'; xhr.responseType = 'text';
xhr.onprogress = (event) => { xhr.onprogress = (event) => {

View File

@@ -0,0 +1,12 @@
import { useContext } from "react";
import { TransportContext } from "./TransportProvider";
export function useTransportServer() {
const context = useContext(TransportContext);
if(!context){
throw new Error("useTransportServer must be used within a TransportProvider");
}
const { transportServer } = context;
return transportServer;
}

View File

@@ -1,8 +1,8 @@
export const SERVERS = [ export const SERVERS = [
//'wss://cdn.rosetta-im.com', //'wss://cdn.rosetta-im.com',
//'ws://10.211.55.2:3000', 'ws://10.211.55.2:3000',
//'ws://192.168.6.82:3000', //'ws://192.168.6.82:3000',
'wss://wss.rosetta.im' //'wss://wss.rosetta.im'
]; ];
export function selectServer(): string { export function selectServer(): string {

View File

@@ -110,8 +110,27 @@ app.whenReady().then(async () => {
startApplication() startApplication()
const isDevBuild =
!app.isPackaged ||
process.env.NODE_ENV === 'development' ||
Boolean(process.env.ELECTRON_RENDERER_URL)
app.on('browser-window-created', (_, window) => { app.on('browser-window-created', (_, window) => {
// В production оставляем стандартную защиту шорткатов
if (!isDevBuild) {
optimizer.watchWindowShortcuts(window) optimizer.watchWindowShortcuts(window)
return
}
// В dev явно разрешаем Ctrl+R и Cmd+R для перезагрузки, так как в режиме разработки это часто нужно
window.webContents.on('before-input-event', (event, input) => {
const key = input.key?.toLowerCase?.() ?? ''
const isReload = input.type === 'keyDown' && (input.meta || input.control) && key === 'r'
if (isReload) {
event.preventDefault()
window.webContents.reloadIgnoringCache()
}
})
}) })
app.on('activate', () => { app.on('activate', () => {