diff --git a/app/components/TextChain/TextChain.tsx b/app/components/TextChain/TextChain.tsx
index 55a9e72..a63b3aa 100644
--- a/app/components/TextChain/TextChain.tsx
+++ b/app/components/TextChain/TextChain.tsx
@@ -5,6 +5,7 @@ import { useState, useEffect } from "react";
interface TextChainProps {
text: string;
mt?: MantineSize;
+ withoutPaper?: boolean;
}
export function TextChain(props : TextChainProps) {
@@ -26,31 +27,41 @@ export function TextChain(props : TextChainProps) {
});
}, [text]);
+ const grid = (
+
+ {text.split(" ").map((v, i) => {
+ return (
+
+ {i + 1}.
+ {v}
+
+ );
+ })}
+
+ );
+
return (
+ !props.withoutPaper ? (
Your seed phrase:
-
- {text.split(" ").map((v, i) => {
- return (
-
- {i + 1}.
- {v}
-
- );
- })}
-
+ {grid}
+ ) : (
+
+ {grid}
+
+ )
)
}
\ No newline at end of file
diff --git a/app/components/TextParser/TextParser.tsx b/app/components/TextParser/TextParser.tsx
index 0573144..9cbdf4c 100644
--- a/app/components/TextParser/TextParser.tsx
+++ b/app/components/TextParser/TextParser.tsx
@@ -46,10 +46,11 @@ export function TextParser(props: TextParserProps) {
{
pattern: [
/(https?:\/\/[^\s]+)/g,
- /\b(?:https?:\/\/)?(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[^\s]*)?/g
+ /\b(?:https?:\/\/)?(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[\w\-\.\/\?=%]*)?/g
],
render: (match: string) => {
- let domainZone = match.split('.').pop() || '';
+ let domain = match.replace(/https?:\/\//, '').split('/')[0];
+ let domainZone = domain.split('.').pop() || '';
domainZone = domainZone.split('/')[0];
if(!ALLOWED_DOMAINS_ZONES.includes(domainZone)) {
return <>{match}>;
diff --git a/app/hooks/useFileStorage.ts b/app/hooks/useFileStorage.ts
index 762addc..c29703e 100644
--- a/app/hooks/useFileStorage.ts
+++ b/app/hooks/useFileStorage.ts
@@ -9,5 +9,15 @@ export function useFileStorage() {
return result;
}
- return {writeFile, readFile};
+ const fileExists = async (file : string, inWorkingDir : boolean = true) => {
+ const result = await window.electron.ipcRenderer.invoke('fileStorage:fileExists', file, inWorkingDir);
+ return result;
+ }
+
+ const size = async (file : string, inWorkingDir : boolean = true) => {
+ const result = await window.electron.ipcRenderer.invoke('fileStorage:size', file, inWorkingDir);
+ return result;
+ }
+
+ return {writeFile, readFile, fileExists, size};
}
\ No newline at end of file
diff --git a/app/providers/AttachmentProvider/useAttachment.ts b/app/providers/AttachmentProvider/useAttachment.ts
index fe25a0a..f7768f2 100644
--- a/app/providers/AttachmentProvider/useAttachment.ts
+++ b/app/providers/AttachmentProvider/useAttachment.ts
@@ -30,7 +30,7 @@ export function useAttachment(attachment: Attachment, keyPlain: string) {
const downloadPercentage = useDownloadStatus(attachment.id);
const [downloadStatus, setDownloadStatus] = useMemory("attachment-downloaded-status-" + attachment.id, DownloadStatus.PENDING, true);
const [downloadTag, setDownloadTag] = useState("");
- const {readFile, writeFile} = useFileStorage();
+ const {readFile, writeFile, fileExists, size} = useFileStorage();
const { downloadFile } = useTransport();
const publicKey = usePublicKey();
const privatePlain = usePrivatePlain();
@@ -83,10 +83,12 @@ export function useAttachment(attachment: Attachment, keyPlain: string) {
* а в загрузках
*/
const preview = getPreview();
+ const filesize = parseInt(preview.split("::")[0]);
const filename = preview.split("::")[1];
let pathInDownloads = window.downloadsPath + "/Rosetta Downloads/" + filename;
- const fileData = await readFile(pathInDownloads, false);
- if(fileData){
+ const exists = await fileExists(pathInDownloads, false);
+ const existsLength = await size(pathInDownloads, false);
+ if(exists && existsLength == filesize){
setDownloadStatus(DownloadStatus.DOWNLOADED);
return;
}
@@ -161,7 +163,17 @@ export function useAttachment(attachment: Attachment, keyPlain: string) {
const filename = preview.split("::")[1];
let buffer = Buffer.from(decrypted.split(",")[1], 'base64');
let pathInDownloads = window.downloadsPath + "/Rosetta Downloads/" + filename;
- await writeFile(pathInDownloads, buffer, false);
+ /**
+ * Пишем файл в загрузки, но перед этим выбираем ему название, если файл в загрузках
+ * уже есть с таким названием то добавляем к названию (1), (2) и так далее, чтобы не перезаписать существующий файл
+ */
+ let finalPath = pathInDownloads;
+ let fileIndex = 1;
+ while (await fileExists(finalPath, false)) {
+ finalPath = window.downloadsPath + "/Rosetta Downloads/" + filename.split(".").slice(0, -1).join(".") + ` (${fileIndex})` + "." + filename.split(".").slice(-1);
+ fileIndex++;
+ }
+ await writeFile(finalPath, buffer, false);
setDownloadStatus(DownloadStatus.DOWNLOADED);
return;
}
diff --git a/app/providers/DialogProvider/DialogProvider.tsx b/app/providers/DialogProvider/DialogProvider.tsx
index 43f244e..448edf4 100644
--- a/app/providers/DialogProvider/DialogProvider.tsx
+++ b/app/providers/DialogProvider/DialogProvider.tsx
@@ -105,6 +105,11 @@ export function DialogProvider(props: DialogProviderProps) {
useEffect(() => {
if(props.dialog == "demo"){
+ /**
+ * Это нужно для демонстрации работы сообщений на странице оформления. Так как там нет хуков и
+ * других инструментов для загрузки сообщений, то мы просто не загружаем сообщения, так как это режим
+ * демонстрации
+ */
return;
}
if(idle){
@@ -122,6 +127,11 @@ export function DialogProvider(props: DialogProviderProps) {
useEffect(() => {
if(props.dialog == "demo"){
+ /**
+ * Это нужно для демонстрации работы сообщений на странице оформления. Так как там нет хуков и
+ * других инструментов для загрузки сообщений, то мы просто не загружаем сообщения, так как это режим
+ * демонстрации
+ */
return;
}
setMessages([]);
@@ -184,15 +194,13 @@ export function DialogProvider(props: DialogProviderProps) {
readUpdated = true;
}
let decryptKey = '';
- if(message.from_me){
+ if(message.from_me && message.chacha_key != "" && message.chacha_key.startsWith("sync:")){
/**
- * Если сообщение от меня, то ключ расшифровки для вложений
- * не нужен, передаем пустую строку, так как под капотом
- * в MessageAttachment.tsx при расшифровке вложений используется
- * локальный ключ, а не тот что в сообщении, так как файл и так находится
- * у нас локально
+ * Если это сообщение от нас, то проверяем, есть ли внутри chacha_key, если есть, значит это
+ * сообщение пришло нам в результате синхронизации и его нужно расшифровать, если chacha_key нет,
+ * значит сообщение отправлено с нашего устройства, и зашифровано на стороне отправки (plain_message)
*/
- decryptKey = '';
+ decryptKey = Buffer.from(await decodeWithPassword(privatePlain, message.chacha_key.replace("sync:", "")), 'binary').toString('utf-8');
}
if(hasGroup(props.dialog)){
/**
@@ -418,6 +426,12 @@ export function DialogProvider(props: DialogProviderProps) {
*/
return;
}
+ if(toPublicKey != props.dialog) {
+ /**
+ * Игнорируем если это не сообщение для этого диалога
+ */
+ return;
+ }
const chachaDecryptedKey = Buffer.from(await decodeWithPassword(privatePlain, aesChachaKey), "binary");
const key = chachaDecryptedKey.slice(0, 32);
const nonce = chachaDecryptedKey.slice(32);
@@ -458,6 +472,13 @@ export function DialogProvider(props: DialogProviderProps) {
const fromPublicKey = packet.getFromPublicKey();
const toPublicKey = packet.getToPublicKey();
+ if(fromPublicKey == publicKey){
+ /**
+ * Это синхронизация, игнорируем ее в этом обработчике
+ */
+ return;
+ }
+
if(hasGroup(props.dialog)){
/**
* Если это групповое сообщение, то для него есть
@@ -529,6 +550,13 @@ export function DialogProvider(props: DialogProviderProps) {
const fromPublicKey = packet.getFromPublicKey();
const toPublicKey = packet.getToPublicKey();
+ if(fromPublicKey == publicKey){
+ /**
+ * Это синхронизация, игнорируем ее в этом обработчике
+ */
+ return;
+ }
+
if(toPublicKey != props.dialog){
/**
* Исправление кросс диалогового сообщения
diff --git a/app/providers/DialogProvider/useDialogFiber.ts b/app/providers/DialogProvider/useDialogFiber.ts
index 4bb598f..0831a27 100644
--- a/app/providers/DialogProvider/useDialogFiber.ts
+++ b/app/providers/DialogProvider/useDialogFiber.ts
@@ -96,15 +96,15 @@ export function useDialogFiber() {
const content = packet.getContent();
const timestamp = packet.getTimestamp();
const messageId = packet.getMessageId();
-
-
if (fromPublicKey != publicKey) {
/**
* Игнорируем если это не сообщение от нас
*/
return;
}
- const chachaDecryptedKey = Buffer.from(await decodeWithPassword(privatePlain, aesChachaKey), "binary");
+
+ 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'));
@@ -160,7 +160,7 @@ export function useDialogFiber() {
content,
timestamp,
0, //по умолчанию не прочитаны
- '',
+ "sync:" + aesChachaKey,
1, //Свои же сообщения всегда от нас
await encodeWithPassword(privatePlain, decryptedContent),
publicKey,
diff --git a/app/version.ts b/app/version.ts
index 169916b..95ebf02 100644
--- a/app/version.ts
+++ b/app/version.ts
@@ -1,7 +1,11 @@
-export const APP_VERSION = "1.0.1";
-export const CORE_MIN_REQUIRED_VERSION = "1.4.8";
+export const APP_VERSION = "1.0.2";
+export const CORE_MIN_REQUIRED_VERSION = "1.4.9";
export const RELEASE_NOTICE = `
-**Update v1.0.1** :emoji_1f631:
-- Fix push notifications on synchronization
+**Update v1.0.2** :emoji_1f631:
+- Support multiple file downloads
+- Fix fallback after boot loading
+- Fix corss-chat reading messages
+- Support sync attachments on other devices
+- Fix UI bugs
`;
\ No newline at end of file
diff --git a/app/views/Backup/Backup.tsx b/app/views/Backup/Backup.tsx
index 29a30cf..411aadf 100644
--- a/app/views/Backup/Backup.tsx
+++ b/app/views/Backup/Backup.tsx
@@ -5,7 +5,7 @@ import { SettingsInput } from "@/app/components/SettingsInput/SettingsInput";
import { TextChain } from "@/app/components/TextChain/TextChain";
import { decodeWithPassword } from "@/app/crypto/crypto";
import { useAccount } from "@/app/providers/AccountProvider/useAccount";
-import { Paper, Text } from "@mantine/core";
+import { Text } from "@mantine/core";
import { useState } from "react";
export function Backup() {
@@ -39,10 +39,8 @@ export function Backup() {
{show.trim() !== "" && (
<>
-
-
-
-
+
+
Please don't share your seed phrase! The administration will never ask you for it.
>
diff --git a/lib/main/boot/bootloader.ts b/lib/main/boot/bootloader.ts
index 531db5b..638d3c5 100644
--- a/lib/main/boot/bootloader.ts
+++ b/lib/main/boot/bootloader.ts
@@ -1,4 +1,4 @@
-import { app, BrowserWindow, ipcMain } from "electron";
+import { app, BrowserWindow } from "electron";
import fs from 'fs/promises'
import { WORKING_DIR } from "../constants";
import path from "path";
@@ -6,30 +6,6 @@ import { Logger } from "../logger";
const logger = Logger('bootloader');
-
-ipcMain.handleOnce('report-boot-process-failed', async () => {
- /**
- * Если процесс загрузки не завершился успешно, то preload показывает
- * экран ошибки, а нам нужно откатиться назад к загрузке dev.html
- * и удалить скомпилированные файлы, чтобы при следующем запуске
- * приложение попыталось загрузиться в режиме разработки.
- */
- let filePath = path.join(WORKING_DIR, 'b');
- if(!await existsFile(filePath)){
- /**
- * Исправление ошибки когда директории нет.
- */
- logger.log("No compiled files to remove");
- return;
- }
- await fs.rmdir(filePath, { recursive: true });
- logger.log("Boot process failed, removed compiled files");
- logger.log(`Removed compiled files at ${filePath}`);
- logger.log(`Restarting application in safe mode`);
- app.relaunch();
- app.exit(0);
-});
-
/**
* Boot функция, эта функция запускает приложение
* @param window окно
diff --git a/lib/main/ipcs/ipcFilestorage.ts b/lib/main/ipcs/ipcFilestorage.ts
index 71c8abd..111049d 100644
--- a/lib/main/ipcs/ipcFilestorage.ts
+++ b/lib/main/ipcs/ipcFilestorage.ts
@@ -2,16 +2,20 @@ import { ipcMain } from "electron";
import { WORKING_DIR } from "../constants";
import fs from 'fs/promises'
import path from 'path'
+import { Logger } from "../logger";
+
+const logger = Logger('ipcFilestorage');
ipcMain.handle('fileStorage:writeFile', async (_, file: string, data: string | Buffer, inWorkingDir : boolean = true) => {
+ logger.log(`System call fileStorage:writeFile with file=${file} inWorkingDir=${inWorkingDir}`);
const fullPath = path.join(inWorkingDir ? WORKING_DIR : '', file);
await fs.mkdir(path.dirname(fullPath), { recursive: true });
await fs.writeFile(fullPath, data);
- console.info("File written to " + fullPath);
return true;
});
ipcMain.handle('fileStorage:readFile', async (_, file: string, inWorkingDir : boolean = true) => {
+ logger.log(`System call fileStorage:readFile with file=${file} inWorkingDir=${inWorkingDir}`);
try{
const fullPath = path.join(inWorkingDir ? WORKING_DIR : '', file);
const data = await fs.readFile(fullPath);
@@ -19,4 +23,26 @@ ipcMain.handle('fileStorage:readFile', async (_, file: string, inWorkingDir : bo
}catch(e){
return null;
}
+});
+
+ipcMain.handle('fileStorage:size', async (_, file: string, inWorkingDir : boolean = true) => {
+ logger.log(`System call fileStorage:size with file=${file} inWorkingDir=${inWorkingDir}`);
+ try{
+ const fullPath = path.join(inWorkingDir ? WORKING_DIR : '', file);
+ const stats = await fs.stat(fullPath);
+ return stats.size;
+ }catch(e){
+ return 0;
+ }
+});
+
+ipcMain.handle('fileStorage:fileExists', async (_, file: string, inWorkingDir : boolean = true) => {
+ logger.log(`System call fileStorage:fileExists with file=${file} inWorkingDir=${inWorkingDir}`);
+ try{
+ const fullPath = path.join(inWorkingDir ? WORKING_DIR : '', file);
+ await fs.access(fullPath);
+ return true;
+ }catch(e){
+ return false;
+ }
});
\ No newline at end of file
diff --git a/lib/preload/preload.ts b/lib/preload/preload.ts
index 0fa6029..6135ec5 100644
--- a/lib/preload/preload.ts
+++ b/lib/preload/preload.ts
@@ -2,86 +2,6 @@ import { contextBridge, ipcRenderer, shell } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
import api from './api'
-const applicationLoader = `
-
-
-
-
-`;
-
-const applicationError = `
-
-
-
-
-
-
Application Error
-
The application failed to load properly. Please wait for application repairing or reinstall application.
- ${applicationLoader}
-
-
-
rosetta - powering freedom. visit about rosetta-im.com. error: boot_process_failed
-
-
-`;
-
const exposeContext = async () => {
let version = await ipcRenderer.invoke("get-core-version");
@@ -89,35 +9,6 @@ const exposeContext = async () => {
let arch = await ipcRenderer.invoke("get-arch");
let deviceName = await ipcRenderer.invoke("device:name");
let deviceId = await ipcRenderer.invoke("device:id");
- let interval : any = 0;
-
- interval = setInterval(() => {
- /**
- * Если после определенного таймаута приложение так и
- * не загрузилось, то считаем, что процесс завис,
- * и показываем экран ошибки. Так же отправляем
- * сигнал в main процесс, чтобы тот мог попытаться
- * откатить обновление
- */
- if (document.body.innerHTML.indexOf("preloadersignature") !== -1) {
- /**
- * Если сейчас показывается прелоадер, то не считаем
- * что обновление битое, так как само обновление еще не
- * загрузилось в приложение
- */
- return;
- }
- if (document.body.innerHTML.length < 100) {
- /**
- * Приложение загружено, а прошло больше 5 секунд
- * с момента прелоадера, значит что-то пошло не так
- * и нужно показать экран ошибки
- */
- document.body.innerHTML = applicationError;
- ipcRenderer.invoke("report-boot-process-failed");
- }
- clearInterval(interval);
- }, 5000);
let downloadsPath = await ipcRenderer.invoke("get-downloads-path");
if (process.contextIsolated) {