'init'
This commit is contained in:
278
app/providers/DialogProvider/useGroups.ts
Normal file
278
app/providers/DialogProvider/useGroups.ts
Normal file
@@ -0,0 +1,278 @@
|
||||
import { useDatabase } from "@/app/providers/DatabaseProvider/useDatabase";
|
||||
import { usePrivatePlain } from "../AccountProvider/usePrivatePlain";
|
||||
import { decodeWithPassword, encodeWithPassword } from "@/app/crypto/crypto";
|
||||
import { generateRandomKey } from "@/app/utils/utils";
|
||||
import { useDialogsList } from "../DialogListProvider/useDialogsList";
|
||||
import { usePublicKey } from "../AccountProvider/usePublicKey";
|
||||
import { DeliveredMessageState } from "./DialogProvider";
|
||||
import { useSender } from "../ProtocolProvider/useSender";
|
||||
import { useState } from "react";
|
||||
import { PacketCreateGroup } from "../ProtocolProvider/protocol/packets/packet.create.group";
|
||||
import { useProtocol } from "../ProtocolProvider/useProtocol";
|
||||
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
|
||||
import { GroupStatus, PacketGroupJoin } from "../ProtocolProvider/protocol/packets/packet.group.join";
|
||||
import { useGroupInviteStatus } from "./useGroupInviteStatus";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useUpdateGroupInformation } from "../InformationProvider/useUpdateGroupInformation";
|
||||
import { PacketGroupLeave } from "../ProtocolProvider/protocol/packets/packet.group.leave";
|
||||
import { PacketGroupBan } from "../ProtocolProvider/protocol/packets/packet.group.ban";
|
||||
|
||||
export function useGroups() : {
|
||||
/**
|
||||
* Получаем ключ шифрования из базы данных по ид группы
|
||||
* @param groupId ид группы
|
||||
* @returns ключ шифрования
|
||||
*/
|
||||
getGroupKey: (groupId: string) => Promise<string>;
|
||||
/**
|
||||
* Получает строку для приглашения в группу
|
||||
* @param groupId ид группы
|
||||
* @param title заголовок
|
||||
* @param encryptKey ключ шифрования
|
||||
* @param description описание
|
||||
* @returns строка, которая нужна для приглашения в группу
|
||||
*/
|
||||
constructGroupString: (groupId: string, title: string, encryptKey: string, description?: string) => Promise<string>;
|
||||
/**
|
||||
* Функция, обратная constructGroupString, парсит строку приглашения в группу
|
||||
* @param groupString строка приглашения в группу
|
||||
* @returns объект с информацией о группе или null, если строка некорректна
|
||||
*/
|
||||
parseGroupString: (groupString: string) => Promise<{
|
||||
groupId: string;
|
||||
title: string;
|
||||
encryptKey: string;
|
||||
description: string;
|
||||
} | null>;
|
||||
/**
|
||||
* Проверяет, является ли диалог группой
|
||||
* @param dialog ид диалога
|
||||
* @returns вернет true, если это группа и false если это пользователь
|
||||
*/
|
||||
hasGroup: (dialog: string) => boolean;
|
||||
/**
|
||||
* Возвращает подготовленный для роута groupId
|
||||
* @param groupId подготавливает groupId для роута
|
||||
* @returns заменяет символы которые может не обрабатывать роутер
|
||||
*/
|
||||
prepareForRoute: (groupId: string) => string;
|
||||
/**
|
||||
* Создает группу
|
||||
* @param title заголовок
|
||||
* @param description описание
|
||||
* @returns
|
||||
*/
|
||||
createGroup: (title: string, description: string) => Promise<void>;
|
||||
/**
|
||||
* Зайдет в группу по строке приглашения
|
||||
* @param groupString строка приглашение
|
||||
* @returns
|
||||
*/
|
||||
joinGroup: (groupString: string) => Promise<void>;
|
||||
/**
|
||||
* Покидает группу
|
||||
* @param groupId ид группы
|
||||
* @returns
|
||||
*/
|
||||
leaveGroup: (groupId: string) => Promise<void>;
|
||||
/**
|
||||
*
|
||||
* @param str
|
||||
* @returns
|
||||
*/
|
||||
normalize: (str: string) => string;
|
||||
banUserOnGroup: (userPublicKey: string, groupId: string) => void;
|
||||
getPrefix: () => string;
|
||||
loading: boolean;
|
||||
} {
|
||||
const {allQuery, runQuery} = useDatabase();
|
||||
const privatePlain = usePrivatePlain();
|
||||
const {updateDialog} = useDialogsList();
|
||||
const publicKey = usePublicKey();
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const send = useSender();
|
||||
const {protocol} = useProtocol();
|
||||
const {info} = useConsoleLogger('useGroups');
|
||||
const {setInviteStatusByGroupId} = useGroupInviteStatus('');
|
||||
const navigate = useNavigate();
|
||||
const updateGroupInformation = useUpdateGroupInformation();
|
||||
|
||||
const constructGroupString = async (groupId: string, title: string, encryptKey: string, description?: string) => {
|
||||
let groupString = `${groupId}:${title}:${encryptKey}`;
|
||||
if (description && description.trim().length > 0) {
|
||||
groupString += `:${description}`;
|
||||
}
|
||||
let encodedPayload = await encodeWithPassword('rosetta_group', groupString);
|
||||
return `#group:${encodedPayload}`;
|
||||
}
|
||||
|
||||
const hasGroup = (dialog: string) => {
|
||||
return dialog.startsWith('#group:');
|
||||
}
|
||||
|
||||
const getPrefix = () => {
|
||||
return '#group:';
|
||||
}
|
||||
|
||||
const parseGroupString = async (groupString: string) => {
|
||||
try{
|
||||
if (!groupString.startsWith('#group:')) {
|
||||
return null;
|
||||
}
|
||||
let encodedPayload = groupString.substring(7);
|
||||
let decodedPayload = await decodeWithPassword('rosetta_group', encodedPayload);
|
||||
let parts = decodedPayload.split(':');
|
||||
return {
|
||||
groupId: parts[0],
|
||||
title: parts[1],
|
||||
encryptKey: parts[2],
|
||||
description: parts[3] || ''
|
||||
}
|
||||
}catch(e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const getGroupKey = async (groupId: string) => {
|
||||
const query = `SELECT key FROM groups WHERE group_id = ? AND account = ? LIMIT 1`;
|
||||
const result = await allQuery(query, [normalize(groupId), publicKey]);
|
||||
if(result.length > 0) {
|
||||
let key = result[0].key;
|
||||
return await decodeWithPassword(privatePlain, key);
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const prepareForRoute = (groupId: string) => {
|
||||
return `#group:${groupId}`.replace('#', '%23');
|
||||
}
|
||||
|
||||
const normalize = (str: string) => {
|
||||
return str.replace('#group:', '').trim();
|
||||
}
|
||||
|
||||
const createGroup = async (title: string, description: string) => {
|
||||
if(title.trim().length === 0){
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
const packet = new PacketCreateGroup();
|
||||
send(packet);
|
||||
protocol.waitPacketOnce(0x11, async (packet : PacketCreateGroup) => {
|
||||
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)}`);
|
||||
});
|
||||
}
|
||||
|
||||
const banUserOnGroup = (userPublicKey: string, groupId: string) => {
|
||||
const packet = new PacketGroupBan();
|
||||
packet.setGroupId(groupId);
|
||||
packet.setPublicKey(userPublicKey);
|
||||
send(packet);
|
||||
}
|
||||
|
||||
const joinGroup = async (groupString: string) => {
|
||||
const parsed = await parseGroupString(groupString);
|
||||
if (!parsed) {
|
||||
return;
|
||||
}
|
||||
const encryptKey = parsed.encryptKey;
|
||||
const groupId = parsed.groupId;
|
||||
const title = parsed.title;
|
||||
const description = parsed.description;
|
||||
|
||||
const packet = new PacketGroupJoin();
|
||||
packet.setGroupId(parsed.groupId);
|
||||
send(packet);
|
||||
setLoading(true);
|
||||
|
||||
protocol.waitPacketOnce(0x14, async (packet: PacketGroupJoin) => {
|
||||
console.info(`Received group join response for group ${parsed.groupId}`);
|
||||
const groupStatus = packet.getGroupStatus();
|
||||
if(groupStatus != GroupStatus.JOINED){
|
||||
info(`Cannot join group ${parsed.groupId}, banned`);
|
||||
setInviteStatusByGroupId(parsed.groupId, groupStatus);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
const secureKey = await encodeWithPassword(privatePlain, encryptKey);
|
||||
let content = await encodeWithPassword(encryptKey, `$a=Group joined`);
|
||||
let plainMessage = await encodeWithPassword(privatePlain, `$a=Group joined`);
|
||||
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);
|
||||
setInviteStatusByGroupId(groupId, GroupStatus.JOINED);
|
||||
setLoading(false);
|
||||
updateGroupInformation({
|
||||
groupId: groupId,
|
||||
title: title,
|
||||
description: description
|
||||
});
|
||||
navigate(`/main/chat/${prepareForRoute(groupId)}`);
|
||||
});
|
||||
}
|
||||
|
||||
const leaveGroup = async (groupId: string) => {
|
||||
const packet = new PacketGroupLeave();
|
||||
packet.setGroupId(groupId);
|
||||
send(packet);
|
||||
setLoading(true);
|
||||
protocol.waitPacketOnce(0x15, async (packet: PacketGroupLeave) => {
|
||||
if(packet.getGroupId() != groupId){
|
||||
return;
|
||||
}
|
||||
await runQuery(`
|
||||
DELETE FROM groups WHERE group_id = ? AND account = ?
|
||||
`, [groupId, publicKey]);
|
||||
await runQuery(`
|
||||
DELETE FROM messages WHERE to_public_key = ? AND account = ?
|
||||
`, ["#group:" + normalize(groupId), publicKey]);
|
||||
updateDialog("#group:" + normalize(groupId));
|
||||
setLoading(false);
|
||||
navigate(`/main`);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
getGroupKey,
|
||||
constructGroupString,
|
||||
parseGroupString,
|
||||
hasGroup,
|
||||
prepareForRoute,
|
||||
createGroup,
|
||||
joinGroup,
|
||||
leaveGroup,
|
||||
getPrefix,
|
||||
banUserOnGroup,
|
||||
normalize,
|
||||
loading
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user