Reviewed-on: #14
This commit was merged in pull request #14.
This commit is contained in:
@@ -18,6 +18,8 @@ import { useDialogInfo } from "@/app/providers/DialogListProvider/useDialogInfo"
|
|||||||
import { useDialogContextMenu } from "@/app/hooks/useDialogContextMenu";
|
import { useDialogContextMenu } from "@/app/hooks/useDialogContextMenu";
|
||||||
import { useDialogPin } from "@/app/providers/DialogStateProvider.tsx/useDialogPin";
|
import { useDialogPin } from "@/app/providers/DialogStateProvider.tsx/useDialogPin";
|
||||||
import { useDialogMute } from "@/app/providers/DialogStateProvider.tsx/useDialogMute";
|
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 {
|
export interface DialogProps extends DialogRow {
|
||||||
onClickDialog: (dialog: string) => void;
|
onClickDialog: (dialog: string) => void;
|
||||||
@@ -51,6 +53,7 @@ export function Dialog(props : DialogProps) {
|
|||||||
|
|
||||||
const isInCurrentDialog = props.dialog_id == сurrentDialogPublicKeyView;
|
const isInCurrentDialog = props.dialog_id == сurrentDialogPublicKeyView;
|
||||||
const currentDialogColor = computedTheme == 'dark' ? '#2a6292' :'#438fd1';
|
const currentDialogColor = computedTheme == 'dark' ? '#2a6292' :'#438fd1';
|
||||||
|
const [protocolState] = useProtocolState();
|
||||||
|
|
||||||
usePacket(0x0B, (packet : PacketTyping) => {
|
usePacket(0x0B, (packet : PacketTyping) => {
|
||||||
if(packet.getFromPublicKey() == opponent && packet.getToPublicKey() == publicKey && !fromMe){
|
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)) && (
|
{!loading && (lastMessage.delivered == DeliveredMessageState.ERROR || (!isMessageDeliveredByTime(lastMessage.timestamp, lastMessage.attachments.length) && lastMessage.delivered != DeliveredMessageState.DELIVERED)) && (
|
||||||
<IconAlertCircle stroke={3} size={15} color={colors.error}></IconAlertCircle>
|
<IconAlertCircle stroke={3} size={15} color={colors.error}></IconAlertCircle>
|
||||||
)}
|
)}
|
||||||
{unreaded > 0 && !lastMessageFromMe && <Badge
|
{unreaded > 0 && !lastMessageFromMe && protocolState != ProtocolState.SYNCHRONIZATION && <Badge
|
||||||
color={isInCurrentDialog ? 'white' : (isMuted ? colors.chevrons.active : colors.brandColor)}
|
color={isInCurrentDialog ? 'white' : (isMuted ? colors.chevrons.active : colors.brandColor)}
|
||||||
c={isInCurrentDialog ? colors.brandColor : 'white'}
|
c={isInCurrentDialog ? colors.brandColor : 'white'}
|
||||||
size={'sm'} circle={unreaded < 10}>{unreaded > 99 ? '99+' : unreaded}</Badge>}
|
size={'sm'} circle={unreaded < 10}>{unreaded > 99 ? '99+' : unreaded}</Badge>}
|
||||||
|
|||||||
@@ -420,13 +420,18 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
const timestamp = packet.getTimestamp();
|
const timestamp = packet.getTimestamp();
|
||||||
const messageId = packet.getMessageId();
|
const messageId = packet.getMessageId();
|
||||||
|
|
||||||
|
|
||||||
if(fromPublicKey != publicKey){
|
if(fromPublicKey != publicKey){
|
||||||
/**
|
/**
|
||||||
* Игнорируем если это не сообщение от нас
|
* Игнорируем если это не сообщение от нас
|
||||||
*/
|
*/
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(hasGroup(toPublicKey)){
|
||||||
|
/**
|
||||||
|
* Есть другой обработчик для синхронизации групп
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(toPublicKey != props.dialog) {
|
if(toPublicKey != props.dialog) {
|
||||||
/**
|
/**
|
||||||
* Игнорируем если это не сообщение для этого диалога
|
* Игнорируем если это не сообщение для этого диалога
|
||||||
@@ -466,6 +471,87 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
setMessages((prev) => ([...prev, newMessage]));
|
setMessages((prev) => ([...prev, newMessage]));
|
||||||
}, [privatePlain]);
|
}, [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]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Обработчик для личных сообщений
|
* Обработчик для личных сообщений
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { useMentions } from "../DialogStateProvider.tsx/useMentions";
|
|||||||
import { runTaskInQueue } from "./dialogQueue";
|
import { runTaskInQueue } from "./dialogQueue";
|
||||||
import { useProtocolState } from "../ProtocolProvider/useProtocolState";
|
import { useProtocolState } from "../ProtocolProvider/useProtocolState";
|
||||||
import { ProtocolState } from "../ProtocolProvider/ProtocolProvider";
|
import { ProtocolState } from "../ProtocolProvider/ProtocolProvider";
|
||||||
|
import { useUpdateSyncTime } from "./useUpdateSyncTime";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* При вызове будет запущен "фоновый" обработчик
|
* При вызове будет запущен "фоновый" обработчик
|
||||||
@@ -53,27 +54,7 @@ export function useDialogFiber() {
|
|||||||
const [userInfo] = useUserInformation(publicKey);
|
const [userInfo] = useUserInformation(publicKey);
|
||||||
const { pushMention } = useMentions();
|
const { pushMention } = useMentions();
|
||||||
const [protocolState] = useProtocolState();
|
const [protocolState] = useProtocolState();
|
||||||
|
const updateSyncTime = useUpdateSyncTime();
|
||||||
/**
|
|
||||||
* Обновляет время последней синхронизации для аккаунта
|
|
||||||
* @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]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Лог
|
* Лог
|
||||||
@@ -82,101 +63,6 @@ export function useDialogFiber() {
|
|||||||
info("Starting passive fiber for dialog packets");
|
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]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Обработчик сообщений для группы
|
* Обработчик сообщений для группы
|
||||||
*/
|
*/
|
||||||
@@ -328,7 +214,7 @@ export function useDialogFiber() {
|
|||||||
addOrUpdateDialogCache(toPublicKey, [...dialogCache, newMessage].slice(-MESSAGE_MAX_LOADED));
|
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 +331,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");
|
notify("New message", "You have a new message");
|
||||||
}
|
}
|
||||||
@@ -457,48 +343,7 @@ export function useDialogFiber() {
|
|||||||
addOrUpdateDialogCache(fromPublicKey, [...dialogCache, newMessage].slice(-MESSAGE_MAX_LOADED));
|
addOrUpdateDialogCache(fromPublicKey, [...dialogCache, newMessage].slice(-MESSAGE_MAX_LOADED));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [blocked, muted, updateDialog, focused, currentDialogPublicKeyView, viewState, idle]);
|
}, [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,20 +385,28 @@ export function useDialogFiber() {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}, [updateDialog, publicKey]);
|
}, [updateDialog, publicKey]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Обработчик прочтения групповых сообщений
|
* Обработчик прочтения групповых сообщений
|
||||||
*/
|
*/
|
||||||
usePacket(0x07, async (packet: PacketRead) => {
|
usePacket(0x07, async (packet: PacketRead) => {
|
||||||
runTaskInQueue(async () => {
|
runTaskInQueue(async () => {
|
||||||
if (!hasGroup(packet.getToPublicKey())) {
|
const fromPublicKey = packet.getFromPublicKey();
|
||||||
|
const toPublicKey = packet.getToPublicKey();
|
||||||
|
if (!hasGroup(toPublicKey)) {
|
||||||
/**
|
/**
|
||||||
* Если это не относится к группам, то игнорируем здесь,
|
* Если это не относится к группам, то игнорируем здесь,
|
||||||
* для этого есть отдельный слушатель usePacket выше
|
* для этого есть отдельный слушатель usePacket выше
|
||||||
*/
|
*/
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fromPublicKey = packet.getFromPublicKey();
|
if(fromPublicKey == publicKey){
|
||||||
const toPublicKey = packet.getToPublicKey();
|
/**
|
||||||
|
* Игнорируем если это наше прочтение
|
||||||
|
* которое получается при синхронизации
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
await runQuery(`UPDATE messages SET read = 1 WHERE to_public_key = ? AND from_public_key = ? AND account = ?`, [toPublicKey, publicKey, publicKey]);
|
await runQuery(`UPDATE messages SET read = 1 WHERE to_public_key = ? AND from_public_key = ? AND account = ?`, [toPublicKey, publicKey, publicKey]);
|
||||||
await updateSyncTime(Date.now());
|
await updateSyncTime(Date.now());
|
||||||
updateDialog(toPublicKey);
|
updateDialog(toPublicKey);
|
||||||
|
|||||||
@@ -162,26 +162,10 @@ export function useGroups() : {
|
|||||||
const groupId = packet.getGroupId();
|
const groupId = packet.getGroupId();
|
||||||
info(`Creating group with id ${groupId}`);
|
info(`Creating group with id ${groupId}`);
|
||||||
const encryptKey = generateRandomKey(64);
|
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(`
|
joinGroup(await constructGroupString(groupId, title, encryptKey, description));
|
||||||
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)}`);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,9 +185,11 @@ export function useGroups() : {
|
|||||||
const groupId = parsed.groupId;
|
const groupId = parsed.groupId;
|
||||||
const title = parsed.title;
|
const title = parsed.title;
|
||||||
const description = parsed.description;
|
const description = parsed.description;
|
||||||
|
const encodedGroupString = await encodeWithPassword(privatePlain, groupString);
|
||||||
|
|
||||||
const packet = new PacketGroupJoin();
|
const packet = new PacketGroupJoin();
|
||||||
packet.setGroupId(parsed.groupId);
|
packet.setGroupId(parsed.groupId);
|
||||||
|
packet.setGroupString(encodedGroupString);
|
||||||
send(packet);
|
send(packet);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
|
|||||||
@@ -6,20 +6,56 @@ import { usePublicKey } from "../AccountProvider/usePublicKey";
|
|||||||
import { PacketSync, SyncStatus } from "../ProtocolProvider/protocol/packets/packet.sync";
|
import { PacketSync, SyncStatus } from "../ProtocolProvider/protocol/packets/packet.sync";
|
||||||
import { useSender } from "../ProtocolProvider/useSender";
|
import { useSender } from "../ProtocolProvider/useSender";
|
||||||
import { usePacket } from "../ProtocolProvider/usePacket";
|
import { usePacket } from "../ProtocolProvider/usePacket";
|
||||||
import { whenFinish } from "./dialogQueue";
|
import { runTaskInQueue, whenFinish } from "./dialogQueue";
|
||||||
import { useProtocol } from "../ProtocolProvider/useProtocol";
|
import { useProtocol } from "../ProtocolProvider/useProtocol";
|
||||||
|
import { PacketGroupJoin } from "../ProtocolProvider/protocol/packets/packet.group.join";
|
||||||
|
import { useGroups } from "./useGroups";
|
||||||
|
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 { 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, 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 } from "@/app/hooks/useViewPanelsState";
|
||||||
|
import { useWindowFocus } from "@/app/hooks/useWindowFocus";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Хук отвечает за синхронизацию сообщений, запрос синхронизации
|
* Хук отвечает за синхронизацию сообщений, запрос синхронизации
|
||||||
* при подключении
|
* при подключении
|
||||||
*/
|
*/
|
||||||
export function useSynchronize() {
|
export function useSynchronize() {
|
||||||
const [_, setProtocolState] = useProtocolState();
|
const [protocolState, setProtocolState] = useProtocolState();
|
||||||
const {getQuery, runQuery} = useDatabase();
|
const {getQuery, runQuery} = useDatabase();
|
||||||
const publicKey = usePublicKey();
|
const publicKey = usePublicKey();
|
||||||
const send = useSender();
|
const send = useSender();
|
||||||
const {protocol} = useProtocol();
|
const {protocol} = useProtocol();
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
if(protocol.handshakeExchangeComplete){
|
if(protocol.handshakeExchangeComplete){
|
||||||
trySync();
|
trySync();
|
||||||
@@ -38,15 +74,47 @@ export function useSynchronize() {
|
|||||||
send(packet);
|
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) => {
|
usePacket(25, async (packet: PacketSync) => {
|
||||||
const status = packet.getStatus();
|
const status = packet.getStatus();
|
||||||
if(status == SyncStatus.BATCH_START){
|
if(status == SyncStatus.BATCH_START){
|
||||||
setProtocolState(ProtocolState.SYNCHRONIZATION);
|
setProtocolState(ProtocolState.SYNCHRONIZATION);
|
||||||
}
|
}
|
||||||
if(status == SyncStatus.BATCH_END){
|
if(status == SyncStatus.BATCH_END){
|
||||||
console.info("Batch start");
|
/**
|
||||||
|
* Этот Promise ждет пока все сообщения синхронизируются и обработаются, только
|
||||||
|
* после этого
|
||||||
|
*/
|
||||||
await whenFinish();
|
await whenFinish();
|
||||||
console.info("Batch finished");
|
|
||||||
await runQuery(
|
await runQuery(
|
||||||
"INSERT INTO accounts_sync_times (account, last_sync) VALUES (?, ?) " +
|
"INSERT INTO accounts_sync_times (account, last_sync) VALUES (?, ?) " +
|
||||||
"ON CONFLICT(account) DO UPDATE SET last_sync = ? WHERE account = ?",
|
"ON CONFLICT(account) DO UPDATE SET last_sync = ? WHERE account = ?",
|
||||||
@@ -62,4 +130,293 @@ export function useSynchronize() {
|
|||||||
setProtocolState(ProtocolState.CONNECTED);
|
setProtocolState(ProtocolState.CONNECTED);
|
||||||
}
|
}
|
||||||
}, [publicKey]);
|
}, [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(hasGroup(toPublicKey)){
|
||||||
|
/**
|
||||||
|
* Игнорируем если это сообщение для группы, для них есть отдельный слушатель usePacket ниже
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
await runQuery(`UPDATE messages SET read = 1 WHERE from_public_key = ? AND to_public_key = ? AND account = ?`,
|
||||||
|
[toPublicKey, fromPublicKey, publicKey]);
|
||||||
|
|
||||||
|
updateDialog(toPublicKey);
|
||||||
|
addOrUpdateDialogCache(fromPublicKey, getDialogCache(fromPublicKey).map((message) => {
|
||||||
|
if (message.from_public_key == toPublicKey && !message.readed) {
|
||||||
|
console.info({ fromPublicKey, toPublicKey });
|
||||||
|
return {
|
||||||
|
...message,
|
||||||
|
readed: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}, [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]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обработчик сообщений для синхронизации своих же сообщений в группе
|
||||||
|
*/
|
||||||
|
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: 0,
|
||||||
|
chacha_key: groupKey,
|
||||||
|
from_me: 1,
|
||||||
|
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,
|
||||||
|
0, //по умолчанию не прочитаны
|
||||||
|
"",
|
||||||
|
1, //Свои же сообщения всегда от нас
|
||||||
|
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]);
|
||||||
}
|
}
|
||||||
33
app/providers/DialogProvider/useUpdateSyncTime.ts
Normal file
33
app/providers/DialogProvider/useUpdateSyncTime.ts
Normal file
@@ -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<void> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -12,6 +12,14 @@ export class PacketGroupJoin extends Packet {
|
|||||||
|
|
||||||
private groupId: string = "";
|
private groupId: string = "";
|
||||||
private groupStatus: GroupStatus = GroupStatus.NOT_JOINED;
|
private groupStatus: GroupStatus = GroupStatus.NOT_JOINED;
|
||||||
|
/**
|
||||||
|
* Строка группы, которая содержит информацию о группе, такую как ее название, описание и ключ
|
||||||
|
* Строка зашифрована обратимым шифрованием, где ключом выступает - реальный приватный ключ
|
||||||
|
* входящего в группу клиента. Нужно это для будущей синхронзации, так как клиенту на его другом
|
||||||
|
* устройстве нужно получить ключ группы и ее информацию. Сервер расшифровать эту строку не может. Эту
|
||||||
|
* строку может расшифровать только клиент, так как она зашифрована его приватным ключом
|
||||||
|
*/
|
||||||
|
private groupString: string = "";
|
||||||
|
|
||||||
public getPacketId(): number {
|
public getPacketId(): number {
|
||||||
return 0x14;
|
return 0x14;
|
||||||
@@ -20,6 +28,7 @@ export class PacketGroupJoin extends Packet {
|
|||||||
public _receive(stream: Stream): void {
|
public _receive(stream: Stream): void {
|
||||||
this.groupId = stream.readString();
|
this.groupId = stream.readString();
|
||||||
this.groupStatus = stream.readInt8();
|
this.groupStatus = stream.readInt8();
|
||||||
|
this.groupString = stream.readString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public _send(): Promise<Stream> | Stream {
|
public _send(): Promise<Stream> | Stream {
|
||||||
@@ -27,6 +36,7 @@ export class PacketGroupJoin extends Packet {
|
|||||||
stream.writeInt16(this.getPacketId());
|
stream.writeInt16(this.getPacketId());
|
||||||
stream.writeString(this.groupId);
|
stream.writeString(this.groupId);
|
||||||
stream.writeInt8(this.groupStatus);
|
stream.writeInt8(this.groupStatus);
|
||||||
|
stream.writeString(this.groupString);
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,5 +55,13 @@ export class PacketGroupJoin extends Packet {
|
|||||||
public getGroupStatus(): GroupStatus {
|
public getGroupStatus(): GroupStatus {
|
||||||
return this.groupStatus;
|
return this.groupStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setGroupString(groupString: string) {
|
||||||
|
this.groupString = groupString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGroupString(): string {
|
||||||
|
return this.groupString;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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 CORE_MIN_REQUIRED_VERSION = "1.5.0";
|
||||||
|
|
||||||
export const RELEASE_NOTICE = `
|
export const RELEASE_NOTICE = `
|
||||||
**Обновление v1.0.6** :emoji_1f631:
|
**Обновление v1.0.7** :emoji_1f631:
|
||||||
- Исправлена очистка сообщения при нажатии ESC
|
- Фикс уведомлений при синхронизации сообщений
|
||||||
- При клике на текст в сообщении теперь сообщение не уходит в ответ
|
- Защищенная синхронизация ключей в группах
|
||||||
|
- Синхронизация сообщений в группах
|
||||||
|
- Синхронизация вложений в группах
|
||||||
|
- Синхронизация индикаторов прочтения
|
||||||
|
- Улучшенная организация кода и оптимизации
|
||||||
`;
|
`;
|
||||||
Reference in New Issue
Block a user