Обновление 1.0.2-1.4.9
This commit was merged in pull request #1.
This commit is contained in:
@@ -5,6 +5,7 @@ import { useState, useEffect } from "react";
|
|||||||
interface TextChainProps {
|
interface TextChainProps {
|
||||||
text: string;
|
text: string;
|
||||||
mt?: MantineSize;
|
mt?: MantineSize;
|
||||||
|
withoutPaper?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TextChain(props : TextChainProps) {
|
export function TextChain(props : TextChainProps) {
|
||||||
@@ -26,31 +27,41 @@ export function TextChain(props : TextChainProps) {
|
|||||||
});
|
});
|
||||||
}, [text]);
|
}, [text]);
|
||||||
|
|
||||||
|
const grid = (
|
||||||
|
<SimpleGrid cols={3} spacing="xs">
|
||||||
|
{text.split(" ").map((v, i) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={i}
|
||||||
|
className={classes.wordBox}
|
||||||
|
style={{
|
||||||
|
opacity: mounted[i] ? 1 : 0,
|
||||||
|
transform: mounted[i] ? 'scale(1)' : 'scale(0.9)',
|
||||||
|
transition: 'opacity 300ms ease, transform 300ms ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text size="xs" c="dimmed" mr={4}>{i + 1}.</Text>
|
||||||
|
<Text size="sm" fw={500}>{v}</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</SimpleGrid>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
!props.withoutPaper ? (
|
||||||
<Box mt={props.mt}>
|
<Box mt={props.mt}>
|
||||||
<Box className={classes.displayArea}>
|
<Box className={classes.displayArea}>
|
||||||
<Text size="sm" mb="md" c="dimmed">
|
<Text size="sm" mb="md" c="dimmed">
|
||||||
Your seed phrase:
|
Your seed phrase:
|
||||||
</Text>
|
</Text>
|
||||||
<SimpleGrid cols={3} spacing="xs">
|
{grid}
|
||||||
{text.split(" ").map((v, i) => {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
key={i}
|
|
||||||
className={classes.wordBox}
|
|
||||||
style={{
|
|
||||||
opacity: mounted[i] ? 1 : 0,
|
|
||||||
transform: mounted[i] ? 'scale(1)' : 'scale(0.9)',
|
|
||||||
transition: 'opacity 300ms ease, transform 300ms ease',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text size="xs" c="dimmed" mr={4}>{i + 1}.</Text>
|
|
||||||
<Text size="sm" fw={500}>{v}</Text>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</SimpleGrid>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box mt={props.mt}>
|
||||||
|
{grid}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -46,10 +46,11 @@ export function TextParser(props: TextParserProps) {
|
|||||||
{
|
{
|
||||||
pattern: [
|
pattern: [
|
||||||
/(https?:\/\/[^\s]+)/g,
|
/(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) => {
|
render: (match: string) => {
|
||||||
let domainZone = match.split('.').pop() || '';
|
let domain = match.replace(/https?:\/\//, '').split('/')[0];
|
||||||
|
let domainZone = domain.split('.').pop() || '';
|
||||||
domainZone = domainZone.split('/')[0];
|
domainZone = domainZone.split('/')[0];
|
||||||
if(!ALLOWED_DOMAINS_ZONES.includes(domainZone)) {
|
if(!ALLOWED_DOMAINS_ZONES.includes(domainZone)) {
|
||||||
return <>{match}</>;
|
return <>{match}</>;
|
||||||
|
|||||||
@@ -9,5 +9,15 @@ export function useFileStorage() {
|
|||||||
return result;
|
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};
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ export function useAttachment(attachment: Attachment, keyPlain: string) {
|
|||||||
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("");
|
||||||
const {readFile, writeFile} = useFileStorage();
|
const {readFile, writeFile, fileExists, size} = useFileStorage();
|
||||||
const { downloadFile } = useTransport();
|
const { downloadFile } = useTransport();
|
||||||
const publicKey = usePublicKey();
|
const publicKey = usePublicKey();
|
||||||
const privatePlain = usePrivatePlain();
|
const privatePlain = usePrivatePlain();
|
||||||
@@ -83,10 +83,12 @@ export function useAttachment(attachment: Attachment, keyPlain: string) {
|
|||||||
* а в загрузках
|
* а в загрузках
|
||||||
*/
|
*/
|
||||||
const preview = getPreview();
|
const preview = getPreview();
|
||||||
|
const filesize = parseInt(preview.split("::")[0]);
|
||||||
const filename = preview.split("::")[1];
|
const filename = preview.split("::")[1];
|
||||||
let pathInDownloads = window.downloadsPath + "/Rosetta Downloads/" + filename;
|
let pathInDownloads = window.downloadsPath + "/Rosetta Downloads/" + filename;
|
||||||
const fileData = await readFile(pathInDownloads, false);
|
const exists = await fileExists(pathInDownloads, false);
|
||||||
if(fileData){
|
const existsLength = await size(pathInDownloads, false);
|
||||||
|
if(exists && existsLength == filesize){
|
||||||
setDownloadStatus(DownloadStatus.DOWNLOADED);
|
setDownloadStatus(DownloadStatus.DOWNLOADED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -161,7 +163,17 @@ export function useAttachment(attachment: Attachment, keyPlain: string) {
|
|||||||
const filename = preview.split("::")[1];
|
const filename = preview.split("::")[1];
|
||||||
let buffer = Buffer.from(decrypted.split(",")[1], 'base64');
|
let buffer = Buffer.from(decrypted.split(",")[1], 'base64');
|
||||||
let pathInDownloads = window.downloadsPath + "/Rosetta Downloads/" + filename;
|
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);
|
setDownloadStatus(DownloadStatus.DOWNLOADED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,11 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(props.dialog == "demo"){
|
if(props.dialog == "demo"){
|
||||||
|
/**
|
||||||
|
* Это нужно для демонстрации работы сообщений на странице оформления. Так как там нет хуков и
|
||||||
|
* других инструментов для загрузки сообщений, то мы просто не загружаем сообщения, так как это режим
|
||||||
|
* демонстрации
|
||||||
|
*/
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(idle){
|
if(idle){
|
||||||
@@ -122,6 +127,11 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(props.dialog == "demo"){
|
if(props.dialog == "demo"){
|
||||||
|
/**
|
||||||
|
* Это нужно для демонстрации работы сообщений на странице оформления. Так как там нет хуков и
|
||||||
|
* других инструментов для загрузки сообщений, то мы просто не загружаем сообщения, так как это режим
|
||||||
|
* демонстрации
|
||||||
|
*/
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setMessages([]);
|
setMessages([]);
|
||||||
@@ -184,15 +194,13 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
readUpdated = true;
|
readUpdated = true;
|
||||||
}
|
}
|
||||||
let decryptKey = '';
|
let decryptKey = '';
|
||||||
if(message.from_me){
|
if(message.from_me && message.chacha_key != "" && message.chacha_key.startsWith("sync:")){
|
||||||
/**
|
/**
|
||||||
* Если сообщение от меня, то ключ расшифровки для вложений
|
* Если это сообщение от нас, то проверяем, есть ли внутри chacha_key, если есть, значит это
|
||||||
* не нужен, передаем пустую строку, так как под капотом
|
* сообщение пришло нам в результате синхронизации и его нужно расшифровать, если chacha_key нет,
|
||||||
* в MessageAttachment.tsx при расшифровке вложений используется
|
* значит сообщение отправлено с нашего устройства, и зашифровано на стороне отправки (plain_message)
|
||||||
* локальный ключ, а не тот что в сообщении, так как файл и так находится
|
|
||||||
* у нас локально
|
|
||||||
*/
|
*/
|
||||||
decryptKey = '';
|
decryptKey = Buffer.from(await decodeWithPassword(privatePlain, message.chacha_key.replace("sync:", "")), 'binary').toString('utf-8');
|
||||||
}
|
}
|
||||||
if(hasGroup(props.dialog)){
|
if(hasGroup(props.dialog)){
|
||||||
/**
|
/**
|
||||||
@@ -418,6 +426,12 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
*/
|
*/
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(toPublicKey != props.dialog) {
|
||||||
|
/**
|
||||||
|
* Игнорируем если это не сообщение для этого диалога
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
const chachaDecryptedKey = Buffer.from(await decodeWithPassword(privatePlain, aesChachaKey), "binary");
|
const chachaDecryptedKey = Buffer.from(await decodeWithPassword(privatePlain, aesChachaKey), "binary");
|
||||||
const key = chachaDecryptedKey.slice(0, 32);
|
const key = chachaDecryptedKey.slice(0, 32);
|
||||||
const nonce = chachaDecryptedKey.slice(32);
|
const nonce = chachaDecryptedKey.slice(32);
|
||||||
@@ -458,6 +472,13 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
const fromPublicKey = packet.getFromPublicKey();
|
const fromPublicKey = packet.getFromPublicKey();
|
||||||
const toPublicKey = packet.getToPublicKey();
|
const toPublicKey = packet.getToPublicKey();
|
||||||
|
|
||||||
|
if(fromPublicKey == publicKey){
|
||||||
|
/**
|
||||||
|
* Это синхронизация, игнорируем ее в этом обработчике
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(hasGroup(props.dialog)){
|
if(hasGroup(props.dialog)){
|
||||||
/**
|
/**
|
||||||
* Если это групповое сообщение, то для него есть
|
* Если это групповое сообщение, то для него есть
|
||||||
@@ -529,6 +550,13 @@ export function DialogProvider(props: DialogProviderProps) {
|
|||||||
const fromPublicKey = packet.getFromPublicKey();
|
const fromPublicKey = packet.getFromPublicKey();
|
||||||
const toPublicKey = packet.getToPublicKey();
|
const toPublicKey = packet.getToPublicKey();
|
||||||
|
|
||||||
|
if(fromPublicKey == publicKey){
|
||||||
|
/**
|
||||||
|
* Это синхронизация, игнорируем ее в этом обработчике
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(toPublicKey != props.dialog){
|
if(toPublicKey != props.dialog){
|
||||||
/**
|
/**
|
||||||
* Исправление кросс диалогового сообщения
|
* Исправление кросс диалогового сообщения
|
||||||
|
|||||||
@@ -96,15 +96,15 @@ export function useDialogFiber() {
|
|||||||
const content = packet.getContent();
|
const content = packet.getContent();
|
||||||
const timestamp = packet.getTimestamp();
|
const timestamp = packet.getTimestamp();
|
||||||
const messageId = packet.getMessageId();
|
const messageId = packet.getMessageId();
|
||||||
|
|
||||||
|
|
||||||
if (fromPublicKey != publicKey) {
|
if (fromPublicKey != publicKey) {
|
||||||
/**
|
/**
|
||||||
* Игнорируем если это не сообщение от нас
|
* Игнорируем если это не сообщение от нас
|
||||||
*/
|
*/
|
||||||
return;
|
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 key = chachaDecryptedKey.slice(0, 32);
|
||||||
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'));
|
||||||
@@ -160,7 +160,7 @@ export function useDialogFiber() {
|
|||||||
content,
|
content,
|
||||||
timestamp,
|
timestamp,
|
||||||
0, //по умолчанию не прочитаны
|
0, //по умолчанию не прочитаны
|
||||||
'',
|
"sync:" + aesChachaKey,
|
||||||
1, //Свои же сообщения всегда от нас
|
1, //Свои же сообщения всегда от нас
|
||||||
await encodeWithPassword(privatePlain, decryptedContent),
|
await encodeWithPassword(privatePlain, decryptedContent),
|
||||||
publicKey,
|
publicKey,
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
export const APP_VERSION = "1.0.1";
|
export const APP_VERSION = "1.0.2";
|
||||||
export const CORE_MIN_REQUIRED_VERSION = "1.4.8";
|
export const CORE_MIN_REQUIRED_VERSION = "1.4.9";
|
||||||
|
|
||||||
export const RELEASE_NOTICE = `
|
export const RELEASE_NOTICE = `
|
||||||
**Update v1.0.1** :emoji_1f631:
|
**Update v1.0.2** :emoji_1f631:
|
||||||
- Fix push notifications on synchronization
|
- Support multiple file downloads
|
||||||
|
- Fix fallback after boot loading
|
||||||
|
- Fix corss-chat reading messages
|
||||||
|
- Support sync attachments on other devices
|
||||||
|
- Fix UI bugs
|
||||||
`;
|
`;
|
||||||
@@ -5,7 +5,7 @@ import { SettingsInput } from "@/app/components/SettingsInput/SettingsInput";
|
|||||||
import { TextChain } from "@/app/components/TextChain/TextChain";
|
import { TextChain } from "@/app/components/TextChain/TextChain";
|
||||||
import { decodeWithPassword } from "@/app/crypto/crypto";
|
import { decodeWithPassword } from "@/app/crypto/crypto";
|
||||||
import { useAccount } from "@/app/providers/AccountProvider/useAccount";
|
import { useAccount } from "@/app/providers/AccountProvider/useAccount";
|
||||||
import { Paper, Text } from "@mantine/core";
|
import { Text } from "@mantine/core";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export function Backup() {
|
export function Backup() {
|
||||||
@@ -39,10 +39,8 @@ export function Backup() {
|
|||||||
</Text>
|
</Text>
|
||||||
{show.trim() !== "" && (
|
{show.trim() !== "" && (
|
||||||
<>
|
<>
|
||||||
<Paper mt={'sm'} p={'md'} withBorder>
|
<TextChain withoutPaper mt={'sm'} text={show}></TextChain>
|
||||||
<TextChain rainbow={true} mt={'sm'} text={show}></TextChain>
|
<Text fz={10} mt={'sm'} c={'gray'} pl={'xs'} pr={'xs'}>
|
||||||
</Paper>
|
|
||||||
<Text fz={10} mt={3} c={'gray'} pl={'xs'} pr={'xs'}>
|
|
||||||
Please don't share your seed phrase! The administration will never ask you for it.
|
Please don't share your seed phrase! The administration will never ask you for it.
|
||||||
</Text>
|
</Text>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { app, BrowserWindow, ipcMain } from "electron";
|
import { app, BrowserWindow } from "electron";
|
||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
import { WORKING_DIR } from "../constants";
|
import { WORKING_DIR } from "../constants";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
@@ -6,30 +6,6 @@ import { Logger } from "../logger";
|
|||||||
|
|
||||||
const logger = Logger('bootloader');
|
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 функция, эта функция запускает приложение
|
* Boot функция, эта функция запускает приложение
|
||||||
* @param window окно
|
* @param window окно
|
||||||
|
|||||||
@@ -2,16 +2,20 @@ import { ipcMain } from "electron";
|
|||||||
import { WORKING_DIR } from "../constants";
|
import { WORKING_DIR } from "../constants";
|
||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
import path from 'path'
|
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) => {
|
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);
|
const fullPath = path.join(inWorkingDir ? WORKING_DIR : '', file);
|
||||||
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
||||||
await fs.writeFile(fullPath, data);
|
await fs.writeFile(fullPath, data);
|
||||||
console.info("File written to " + fullPath);
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('fileStorage:readFile', async (_, file: string, inWorkingDir : boolean = true) => {
|
ipcMain.handle('fileStorage:readFile', async (_, file: string, inWorkingDir : boolean = true) => {
|
||||||
|
logger.log(`System call fileStorage:readFile with file=${file} inWorkingDir=${inWorkingDir}`);
|
||||||
try{
|
try{
|
||||||
const fullPath = path.join(inWorkingDir ? WORKING_DIR : '', file);
|
const fullPath = path.join(inWorkingDir ? WORKING_DIR : '', file);
|
||||||
const data = await fs.readFile(fullPath);
|
const data = await fs.readFile(fullPath);
|
||||||
@@ -19,4 +23,26 @@ ipcMain.handle('fileStorage:readFile', async (_, file: string, inWorkingDir : bo
|
|||||||
}catch(e){
|
}catch(e){
|
||||||
return null;
|
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;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
@@ -2,86 +2,6 @@ import { contextBridge, ipcRenderer, shell } from 'electron'
|
|||||||
import { electronAPI } from '@electron-toolkit/preload'
|
import { electronAPI } from '@electron-toolkit/preload'
|
||||||
import api from './api'
|
import api from './api'
|
||||||
|
|
||||||
const applicationLoader = `
|
|
||||||
<div style="
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', sans-serif;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #ffffff;
|
|
||||||
margin-top: 40px;
|
|
||||||
text-align: center;
|
|
||||||
">
|
|
||||||
<div style="
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border: 4px solid #333333;
|
|
||||||
border-top-color: #0071e3;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
"></div>
|
|
||||||
<style>
|
|
||||||
@keyframes spin {
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const applicationError = `
|
|
||||||
<div style="
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', sans-serif;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
height: 100vh;
|
|
||||||
color: #ffffff;
|
|
||||||
padding: 40px;
|
|
||||||
text-align: center;
|
|
||||||
">
|
|
||||||
<div style="
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
">
|
|
||||||
<div style="
|
|
||||||
font-size: 72px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
">
|
|
||||||
<svg width="64" height="64" zoomAndPan="magnify" viewBox="0 0 384 383.999986" preserveAspectRatio="xMidYMid meet" version="1.0"><defs><clipPath id="ab9f3e3410"><path d="M 134 109 L 369.46875 109 L 369.46875 381.34375 L 134 381.34375 Z M 134 109 " clip-rule="nonzero"/></clipPath><clipPath id="39bead0a6b"><path d="M 14.71875 2.59375 L 249 2.59375 L 249 222 L 14.71875 222 Z M 14.71875 2.59375 " clip-rule="nonzero"/></clipPath></defs><g clip-path="url(#ab9f3e3410)"><path fill="#ffffff" d="M 254.15625 284.453125 C 288.414062 275.191406 316.179688 260.617188 337.414062 240.769531 C 358.632812 220.917969 369.257812 195.238281 369.257812 163.691406 L 369.257812 109.996094 L 249.550781 110.222656 L 249.550781 168.148438 C 249.550781 184.847656 241.75 198.195312 226.148438 208.21875 C 210.550781 218.226562 188.175781 223.234375 159.007812 223.234375 L 134.070312 223.234375 L 134.070312 300.996094 L 206.652344 381.429688 L 344.765625 381.429688 L 254.15625 284.453125 " fill-opacity="1" fill-rule="nonzero"/></g><g clip-path="url(#39bead0a6b)"><path fill="#ffffff" d="M 248.417969 109.257812 L 248.417969 2.605469 L 14.769531 2.605469 L 14.769531 221.519531 L 132.9375 221.519531 L 132.9375 109.257812 L 248.417969 109.257812 " fill-opacity="1" fill-rule="nonzero"/></g></svg>
|
|
||||||
</div>
|
|
||||||
<h1 style="
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0 0 12px 0;
|
|
||||||
letter-spacing: -0.5px;
|
|
||||||
">Application Error</h1>
|
|
||||||
<p style="
|
|
||||||
font-size: 17px;
|
|
||||||
color: #a1a1a6;
|
|
||||||
margin: 0 0 32px 0;
|
|
||||||
max-width: 500px;
|
|
||||||
line-height: 1.5;
|
|
||||||
">The application failed to load properly. Please wait for application repairing or reinstall application.</p>
|
|
||||||
${applicationLoader}
|
|
||||||
</div>
|
|
||||||
<div style="
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 8px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
">
|
|
||||||
<p style="
|
|
||||||
font-size: 13px;
|
|
||||||
color: #5a5a5f;
|
|
||||||
">rosetta - powering freedom. visit about rosetta-im.com. error: boot_process_failed</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
|
||||||
const exposeContext = async () => {
|
const exposeContext = async () => {
|
||||||
let version = await ipcRenderer.invoke("get-core-version");
|
let version = await ipcRenderer.invoke("get-core-version");
|
||||||
@@ -89,35 +9,6 @@ const exposeContext = async () => {
|
|||||||
let arch = await ipcRenderer.invoke("get-arch");
|
let arch = await ipcRenderer.invoke("get-arch");
|
||||||
let deviceName = await ipcRenderer.invoke("device:name");
|
let deviceName = await ipcRenderer.invoke("device:name");
|
||||||
let deviceId = await ipcRenderer.invoke("device:id");
|
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");
|
let downloadsPath = await ipcRenderer.invoke("get-downloads-path");
|
||||||
if (process.contextIsolated) {
|
if (process.contextIsolated) {
|
||||||
|
|||||||
Reference in New Issue
Block a user