Compare commits
15 Commits
e727529b89
...
d23ca97be9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d23ca97be9 | ||
|
|
519aa8802f | ||
|
|
2f2a0b5376 | ||
|
|
61e83bdd43 | ||
|
|
f5bfa153b6 | ||
|
|
81f5e66c56 | ||
|
|
aaa4b4283a | ||
|
|
c9cff515e5 | ||
|
|
94ba139541 | ||
|
|
7e0e97f472 | ||
|
|
8d6090e632 | ||
|
|
fd3fac54f6 | ||
|
|
bd3c0eec69 | ||
|
|
697b797f8c | ||
|
|
429aa614d7 |
@@ -40,6 +40,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build the application
|
- name: Build the application
|
||||||
run: |
|
run: |
|
||||||
|
mkdir -p dist/builds/linux/x64
|
||||||
mkdir -p dist/builds/linux/${{ matrix.out_dir }}
|
mkdir -p dist/builds/linux/${{ matrix.out_dir }}
|
||||||
npx electron-vite build
|
npx electron-vite build
|
||||||
npx electron-builder --linux --$ARCH
|
npx electron-builder --linux --$ARCH
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export function ChatHeader() {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex h={'100%'} align={'center'} gap={'sm'}>
|
<Flex h={'100%'} align={'center'} gap={'sm'}>
|
||||||
{publicKey != opponent.publicKey && (
|
{publicKey != opponent.publicKey && !isSystemAccount && (
|
||||||
<IconPhone
|
<IconPhone
|
||||||
onClick={() => call(dialog)}
|
onClick={() => call(dialog)}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -89,7 +89,11 @@ export function DialogInput() {
|
|||||||
blob: fileContent,
|
blob: fileContent,
|
||||||
id: generateRandomKey(8),
|
id: generateRandomKey(8),
|
||||||
type: AttachmentType.FILE,
|
type: AttachmentType.FILE,
|
||||||
preview: files[0].size + "::" + files[0].name
|
preview: files[0].size + "::" + files[0].name,
|
||||||
|
transport: {
|
||||||
|
transport_server: "",
|
||||||
|
transport_tag: ""
|
||||||
|
}
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -116,7 +120,11 @@ export function DialogInput() {
|
|||||||
type: AttachmentType.MESSAGES,
|
type: AttachmentType.MESSAGES,
|
||||||
id: generateRandomKey(8),
|
id: generateRandomKey(8),
|
||||||
blob: JSON.stringify([...replyMessages.messages]),
|
blob: JSON.stringify([...replyMessages.messages]),
|
||||||
preview: ""
|
preview: "",
|
||||||
|
transport: {
|
||||||
|
transport_server: "",
|
||||||
|
transport_tag: ""
|
||||||
|
}
|
||||||
}]);
|
}]);
|
||||||
if(editableDivRef.current){
|
if(editableDivRef.current){
|
||||||
editableDivRef.current.focus();
|
editableDivRef.current.focus();
|
||||||
@@ -230,7 +238,11 @@ export function DialogInput() {
|
|||||||
blob: avatars[0].avatar,
|
blob: avatars[0].avatar,
|
||||||
id: generateRandomKey(8),
|
id: generateRandomKey(8),
|
||||||
type: AttachmentType.AVATAR,
|
type: AttachmentType.AVATAR,
|
||||||
preview: ""
|
preview: "",
|
||||||
|
transport: {
|
||||||
|
transport_server: "",
|
||||||
|
transport_tag: ""
|
||||||
|
}
|
||||||
}]);
|
}]);
|
||||||
if(editableDivRef.current){
|
if(editableDivRef.current){
|
||||||
editableDivRef.current.focus();
|
editableDivRef.current.focus();
|
||||||
@@ -270,7 +282,11 @@ export function DialogInput() {
|
|||||||
blob: base64Image,
|
blob: base64Image,
|
||||||
id: attachmentId,
|
id: attachmentId,
|
||||||
type: AttachmentType.IMAGE,
|
type: AttachmentType.IMAGE,
|
||||||
preview: ""
|
preview: "",
|
||||||
|
transport: {
|
||||||
|
transport_server: "",
|
||||||
|
transport_tag: ""
|
||||||
|
}
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
if(editableDivRef.current){
|
if(editableDivRef.current){
|
||||||
@@ -304,7 +320,11 @@ export function DialogInput() {
|
|||||||
blob: fileContent,
|
blob: fileContent,
|
||||||
id: attachmentId,
|
id: attachmentId,
|
||||||
type: AttachmentType.FILE,
|
type: AttachmentType.FILE,
|
||||||
preview: files[0].size + "::" + files[0].name
|
preview: files[0].size + "::" + files[0].name,
|
||||||
|
transport: {
|
||||||
|
transport_server: "",
|
||||||
|
transport_tag: ""
|
||||||
|
}
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export function MessageImage(props: AttachmentProps) {
|
|||||||
const [blurhashPreview, setBlurhashPreview] = useState("");
|
const [blurhashPreview, setBlurhashPreview] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.info(props.attachment);
|
||||||
console.info("Consturcting image, download status: " + downloadStatus);
|
console.info("Consturcting image, download status: " + downloadStatus);
|
||||||
constructBlob();
|
constructBlob();
|
||||||
constructFromBlurhash();
|
constructFromBlurhash();
|
||||||
@@ -158,7 +159,7 @@ export function MessageImage(props: AttachmentProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{(downloadStatus == DownloadStatus.NOT_DOWNLOADED || downloadStatus == DownloadStatus.DOWNLOADING) && (<Flex direction={'column'} pos={'absolute'} top={0} justify={'center'} h={'100%'} align={'center'} gap={'xs'}>
|
{(downloadStatus == DownloadStatus.NOT_DOWNLOADED || downloadStatus == DownloadStatus.DOWNLOADING) && (<Flex direction={'column'} pos={'absolute'} top={0} justify={'center'} h={'100%'} align={'center'} gap={'xs'}>
|
||||||
{!error && (
|
{!error && downloadStatus == DownloadStatus.DOWNLOADING && (
|
||||||
<Box style={{
|
<Box style={{
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
||||||
borderRadius: 50,
|
borderRadius: 50,
|
||||||
@@ -168,11 +169,20 @@ export function MessageImage(props: AttachmentProps) {
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
}}>
|
}}>
|
||||||
{downloadPercentage > 0 ? (
|
<AnimatedRoundedProgress size={40} value={Math.max(1, downloadPercentage)} color="white"></AnimatedRoundedProgress>
|
||||||
<AnimatedRoundedProgress size={40} value={downloadPercentage} color="white"></AnimatedRoundedProgress>
|
</Box>
|
||||||
) : (
|
|
||||||
<IconArrowDown size={25} color={'white'} />
|
|
||||||
)}
|
)}
|
||||||
|
{!error && downloadStatus != DownloadStatus.DOWNLOADING && (
|
||||||
|
<Box style={{
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
||||||
|
borderRadius: 50,
|
||||||
|
height: 40,
|
||||||
|
width: 40,
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}>
|
||||||
|
<IconArrowDown size={25} color={'white'} />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{error && (
|
{error && (
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ReplyedMessage } from "../ReplyedMessage/ReplyedMessage";
|
|||||||
import { IconX } from "@tabler/icons-react";
|
import { IconX } from "@tabler/icons-react";
|
||||||
import { useSetting } from "@/app/providers/SettingsProvider/useSetting";
|
import { useSetting } from "@/app/providers/SettingsProvider/useSetting";
|
||||||
import { modals } from "@mantine/modals";
|
import { modals } from "@mantine/modals";
|
||||||
|
import { MessageReply } from "@/app/providers/DialogProvider/useReplyMessages";
|
||||||
|
|
||||||
export function MessageReplyMessages(props: AttachmentProps) {
|
export function MessageReplyMessages(props: AttachmentProps) {
|
||||||
const colors = useRosettaColors();
|
const colors = useRosettaColors();
|
||||||
@@ -14,7 +15,7 @@ export function MessageReplyMessages(props: AttachmentProps) {
|
|||||||
('bgInReplyMessages', '');
|
('bgInReplyMessages', '');
|
||||||
const reply = JSON.parse(props.attachment.blob);
|
const reply = JSON.parse(props.attachment.blob);
|
||||||
|
|
||||||
//console.info("Mreply", reply);
|
console.info("Mreply", reply);
|
||||||
|
|
||||||
const closeAlert = () => {
|
const closeAlert = () => {
|
||||||
modals.openConfirmModal({
|
modals.openConfirmModal({
|
||||||
@@ -40,8 +41,8 @@ export function MessageReplyMessages(props: AttachmentProps) {
|
|||||||
{reply.length <= 0 &&
|
{reply.length <= 0 &&
|
||||||
<Skeleton h={50} w={'100%'}></Skeleton>
|
<Skeleton h={50} w={'100%'}></Skeleton>
|
||||||
}
|
}
|
||||||
{reply.map((msg, index) => (
|
{reply.map((msg : MessageReply, index) => (
|
||||||
<ReplyedMessage parent={props.parent} chacha_key_plain={props.chacha_key_plain} key={index} messageReply={msg}></ReplyedMessage>
|
<ReplyedMessage parent={props.parent} chacha_key_plain={msg.chacha_key_plain} key={index} messageReply={msg}></ReplyedMessage>
|
||||||
))}
|
))}
|
||||||
{showAlertInReplyMessages && <Alert style={{
|
{showAlertInReplyMessages && <Alert style={{
|
||||||
borderTopLeftRadius: 0,
|
borderTopLeftRadius: 0,
|
||||||
|
|||||||
@@ -103,7 +103,11 @@ export function Message(props: MessageProps) {
|
|||||||
publicKey: user.publicKey,
|
publicKey: user.publicKey,
|
||||||
message: props.message,
|
message: props.message,
|
||||||
attachments: props.attachments.filter(a => a.type != AttachmentType.MESSAGES),
|
attachments: props.attachments.filter(a => a.type != AttachmentType.MESSAGES),
|
||||||
message_id: props.message_id
|
message_id: props.message_id,
|
||||||
|
/**
|
||||||
|
* Кодируем в hex чтобы было удобнее передавать по сети
|
||||||
|
*/
|
||||||
|
chacha_key_plain: props.chacha_key_plain
|
||||||
};
|
};
|
||||||
|
|
||||||
const avatars = useAvatars(user.publicKey);
|
const avatars = useAvatars(user.publicKey);
|
||||||
@@ -125,6 +129,9 @@ export function Message(props: MessageProps) {
|
|||||||
if (props.replyed) {
|
if (props.replyed) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if(props.chacha_key_plain == ""){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (messageReply.attachments.find((v) => ATTACHMENTS_NOT_ALLOWED_TO_REPLY.includes(v.type))) {
|
if (messageReply.attachments.find((v) => ATTACHMENTS_NOT_ALLOWED_TO_REPLY.includes(v.type))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,5 +57,7 @@ export const ALLOWED_DOMAINS_ZONES = [
|
|||||||
'fm',
|
'fm',
|
||||||
'tv',
|
'tv',
|
||||||
'im',
|
'im',
|
||||||
'sc'
|
'sc',
|
||||||
|
'su',
|
||||||
|
'by'
|
||||||
];
|
];
|
||||||
@@ -27,11 +27,10 @@ export enum DownloadStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useAttachment(attachment: Attachment, parentMessage: MessageProps) {
|
export function useAttachment(attachment: Attachment, parentMessage: MessageProps) {
|
||||||
const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
||||||
const uploadedPercentage = useUploadStatus(attachment.id);
|
const uploadedPercentage = useUploadStatus(attachment.id);
|
||||||
const downloadPercentage = useDownloadStatus(attachment.id);
|
const downloadPercentage = useDownloadStatus(attachment.id);
|
||||||
const [downloadStatus, setDownloadStatus] = useMemory("attachment-downloaded-status-" + attachment.id, DownloadStatus.PENDING, true);
|
const [downloadStatus, setDownloadStatus] = useMemory("attachment-downloaded-status-" + attachment.id, DownloadStatus.PENDING, true);
|
||||||
const [downloadTag, setDownloadTag] = useState("");
|
const [downloadTag, setDownloadTag] = useState(attachment.transport.transport_tag || "");
|
||||||
const {readFile, writeFile, fileExists, size} = useFileStorage();
|
const {readFile, writeFile, fileExists, size} = useFileStorage();
|
||||||
const { downloadFile } = useTransport();
|
const { downloadFile } = useTransport();
|
||||||
const publicKey = usePublicKey();
|
const publicKey = usePublicKey();
|
||||||
@@ -50,36 +49,26 @@ export function useAttachment(attachment: Attachment, parentMessage: MessageProp
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getPreview = () => {
|
const getPreview = () => {
|
||||||
if(attachment.preview.split("::")[0].match(uuidRegex)){
|
|
||||||
/**
|
|
||||||
* Это тег загрузки
|
|
||||||
*/
|
|
||||||
return attachment.preview.split("::").splice(1).join("::");
|
|
||||||
}
|
|
||||||
return attachment.preview;
|
return attachment.preview;
|
||||||
}
|
}
|
||||||
|
|
||||||
const calcDownloadStatus = async () => {
|
const calcDownloadStatus = async () => {
|
||||||
if(attachment.preview.split("::")[0].match(uuidRegex)){
|
console.info("ds", attachment);
|
||||||
/**
|
if (downloadStatus == DownloadStatus.DOWNLOADED) {
|
||||||
* Это тег загрузки
|
|
||||||
*/
|
|
||||||
setDownloadTag(attachment.preview.split("::")[0]);
|
|
||||||
}
|
|
||||||
if(!attachment.preview.split("::")[0].match(uuidRegex)){
|
|
||||||
/**
|
|
||||||
* Там не тег загрузки, значит это наш файл
|
|
||||||
*/
|
|
||||||
setDownloadStatus(DownloadStatus.DOWNLOADED);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (downloadStatus == DownloadStatus.DOWNLOADED) {
|
if(attachment.transport.transport_tag == ""){
|
||||||
|
/**
|
||||||
|
* Транспортного тега нет только у сообщений отправленных нами, значит он точно наш
|
||||||
|
*/
|
||||||
|
setDownloadStatus(DownloadStatus.DOWNLOADED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(attachment.type == AttachmentType.FILE){
|
if(attachment.type == AttachmentType.FILE){
|
||||||
/**
|
/**
|
||||||
* Если это файл, то он хранится не в папке медиа,
|
* Если это файл, то он хранится не в папке медиа,
|
||||||
* а в загрузках
|
* а в загрузках, статус скачивания определяем не только по названию файла,
|
||||||
|
* но и по его размеру (если размеры и название совпало, то считаем файл скаченным)
|
||||||
*/
|
*/
|
||||||
const preview = getPreview();
|
const preview = getPreview();
|
||||||
const filesize = parseInt(preview.split("::")[0]);
|
const filesize = parseInt(preview.split("::")[0]);
|
||||||
@@ -89,6 +78,9 @@ export function useAttachment(attachment: Attachment, parentMessage: MessageProp
|
|||||||
const exists = await fileExists(pathInDownloads, false);
|
const exists = await fileExists(pathInDownloads, false);
|
||||||
const existsLength = await size(pathInDownloads, false);
|
const existsLength = await size(pathInDownloads, false);
|
||||||
if(exists && existsLength == filesize){
|
if(exists && existsLength == filesize){
|
||||||
|
/**
|
||||||
|
* Если название файла и его размер совпадают (и он существует), то считаем его скаченным
|
||||||
|
*/
|
||||||
setDownloadStatus(DownloadStatus.DOWNLOADED);
|
setDownloadStatus(DownloadStatus.DOWNLOADED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -143,7 +135,7 @@ export function useAttachment(attachment: Attachment, parentMessage: MessageProp
|
|||||||
let downloadedBlob = '';
|
let downloadedBlob = '';
|
||||||
try {
|
try {
|
||||||
downloadedBlob = await downloadFile(attachment.id,
|
downloadedBlob = await downloadFile(attachment.id,
|
||||||
downloadTag);
|
downloadTag, attachment.transport.transport_server);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.info(e);
|
console.info(e);
|
||||||
info("Error downloading attachment: " + attachment.id);
|
info("Error downloading attachment: " + attachment.id);
|
||||||
@@ -151,6 +143,7 @@ export function useAttachment(attachment: Attachment, parentMessage: MessageProp
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setDownloadStatus(DownloadStatus.DECRYPTING);
|
setDownloadStatus(DownloadStatus.DECRYPTING);
|
||||||
|
console.info("decoding with key " + parentMessage.chacha_key_plain);
|
||||||
//console.info("Decrypted attachment ", Buffer.from(keyPlain, 'binary').toString('hex'));
|
//console.info("Decrypted attachment ", Buffer.from(keyPlain, 'binary').toString('hex'));
|
||||||
const decrypted = await decodeWithPassword(parentMessage.chacha_key_plain, downloadedBlob);
|
const decrypted = await decodeWithPassword(parentMessage.chacha_key_plain, downloadedBlob);
|
||||||
setDownloadTag("");
|
setDownloadTag("");
|
||||||
|
|||||||
@@ -9,16 +9,18 @@ import { useDialogsList } from "../DialogListProvider/useDialogsList";
|
|||||||
import { useDatabase } from "../DatabaseProvider/useDatabase";
|
import { useDatabase } from "../DatabaseProvider/useDatabase";
|
||||||
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
|
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
|
||||||
import { useDialogsCache } from "../DialogProvider/useDialogsCache";
|
import { useDialogsCache } from "../DialogProvider/useDialogsCache";
|
||||||
import { DialogContext } from "../DialogProvider/DialogProvider";
|
import { AttachmentMeta, DialogContext } from "../DialogProvider/DialogProvider";
|
||||||
|
import { useTransportServer } from "../TransportProvider/useTransportServer";
|
||||||
|
|
||||||
export function usePrepareAttachment() {
|
export function usePrepareAttachment() {
|
||||||
const intervalsRef = useRef<NodeJS.Timeout>(null);
|
const intervalsRef = useRef<NodeJS.Timeout>(null);
|
||||||
const {uploadFile} = useTransport();
|
const {uploadFile} = useTransport();
|
||||||
const {updateDialog} = useDialogsList();
|
const {updateDialog} = useDialogsList();
|
||||||
const {runQuery} = useDatabase();
|
const {runQuery, getQuery} = useDatabase();
|
||||||
const {info} = useConsoleLogger('usePrepareAttachment');
|
const {info} = useConsoleLogger('usePrepareAttachment');
|
||||||
const {getDialogCache} = useDialogsCache();
|
const {getDialogCache} = useDialogsCache();
|
||||||
const context = useContext(DialogContext);
|
const context = useContext(DialogContext);
|
||||||
|
const transportServer = useTransportServer();
|
||||||
|
|
||||||
const updateTimestampInDialogCache = (dialog : string, message_id: string) => {
|
const updateTimestampInDialogCache = (dialog : string, message_id: string) => {
|
||||||
const dialogCache = getDialogCache(dialog);
|
const dialogCache = getDialogCache(dialog);
|
||||||
@@ -33,6 +35,79 @@ export function usePrepareAttachment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновляет транспортный сервер в кэше, чтобы поддерживать его в актуальном состоянии после загрузки
|
||||||
|
*/
|
||||||
|
const updateAttachmentTransportInCache = (dialog: string, message_id : string, attachment: Attachment) => {
|
||||||
|
const dialogCache = getDialogCache(dialog);
|
||||||
|
if(dialogCache == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(let i = 0; i < dialogCache.length; i++){
|
||||||
|
if(dialogCache[i].message_id == message_id){
|
||||||
|
for(let j = 0; j < dialogCache[i].attachments.length; j++){
|
||||||
|
if(dialogCache[i].attachments[j].id == attachment.id){
|
||||||
|
dialogCache[i].attachments[j].transport = attachment.transport;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновляет транспорт в базе после загрузки вложения (нам нужно сохранить транспорт)
|
||||||
|
*/
|
||||||
|
const updateAttachmentTransportInDatabase = async (message_id : string, attachment: Attachment) => {
|
||||||
|
let message = await getQuery(`SELECT attachments FROM messages WHERE message_id = ?`, [message_id]);
|
||||||
|
console.info(message)
|
||||||
|
if(!message){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(message.attachments == '[]'){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let meta : AttachmentMeta[] = JSON.parse(message.attachments);
|
||||||
|
for(let i = 0; i < meta.length; i++){
|
||||||
|
if(meta[i].id == attachment.id){
|
||||||
|
meta[i].transport = attachment.transport;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await runQuery(`UPDATE messages SET attachments = ? WHERE message_id = ?`, [JSON.stringify(meta), message_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновляет вложение в стейте сообщений
|
||||||
|
*/
|
||||||
|
const updateAttachmentTransportInContext = (message_id: string, attachment : Attachment) => {
|
||||||
|
if(context == null || !context){
|
||||||
|
/**
|
||||||
|
* Если этот диалог сейчас не открыт
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.setMessages((prev) => {
|
||||||
|
return prev.map((value) => {
|
||||||
|
if(value.message_id != message_id){
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
for(let i = 0; i < value.attachments.length; i++){
|
||||||
|
if(value.attachments[i].id != attachment.id){
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
value.attachments[i].transport = attachment.transport;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateTransportAfterUploading = async (dialog: string, message_id : string, attachment: Attachment) => {
|
||||||
|
updateAttachmentTransportInCache(dialog, message_id, attachment);
|
||||||
|
updateAttachmentTransportInDatabase(message_id, attachment);
|
||||||
|
updateAttachmentTransportInContext(message_id, attachment);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Обновляет временную метку в сообщении, пока вложения отправляются,
|
* Обновляет временную метку в сообщении, пока вложения отправляются,
|
||||||
* потому что если этого не делать, то сообщение может быть помечено как
|
* потому что если этого не делать, то сообщение может быть помечено как
|
||||||
@@ -74,18 +149,6 @@ export function usePrepareAttachment() {
|
|||||||
}, (MESSAGE_MAX_TIME_TO_DELEVERED_S / 2) * 1000);
|
}, (MESSAGE_MAX_TIME_TO_DELEVERED_S / 2) * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Удаляет старый тег если вложения были подготовлены заново
|
|
||||||
* например при пересылке сообщений
|
|
||||||
*/
|
|
||||||
const removeOldTagIfAttachemtnsRePreapred = (preview : string) => {
|
|
||||||
if(preview.indexOf("::") == -1){
|
|
||||||
return preview;
|
|
||||||
}
|
|
||||||
let parts = preview.split("::");
|
|
||||||
return parts.slice(1).join("::");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Подготавливает вложения для отправки. Подготовка
|
* Подготавливает вложения для отправки. Подготовка
|
||||||
* состоит в загрузке файлов на транспортный сервер, мы не делаем
|
* состоит в загрузке файлов на транспортный сервер, мы не делаем
|
||||||
@@ -93,7 +156,7 @@ export function usePrepareAttachment() {
|
|||||||
* а так же из-за надежности доставки файлов через HTTP
|
* а так же из-за надежности доставки файлов через HTTP
|
||||||
* @param attachments Attachments to prepare for sending
|
* @param attachments Attachments to prepare for sending
|
||||||
*/
|
*/
|
||||||
const prepareAttachmentsToSend = async (message_id: string, dialog: string, password: string, attachments : Attachment[], rePrepared : boolean = false) : Promise<Attachment[]> => {
|
const prepareAttachmentsToSend = async (message_id: string, dialog: string, password: string, attachments : Attachment[]) : Promise<Attachment[]> => {
|
||||||
if(attachments.length <= 0){
|
if(attachments.length <= 0){
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -109,9 +172,11 @@ export function usePrepareAttachment() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(attachment.type == AttachmentType.MESSAGES){
|
if(attachment.type == AttachmentType.MESSAGES){
|
||||||
let reply : MessageReply[] = JSON.parse(attachment.blob)
|
let reply : MessageReply[] = JSON.parse(attachment.blob);
|
||||||
for(let j = 0; j < reply.length; j++){
|
for(let j = 0; j < reply.length; j++){
|
||||||
reply[j].attachments = await prepareAttachmentsToSend(message_id, dialog, password, reply[j].attachments, true);
|
for(let k = 0; k < reply[j].attachments.length; k++){
|
||||||
|
reply[j].attachments[k].blob = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
prepared.push({
|
prepared.push({
|
||||||
...attachment,
|
...attachment,
|
||||||
@@ -137,11 +202,17 @@ export function usePrepareAttachment() {
|
|||||||
if(intervalsRef.current != null){
|
if(intervalsRef.current != null){
|
||||||
clearInterval(intervalsRef.current);
|
clearInterval(intervalsRef.current);
|
||||||
}
|
}
|
||||||
prepared.push({
|
const preparedAttachment : Attachment = {
|
||||||
...attachment,
|
...attachment,
|
||||||
preview: tag + "::" + (rePrepared ? removeOldTagIfAttachemtnsRePreapred(attachment.preview) : attachment.preview),
|
transport: {
|
||||||
|
transport_server: transportServer || "",
|
||||||
|
transport_tag: tag
|
||||||
|
},
|
||||||
|
preview: attachment.preview,
|
||||||
blob: ""
|
blob: ""
|
||||||
});
|
};
|
||||||
|
await updateTransportAfterUploading(dialog, message_id, preparedAttachment);
|
||||||
|
prepared.push(preparedAttachment);
|
||||||
}
|
}
|
||||||
return prepared;
|
return prepared;
|
||||||
}catch(e){
|
}catch(e){
|
||||||
|
|||||||
@@ -472,6 +472,10 @@ export function CallProvider(props : CallProviderProps) {
|
|||||||
id: generateRandomKey(16),
|
id: generateRandomKey(16),
|
||||||
preview: duration.toString(),
|
preview: duration.toString(),
|
||||||
type: AttachmentType.CALL,
|
type: AttachmentType.CALL,
|
||||||
|
transport: {
|
||||||
|
transport_server: "",
|
||||||
|
transport_tag: ""
|
||||||
|
},
|
||||||
blob: ""
|
blob: ""
|
||||||
}], true);
|
}], true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { chacha20Decrypt, decodeWithPassword, decrypt, generateMd5 } from '@/app/workers/crypto/crypto';
|
import { chacha20Decrypt, decodeWithPassword, decrypt, generateMd5 } from '@/app/workers/crypto/crypto';
|
||||||
import { useDatabase } from '@/app/providers/DatabaseProvider/useDatabase';
|
import { useDatabase } from '@/app/providers/DatabaseProvider/useDatabase';
|
||||||
import { createContext, useEffect, useRef, useState } from 'react';
|
import { createContext, useEffect, useRef, useState } from 'react';
|
||||||
import { Attachment, AttachmentType, PacketMessage } from '@/app/providers/ProtocolProvider/protocol/packets/packet.message';
|
import { Attachment, AttachmentTransport, AttachmentType, PacketMessage } from '@/app/providers/ProtocolProvider/protocol/packets/packet.message';
|
||||||
import { usePrivatePlain } from '../AccountProvider/usePrivatePlain';
|
import { usePrivatePlain } from '../AccountProvider/usePrivatePlain';
|
||||||
import { usePublicKey } from '../AccountProvider/usePublicKey';
|
import { usePublicKey } from '../AccountProvider/usePublicKey';
|
||||||
import { PacketRead } from '@/app/providers/ProtocolProvider/protocol/packets/packet.read';
|
import { PacketRead } from '@/app/providers/ProtocolProvider/protocol/packets/packet.read';
|
||||||
@@ -46,6 +46,7 @@ export interface AttachmentMeta {
|
|||||||
id: string;
|
id: string;
|
||||||
type: AttachmentType;
|
type: AttachmentType;
|
||||||
preview: string;
|
preview: string;
|
||||||
|
transport: AttachmentTransport;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Message {
|
export interface Message {
|
||||||
@@ -214,13 +215,23 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
readUpdated = true;
|
readUpdated = true;
|
||||||
}
|
}
|
||||||
let decryptKey = '';
|
let decryptKey = '';
|
||||||
|
if(message.from_me && message.chacha_key != "" && !message.chacha_key.startsWith("sync:")){
|
||||||
|
/**
|
||||||
|
* Если это сообщение от нас, то проверяем, есть ли внутри chacha_key
|
||||||
|
*/
|
||||||
|
try{
|
||||||
|
decryptKey = Buffer.from(await decodeWithPassword(privatePlain, message.chacha_key), 'binary').toString('hex');
|
||||||
|
}catch(e) {
|
||||||
|
decryptKey = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
if(message.from_me && message.chacha_key != "" && message.chacha_key.startsWith("sync:")){
|
if(message.from_me && message.chacha_key != "" && message.chacha_key.startsWith("sync:")){
|
||||||
/**
|
/**
|
||||||
* Если это сообщение от нас, то проверяем, есть ли внутри chacha_key, если есть, значит это
|
* Если это сообщение от нас, то проверяем, есть ли внутри chacha_key, если есть, значит это
|
||||||
* сообщение пришло нам в результате синхронизации и его нужно расшифровать, если chacha_key нет,
|
* сообщение пришло нам в результате синхронизации и его нужно расшифровать, если chacha_key нет,
|
||||||
* значит сообщение отправлено с нашего устройства, и зашифровано на стороне отправки (plain_message)
|
* значит сообщение отправлено с нашего устройства, и зашифровано на стороне отправки (plain_message)
|
||||||
*/
|
*/
|
||||||
decryptKey = Buffer.from(await decodeWithPassword(privatePlain, message.chacha_key.replace("sync:", "")), 'binary').toString('utf-8');
|
decryptKey = Buffer.from(await decodeWithPassword(privatePlain, message.chacha_key.replace("sync:", "")), 'binary').toString('hex');
|
||||||
}
|
}
|
||||||
if(hasGroup(props.dialog)){
|
if(hasGroup(props.dialog)){
|
||||||
/**
|
/**
|
||||||
@@ -233,7 +244,7 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
* Если сообщение не от меня и не групповое,
|
* Если сообщение не от меня и не групповое,
|
||||||
* расшифровываем ключ чачи своим приватным ключом
|
* расшифровываем ключ чачи своим приватным ключом
|
||||||
*/
|
*/
|
||||||
decryptKey = Buffer.from(await decrypt(message.chacha_key, privatePlain), "binary").toString('utf-8');
|
decryptKey = Buffer.from(await decrypt(message.chacha_key, privatePlain), "binary").toString('hex');
|
||||||
}
|
}
|
||||||
finalMessages.push({
|
finalMessages.push({
|
||||||
from_public_key: message.from_public_key,
|
from_public_key: message.from_public_key,
|
||||||
@@ -469,10 +480,8 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
for(let i = 0; i < packet.getAttachments().length; i++) {
|
for(let i = 0; i < packet.getAttachments().length; i++) {
|
||||||
const attachment = packet.getAttachments()[i];
|
const attachment = packet.getAttachments()[i];
|
||||||
attachments.push({
|
attachments.push({
|
||||||
id: attachment.id,
|
...attachment,
|
||||||
preview: attachment.preview,
|
blob: attachment.type == AttachmentType.MESSAGES ? await decodeWithPassword(chachaDecryptedKey.toString('hex'), attachment.blob) : ""
|
||||||
type: attachment.type,
|
|
||||||
blob: attachment.type == AttachmentType.MESSAGES ? await decodeWithPassword(chachaDecryptedKey.toString('utf-8'), attachment.blob) : ""
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,7 +491,7 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
content: content,
|
content: content,
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
readed: 0, //сообщение прочитано
|
readed: 0, //сообщение прочитано
|
||||||
chacha_key: chachaDecryptedKey.toString('utf-8'),
|
chacha_key: chachaDecryptedKey.toString('hex'),
|
||||||
from_me: 1, //сообщение от нас
|
from_me: 1, //сообщение от нас
|
||||||
plain_message: (decryptedContent as string),
|
plain_message: (decryptedContent as string),
|
||||||
delivered: DeliveredMessageState.DELIVERED,
|
delivered: DeliveredMessageState.DELIVERED,
|
||||||
@@ -549,9 +558,7 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
for(let i = 0; i < packet.getAttachments().length; i++) {
|
for(let i = 0; i < packet.getAttachments().length; i++) {
|
||||||
const attachment = packet.getAttachments()[i];
|
const attachment = packet.getAttachments()[i];
|
||||||
attachments.push({
|
attachments.push({
|
||||||
id: attachment.id,
|
...attachment,
|
||||||
preview: attachment.preview,
|
|
||||||
type: attachment.type,
|
|
||||||
blob: attachment.type == AttachmentType.MESSAGES ? await decodeWithPassword(groupKey, attachment.blob) : ""
|
blob: attachment.type == AttachmentType.MESSAGES ? await decodeWithPassword(groupKey, attachment.blob) : ""
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -627,20 +634,18 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
for(let i = 0; i < packet.getAttachments().length; i++) {
|
for(let i = 0; i < packet.getAttachments().length; i++) {
|
||||||
const attachment = packet.getAttachments()[i];
|
const attachment = packet.getAttachments()[i];
|
||||||
attachments.push({
|
attachments.push({
|
||||||
id: attachment.id,
|
...attachment,
|
||||||
preview: attachment.preview,
|
blob: attachment.type == AttachmentType.MESSAGES ? await decodeWithPassword(chachaDecryptedKey.toString('hex'), attachment.blob) : ""
|
||||||
type: attachment.type,
|
|
||||||
blob: attachment.type == AttachmentType.MESSAGES ? await decodeWithPassword(chachaDecryptedKey.toString('utf-8'), attachment.blob) : ""
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
console.info(attachments);
|
||||||
const newMessage : Message = {
|
const newMessage : Message = {
|
||||||
from_public_key: fromPublicKey,
|
from_public_key: fromPublicKey,
|
||||||
to_public_key: toPublicKey,
|
to_public_key: toPublicKey,
|
||||||
content: content,
|
content: content,
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
readed: idle ? 0 : 1,
|
readed: idle ? 0 : 1,
|
||||||
chacha_key: chachaDecryptedKey.toString('utf-8'),
|
chacha_key: chachaDecryptedKey.toString('hex'),
|
||||||
from_me: fromPublicKey == publicKey ? 1 : 0,
|
from_me: fromPublicKey == publicKey ? 1 : 0,
|
||||||
plain_message: (decryptedContent as string),
|
plain_message: (decryptedContent as string),
|
||||||
delivered: DeliveredMessageState.DELIVERED,
|
delivered: DeliveredMessageState.DELIVERED,
|
||||||
@@ -707,9 +712,7 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
for(let i = 0; i < packet.getAttachments().length; i++) {
|
for(let i = 0; i < packet.getAttachments().length; i++) {
|
||||||
const attachment = packet.getAttachments()[i];
|
const attachment = packet.getAttachments()[i];
|
||||||
attachments.push({
|
attachments.push({
|
||||||
id: attachment.id,
|
...attachment,
|
||||||
preview: attachment.preview,
|
|
||||||
type: attachment.type,
|
|
||||||
blob: attachment.type == AttachmentType.MESSAGES ? await decodeWithPassword(groupKey, attachment.blob) : ""
|
blob: attachment.type == AttachmentType.MESSAGES ? await decodeWithPassword(groupKey, attachment.blob) : ""
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -794,7 +797,7 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
* Если сообщение не от меня и не групповое,
|
* Если сообщение не от меня и не групповое,
|
||||||
* расшифровываем ключ чачи своим приватным ключом
|
* расшифровываем ключ чачи своим приватным ключом
|
||||||
*/
|
*/
|
||||||
decryptKey = Buffer.from(await decrypt(message.chacha_key, privatePlain), "binary").toString('utf-8');
|
decryptKey = Buffer.from(await decrypt(message.chacha_key, privatePlain), "binary").toString('hex');
|
||||||
}
|
}
|
||||||
finalMessages.push({
|
finalMessages.push({
|
||||||
from_public_key: message.from_public_key,
|
from_public_key: message.from_public_key,
|
||||||
@@ -879,7 +882,7 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
* Если сообщение не от меня и не групповое,
|
* Если сообщение не от меня и не групповое,
|
||||||
* расшифровываем ключ чачи своим приватным ключом
|
* расшифровываем ключ чачи своим приватным ключом
|
||||||
*/
|
*/
|
||||||
decryptKey = Buffer.from(await decrypt(message.chacha_key, privatePlain), "binary").toString('utf-8');
|
decryptKey = Buffer.from(await decrypt(message.chacha_key, privatePlain), "binary").toString('hex');
|
||||||
}
|
}
|
||||||
finalMessages.push({
|
finalMessages.push({
|
||||||
from_public_key: message.from_public_key,
|
from_public_key: message.from_public_key,
|
||||||
@@ -964,7 +967,8 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
id: meta.id,
|
id: meta.id,
|
||||||
blob: blob,
|
blob: blob,
|
||||||
type: meta.type,
|
type: meta.type,
|
||||||
preview: meta.preview
|
preview: meta.preview,
|
||||||
|
transport: meta.transport
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return attachments;
|
return attachments;
|
||||||
|
|||||||
@@ -106,7 +106,8 @@ export function useDeattachedSender() {
|
|||||||
attachmentsMeta.push({
|
attachmentsMeta.push({
|
||||||
id: attachment.id,
|
id: attachment.id,
|
||||||
type: attachment.type,
|
type: attachment.type,
|
||||||
preview: attachment.preview
|
preview: attachment.preview,
|
||||||
|
transport: attachment.transport
|
||||||
});
|
});
|
||||||
if(attachment.type == AttachmentType.FILE){
|
if(attachment.type == AttachmentType.FILE){
|
||||||
/**
|
/**
|
||||||
@@ -132,7 +133,7 @@ export function useDeattachedSender() {
|
|||||||
|| publicKey == dialog) {
|
|| publicKey == dialog) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let preparedToNetworkSendAttachements : Attachment[] = await prepareAttachmentsToSend(messageId, dialog, key.toString('utf-8'), attachemnts);
|
let preparedToNetworkSendAttachements : Attachment[] = await prepareAttachmentsToSend(messageId, dialog, key.toString('hex'), attachemnts);
|
||||||
if(attachemnts.length <= 0 && message.trim() == ""){
|
if(attachemnts.length <= 0 && message.trim() == ""){
|
||||||
runQuery("UPDATE messages SET delivered = ? WHERE message_id = ?", [DeliveredMessageState.ERROR, messageId]);
|
runQuery("UPDATE messages SET delivered = ? WHERE message_id = ?", [DeliveredMessageState.ERROR, messageId]);
|
||||||
updateDialog(dialog);
|
updateDialog(dialog);
|
||||||
|
|||||||
@@ -96,14 +96,13 @@ export function useDialog() : {
|
|||||||
* же сообщений (смотреть problem_sync.md)
|
* же сообщений (смотреть problem_sync.md)
|
||||||
*/
|
*/
|
||||||
const aesChachaKey = await encodeWithPassword(privatePlain, key.toString('binary'));
|
const aesChachaKey = await encodeWithPassword(privatePlain, key.toString('binary'));
|
||||||
|
|
||||||
setMessages((prev : Message[]) => ([...prev, {
|
setMessages((prev : Message[]) => ([...prev, {
|
||||||
from_public_key: publicKey,
|
from_public_key: publicKey,
|
||||||
to_public_key: dialog,
|
to_public_key: dialog,
|
||||||
content: content,
|
content: content,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
readed: publicKey == dialog ? 1 : 0,
|
readed: publicKey == dialog ? 1 : 0,
|
||||||
chacha_key: "",
|
chacha_key: key.toString('hex'),
|
||||||
from_me: 1,
|
from_me: 1,
|
||||||
plain_message: message,
|
plain_message: message,
|
||||||
delivered: publicKey == dialog ? DeliveredMessageState.DELIVERED : DeliveredMessageState.WAITING,
|
delivered: publicKey == dialog ? DeliveredMessageState.DELIVERED : DeliveredMessageState.WAITING,
|
||||||
@@ -118,7 +117,8 @@ export function useDialog() : {
|
|||||||
attachmentsMeta.push({
|
attachmentsMeta.push({
|
||||||
id: attachment.id,
|
id: attachment.id,
|
||||||
type: attachment.type,
|
type: attachment.type,
|
||||||
preview: attachment.preview
|
preview: attachment.preview,
|
||||||
|
transport: attachment.transport
|
||||||
});
|
});
|
||||||
if(attachment.type == AttachmentType.FILE){
|
if(attachment.type == AttachmentType.FILE){
|
||||||
/**
|
/**
|
||||||
@@ -135,7 +135,7 @@ export function useDialog() : {
|
|||||||
await runQuery(`
|
await runQuery(`
|
||||||
INSERT INTO messages
|
INSERT INTO messages
|
||||||
(from_public_key, to_public_key, content, timestamp, read, chacha_key, from_me, plain_message, account, message_id, delivered, attachments) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
(from_public_key, to_public_key, content, timestamp, read, chacha_key, from_me, plain_message, account, message_id, delivered, attachments) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`, [publicKey, dialog, content, Date.now(), publicKey == dialog ? 1 : 0, encryptedKey, 1, plainMessage, publicKey, messageId, publicKey == dialog ? DeliveredMessageState.DELIVERED : (
|
`, [publicKey, dialog, content, Date.now(), publicKey == dialog ? 1 : 0, aesChachaKey, 1, plainMessage, publicKey, messageId, publicKey == dialog ? DeliveredMessageState.DELIVERED : (
|
||||||
protocolState != ProtocolState.CONNECTED ? DeliveredMessageState.ERROR : DeliveredMessageState.WAITING
|
protocolState != ProtocolState.CONNECTED ? DeliveredMessageState.ERROR : DeliveredMessageState.WAITING
|
||||||
), JSON.stringify(attachmentsMeta)]);
|
), JSON.stringify(attachmentsMeta)]);
|
||||||
updateDialog(dialog);
|
updateDialog(dialog);
|
||||||
@@ -145,9 +145,8 @@ export function useDialog() : {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//98acbbc68f4b2449daf0a39d1b3eab9a3056da5d45b811bbc903e214c21d39643394980231e1a89c811830d870f3354184319665327ca8bd
|
|
||||||
console.info("Sending key for message ", key.toString('hex'));
|
console.info("Sending key for message ", key.toString('hex'));
|
||||||
let preparedToNetworkSendAttachements : Attachment[] = await prepareAttachmentsToSend(messageId, dialog, key.toString('utf-8'), attachemnts);
|
let preparedToNetworkSendAttachements : Attachment[] = await prepareAttachmentsToSend(messageId, dialog, key.toString('hex'), attachemnts);
|
||||||
if(attachemnts.length <= 0 && message.trim() == ""){
|
if(attachemnts.length <= 0 && message.trim() == ""){
|
||||||
runQuery("UPDATE messages SET delivered = ? WHERE message_id = ?", [DeliveredMessageState.ERROR, messageId]);
|
runQuery("UPDATE messages SET delivered = ? WHERE message_id = ?", [DeliveredMessageState.ERROR, messageId]);
|
||||||
updateDialog(dialog);
|
updateDialog(dialog);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { useDatabase } from "@/app/providers/DatabaseProvider/useDatabase";
|
|||||||
import { usePrivatePlain } from "../AccountProvider/usePrivatePlain";
|
import { usePrivatePlain } from "../AccountProvider/usePrivatePlain";
|
||||||
import { usePublicKey } from "../AccountProvider/usePublicKey";
|
import { usePublicKey } from "../AccountProvider/usePublicKey";
|
||||||
import { chacha20Decrypt, decodeWithPassword, decrypt, encodeWithPassword, generateMd5 } from "@/app/workers/crypto/crypto";
|
import { chacha20Decrypt, decodeWithPassword, decrypt, encodeWithPassword, generateMd5 } from "@/app/workers/crypto/crypto";
|
||||||
import { DeliveredMessageState, Message } from "./DialogProvider";
|
import { AttachmentMeta, DeliveredMessageState, Message } from "./DialogProvider";
|
||||||
import { PacketRead } from "../ProtocolProvider/protocol/packets/packet.read";
|
import { PacketRead } from "../ProtocolProvider/protocol/packets/packet.read";
|
||||||
import { PacketDelivery } from "../ProtocolProvider/protocol/packets/packet.delivery";
|
import { PacketDelivery } from "../ProtocolProvider/protocol/packets/packet.delivery";
|
||||||
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
|
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
|
||||||
@@ -104,7 +104,7 @@ export function useDialogFiber() {
|
|||||||
decryptedContent = '';
|
decryptedContent = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
let attachmentsMeta: any[] = [];
|
let attachmentsMeta: AttachmentMeta[] = [];
|
||||||
let messageAttachments: Attachment[] = [];
|
let messageAttachments: Attachment[] = [];
|
||||||
for (let i = 0; i < packet.getAttachments().length; i++) {
|
for (let i = 0; i < packet.getAttachments().length; i++) {
|
||||||
const attachment = packet.getAttachments()[i];
|
const attachment = packet.getAttachments()[i];
|
||||||
@@ -129,7 +129,8 @@ export function useDialogFiber() {
|
|||||||
attachmentsMeta.push({
|
attachmentsMeta.push({
|
||||||
id: attachment.id,
|
id: attachment.id,
|
||||||
type: attachment.type,
|
type: attachment.type,
|
||||||
preview: attachment.preview
|
preview: attachment.preview,
|
||||||
|
transport: attachment.transport
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,7 +262,7 @@ export function useDialogFiber() {
|
|||||||
const nonce = chachaDecryptedKey.slice(32);
|
const nonce = chachaDecryptedKey.slice(32);
|
||||||
const decryptedContent = await chacha20Decrypt(content, nonce.toString('hex'), key.toString('hex'));
|
const decryptedContent = await chacha20Decrypt(content, nonce.toString('hex'), key.toString('hex'));
|
||||||
|
|
||||||
let attachmentsMeta: any[] = [];
|
let attachmentsMeta: AttachmentMeta[] = [];
|
||||||
let messageAttachments: Attachment[] = [];
|
let messageAttachments: Attachment[] = [];
|
||||||
for (let i = 0; i < packet.getAttachments().length; i++) {
|
for (let i = 0; i < packet.getAttachments().length; i++) {
|
||||||
const attachment = packet.getAttachments()[i];
|
const attachment = packet.getAttachments()[i];
|
||||||
@@ -277,7 +278,7 @@ export function useDialogFiber() {
|
|||||||
* Этот тип вложения приходит сразу в blob и не нуждается
|
* Этот тип вложения приходит сразу в blob и не нуждается
|
||||||
* в последующем скачивании
|
* в последующем скачивании
|
||||||
*/
|
*/
|
||||||
const decryptedBlob = await decodeWithPassword(chachaDecryptedKey.toString('utf-8'), attachment.blob);
|
const decryptedBlob = await decodeWithPassword(chachaDecryptedKey.toString('hex'), attachment.blob);
|
||||||
writeFile(`m/${await generateMd5(attachment.id + publicKey)}`,
|
writeFile(`m/${await generateMd5(attachment.id + publicKey)}`,
|
||||||
Buffer.from(await encodeWithPassword(privatePlain, decryptedBlob)).toString('binary'));
|
Buffer.from(await encodeWithPassword(privatePlain, decryptedBlob)).toString('binary'));
|
||||||
messageAttachments[nextLength - 1].blob = decryptedBlob;
|
messageAttachments[nextLength - 1].blob = decryptedBlob;
|
||||||
@@ -286,7 +287,8 @@ export function useDialogFiber() {
|
|||||||
attachmentsMeta.push({
|
attachmentsMeta.push({
|
||||||
id: attachment.id,
|
id: attachment.id,
|
||||||
type: attachment.type,
|
type: attachment.type,
|
||||||
preview: attachment.preview
|
preview: attachment.preview,
|
||||||
|
transport: attachment.transport
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +298,7 @@ export function useDialogFiber() {
|
|||||||
content: content,
|
content: content,
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
readed: idle ? 0 : 1,
|
readed: idle ? 0 : 1,
|
||||||
chacha_key: chachaDecryptedKey.toString('utf-8'),
|
chacha_key: chachaDecryptedKey.toString('hex'),
|
||||||
from_me: fromPublicKey == publicKey ? 1 : 0,
|
from_me: fromPublicKey == publicKey ? 1 : 0,
|
||||||
plain_message: (decryptedContent as string),
|
plain_message: (decryptedContent as string),
|
||||||
delivered: DeliveredMessageState.DELIVERED,
|
delivered: DeliveredMessageState.DELIVERED,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export interface MessageReply {
|
|||||||
message: string;
|
message: string;
|
||||||
attachments: Attachment[];
|
attachments: Attachment[];
|
||||||
message_id: string;
|
message_id: string;
|
||||||
|
chacha_key_plain: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useReplyMessages() {
|
export function useReplyMessages() {
|
||||||
@@ -51,9 +52,7 @@ export function useReplyMessages() {
|
|||||||
*/
|
*/
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
replyMessages.messages.push(message);
|
|
||||||
const sortedByTime = replyMessages.messages.sort((a, b) => a.timestamp - b.timestamp);
|
const sortedByTime = replyMessages.messages.sort((a, b) => a.timestamp - b.timestamp);
|
||||||
|
|
||||||
setReplyMessages({
|
setReplyMessages({
|
||||||
publicKey: dialog,
|
publicKey: dialog,
|
||||||
messages: sortedByTime
|
messages: sortedByTime
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { useGroupInviteStatus } from "./useGroupInviteStatus";
|
|||||||
import { Attachment, AttachmentType, PacketMessage } from "../ProtocolProvider/protocol/packets/packet.message";
|
import { Attachment, AttachmentType, PacketMessage } from "../ProtocolProvider/protocol/packets/packet.message";
|
||||||
import { useUpdateSyncTime } from "./useUpdateSyncTime";
|
import { useUpdateSyncTime } from "./useUpdateSyncTime";
|
||||||
import { useFileStorage } from "@/app/hooks/useFileStorage";
|
import { useFileStorage } from "@/app/hooks/useFileStorage";
|
||||||
import { DeliveredMessageState, Message } from "./DialogProvider";
|
import { AttachmentMeta, DeliveredMessageState, Message } from "./DialogProvider";
|
||||||
import { MESSAGE_MAX_LOADED, TIME_TO_INACTIVE_FOR_MESSAGES_UNREAD } from "@/app/constants";
|
import { MESSAGE_MAX_LOADED, TIME_TO_INACTIVE_FOR_MESSAGES_UNREAD } from "@/app/constants";
|
||||||
import { useMemory } from "../MemoryProvider/useMemory";
|
import { useMemory } from "../MemoryProvider/useMemory";
|
||||||
import { useDialogsCache } from "./useDialogsCache";
|
import { useDialogsCache } from "./useDialogsCache";
|
||||||
@@ -165,7 +165,7 @@ export function useSynchronize() {
|
|||||||
const nonce = chachaDecryptedKey.slice(32);
|
const nonce = chachaDecryptedKey.slice(32);
|
||||||
const decryptedContent = await chacha20Decrypt(content, nonce.toString('hex'), key.toString('hex'));
|
const decryptedContent = await chacha20Decrypt(content, nonce.toString('hex'), key.toString('hex'));
|
||||||
await updateSyncTime(timestamp);
|
await updateSyncTime(timestamp);
|
||||||
let attachmentsMeta: any[] = [];
|
let attachmentsMeta: AttachmentMeta[] = [];
|
||||||
let messageAttachments: Attachment[] = [];
|
let messageAttachments: Attachment[] = [];
|
||||||
for (let i = 0; i < packet.getAttachments().length; i++) {
|
for (let i = 0; i < packet.getAttachments().length; i++) {
|
||||||
const attachment = packet.getAttachments()[i];
|
const attachment = packet.getAttachments()[i];
|
||||||
@@ -181,7 +181,7 @@ export function useSynchronize() {
|
|||||||
* Этот тип вложения приходит сразу в blob и не нуждается
|
* Этот тип вложения приходит сразу в blob и не нуждается
|
||||||
* в последующем скачивании
|
* в последующем скачивании
|
||||||
*/
|
*/
|
||||||
const decryptedBlob = await decodeWithPassword(chachaDecryptedKey.toString('utf-8'), attachment.blob);
|
const decryptedBlob = await decodeWithPassword(chachaDecryptedKey.toString('hex'), attachment.blob);
|
||||||
writeFile(`m/${await generateMd5(attachment.id + publicKey)}`,
|
writeFile(`m/${await generateMd5(attachment.id + publicKey)}`,
|
||||||
Buffer.from(await encodeWithPassword(privatePlain, decryptedBlob)).toString('binary'));
|
Buffer.from(await encodeWithPassword(privatePlain, decryptedBlob)).toString('binary'));
|
||||||
messageAttachments[nextLength - 1].blob = decryptedBlob;
|
messageAttachments[nextLength - 1].blob = decryptedBlob;
|
||||||
@@ -190,7 +190,8 @@ export function useSynchronize() {
|
|||||||
attachmentsMeta.push({
|
attachmentsMeta.push({
|
||||||
id: attachment.id,
|
id: attachment.id,
|
||||||
type: attachment.type,
|
type: attachment.type,
|
||||||
preview: attachment.preview
|
preview: attachment.preview,
|
||||||
|
transport: attachment.transport
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +201,7 @@ export function useSynchronize() {
|
|||||||
content: content,
|
content: content,
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
readed: 1, //сообщение прочитано
|
readed: 1, //сообщение прочитано
|
||||||
chacha_key: chachaDecryptedKey.toString('utf-8'),
|
chacha_key: chachaDecryptedKey.toString('hex'),
|
||||||
from_me: 1, //сообщение от нас
|
from_me: 1, //сообщение от нас
|
||||||
plain_message: (decryptedContent as string),
|
plain_message: (decryptedContent as string),
|
||||||
delivered: DeliveredMessageState.DELIVERED,
|
delivered: DeliveredMessageState.DELIVERED,
|
||||||
@@ -347,7 +348,7 @@ export function useSynchronize() {
|
|||||||
decryptedContent = '';
|
decryptedContent = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
let attachmentsMeta: any[] = [];
|
let attachmentsMeta: AttachmentMeta[] = [];
|
||||||
let messageAttachments: Attachment[] = [];
|
let messageAttachments: Attachment[] = [];
|
||||||
for (let i = 0; i < packet.getAttachments().length; i++) {
|
for (let i = 0; i < packet.getAttachments().length; i++) {
|
||||||
const attachment = packet.getAttachments()[i];
|
const attachment = packet.getAttachments()[i];
|
||||||
@@ -372,7 +373,8 @@ export function useSynchronize() {
|
|||||||
attachmentsMeta.push({
|
attachmentsMeta.push({
|
||||||
id: attachment.id,
|
id: attachment.id,
|
||||||
type: attachment.type,
|
type: attachment.type,
|
||||||
preview: attachment.preview
|
preview: attachment.preview,
|
||||||
|
transport: attachment.transport
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,21 @@ export enum AttachmentType {
|
|||||||
CALL = 4
|
CALL = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Информация о транспортировке вложения, нужна для загрузки и скачивания вложений с транспортного сервера
|
||||||
|
*/
|
||||||
|
export interface AttachmentTransport {
|
||||||
|
transport_tag: string;
|
||||||
|
transport_server: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface Attachment {
|
export interface Attachment {
|
||||||
id: string;
|
id: string;
|
||||||
blob: string;
|
blob: string;
|
||||||
type: AttachmentType;
|
type: AttachmentType;
|
||||||
preview: string;
|
preview: string;
|
||||||
|
transport: AttachmentTransport;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PacketMessage extends Packet {
|
export class PacketMessage extends Packet {
|
||||||
@@ -42,7 +52,7 @@ export class PacketMessage extends Packet {
|
|||||||
this.toPublicKey = stream.readString();
|
this.toPublicKey = stream.readString();
|
||||||
this.content = stream.readString();
|
this.content = stream.readString();
|
||||||
this.chachaKey = stream.readString();
|
this.chachaKey = stream.readString();
|
||||||
this.timestamp = stream.readInt64();
|
this.timestamp = Number(stream.readInt64());
|
||||||
this.privateKey = stream.readString();
|
this.privateKey = stream.readString();
|
||||||
this.messageId = stream.readString();
|
this.messageId = stream.readString();
|
||||||
let attachmentsCount = stream.readInt8();
|
let attachmentsCount = stream.readInt8();
|
||||||
@@ -51,7 +61,11 @@ export class PacketMessage extends Packet {
|
|||||||
let preview = stream.readString();
|
let preview = stream.readString();
|
||||||
let blob = stream.readString();
|
let blob = stream.readString();
|
||||||
let type = stream.readInt8() as AttachmentType;
|
let type = stream.readInt8() as AttachmentType;
|
||||||
this.attachments.push({id, preview, type, blob});
|
const transport : AttachmentTransport = {
|
||||||
|
transport_tag: stream.readString(),
|
||||||
|
transport_server: stream.readString()
|
||||||
|
}
|
||||||
|
this.attachments.push({id, preview, type, blob, transport});
|
||||||
}
|
}
|
||||||
this.aesChachaKey = stream.readString();
|
this.aesChachaKey = stream.readString();
|
||||||
}
|
}
|
||||||
@@ -63,7 +77,7 @@ export class PacketMessage extends Packet {
|
|||||||
stream.writeString(this.toPublicKey);
|
stream.writeString(this.toPublicKey);
|
||||||
stream.writeString(this.content);
|
stream.writeString(this.content);
|
||||||
stream.writeString(this.chachaKey);
|
stream.writeString(this.chachaKey);
|
||||||
stream.writeInt64(this.timestamp);
|
stream.writeInt64(BigInt(this.timestamp));
|
||||||
stream.writeString(this.privateKey);
|
stream.writeString(this.privateKey);
|
||||||
stream.writeString(this.messageId);
|
stream.writeString(this.messageId);
|
||||||
stream.writeInt8(this.attachments.length);
|
stream.writeInt8(this.attachments.length);
|
||||||
@@ -72,6 +86,8 @@ export class PacketMessage extends Packet {
|
|||||||
stream.writeString(this.attachments[i].preview);
|
stream.writeString(this.attachments[i].preview);
|
||||||
stream.writeString(this.attachments[i].blob);
|
stream.writeString(this.attachments[i].blob);
|
||||||
stream.writeInt8(this.attachments[i].type);
|
stream.writeInt8(this.attachments[i].type);
|
||||||
|
stream.writeString(this.attachments[i].transport.transport_tag);
|
||||||
|
stream.writeString(this.attachments[i].transport.transport_server);
|
||||||
}
|
}
|
||||||
stream.writeString(this.aesChachaKey);
|
stream.writeString(this.aesChachaKey);
|
||||||
return stream;
|
return stream;
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ export class PacketSync extends Packet {
|
|||||||
|
|
||||||
public _receive(stream: Stream): void {
|
public _receive(stream: Stream): void {
|
||||||
this.status = stream.readInt8() as SyncStatus;
|
this.status = stream.readInt8() as SyncStatus;
|
||||||
this.timestamp = stream.readInt64();
|
this.timestamp = Number(stream.readInt64());
|
||||||
}
|
}
|
||||||
|
|
||||||
public _send(): Promise<Stream> | Stream {
|
public _send(): Promise<Stream> | Stream {
|
||||||
let stream = new Stream();
|
let stream = new Stream();
|
||||||
stream.writeInt16(this.getPacketId());
|
stream.writeInt16(this.getPacketId());
|
||||||
stream.writeInt8(this.status);
|
stream.writeInt8(this.status);
|
||||||
stream.writeInt64(this.timestamp);
|
stream.writeInt64(BigInt(this.timestamp));
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,151 +1,372 @@
|
|||||||
export default class Stream {
|
export default class Stream {
|
||||||
|
private stream: Uint8Array;
|
||||||
|
private readPointer = 0; // bits
|
||||||
|
private writePointer = 0; // bits
|
||||||
|
|
||||||
private _stream: number[];
|
constructor(stream?: Uint8Array | number[]) {
|
||||||
private _readPoiner: number = 0;
|
if (!stream) {
|
||||||
private _writePointer: number = 0;
|
this.stream = new Uint8Array(0);
|
||||||
|
} else {
|
||||||
constructor(stream : number[] = []) {
|
const src = stream instanceof Uint8Array ? stream : Uint8Array.from(stream);
|
||||||
this._stream = stream;
|
this.stream = src;
|
||||||
}
|
this.writePointer = this.stream.length << 3;
|
||||||
|
|
||||||
public getStream(): number[] {
|
|
||||||
return this._stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setStream(stream: number[]) {
|
|
||||||
this._stream = stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public writeInt8(value: number) {
|
|
||||||
const negationBit = value < 0 ? 1 : 0;
|
|
||||||
const int8Value = Math.abs(value) & 0xFF;
|
|
||||||
this._stream[this._writePointer >> 3] |= negationBit << (7 - (this._writePointer & 7));
|
|
||||||
this._writePointer++;
|
|
||||||
for (let i = 0; i < 8; i++) {
|
|
||||||
const bit = (int8Value >> (7 - i)) & 1;
|
|
||||||
this._stream[this._writePointer >> 3] |= bit << (7 - (this._writePointer & 7));
|
|
||||||
this._writePointer++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public readInt8(): number {
|
getStream(): Uint8Array {
|
||||||
let value = 0;
|
return this.stream.slice(0, this.length());
|
||||||
const negationBit = (this._stream[this._readPoiner >> 3] >> (7 - (this._readPoiner & 7))) & 1;
|
|
||||||
this._readPoiner++;
|
|
||||||
for (let i = 0; i < 8; i++) {
|
|
||||||
const bit = (this._stream[this._readPoiner >> 3] >> (7 - (this._readPoiner & 7))) & 1;
|
|
||||||
value |= bit << (7 - i);
|
|
||||||
this._readPoiner++;
|
|
||||||
}
|
|
||||||
return negationBit ? -value : value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public writeBit(value: number) {
|
setStream(stream?: Uint8Array | number[]) {
|
||||||
const bit = value & 1;
|
if (!stream) {
|
||||||
this._stream[this._writePointer >> 3] |= bit << (7 - (this._writePointer & 7));
|
this.stream = new Uint8Array(0);
|
||||||
this._writePointer++;
|
this.readPointer = 0;
|
||||||
|
this.writePointer = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const src = stream instanceof Uint8Array ? stream : Uint8Array.from(stream);
|
||||||
|
this.stream = src;
|
||||||
|
this.readPointer = 0;
|
||||||
|
this.writePointer = this.stream.length << 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readBit(): number {
|
getBuffer(): Uint8Array {
|
||||||
const bit = (this._stream[this._readPoiner >> 3] >> (7 - (this._readPoiner & 7))) & 1;
|
return this.getStream();
|
||||||
this._readPoiner++;
|
|
||||||
return bit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public writeBoolean(value: boolean) {
|
isEmpty(): boolean {
|
||||||
|
return this.writePointer === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
length(): number {
|
||||||
|
return (this.writePointer + 7) >> 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- bit / boolean ----------
|
||||||
|
|
||||||
|
writeBit(value: number) {
|
||||||
|
this.writeBits(BigInt(value & 1), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
readBit(): number {
|
||||||
|
return Number(this.readBits(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
writeBoolean(value: boolean) {
|
||||||
this.writeBit(value ? 1 : 0);
|
this.writeBit(value ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readBoolean(): boolean {
|
readBoolean(): boolean {
|
||||||
return this.readBit() === 1;
|
return this.readBit() === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public writeInt16(value: number) {
|
// ---------- byte ----------
|
||||||
this.writeInt8(value >> 8);
|
|
||||||
this.writeInt8(value & 0xFF);
|
writeByte(b: number) {
|
||||||
|
this.writeUInt8(b & 0xff);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readInt16(): number {
|
readByte(): number {
|
||||||
const value = this.readInt8() << 8;
|
const v = this.readUInt8();
|
||||||
return value | this.readInt8();
|
return (v << 24) >> 24; // signed byte
|
||||||
}
|
}
|
||||||
|
|
||||||
public writeInt32(value: number) {
|
// ---------- UInt / Int 8 ----------
|
||||||
this.writeInt16(value >> 16);
|
|
||||||
this.writeInt16(value & 0xFFFF);
|
writeUInt8(value: number) {
|
||||||
|
const v = value & 0xff;
|
||||||
|
|
||||||
|
if ((this.writePointer & 7) === 0) {
|
||||||
|
this.reserveBits(8);
|
||||||
|
this.stream[this.writePointer >> 3] = v;
|
||||||
|
this.writePointer += 8;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readInt32(): number {
|
this.writeBits(BigInt(v), 8);
|
||||||
const value = this.readInt16() << 16;
|
|
||||||
return value | this.readInt16();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public writeInt64(value: number) {
|
readUInt8(): number {
|
||||||
const high = Math.floor(value / 0x100000000);
|
if (this.remainingBits() < 8n) {
|
||||||
const low = value >>> 0;
|
throw new Error("Not enough bits to read UInt8");
|
||||||
this.writeInt32(high);
|
|
||||||
this.writeInt32(low);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public readInt64(): number {
|
if ((this.readPointer & 7) === 0) {
|
||||||
const high = this.readInt32();
|
const v = this.stream[this.readPointer >> 3] & 0xff;
|
||||||
const low = this.readInt32() >>> 0;
|
this.readPointer += 8;
|
||||||
return high * 0x100000000 + low;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
public writeFloat32(value: number) {
|
return Number(this.readBits(8));
|
||||||
const buffer = new ArrayBuffer(4);
|
|
||||||
new DataView(buffer).setFloat32(0, value, true);
|
|
||||||
const float32Value = new Uint32Array(buffer)[0];
|
|
||||||
this.writeInt32(float32Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public readFloat32(): number {
|
writeInt8(value: number) {
|
||||||
const float32Value = this.readInt32();
|
this.writeUInt8(value);
|
||||||
const buffer = new ArrayBuffer(4);
|
|
||||||
new Uint32Array(buffer)[0] = float32Value;
|
|
||||||
return new DataView(buffer).getFloat32(0, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public writeString(value: string) {
|
readInt8(): number {
|
||||||
let length = value.length;
|
const u = this.readUInt8();
|
||||||
this.writeInt32(length);
|
return (u << 24) >> 24;
|
||||||
for (let i = 0; i < value.length; i++) {
|
}
|
||||||
this.writeInt16(value.charCodeAt(i));
|
|
||||||
|
// ---------- UInt / Int 16 ----------
|
||||||
|
|
||||||
|
writeUInt16(value: number) {
|
||||||
|
const v = value & 0xffff;
|
||||||
|
this.writeUInt8((v >>> 8) & 0xff);
|
||||||
|
this.writeUInt8(v & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
readUInt16(): number {
|
||||||
|
const hi = this.readUInt8();
|
||||||
|
const lo = this.readUInt8();
|
||||||
|
return (hi << 8) | lo;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeInt16(value: number) {
|
||||||
|
this.writeUInt16(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
readInt16(): number {
|
||||||
|
const u = this.readUInt16();
|
||||||
|
return (u << 16) >> 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- UInt / Int 32 ----------
|
||||||
|
|
||||||
|
writeUInt32(value: number) {
|
||||||
|
if (!Number.isFinite(value) || value < 0 || value > 0xffffffff) {
|
||||||
|
throw new Error(`UInt32 out of range: ${value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const v = Math.floor(value);
|
||||||
|
this.writeUInt8((v >>> 24) & 0xff);
|
||||||
|
this.writeUInt8((v >>> 16) & 0xff);
|
||||||
|
this.writeUInt8((v >>> 8) & 0xff);
|
||||||
|
this.writeUInt8(v & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
readUInt32(): number {
|
||||||
|
const b1 = this.readUInt8();
|
||||||
|
const b2 = this.readUInt8();
|
||||||
|
const b3 = this.readUInt8();
|
||||||
|
const b4 = this.readUInt8();
|
||||||
|
return (((b1 * 0x1000000) + (b2 << 16) + (b3 << 8) + b4) >>> 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeInt32(value: number) {
|
||||||
|
this.writeUInt32(value >>> 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
readInt32(): number {
|
||||||
|
return this.readUInt32() | 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- UInt / Int 64 ----------
|
||||||
|
|
||||||
|
writeUInt64(value: bigint) {
|
||||||
|
if (value < 0n || value > 0xffff_ffff_ffff_ffffn) {
|
||||||
|
throw new Error(`UInt64 out of range: ${value.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.writeUInt8(Number((value >> 56n) & 0xffn));
|
||||||
|
this.writeUInt8(Number((value >> 48n) & 0xffn));
|
||||||
|
this.writeUInt8(Number((value >> 40n) & 0xffn));
|
||||||
|
this.writeUInt8(Number((value >> 32n) & 0xffn));
|
||||||
|
this.writeUInt8(Number((value >> 24n) & 0xffn));
|
||||||
|
this.writeUInt8(Number((value >> 16n) & 0xffn));
|
||||||
|
this.writeUInt8(Number((value >> 8n) & 0xffn));
|
||||||
|
this.writeUInt8(Number(value & 0xffn));
|
||||||
|
}
|
||||||
|
|
||||||
|
readUInt64(): bigint {
|
||||||
|
const high = BigInt(this.readUInt32() >>> 0);
|
||||||
|
const low = BigInt(this.readUInt32() >>> 0);
|
||||||
|
return (high << 32n) | low;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeInt64(value: bigint) {
|
||||||
|
const u = BigInt.asUintN(64, value);
|
||||||
|
this.writeUInt64(u);
|
||||||
|
}
|
||||||
|
|
||||||
|
readInt64(): bigint {
|
||||||
|
return BigInt.asIntN(64, this.readUInt64());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- float ----------
|
||||||
|
|
||||||
|
writeFloat32(value: number) {
|
||||||
|
const ab = new ArrayBuffer(4);
|
||||||
|
const dv = new DataView(ab);
|
||||||
|
dv.setFloat32(0, value, false); // big-endian
|
||||||
|
this.writeUInt8(dv.getUint8(0));
|
||||||
|
this.writeUInt8(dv.getUint8(1));
|
||||||
|
this.writeUInt8(dv.getUint8(2));
|
||||||
|
this.writeUInt8(dv.getUint8(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
readFloat32(): number {
|
||||||
|
const ab = new ArrayBuffer(4);
|
||||||
|
const dv = new DataView(ab);
|
||||||
|
dv.setUint8(0, this.readUInt8());
|
||||||
|
dv.setUint8(1, this.readUInt8());
|
||||||
|
dv.setUint8(2, this.readUInt8());
|
||||||
|
dv.setUint8(3, this.readUInt8());
|
||||||
|
return dv.getFloat32(0, false); // big-endian
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- string / bytes ----------
|
||||||
|
// String: length(UInt32) + chars(UInt16), как в Java charAt()
|
||||||
|
|
||||||
|
writeString(value: string | null | undefined) {
|
||||||
|
const s = value ?? "";
|
||||||
|
this.writeUInt32(s.length);
|
||||||
|
|
||||||
|
if (s.length === 0) return;
|
||||||
|
|
||||||
|
this.reserveBits(BigInt(s.length) * 16n);
|
||||||
|
for (let i = 0; i < s.length; i++) {
|
||||||
|
this.writeUInt16(s.charCodeAt(i) & 0xffff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public readString(): string {
|
readString(): string {
|
||||||
let length = this.readInt32();
|
const len = this.readUInt32();
|
||||||
/**
|
if (len > 0x7fffffff) {
|
||||||
* Фикс уязвимости с длинной строки, превышающей
|
throw new Error(`String length too large: ${len}`);
|
||||||
* возможность для чтения _stream
|
|
||||||
*/
|
|
||||||
if (length < 0 || length > (this._stream.length - (this._readPoiner >> 3))) {
|
|
||||||
console.info("Stream readString length invalid", length, this._stream.length, this._readPoiner);
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
let value = "";
|
|
||||||
for (let i = 0; i < length; i++) {
|
const requiredBits = BigInt(len) * 16n;
|
||||||
value += String.fromCharCode(this.readInt16());
|
if (requiredBits > this.remainingBits()) {
|
||||||
|
throw new Error("Not enough bits to read string");
|
||||||
|
}
|
||||||
|
|
||||||
|
const chars = new Array<number>(len);
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
chars[i] = this.readUInt16();
|
||||||
|
}
|
||||||
|
return String.fromCharCode(...chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
// byte[]: length(UInt32) + payload
|
||||||
|
writeBytes(value: Uint8Array | number[] | null | undefined) {
|
||||||
|
const arr = value == null
|
||||||
|
? new Uint8Array(0)
|
||||||
|
: (value instanceof Uint8Array ? value : Uint8Array.from(value));
|
||||||
|
|
||||||
|
this.writeUInt32(arr.length);
|
||||||
|
if (arr.length === 0) return;
|
||||||
|
|
||||||
|
this.reserveBits(BigInt(arr.length) * 8n);
|
||||||
|
|
||||||
|
if ((this.writePointer & 7) === 0) {
|
||||||
|
const byteIndex = this.writePointer >> 3;
|
||||||
|
this.ensureCapacity(byteIndex + arr.length - 1);
|
||||||
|
this.stream.set(arr, byteIndex);
|
||||||
|
this.writePointer += arr.length << 3;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
this.writeUInt8(arr[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readBytes(): Uint8Array {
|
||||||
|
const len = this.readUInt32();
|
||||||
|
if (len === 0) return new Uint8Array(0);
|
||||||
|
|
||||||
|
const requiredBits = BigInt(len) * 8n;
|
||||||
|
if (requiredBits > this.remainingBits()) {
|
||||||
|
return new Uint8Array(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const out = new Uint8Array(len);
|
||||||
|
|
||||||
|
if ((this.readPointer & 7) === 0) {
|
||||||
|
const byteIndex = this.readPointer >> 3;
|
||||||
|
out.set(this.stream.slice(byteIndex, byteIndex + len));
|
||||||
|
this.readPointer += len << 3;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
out[i] = this.readUInt8();
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- internals ----------
|
||||||
|
|
||||||
|
private remainingBits(): bigint {
|
||||||
|
return BigInt(this.writePointer - this.readPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private writeBits(value: bigint, bits: number) {
|
||||||
|
if (bits <= 0) return;
|
||||||
|
|
||||||
|
this.reserveBits(bits);
|
||||||
|
|
||||||
|
for (let i = bits - 1; i >= 0; i--) {
|
||||||
|
const bit = Number((value >> BigInt(i)) & 1n);
|
||||||
|
const byteIndex = this.writePointer >> 3;
|
||||||
|
const shift = 7 - (this.writePointer & 7);
|
||||||
|
|
||||||
|
if (bit === 1) {
|
||||||
|
this.stream[byteIndex] = this.stream[byteIndex] | (1 << shift);
|
||||||
|
} else {
|
||||||
|
this.stream[byteIndex] = this.stream[byteIndex] & ~(1 << shift);
|
||||||
|
}
|
||||||
|
this.writePointer++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readBits(bits: number): bigint {
|
||||||
|
if (bits <= 0) return 0n;
|
||||||
|
if (this.remainingBits() < BigInt(bits)) {
|
||||||
|
throw new Error("Not enough bits to read");
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = 0n;
|
||||||
|
for (let i = 0; i < bits; i++) {
|
||||||
|
const bit = (this.stream[this.readPointer >> 3] >> (7 - (this.readPointer & 7))) & 1;
|
||||||
|
value = (value << 1n) | BigInt(bit);
|
||||||
|
this.readPointer++;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public writeBytes(value: number[]) {
|
private reserveBits(bitsToWrite: number | bigint) {
|
||||||
this.writeInt32(value.length);
|
const bits = typeof bitsToWrite === "number" ? BigInt(bitsToWrite) : bitsToWrite;
|
||||||
for (let i = 0; i < value.length; i++) {
|
if (bits <= 0n) return;
|
||||||
this.writeInt8(value[i]);
|
|
||||||
}
|
const lastBitIndex = BigInt(this.writePointer) + bits - 1n;
|
||||||
|
if (lastBitIndex < 0n) throw new Error("Bit index overflow");
|
||||||
|
|
||||||
|
const byteIndex = lastBitIndex >> 3n;
|
||||||
|
if (byteIndex > BigInt(Number.MAX_SAFE_INTEGER)) {
|
||||||
|
throw new Error("Stream too large");
|
||||||
}
|
}
|
||||||
|
|
||||||
public readBytes(): number[] {
|
this.ensureCapacity(Number(byteIndex));
|
||||||
let length = this.readInt32();
|
|
||||||
let value : any = [];
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
value.push(this.readInt8());
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ensureCapacity(byteIndex: number) {
|
||||||
|
const requiredSize = byteIndex + 1;
|
||||||
|
if (requiredSize <= this.stream.length) return;
|
||||||
|
|
||||||
|
let newSize = this.stream.length === 0 ? 32 : this.stream.length;
|
||||||
|
while (newSize < requiredSize) {
|
||||||
|
if (newSize > (0x7fffffff >> 1)) {
|
||||||
|
newSize = requiredSize;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
newSize <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = new Uint8Array(newSize);
|
||||||
|
next.set(this.stream);
|
||||||
|
this.stream = next;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
|
|||||||
interface TransportContextValue {
|
interface TransportContextValue {
|
||||||
transportServer: string | null;
|
transportServer: string | null;
|
||||||
uploadFile: (id: string, content: string) => Promise<any>;
|
uploadFile: (id: string, content: string) => Promise<any>;
|
||||||
downloadFile: (id: string, tag: string) => Promise<string>;
|
downloadFile: (id: string, tag: string, transportServer: string) => Promise<string>;
|
||||||
uploading: TransportState[];
|
uploading: TransportState[];
|
||||||
downloading: TransportState[];
|
downloading: TransportState[];
|
||||||
}
|
}
|
||||||
@@ -86,14 +86,14 @@ export function TransportProvider(props: TransportProviderProps) {
|
|||||||
* @param tag тег файла
|
* @param tag тег файла
|
||||||
* @param chachaDecryptedKey ключ для расшифровки файла
|
* @param chachaDecryptedKey ключ для расшифровки файла
|
||||||
*/
|
*/
|
||||||
const downloadFile = (id: string, tag : string) : Promise<string> => {
|
const downloadFile = (id: string, tag : string, transportServer: string) : Promise<string> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!transportServerRef.current) {
|
if (!transportServer) {
|
||||||
throw new Error("Transport server is not set");
|
throw new Error("Transport server is not set");
|
||||||
}
|
}
|
||||||
setDownloading(prev => [...prev, { id: id, progress: 0 }]);
|
setDownloading(prev => [...prev, { id: id, progress: 0 }]);
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
xhr.open('GET', `${transportServerRef.current}/d/${tag}`);
|
xhr.open('GET', `${transportServer}/d/${tag}`);
|
||||||
xhr.responseType = 'text';
|
xhr.responseType = 'text';
|
||||||
|
|
||||||
xhr.onprogress = (event) => {
|
xhr.onprogress = (event) => {
|
||||||
|
|||||||
12
app/providers/TransportProvider/useTransportServer.ts
Normal file
12
app/providers/TransportProvider/useTransportServer.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { TransportContext } from "./TransportProvider";
|
||||||
|
|
||||||
|
export function useTransportServer() {
|
||||||
|
const context = useContext(TransportContext);
|
||||||
|
if(!context){
|
||||||
|
throw new Error("useTransportServer must be used within a TransportProvider");
|
||||||
|
}
|
||||||
|
const { transportServer } = context;
|
||||||
|
|
||||||
|
return transportServer;
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
export const APP_VERSION = "1.1.4";
|
export const APP_VERSION = "1.1.5";
|
||||||
export const CORE_MIN_REQUIRED_VERSION = "1.5.4";
|
export const CORE_MIN_REQUIRED_VERSION = "1.5.4";
|
||||||
|
|
||||||
export const RELEASE_NOTICE = `
|
export const RELEASE_NOTICE = `
|
||||||
**Обновление v1.1.4** :emoji_1f631:
|
**Обновление v1.1.5** :emoji_1f631:
|
||||||
- Улучшено отображение звонка
|
- Улучшена передача данных по сети (сокращение сетевых расходов на 10%).
|
||||||
- Убрана возможность зума в окне приложения
|
- Улучшена пересылка вложений, теперь можно пересылать любые вложения в том числе большие,
|
||||||
|
без перекодирования.
|
||||||
|
- Улучшен протокол общения с сервером по части передачи сообщений.
|
||||||
|
- Улучшена логика, почищен код.
|
||||||
|
- Исправлен баг интерфейса связанный с долгой загрузкой вложений.
|
||||||
|
- Исправлена возможность позвонить в системный аккаунт.
|
||||||
`;
|
`;
|
||||||
@@ -110,8 +110,27 @@ app.whenReady().then(async () => {
|
|||||||
|
|
||||||
startApplication()
|
startApplication()
|
||||||
|
|
||||||
|
const isDevBuild =
|
||||||
|
!app.isPackaged ||
|
||||||
|
process.env.NODE_ENV === 'development' ||
|
||||||
|
Boolean(process.env.ELECTRON_RENDERER_URL)
|
||||||
|
|
||||||
app.on('browser-window-created', (_, window) => {
|
app.on('browser-window-created', (_, window) => {
|
||||||
|
// В production оставляем стандартную защиту шорткатов
|
||||||
|
if (!isDevBuild) {
|
||||||
optimizer.watchWindowShortcuts(window)
|
optimizer.watchWindowShortcuts(window)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// В dev явно разрешаем Ctrl+R и Cmd+R для перезагрузки, так как в режиме разработки это часто нужно
|
||||||
|
window.webContents.on('before-input-event', (event, input) => {
|
||||||
|
const key = input.key?.toLowerCase?.() ?? ''
|
||||||
|
const isReload = input.type === 'keyDown' && (input.meta || input.control) && key === 'r'
|
||||||
|
if (isReload) {
|
||||||
|
event.preventDefault()
|
||||||
|
window.webContents.reloadIgnoringCache()
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user