From 8952fe43e81dc4415d66d69a0bf432f8f65320f5 Mon Sep 17 00:00:00 2001 From: RoyceDa Date: Mon, 23 Feb 2026 13:20:40 +0200 Subject: [PATCH 1/8] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81=20=D1=83=D0=B2?= =?UTF-8?q?=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=20=D1=81=D0=B8=D0=BD=D1=85=D1=80=D0=BE=D0=BD=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B2=20=D1=80=D0=B5=D0=B7?= =?UTF-8?q?=D1=83=D0=BB=D1=8C=D1=82=D0=B0=D1=82=D0=B5=20=D0=BD=D0=B5=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D1=83=D1=81=D0=BB=D0=BE=D0=B2=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/providers/DialogProvider/useDialogFiber.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/providers/DialogProvider/useDialogFiber.ts b/app/providers/DialogProvider/useDialogFiber.ts index b3710b3..8373d2d 100644 --- a/app/providers/DialogProvider/useDialogFiber.ts +++ b/app/providers/DialogProvider/useDialogFiber.ts @@ -328,7 +328,7 @@ export function useDialogFiber() { addOrUpdateDialogCache(toPublicKey, [...dialogCache, newMessage].slice(-MESSAGE_MAX_LOADED)); } }); - }, [blocked, muted, updateDialog, focused, currentDialogPublicKeyView, viewState, idle]); + }, [blocked, muted, updateDialog, focused, currentDialogPublicKeyView, viewState, idle, protocolState]); /** * Обработчик личных сообщений */ @@ -445,9 +445,9 @@ export function useDialogFiber() { * чтобы когда приходит пачка сообщений с сервера в момент того как * пользователь был неактивен, не слать уведомления по всем этим сообщениям */ - if (!muted.includes(fromPublicKey) || protocolState != ProtocolState.SYNCHRONIZATION) { + if (!muted.includes(fromPublicKey) && protocolState != ProtocolState.SYNCHRONIZATION) { /** - * Если пользователь в муте или сейчас идет синхронизация - не отправляем уведомление + * Если пользователь в муте И сейчас не идет синхронизация, то не отправляем уведомление */ notify("New message", "You have a new message"); } @@ -457,7 +457,7 @@ export function useDialogFiber() { addOrUpdateDialogCache(fromPublicKey, [...dialogCache, newMessage].slice(-MESSAGE_MAX_LOADED)); } }); - }, [blocked, muted, updateDialog, focused, currentDialogPublicKeyView, viewState, idle]); + }, [blocked, muted, updateDialog, focused, currentDialogPublicKeyView, viewState, idle, protocolState]); /** * Обработчик синхронизации прочтения личных сообщений -- 2.49.1 From bf057c14f48c1f55af00ef0865928a465a7a4c63 Mon Sep 17 00:00:00 2001 From: RoyceDa Date: Tue, 24 Feb 2026 14:33:47 +0200 Subject: [PATCH 2/8] =?UTF-8?q?=D0=97=D0=B0=D1=89=D0=B8=D1=89=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D0=B0=D1=8F=20=D1=81=D0=B8=D0=BD=D1=85=D1=80=D0=BE=D0=BD?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D0=B5=D0=B9=20=D0=B8=20=D0=BC=D0=B5=D1=82=D0=B0-=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D1=85=20=D0=B3=D1=80=D1=83=D0=BF=D0=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/providers/DialogProvider/useGroups.ts | 26 +++------ .../DialogProvider/useSynchronize.ts | 53 ++++++++++++++++++- .../protocol/packets/packet.group.join.ts | 18 +++++++ app/servers.ts | 4 +- 4 files changed, 77 insertions(+), 24 deletions(-) diff --git a/app/providers/DialogProvider/useGroups.ts b/app/providers/DialogProvider/useGroups.ts index 78499d6..1935cdc 100644 --- a/app/providers/DialogProvider/useGroups.ts +++ b/app/providers/DialogProvider/useGroups.ts @@ -162,26 +162,10 @@ export function useGroups() : { const groupId = packet.getGroupId(); info(`Creating group with id ${groupId}`); const encryptKey = generateRandomKey(64); - const secureKey = await encodeWithPassword(privatePlain, encryptKey); - let content = await encodeWithPassword(encryptKey, `$a=Group created`); - let plainMessage = await encodeWithPassword(privatePlain, `$a=Group created`); - await runQuery(` - INSERT INTO groups (account, group_id, title, description, key) VALUES (?, ?, ?, ?, ?) - `, [publicKey, groupId, title, description, secureKey]); - await runQuery(` - INSERT INTO messages - (from_public_key, to_public_key, content, timestamp, read, chacha_key, from_me, plain_message, account, message_id, delivered, attachments) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `, [publicKey, "#group:" + groupId, content, Date.now(), 1, "", 1, plainMessage, publicKey, generateRandomKey(16), - DeliveredMessageState.DELIVERED - , '[]']); - updateDialog("#group:" + groupId); - updateGroupInformation({ - groupId: groupId, - title: title, - description: description - }); - setLoading(false); - navigate(`/main/chat/${prepareForRoute(groupId)}`); + /** + * После создания группы в нее необходимо зайти, в соотвествии с новым протоколом + */ + joinGroup(await constructGroupString(groupId, title, encryptKey, description)); }); } @@ -201,9 +185,11 @@ export function useGroups() : { const groupId = parsed.groupId; const title = parsed.title; const description = parsed.description; + const encodedGroupString = await encodeWithPassword(privatePlain, groupString); const packet = new PacketGroupJoin(); packet.setGroupId(parsed.groupId); + packet.setGroupString(encodedGroupString); send(packet); setLoading(true); diff --git a/app/providers/DialogProvider/useSynchronize.ts b/app/providers/DialogProvider/useSynchronize.ts index 0456a64..332ab79 100644 --- a/app/providers/DialogProvider/useSynchronize.ts +++ b/app/providers/DialogProvider/useSynchronize.ts @@ -8,6 +8,16 @@ import { useSender } from "../ProtocolProvider/useSender"; import { usePacket } from "../ProtocolProvider/usePacket"; import { whenFinish } from "./dialogQueue"; import { useProtocol } from "../ProtocolProvider/useProtocol"; +import { PacketGroupJoin } from "../ProtocolProvider/protocol/packets/packet.group.join"; +import { useGroups } from "./useGroups"; +import { decodeWithPassword, encodeWithPassword } from "@/app/workers/crypto/crypto"; +import { usePrivatePlain } from "../AccountProvider/usePrivatePlain"; +import { GroupStatus } from "../ProtocolProvider/protocol/packets/packet.group.invite.info"; +import { useConsoleLogger } from "@/app/hooks/useConsoleLogger"; +import { useNavigate } from "react-router-dom"; +import { useDialogsList } from "../DialogListProvider/useDialogsList"; +import { useUpdateGroupInformation } from "../InformationProvider/useUpdateGroupInformation"; +import { useGroupInviteStatus } from "./useGroupInviteStatus"; /** * Хук отвечает за синхронизацию сообщений, запрос синхронизации @@ -19,7 +29,14 @@ export function useSynchronize() { const publicKey = usePublicKey(); const send = useSender(); const {protocol} = useProtocol(); + const {parseGroupString} = useGroups(); + const privatePlain = usePrivatePlain(); + const {error, info} = useConsoleLogger('useSynchronize'); + const {setInviteStatusByGroupId} = useGroupInviteStatus(''); + const updateGroupInformation = useUpdateGroupInformation(); + const {updateDialog} = useDialogsList(); + useEffect(() => { if(protocol.handshakeExchangeComplete){ trySync(); @@ -38,15 +55,47 @@ export function useSynchronize() { send(packet); } + /** + * Пакет приходит либо при входе в группу (но там используется слушатель once), либо при + * синхронизации. В данном случае этот пакет прийдет только при синхронизации + */ + usePacket(20, async (packet: PacketGroupJoin) => { + const decryptedGroupString = await decodeWithPassword(privatePlain, packet.getGroupString()); + const parsed = await parseGroupString(decryptedGroupString); + if(!parsed){ + error("Received invalid group string, skipping"); + return; + } + const groupStatus = packet.getGroupStatus(); + if(groupStatus != GroupStatus.JOINED){ + error("Cannot sync group that is not joined, skipping"); + return; + } + const secureKey = await encodeWithPassword(privatePlain, parsed.encryptKey); + await runQuery(` + INSERT INTO groups (account, group_id, title, description, key) VALUES (?, ?, ?, ?, ?) + `, [publicKey, parsed.groupId, parsed.title, parsed.description, secureKey]); + updateDialog("#group:" + parsed.groupId); + setInviteStatusByGroupId(parsed.groupId, GroupStatus.JOINED); + updateGroupInformation({ + groupId: parsed.groupId, + title: parsed.title, + description: parsed.description + }); + info("Group synchronized " + parsed.groupId); + }, [publicKey]); + usePacket(25, async (packet: PacketSync) => { const status = packet.getStatus(); if(status == SyncStatus.BATCH_START){ setProtocolState(ProtocolState.SYNCHRONIZATION); } if(status == SyncStatus.BATCH_END){ - console.info("Batch start"); + /** + * Этот Promise ждет пока все сообщения синхронизируются и обработаются, только + * после этого + */ await whenFinish(); - console.info("Batch finished"); await runQuery( "INSERT INTO accounts_sync_times (account, last_sync) VALUES (?, ?) " + "ON CONFLICT(account) DO UPDATE SET last_sync = ? WHERE account = ?", diff --git a/app/providers/ProtocolProvider/protocol/packets/packet.group.join.ts b/app/providers/ProtocolProvider/protocol/packets/packet.group.join.ts index 89c271c..2f9f71c 100644 --- a/app/providers/ProtocolProvider/protocol/packets/packet.group.join.ts +++ b/app/providers/ProtocolProvider/protocol/packets/packet.group.join.ts @@ -12,6 +12,14 @@ export class PacketGroupJoin extends Packet { private groupId: string = ""; private groupStatus: GroupStatus = GroupStatus.NOT_JOINED; + /** + * Строка группы, которая содержит информацию о группе, такую как ее название, описание и ключ + * Строка зашифрована обратимым шифрованием, где ключом выступает - реальный приватный ключ + * входящего в группу клиента. Нужно это для будущей синхронзации, так как клиенту на его другом + * устройстве нужно получить ключ группы и ее информацию. Сервер расшифровать эту строку не может. Эту + * строку может расшифровать только клиент, так как она зашифрована его приватным ключом + */ + private groupString: string = ""; public getPacketId(): number { return 0x14; @@ -20,6 +28,7 @@ export class PacketGroupJoin extends Packet { public _receive(stream: Stream): void { this.groupId = stream.readString(); this.groupStatus = stream.readInt8(); + this.groupString = stream.readString(); } public _send(): Promise | Stream { @@ -27,6 +36,7 @@ export class PacketGroupJoin extends Packet { stream.writeInt16(this.getPacketId()); stream.writeString(this.groupId); stream.writeInt8(this.groupStatus); + stream.writeString(this.groupString); return stream; } @@ -45,5 +55,13 @@ export class PacketGroupJoin extends Packet { public getGroupStatus(): GroupStatus { return this.groupStatus; } + + public setGroupString(groupString: string) { + this.groupString = groupString; + } + + public getGroupString(): string { + return this.groupString; + } } \ No newline at end of file diff --git a/app/servers.ts b/app/servers.ts index aa52cbb..53d4fca 100644 --- a/app/servers.ts +++ b/app/servers.ts @@ -1,8 +1,8 @@ export const SERVERS = [ //'wss://cdn.rosetta-im.com', //'ws://10.211.55.2:3000', - //'ws://127.0.0.1:3000', - 'wss://wss.rosetta.im' + 'ws://127.0.0.1:3000', + //'wss://wss.rosetta.im' ]; export function selectServer(): string { -- 2.49.1 From fbc4f73f3d57e129f8c7784473290fba2b32fab2 Mon Sep 17 00:00:00 2001 From: RoyceDa Date: Tue, 24 Feb 2026 15:42:15 +0200 Subject: [PATCH 3/8] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D0=B0=D1=8F=20=D0=BE=D1=80=D0=B3=D0=B0=D0=BD=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BA=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DialogProvider/useDialogFiber.ts | 160 +----------------- .../DialogProvider/useSynchronize.ts | 154 ++++++++++++++++- .../DialogProvider/useUpdateSyncTime.ts | 33 ++++ 3 files changed, 186 insertions(+), 161 deletions(-) create mode 100644 app/providers/DialogProvider/useUpdateSyncTime.ts diff --git a/app/providers/DialogProvider/useDialogFiber.ts b/app/providers/DialogProvider/useDialogFiber.ts index 8373d2d..112bb5c 100644 --- a/app/providers/DialogProvider/useDialogFiber.ts +++ b/app/providers/DialogProvider/useDialogFiber.ts @@ -27,6 +27,7 @@ import { useMentions } from "../DialogStateProvider.tsx/useMentions"; import { runTaskInQueue } from "./dialogQueue"; import { useProtocolState } from "../ProtocolProvider/useProtocolState"; import { ProtocolState } from "../ProtocolProvider/ProtocolProvider"; +import { useUpdateSyncTime } from "./useUpdateSyncTime"; /** * При вызове будет запущен "фоновый" обработчик @@ -53,27 +54,7 @@ export function useDialogFiber() { const [userInfo] = useUserInformation(publicKey); const { pushMention } = useMentions(); const [protocolState] = useProtocolState(); - - /** - * Обновляет время последней синхронизации для аккаунта - * @param timestamp время - */ - const updateSyncTime = async (timestamp: number) => { - if(protocolState == ProtocolState.SYNCHRONIZATION){ - /** - * Если сейчас идет синхронизация то чтобы при синхронизации - * не создавать нагрузку на базу данных - * по постоянному обновлению, обновляем базу один раз - когда - * приходит пакет о том что синхронизация закончилась - */ - return; - } - await runQuery( - "INSERT INTO accounts_sync_times (account, last_sync) VALUES (?, ?) " + - "ON CONFLICT(account) DO UPDATE SET last_sync = ? WHERE account = ?", - [publicKey, timestamp, timestamp, publicKey] - ); - }; + const updateSyncTime = useUpdateSyncTime(); /** * Лог @@ -82,101 +63,6 @@ export function useDialogFiber() { info("Starting passive fiber for dialog packets"); }, []); - /** - * Нам приходят сообщения от себя самих же при синхронизации - * нужно обрабатывать их особым образом соотвественно - * - * Метод нужен для синхронизации своих сообщений - */ - usePacket(0x06, async (packet: PacketMessage) => { - runTaskInQueue(async () => { - const fromPublicKey = packet.getFromPublicKey(); - const toPublicKey = packet.getToPublicKey(); - const aesChachaKey = packet.getAesChachaKey(); - const content = packet.getContent(); - const timestamp = packet.getTimestamp(); - const messageId = packet.getMessageId(); - if (fromPublicKey != publicKey) { - /** - * Игнорируем если это не сообщение от нас - */ - return; - } - - const chachaKey = await decodeWithPassword(privatePlain, aesChachaKey); - const chachaDecryptedKey = Buffer.from(chachaKey, "binary"); - const key = chachaDecryptedKey.slice(0, 32); - const nonce = chachaDecryptedKey.slice(32); - const decryptedContent = await chacha20Decrypt(content, nonce.toString('hex'), key.toString('hex')); - await updateSyncTime(timestamp); - let attachmentsMeta: any[] = []; - let messageAttachments: Attachment[] = []; - for (let i = 0; i < packet.getAttachments().length; i++) { - const attachment = packet.getAttachments()[i]; - log("Attachment received id " + attachment.id + " type " + attachment.type); - - let nextLength = messageAttachments.push({ - ...attachment, - blob: "" - }); - - if (attachment.type == AttachmentType.MESSAGES) { - /** - * Этот тип вложения приходит сразу в blob и не нуждается - * в последующем скачивании - */ - const decryptedBlob = await decodeWithPassword(chachaDecryptedKey.toString('utf-8'), attachment.blob); - writeFile(`m/${await generateMd5(attachment.id + publicKey)}`, - Buffer.from(await encodeWithPassword(privatePlain, decryptedBlob)).toString('binary')); - messageAttachments[nextLength - 1].blob = decryptedBlob; - } - - attachmentsMeta.push({ - id: attachment.id, - type: attachment.type, - preview: attachment.preview - }); - } - - const newMessage: Message = { - from_public_key: fromPublicKey, - to_public_key: toPublicKey, - content: content, - timestamp: timestamp, - readed: 1, //сообщение прочитано - chacha_key: chachaDecryptedKey.toString('utf-8'), - from_me: 1, //сообщение от нас - plain_message: (decryptedContent as string), - delivered: DeliveredMessageState.DELIVERED, - message_id: messageId, - attachments: messageAttachments - }; - - await runQuery(` - INSERT INTO messages - (from_public_key, to_public_key, content, timestamp, read, chacha_key, from_me, plain_message, account, message_id, delivered, attachments) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `, [fromPublicKey, - toPublicKey, - content, - timestamp, - 0, //по умолчанию не прочитаны - "sync:" + aesChachaKey, - 1, //Свои же сообщения всегда от нас - await encodeWithPassword(privatePlain, decryptedContent), - publicKey, - messageId, - DeliveredMessageState.DELIVERED, - JSON.stringify(attachmentsMeta)]); - - updateDialog(toPublicKey); - - let dialogCache = getDialogCache(toPublicKey); - if (currentDialogPublicKeyView !== toPublicKey && dialogCache.length > 0) { - addOrUpdateDialogCache(toPublicKey, [...dialogCache, newMessage].slice(-MESSAGE_MAX_LOADED)); - } - }); - }, [privatePlain, currentDialogPublicKeyView]); - /** * Обработчик сообщений для группы */ @@ -459,47 +345,6 @@ export function useDialogFiber() { }); }, [blocked, muted, updateDialog, focused, currentDialogPublicKeyView, viewState, idle, protocolState]); - /** - * Обработчик синхронизации прочтения личных сообщений - */ - usePacket(0x07, async (packet: PacketRead) => { - runTaskInQueue(async () => { - if (hasGroup(packet.getToPublicKey())) { - /** - * Если это относится к группам, то игнорируем здесь, - * для этого есть отдельный слушатель usePacket ниже - */ - return; - } - const fromPublicKey = packet.getFromPublicKey(); - const toPublicKey = packet.getToPublicKey(); - if (fromPublicKey != publicKey) { - /** - * Игнорируем если это не синхронизация нашего прочтения - */ - return; - } - console.info("PACKED_READ_SYNC"); - await runQuery(`UPDATE messages SET read = 1 WHERE from_public_key = ? AND to_public_key = ? AND account = ?`, - [toPublicKey, fromPublicKey, publicKey]); - - console.info("updating with params ", [fromPublicKey, toPublicKey, publicKey]); - updateDialog(toPublicKey); - log("Read sync packet from other device"); - addOrUpdateDialogCache(fromPublicKey, getDialogCache(fromPublicKey).map((message) => { - if (message.from_public_key == toPublicKey && !message.readed) { - console.info("Marking message as read in cache for dialog with " + fromPublicKey); - console.info({ fromPublicKey, toPublicKey }); - return { - ...message, - readed: 1 - } - } - return message; - })); - }); - }, [updateDialog, publicKey]); - /** * Обработчик прочтения личных сообщений */ @@ -540,6 +385,7 @@ export function useDialogFiber() { })); }); }, [updateDialog, publicKey]); + /** * Обработчик прочтения групповых сообщений */ diff --git a/app/providers/DialogProvider/useSynchronize.ts b/app/providers/DialogProvider/useSynchronize.ts index 332ab79..b7248d3 100644 --- a/app/providers/DialogProvider/useSynchronize.ts +++ b/app/providers/DialogProvider/useSynchronize.ts @@ -6,18 +6,25 @@ import { usePublicKey } from "../AccountProvider/usePublicKey"; import { PacketSync, SyncStatus } from "../ProtocolProvider/protocol/packets/packet.sync"; import { useSender } from "../ProtocolProvider/useSender"; import { usePacket } from "../ProtocolProvider/usePacket"; -import { whenFinish } from "./dialogQueue"; +import { runTaskInQueue, whenFinish } from "./dialogQueue"; import { useProtocol } from "../ProtocolProvider/useProtocol"; import { PacketGroupJoin } from "../ProtocolProvider/protocol/packets/packet.group.join"; import { useGroups } from "./useGroups"; -import { decodeWithPassword, encodeWithPassword } from "@/app/workers/crypto/crypto"; +import { chacha20Decrypt, decodeWithPassword, encodeWithPassword, generateMd5 } from "@/app/workers/crypto/crypto"; import { usePrivatePlain } from "../AccountProvider/usePrivatePlain"; import { GroupStatus } from "../ProtocolProvider/protocol/packets/packet.group.invite.info"; import { useConsoleLogger } from "@/app/hooks/useConsoleLogger"; -import { useNavigate } from "react-router-dom"; import { useDialogsList } from "../DialogListProvider/useDialogsList"; import { useUpdateGroupInformation } from "../InformationProvider/useUpdateGroupInformation"; import { useGroupInviteStatus } from "./useGroupInviteStatus"; +import { Attachment, AttachmentType, PacketMessage } from "../ProtocolProvider/protocol/packets/packet.message"; +import { useUpdateSyncTime } from "./useUpdateSyncTime"; +import { useFileStorage } from "@/app/hooks/useFileStorage"; +import { DeliveredMessageState, Message } from "./DialogProvider"; +import { MESSAGE_MAX_LOADED } from "@/app/constants"; +import { useMemory } from "../MemoryProvider/useMemory"; +import { useDialogsCache } from "./useDialogsCache"; +import { PacketRead } from "../ProtocolProvider/protocol/packets/packet.read"; /** * Хук отвечает за синхронизацию сообщений, запрос синхронизации @@ -29,12 +36,16 @@ export function useSynchronize() { const publicKey = usePublicKey(); const send = useSender(); const {protocol} = useProtocol(); - const {parseGroupString} = useGroups(); + const {parseGroupString, hasGroup} = useGroups(); const privatePlain = usePrivatePlain(); const {error, info} = useConsoleLogger('useSynchronize'); const {setInviteStatusByGroupId} = useGroupInviteStatus(''); const updateGroupInformation = useUpdateGroupInformation(); const {updateDialog} = useDialogsList(); + const updateSyncTime = useUpdateSyncTime(); + const {writeFile} = useFileStorage(); + const { getDialogCache, addOrUpdateDialogCache } = useDialogsCache(); + const [currentDialogPublicKeyView, __] = useMemory("current-dialog-public-key-view", "", true); useEffect(() => { @@ -111,4 +122,139 @@ export function useSynchronize() { setProtocolState(ProtocolState.CONNECTED); } }, [publicKey]); + + /** + * Нам приходят сообщения от себя самих же при синхронизации + * нужно обрабатывать их особым образом соотвественно + * + * Метод нужен для синхронизации своих сообщений + */ + usePacket(0x06, async (packet: PacketMessage) => { + runTaskInQueue(async () => { + const fromPublicKey = packet.getFromPublicKey(); + const toPublicKey = packet.getToPublicKey(); + const aesChachaKey = packet.getAesChachaKey(); + const content = packet.getContent(); + const timestamp = packet.getTimestamp(); + const messageId = packet.getMessageId(); + if (fromPublicKey != publicKey) { + /** + * Игнорируем если это не сообщение от нас + */ + return; + } + + const chachaKey = await decodeWithPassword(privatePlain, aesChachaKey); + const chachaDecryptedKey = Buffer.from(chachaKey, "binary"); + const key = chachaDecryptedKey.slice(0, 32); + const nonce = chachaDecryptedKey.slice(32); + const decryptedContent = await chacha20Decrypt(content, nonce.toString('hex'), key.toString('hex')); + await updateSyncTime(timestamp); + let attachmentsMeta: any[] = []; + let messageAttachments: Attachment[] = []; + for (let i = 0; i < packet.getAttachments().length; i++) { + const attachment = packet.getAttachments()[i]; + + + let nextLength = messageAttachments.push({ + ...attachment, + blob: "" + }); + + if (attachment.type == AttachmentType.MESSAGES) { + /** + * Этот тип вложения приходит сразу в blob и не нуждается + * в последующем скачивании + */ + const decryptedBlob = await decodeWithPassword(chachaDecryptedKey.toString('utf-8'), attachment.blob); + writeFile(`m/${await generateMd5(attachment.id + publicKey)}`, + Buffer.from(await encodeWithPassword(privatePlain, decryptedBlob)).toString('binary')); + messageAttachments[nextLength - 1].blob = decryptedBlob; + } + + attachmentsMeta.push({ + id: attachment.id, + type: attachment.type, + preview: attachment.preview + }); + } + + const newMessage: Message = { + from_public_key: fromPublicKey, + to_public_key: toPublicKey, + content: content, + timestamp: timestamp, + readed: 1, //сообщение прочитано + chacha_key: chachaDecryptedKey.toString('utf-8'), + from_me: 1, //сообщение от нас + plain_message: (decryptedContent as string), + delivered: DeliveredMessageState.DELIVERED, + message_id: messageId, + attachments: messageAttachments + }; + + await runQuery(` + INSERT INTO messages + (from_public_key, to_public_key, content, timestamp, read, chacha_key, from_me, plain_message, account, message_id, delivered, attachments) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, [fromPublicKey, + toPublicKey, + content, + timestamp, + 0, //по умолчанию не прочитаны + "sync:" + aesChachaKey, + 1, //Свои же сообщения всегда от нас + await encodeWithPassword(privatePlain, decryptedContent), + publicKey, + messageId, + DeliveredMessageState.DELIVERED, + JSON.stringify(attachmentsMeta)]); + + updateDialog(toPublicKey); + + let dialogCache = getDialogCache(toPublicKey); + if (currentDialogPublicKeyView !== toPublicKey && dialogCache.length > 0) { + addOrUpdateDialogCache(toPublicKey, [...dialogCache, newMessage].slice(-MESSAGE_MAX_LOADED)); + } + }); + }, [privatePlain, currentDialogPublicKeyView]); + + /** + * Обработчик синхронизации прочтения личных сообщений + */ + usePacket(0x07, async (packet: PacketRead) => { + runTaskInQueue(async () => { + if (hasGroup(packet.getToPublicKey())) { + /** + * Если это относится к группам, то игнорируем здесь, + * для этого есть отдельный слушатель usePacket ниже + */ + return; + } + const fromPublicKey = packet.getFromPublicKey(); + const toPublicKey = packet.getToPublicKey(); + if (fromPublicKey != publicKey) { + /** + * Игнорируем если это не синхронизация нашего прочтения + */ + return; + } + console.info("PACKED_READ_SYNC"); + await runQuery(`UPDATE messages SET read = 1 WHERE from_public_key = ? AND to_public_key = ? AND account = ?`, + [toPublicKey, fromPublicKey, publicKey]); + + console.info("updating with params ", [fromPublicKey, toPublicKey, publicKey]); + updateDialog(toPublicKey); + addOrUpdateDialogCache(fromPublicKey, getDialogCache(fromPublicKey).map((message) => { + if (message.from_public_key == toPublicKey && !message.readed) { + console.info("Marking message as read in cache for dialog with " + fromPublicKey); + console.info({ fromPublicKey, toPublicKey }); + return { + ...message, + readed: 1 + } + } + return message; + })); + }); + }, [updateDialog, publicKey]); } \ No newline at end of file diff --git a/app/providers/DialogProvider/useUpdateSyncTime.ts b/app/providers/DialogProvider/useUpdateSyncTime.ts new file mode 100644 index 0000000..45ad845 --- /dev/null +++ b/app/providers/DialogProvider/useUpdateSyncTime.ts @@ -0,0 +1,33 @@ +import { usePublicKey } from "../AccountProvider/usePublicKey"; +import { useDatabase } from "../DatabaseProvider/useDatabase"; +import { ProtocolState } from "../ProtocolProvider/ProtocolProvider"; +import { useProtocolState } from "../ProtocolProvider/useProtocolState"; + +export function useUpdateSyncTime() : (timestamp: number) => Promise { + const [protocolState] = useProtocolState(); + const {runQuery} = useDatabase(); + const publicKey = usePublicKey(); + + /** + * Обновляет время последней синхронизации для аккаунта + * @param timestamp время + */ + const updateSyncTime = async (timestamp: number) => { + if(protocolState == ProtocolState.SYNCHRONIZATION){ + /** + * Если сейчас идет синхронизация то чтобы при синхронизации + * не создавать нагрузку на базу данных + * по постоянному обновлению, обновляем базу один раз - когда + * приходит пакет о том что синхронизация закончилась + */ + return; + } + await runQuery( + "INSERT INTO accounts_sync_times (account, last_sync) VALUES (?, ?) " + + "ON CONFLICT(account) DO UPDATE SET last_sync = ? WHERE account = ?", + [publicKey, timestamp, timestamp, publicKey] + ); + }; + + return updateSyncTime; +} \ No newline at end of file -- 2.49.1 From 089fa055d3aa6e72febf31ef7a3bb7a8e7c8801b Mon Sep 17 00:00:00 2001 From: RoyceDa Date: Tue, 24 Feb 2026 16:06:21 +0200 Subject: [PATCH 4/8] =?UTF-8?q?=D0=A1=D0=B8=D0=BD=D1=85=D1=80=D0=BE=D0=BD?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D1=81=D0=BE=D0=BE=D0=B1?= =?UTF-8?q?=D1=89=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B2=20=D0=B3=D1=80=D1=83?= =?UTF-8?q?=D0=BF=D0=BF=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DialogProvider/useSynchronize.ts | 131 +++++++++++++++++- 1 file changed, 125 insertions(+), 6 deletions(-) diff --git a/app/providers/DialogProvider/useSynchronize.ts b/app/providers/DialogProvider/useSynchronize.ts index b7248d3..1692014 100644 --- a/app/providers/DialogProvider/useSynchronize.ts +++ b/app/providers/DialogProvider/useSynchronize.ts @@ -21,31 +21,39 @@ import { Attachment, AttachmentType, PacketMessage } from "../ProtocolProvider/p import { useUpdateSyncTime } from "./useUpdateSyncTime"; import { useFileStorage } from "@/app/hooks/useFileStorage"; import { DeliveredMessageState, Message } from "./DialogProvider"; -import { MESSAGE_MAX_LOADED } from "@/app/constants"; +import { MESSAGE_MAX_LOADED, TIME_TO_INACTIVE_FOR_MESSAGES_UNREAD } from "@/app/constants"; import { useMemory } from "../MemoryProvider/useMemory"; import { useDialogsCache } from "./useDialogsCache"; import { PacketRead } from "../ProtocolProvider/protocol/packets/packet.read"; +import { useLogger } from "@/app/hooks/useLogger"; +import { useIdle } from "@mantine/hooks"; +import { useViewPanelsState, ViewPanelsState } from "@/app/hooks/useViewPanelsState"; +import { useWindowFocus } from "@/app/hooks/useWindowFocus"; /** * Хук отвечает за синхронизацию сообщений, запрос синхронизации * при подключении */ export function useSynchronize() { - const [_, setProtocolState] = useProtocolState(); + const [protocolState, setProtocolState] = useProtocolState(); const {getQuery, runQuery} = useDatabase(); const publicKey = usePublicKey(); const send = useSender(); const {protocol} = useProtocol(); - const {parseGroupString, hasGroup} = useGroups(); + const {parseGroupString, hasGroup, getGroupKey} = useGroups(); const privatePlain = usePrivatePlain(); const {error, info} = useConsoleLogger('useSynchronize'); + const log = useLogger('useSynchronize'); const {setInviteStatusByGroupId} = useGroupInviteStatus(''); const updateGroupInformation = useUpdateGroupInformation(); const {updateDialog} = useDialogsList(); + const idle = useIdle(TIME_TO_INACTIVE_FOR_MESSAGES_UNREAD * 1000); const updateSyncTime = useUpdateSyncTime(); const {writeFile} = useFileStorage(); const { getDialogCache, addOrUpdateDialogCache } = useDialogsCache(); const [currentDialogPublicKeyView, __] = useMemory("current-dialog-public-key-view", "", true); + const [viewState] = useViewPanelsState(); + const focused = useWindowFocus(); useEffect(() => { @@ -238,15 +246,12 @@ export function useSynchronize() { */ return; } - console.info("PACKED_READ_SYNC"); await runQuery(`UPDATE messages SET read = 1 WHERE from_public_key = ? AND to_public_key = ? AND account = ?`, [toPublicKey, fromPublicKey, publicKey]); - console.info("updating with params ", [fromPublicKey, toPublicKey, publicKey]); updateDialog(toPublicKey); addOrUpdateDialogCache(fromPublicKey, getDialogCache(fromPublicKey).map((message) => { if (message.from_public_key == toPublicKey && !message.readed) { - console.info("Marking message as read in cache for dialog with " + fromPublicKey); console.info({ fromPublicKey, toPublicKey }); return { ...message, @@ -257,4 +262,118 @@ export function useSynchronize() { })); }); }, [updateDialog, publicKey]); + + /** + * Обработчик сообщений для синхронизации своих же сообщений в группе + */ + usePacket(0x06, async (packet: PacketMessage) => { + runTaskInQueue(async () => { + const fromPublicKey = packet.getFromPublicKey(); + const toPublicKey = packet.getToPublicKey(); + const content = packet.getContent(); + const timestamp = packet.getTimestamp(); + const messageId = packet.getMessageId(); + if (!hasGroup(toPublicKey)) { + /** + * Если это личное сообщение, то игнорируем его здесь + * для него есть отдельный слушатель usePacket (снизу) + */ + return; + } + if (fromPublicKey != publicKey) { + /** + * Игнорируем если это сообщения не от нас + */ + return; + } + await updateSyncTime(timestamp); + const groupKey = await getGroupKey(toPublicKey); + if (!groupKey) { + log("Group key not found for group " + toPublicKey); + error("Message dropped because group key not found for group " + toPublicKey); + return; + } + info("New group message packet received from " + fromPublicKey); + + let decryptedContent = ''; + + try { + decryptedContent = await decodeWithPassword(groupKey, content); + } catch (e) { + decryptedContent = ''; + } + + let attachmentsMeta: any[] = []; + let messageAttachments: Attachment[] = []; + for (let i = 0; i < packet.getAttachments().length; i++) { + const attachment = packet.getAttachments()[i]; + log("Attachment received id " + attachment.id + " type " + attachment.type); + + let nextLength = messageAttachments.push({ + ...attachment, + blob: "" + }); + + if (attachment.type == AttachmentType.MESSAGES) { + /** + * Этот тип вложения приходит сразу в blob и не нуждается + * в последующем скачивании + */ + const decryptedBlob = await decodeWithPassword(groupKey, attachment.blob); + writeFile(`m/${await generateMd5(attachment.id + publicKey)}`, + Buffer.from(await encodeWithPassword(privatePlain, decryptedBlob)).toString('binary')); + messageAttachments[nextLength - 1].blob = decryptedBlob; + } + + attachmentsMeta.push({ + id: attachment.id, + type: attachment.type, + preview: attachment.preview + }); + } + + const newMessage: Message = { + from_public_key: fromPublicKey, + to_public_key: toPublicKey, + content: content, + timestamp: timestamp, + readed: idle ? 0 : 1, + chacha_key: groupKey, + from_me: fromPublicKey == publicKey ? 1 : 0, + plain_message: decryptedContent, + delivered: DeliveredMessageState.DELIVERED, + message_id: messageId, + attachments: messageAttachments + }; + + await runQuery(` + INSERT INTO messages + (from_public_key, to_public_key, content, timestamp, read, chacha_key, from_me, plain_message, account, message_id, delivered, attachments) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, [fromPublicKey, + toPublicKey, + content, + timestamp, + /**если текущий открытый диалог == беседе (которая приходит в toPublicKey) */ + (currentDialogPublicKeyView == toPublicKey && !idle && viewState != ViewPanelsState.DIALOGS_PANEL_ONLY) ? 1 : 0, + '', + 0, + await encodeWithPassword(privatePlain, decryptedContent), + publicKey, + messageId, + DeliveredMessageState.DELIVERED, + JSON.stringify(attachmentsMeta)]); + + /** + * Так как у нас в toPublicKey приходит ID группы, + * то обновляем диалог по этому ID, а не по fromPublicKey + * как это сделано в личных сообщениях + */ + updateDialog(toPublicKey); + + let dialogCache = getDialogCache(toPublicKey); + if (currentDialogPublicKeyView !== toPublicKey && dialogCache.length > 0) { + addOrUpdateDialogCache(toPublicKey, [...dialogCache, newMessage].slice(-MESSAGE_MAX_LOADED)); + } + }); + }, [updateDialog, focused, currentDialogPublicKeyView, viewState, idle, protocolState]); } \ No newline at end of file -- 2.49.1 From 785406671c093bd14468e338994de20c981882db Mon Sep 17 00:00:00 2001 From: RoyceDa Date: Tue, 24 Feb 2026 17:33:26 +0200 Subject: [PATCH 5/8] =?UTF-8?q?=D0=A1=D0=B8=D0=BD=D1=85=D1=80=D0=BE=D0=BD?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BF=D1=80=D0=BE=D1=87?= =?UTF-8?q?=D1=82=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D0=B3=D1=80=D1=83?= =?UTF-8?q?=D0=BF=D0=BF=D0=B0=D1=85=20=D0=B8=20=D1=84=D0=B8=D0=BA=D1=81=20?= =?UTF-8?q?=D1=81=D0=B8=D0=BD=D1=85=D1=80=D0=BE=D0=BD=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B9=20=D0=B2=20=D0=B3=D1=80=D1=83=D0=BF=D0=BF=D0=B0?= =?UTF-8?q?=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DialogProvider/DialogProvider.tsx | 81 +++++++++++++++++++ .../DialogProvider/useDialogFiber.ts | 13 ++- .../DialogProvider/useSynchronize.ts | 57 +++++++++++-- 3 files changed, 141 insertions(+), 10 deletions(-) diff --git a/app/providers/DialogProvider/DialogProvider.tsx b/app/providers/DialogProvider/DialogProvider.tsx index 38affed..94b3ce9 100644 --- a/app/providers/DialogProvider/DialogProvider.tsx +++ b/app/providers/DialogProvider/DialogProvider.tsx @@ -466,6 +466,87 @@ export function DialogProvider(props: DialogProviderProps) { setMessages((prev) => ([...prev, newMessage])); }, [privatePlain]); + /** + * Обработчик сообщений для синхронизации своих же сообщений в группе + */ + usePacket(0x06, async (packet: PacketMessage) => { + const fromPublicKey = packet.getFromPublicKey(); + const toPublicKey = packet.getToPublicKey(); + + if(fromPublicKey != publicKey){ + /** + * Это не синхронизация, игнорируем ее в этом обработчике + */ + return; + } + + if(toPublicKey != props.dialog){ + /** + * Исправление кросс диалогового сообщения + */ + return; + } + + if(!hasGroup(props.dialog)){ + /** + * Если это не групповое сообщение, то для него есть + * другой обработчик выше + */ + return; + } + const content = packet.getContent(); + const timestamp = packet.getTimestamp(); + /** + * Генерация рандомного ID сообщения по SEED нужна для того, + * чтобы сообщение записанное здесь в стек сообщений совпадало + * с тем что записывается в БД в файле useDialogFiber.ts + */ + const messageId = packet.getMessageId(); + + const groupKey = await getGroupKey(toPublicKey); + if(!groupKey){ + log("Group key not found for group " + toPublicKey); + error("Message dropped because group key not found for group " + toPublicKey); + return; + } + info("New group message packet received from " + fromPublicKey); + + let decryptedContent = ''; + + try{ + decryptedContent = await decodeWithPassword(groupKey, content); + }catch(e) { + decryptedContent = ''; + } + + let attachments: Attachment[] = []; + for(let i = 0; i < packet.getAttachments().length; i++) { + const attachment = packet.getAttachments()[i]; + attachments.push({ + id: attachment.id, + preview: attachment.preview, + type: attachment.type, + blob: attachment.type == AttachmentType.MESSAGES ? await decodeWithPassword(groupKey, attachment.blob) : "" + }); + } + + const newMessage : Message = { + from_public_key: fromPublicKey, + to_public_key: toPublicKey, + content: content, + timestamp: timestamp, + readed: 0, + chacha_key: groupKey, + from_me: 1, + plain_message: decryptedContent, + delivered: DeliveredMessageState.DELIVERED, + message_id: messageId, + attachments: attachments + }; + + setMessages((prev) => ([...prev, newMessage])); + }, [messages, idle, props.dialog]); + /** * Обработчик для личных сообщений */ diff --git a/app/providers/DialogProvider/useDialogFiber.ts b/app/providers/DialogProvider/useDialogFiber.ts index 112bb5c..c2399ce 100644 --- a/app/providers/DialogProvider/useDialogFiber.ts +++ b/app/providers/DialogProvider/useDialogFiber.ts @@ -391,15 +391,22 @@ export function useDialogFiber() { */ usePacket(0x07, async (packet: PacketRead) => { runTaskInQueue(async () => { - if (!hasGroup(packet.getToPublicKey())) { + const fromPublicKey = packet.getFromPublicKey(); + const toPublicKey = packet.getToPublicKey(); + if (!hasGroup(toPublicKey)) { /** * Если это не относится к группам, то игнорируем здесь, * для этого есть отдельный слушатель usePacket выше */ return; } - const fromPublicKey = packet.getFromPublicKey(); - const toPublicKey = packet.getToPublicKey(); + if(fromPublicKey == publicKey){ + /** + * Игнорируем если это наше прочтение + * которое получается при синхронизации + */ + return; + } await runQuery(`UPDATE messages SET read = 1 WHERE to_public_key = ? AND from_public_key = ? AND account = ?`, [toPublicKey, publicKey, publicKey]); await updateSyncTime(Date.now()); updateDialog(toPublicKey); diff --git a/app/providers/DialogProvider/useSynchronize.ts b/app/providers/DialogProvider/useSynchronize.ts index 1692014..82c5bef 100644 --- a/app/providers/DialogProvider/useSynchronize.ts +++ b/app/providers/DialogProvider/useSynchronize.ts @@ -27,7 +27,7 @@ import { useDialogsCache } from "./useDialogsCache"; import { PacketRead } from "../ProtocolProvider/protocol/packets/packet.read"; import { useLogger } from "@/app/hooks/useLogger"; import { useIdle } from "@mantine/hooks"; -import { useViewPanelsState, ViewPanelsState } from "@/app/hooks/useViewPanelsState"; +import { useViewPanelsState } from "@/app/hooks/useViewPanelsState"; import { useWindowFocus } from "@/app/hooks/useWindowFocus"; /** @@ -145,6 +145,13 @@ export function useSynchronize() { const content = packet.getContent(); const timestamp = packet.getTimestamp(); const messageId = packet.getMessageId(); + if(hasGroup(toPublicKey)){ + /** + * Игнорируем если это сообщение для группы, для них есть отдельный слушатель usePacket ниже + */ + return; + } + if (fromPublicKey != publicKey) { /** * Игнорируем если это не сообщение от нас @@ -262,6 +269,43 @@ export function useSynchronize() { })); }); }, [updateDialog, publicKey]); + + /** + * Обработчик синхронизации прочтения групповых сообщений + */ + usePacket(0x07, async (packet: PacketRead) => { + runTaskInQueue(async () => { + const fromPublicKey = packet.getFromPublicKey(); + const toPublicKey = packet.getToPublicKey(); + if (!hasGroup(toPublicKey)) { + /** + * Если это не относится к группам, то игнорируем здесь, + * для этого есть отдельный слушатель usePacket выше + */ + return; + } + if(fromPublicKey != publicKey){ + /** + * Игнорируем если это наше прочтение + * которое получается при синхронизации + */ + return; + } + await runQuery(`UPDATE messages SET read = 1 WHERE to_public_key = ? AND from_public_key != ? AND account = ?`, + [toPublicKey, publicKey, publicKey]); + await updateSyncTime(Date.now()); + updateDialog(toPublicKey); + addOrUpdateDialogCache(toPublicKey, getDialogCache(toPublicKey).map((message) => { + if (!message.readed && message.from_public_key != publicKey) { + return { + ...message, + readed: 1 + } + } + return message; + })); + }); + }, [updateDialog]); /** * Обработчик сообщений для синхронизации своих же сообщений в группе @@ -337,9 +381,9 @@ export function useSynchronize() { to_public_key: toPublicKey, content: content, timestamp: timestamp, - readed: idle ? 0 : 1, + readed: 0, chacha_key: groupKey, - from_me: fromPublicKey == publicKey ? 1 : 0, + from_me: 1, plain_message: decryptedContent, delivered: DeliveredMessageState.DELIVERED, message_id: messageId, @@ -353,10 +397,9 @@ export function useSynchronize() { toPublicKey, content, timestamp, - /**если текущий открытый диалог == беседе (которая приходит в toPublicKey) */ - (currentDialogPublicKeyView == toPublicKey && !idle && viewState != ViewPanelsState.DIALOGS_PANEL_ONLY) ? 1 : 0, - '', - 0, + 0, //по умолчанию не прочитаны + "", + 1, //Свои же сообщения всегда от нас await encodeWithPassword(privatePlain, decryptedContent), publicKey, messageId, -- 2.49.1 From 453cc55fc05eb972e05ede33331b2a3262ff9988 Mon Sep 17 00:00:00 2001 From: RoyceDa Date: Tue, 24 Feb 2026 18:17:48 +0200 Subject: [PATCH 6/8] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BD=D1=81=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D0=BE=D0=B9=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BF=D1=80=D0=B8=20=D1=81=D0=B8=D0=BD=D1=85=D1=80=D0=BE?= =?UTF-8?q?=D0=BD=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/providers/DialogProvider/DialogProvider.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/providers/DialogProvider/DialogProvider.tsx b/app/providers/DialogProvider/DialogProvider.tsx index 94b3ce9..adfdc44 100644 --- a/app/providers/DialogProvider/DialogProvider.tsx +++ b/app/providers/DialogProvider/DialogProvider.tsx @@ -420,13 +420,18 @@ export function DialogProvider(props: DialogProviderProps) { const timestamp = packet.getTimestamp(); const messageId = packet.getMessageId(); - if(fromPublicKey != publicKey){ /** * Игнорируем если это не сообщение от нас */ return; } + if(hasGroup(toPublicKey)){ + /** + * Есть другой обработчик для синхронизации групп + */ + return; + } if(toPublicKey != props.dialog) { /** * Игнорируем если это не сообщение для этого диалога -- 2.49.1 From f1fb7ba252e905f09fe5e83ffc3577aa9ec2d8f6 Mon Sep 17 00:00:00 2001 From: RoyceDa Date: Tue, 24 Feb 2026 18:31:04 +0200 Subject: [PATCH 7/8] =?UTF-8?q?=D0=A1=D0=BA=D1=80=D1=8B=D0=B2=D0=B0=D0=B5?= =?UTF-8?q?=D0=BC=20=D1=81=D1=87=D0=B5=D1=82=D1=87=D0=B8=D0=BA=20=D0=BD?= =?UTF-8?q?=D0=B5=D0=BF=D1=80=D0=BE=D1=87=D0=B8=D1=82=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D1=85=20=D0=BF=D1=80=D0=B8=20=D1=81=D0=B8=D0=BD=D1=85?= =?UTF-8?q?=D1=80=D0=BE=D0=BD=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/Dialog/Dialog.tsx | 5 ++++- app/servers.ts | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/components/Dialog/Dialog.tsx b/app/components/Dialog/Dialog.tsx index d83ed15..9c4137c 100644 --- a/app/components/Dialog/Dialog.tsx +++ b/app/components/Dialog/Dialog.tsx @@ -18,6 +18,8 @@ import { useDialogInfo } from "@/app/providers/DialogListProvider/useDialogInfo" import { useDialogContextMenu } from "@/app/hooks/useDialogContextMenu"; import { useDialogPin } from "@/app/providers/DialogStateProvider.tsx/useDialogPin"; import { useDialogMute } from "@/app/providers/DialogStateProvider.tsx/useDialogMute"; +import { useProtocolState } from "@/app/providers/ProtocolProvider/useProtocolState"; +import { ProtocolState } from "@/app/providers/ProtocolProvider/ProtocolProvider"; export interface DialogProps extends DialogRow { onClickDialog: (dialog: string) => void; @@ -51,6 +53,7 @@ export function Dialog(props : DialogProps) { const isInCurrentDialog = props.dialog_id == сurrentDialogPublicKeyView; const currentDialogColor = computedTheme == 'dark' ? '#2a6292' :'#438fd1'; + const [protocolState] = useProtocolState(); usePacket(0x0B, (packet : PacketTyping) => { if(packet.getFromPublicKey() == opponent && packet.getToPublicKey() == publicKey && !fromMe){ @@ -153,7 +156,7 @@ export function Dialog(props : DialogProps) { {!loading && (lastMessage.delivered == DeliveredMessageState.ERROR || (!isMessageDeliveredByTime(lastMessage.timestamp, lastMessage.attachments.length) && lastMessage.delivered != DeliveredMessageState.DELIVERED)) && ( )} - {unreaded > 0 && !lastMessageFromMe && 0 && !lastMessageFromMe && protocolState != ProtocolState.SYNCHRONIZATION && {unreaded > 99 ? '99+' : unreaded}} diff --git a/app/servers.ts b/app/servers.ts index 53d4fca..aa52cbb 100644 --- a/app/servers.ts +++ b/app/servers.ts @@ -1,8 +1,8 @@ export const SERVERS = [ //'wss://cdn.rosetta-im.com', //'ws://10.211.55.2:3000', - 'ws://127.0.0.1:3000', - //'wss://wss.rosetta.im' + //'ws://127.0.0.1:3000', + 'wss://wss.rosetta.im' ]; export function selectServer(): string { -- 2.49.1 From 7c806149b3d6b73e4293e20b86aea39e5ea17045 Mon Sep 17 00:00:00 2001 From: RoyceDa Date: Tue, 24 Feb 2026 18:44:37 +0200 Subject: [PATCH 8/8] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=BD=D1=8F=D1=82=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/version.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/version.ts b/app/version.ts index 9ff6ed3..bc70765 100644 --- a/app/version.ts +++ b/app/version.ts @@ -1,8 +1,12 @@ -export const APP_VERSION = "1.0.6"; +export const APP_VERSION = "1.0.7"; export const CORE_MIN_REQUIRED_VERSION = "1.5.0"; export const RELEASE_NOTICE = ` -**Обновление v1.0.6** :emoji_1f631: -- Исправлена очистка сообщения при нажатии ESC -- При клике на текст в сообщении теперь сообщение не уходит в ответ +**Обновление v1.0.7** :emoji_1f631: +- Фикс уведомлений при синхронизации сообщений +- Защищенная синхронизация ключей в группах +- Синхронизация сообщений в группах +- Синхронизация вложений в группах +- Синхронизация индикаторов прочтения +- Улучшенная организация кода и оптимизации `; \ No newline at end of file -- 2.49.1