@@ -10,8 +10,6 @@ on:
|
||||
paths:
|
||||
- 'lib/**'
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
||||
@@ -4,7 +4,7 @@ import animationData from './lottie.json';
|
||||
import { Box, Flex, Skeleton, Text } from "@mantine/core";
|
||||
import { useDialogsList } from "@/app/providers/DialogListProvider/useDialogsList";
|
||||
import { GroupDialog } from "../GroupDialog/GroupDialog";
|
||||
import React from "react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
interface DialogsListProps {
|
||||
mode: 'all' | 'requests';
|
||||
@@ -13,6 +13,7 @@ interface DialogsListProps {
|
||||
|
||||
export function DialogsList(props : DialogsListProps) {
|
||||
const {dialogs, loadingDialogs} = useDialogsList();
|
||||
const filteredDialogs = dialogs.filter(v => (v.is_request == (props.mode == 'requests')));
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -36,21 +37,30 @@ export function DialogsList(props : DialogsListProps) {
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{loadingDialogs === 0 && dialogs.filter(v => (v.is_request == (props.mode == 'requests'))).map((dialog) => (
|
||||
<React.Fragment key={dialog.dialog_id}>
|
||||
{dialog.dialog_id.startsWith('#group:') ? (
|
||||
<GroupDialog
|
||||
onClickDialog={props.onSelectDialog}
|
||||
{...dialog}
|
||||
/>
|
||||
) : (
|
||||
<Dialog
|
||||
onClickDialog={props.onSelectDialog}
|
||||
{...dialog}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
<motion.div style={{display: 'flex', flexDirection: 'column'}}>
|
||||
<AnimatePresence mode="popLayout">
|
||||
{loadingDialogs === 0 && filteredDialogs.map((dialog) => (
|
||||
<motion.div
|
||||
key={dialog.dialog_id}
|
||||
layout
|
||||
initial={false}
|
||||
transition={{ duration: 0.1, ease: 'easeInOut' }}
|
||||
>
|
||||
{dialog.dialog_id.startsWith('#group:') ? (
|
||||
<GroupDialog
|
||||
onClickDialog={props.onSelectDialog}
|
||||
{...dialog}
|
||||
/>
|
||||
) : (
|
||||
<Dialog
|
||||
onClickDialog={props.onSelectDialog}
|
||||
{...dialog}
|
||||
/>
|
||||
)}
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -7,14 +7,16 @@ import { useHotkeys } from "@mantine/hooks";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { usePublicKey } from "@/app/providers/AccountProvider/usePublicKey";
|
||||
import { DialogHeaderText } from "../DialogHeaderText/DialogHeaderText";
|
||||
import { useCoreDevice } from "@/app/providers/DeviceProvider/useCoreDevice";
|
||||
|
||||
export function DialogsPanelHeader() {
|
||||
const colors = useRosettaColors();
|
||||
const logout = useLogout();
|
||||
const navigate = useNavigate();
|
||||
const publicKey = usePublicKey();
|
||||
const viewKeys = window.platform == 'darwin' ? '⌘' : 'Ctrl+';
|
||||
const triggerKeys = window.platform == 'darwin' ? 'mod' : 'Ctrl';
|
||||
const {platform} = useCoreDevice();
|
||||
const viewKeys = platform == 'darwin' ? '⌘' : 'Ctrl+';
|
||||
const triggerKeys = platform == 'darwin' ? 'mod' : 'Ctrl';
|
||||
|
||||
useHotkeys([
|
||||
[`${triggerKeys}+L`, () => logout()],
|
||||
|
||||
@@ -42,6 +42,7 @@ export function MessageAttachments(props: MessageAttachmentsProps) {
|
||||
text: props.text,
|
||||
parent: props.parent,
|
||||
}
|
||||
console.info("Rendering attachment", attachProps);
|
||||
switch (att.type) {
|
||||
case AttachmentType.MESSAGES:
|
||||
return <MessageReplyMessages {...attachProps} key={index}></MessageReplyMessages>
|
||||
|
||||
@@ -19,7 +19,7 @@ export function MessageAvatar(props: AttachmentProps) {
|
||||
download,
|
||||
downloadStatus,
|
||||
getBlob,
|
||||
getPreview} = useAttachment(props.attachment, props.chacha_key_plain);
|
||||
getPreview} = useAttachment(props.attachment, props.parent);
|
||||
const mainRef = useRef<HTMLDivElement>(null);
|
||||
const { open } = useImageViewer();
|
||||
const preview = getPreview();
|
||||
|
||||
@@ -6,6 +6,7 @@ import { IconArrowDown, IconFile, IconX } from "@tabler/icons-react";
|
||||
import { dotCenterIfNeeded, humanFilesize } from "@/app/utils/utils";
|
||||
import { AnimatedRoundedProgress } from "../AnimatedRoundedProgress/AnimatedRoundedProgress";
|
||||
import { DeliveredMessageState } from "@/app/providers/DialogProvider/DialogProvider";
|
||||
import { useCore } from "@/app/hooks/useCore";
|
||||
|
||||
export function MessageFile(props : AttachmentProps) {
|
||||
const colors = useRosettaColors();
|
||||
@@ -18,7 +19,7 @@ export function MessageFile(props : AttachmentProps) {
|
||||
} =
|
||||
useAttachment(
|
||||
props.attachment,
|
||||
props.chacha_key_plain,
|
||||
props.parent,
|
||||
);
|
||||
const preview = getPreview();
|
||||
const error = downloadStatus == DownloadStatus.ERROR;
|
||||
@@ -27,15 +28,15 @@ export function MessageFile(props : AttachmentProps) {
|
||||
const filetype = filename.split(".")[filename.split(".").length - 1];
|
||||
const isEncrypting = props.delivered == DeliveredMessageState.WAITING && uploadedPercentage <= 0;
|
||||
const isUploading = props.delivered == DeliveredMessageState.WAITING && uploadedPercentage > 0 && uploadedPercentage < 100;
|
||||
const {getDownloadsPath} = useCore();
|
||||
|
||||
const onClick = async () => {
|
||||
if(downloadStatus == DownloadStatus.ERROR){
|
||||
return;
|
||||
}
|
||||
if(downloadStatus == DownloadStatus.DOWNLOADED){
|
||||
//let content = await getBlob();
|
||||
//let buffer = Buffer.from(content.split(",")[1], 'base64');
|
||||
let pathInDownloads = window.downloadsPath + "/Rosetta Downloads/" + filename;
|
||||
const downloadsPath = await getDownloadsPath();
|
||||
let pathInDownloads = downloadsPath + "/Rosetta Downloads/" + filename;
|
||||
//await writeFile(pathInDownloads, buffer, false);
|
||||
window.shell.showItemInFolder(pathInDownloads);
|
||||
return;
|
||||
|
||||
@@ -19,7 +19,7 @@ export function MessageImage(props: AttachmentProps) {
|
||||
download,
|
||||
downloadStatus,
|
||||
getBlob,
|
||||
getPreview } = useAttachment(props.attachment, props.chacha_key_plain);
|
||||
getPreview } = useAttachment(props.attachment, props.parent);
|
||||
const mainRef = useRef<HTMLDivElement>(null);
|
||||
const error = downloadStatus == DownloadStatus.ERROR;
|
||||
const { open } = useImageViewer();
|
||||
@@ -29,6 +29,7 @@ export function MessageImage(props: AttachmentProps) {
|
||||
const [blurhashPreview, setBlurhashPreview] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
console.info("Consturcting image, download status: " + downloadStatus);
|
||||
constructBlob();
|
||||
constructFromBlurhash();
|
||||
}, [downloadStatus]);
|
||||
|
||||
@@ -21,6 +21,7 @@ export function Messages() {
|
||||
const isFirstRenderRef = useRef(true);
|
||||
const previousScrollHeightRef = useRef(0);
|
||||
const distanceFromButtomRef = useRef(0);
|
||||
const distanceFromTopRef = useRef(0);
|
||||
|
||||
const [affix, setAffix] = useState(false);
|
||||
const [wallpaper] = useSetting<string>
|
||||
@@ -120,7 +121,8 @@ export function Messages() {
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
|
||||
// Скроллим если пользователь внизу или это его собственное сообщение
|
||||
if ((shouldAutoScrollRef.current || lastMessage.from_me)) {
|
||||
if ((shouldAutoScrollRef.current || lastMessage.from_me) && distanceFromTopRef.current > 10) {
|
||||
console.info(distanceFromTopRef.current);
|
||||
scrollToBottom(true);
|
||||
}
|
||||
}, [messages.length, loading, scrollToBottom]);
|
||||
@@ -175,6 +177,8 @@ export function Messages() {
|
||||
onScrollPositionChange={(scroll) => {
|
||||
if (!viewportRef.current) return;
|
||||
|
||||
distanceFromTopRef.current = scroll.y;
|
||||
|
||||
// Загружаем старые сообщения при достижении верха
|
||||
if (scroll.y === 0 && !loading && messages.length >= 20) {
|
||||
loadMessagesToScrollAreaTop();
|
||||
|
||||
@@ -5,17 +5,19 @@ import { useProtocolState } from "@/app/providers/ProtocolProvider/useProtocolSt
|
||||
import { ProtocolState } from "@/app/providers/ProtocolProvider/ProtocolProvider";
|
||||
import { WindowsFrameButtons } from "../WindowsFrameButtons/WindowsFrameButtons";
|
||||
import { MacFrameButtons } from "../MacFrameButtons/MacFrameButtons";
|
||||
import { useCoreDevice } from "@/app/providers/DeviceProvider/useCoreDevice";
|
||||
|
||||
export function Topbar() {
|
||||
const colors = useRosettaColors();
|
||||
const [protocolState] = useProtocolState();
|
||||
const {platform} = useCoreDevice();
|
||||
|
||||
|
||||
return (
|
||||
<Box className={classes.drag} ta={'center'} p={3} bg={colors.mainColor}>
|
||||
{window.platform == 'win32' && <WindowsFrameButtons></WindowsFrameButtons>}
|
||||
{window.platform == 'darwin' && <MacFrameButtons></MacFrameButtons>}
|
||||
{window.platform == 'linux' && <WindowsFrameButtons></WindowsFrameButtons>}
|
||||
{platform == 'win32' && <WindowsFrameButtons></WindowsFrameButtons>}
|
||||
{platform == 'darwin' && <MacFrameButtons></MacFrameButtons>}
|
||||
{platform == 'linux' && <WindowsFrameButtons></WindowsFrameButtons>}
|
||||
{(protocolState == ProtocolState.CONNECTED || protocolState == ProtocolState.SYNCHRONIZATION || !window.location.hash.includes("main")) &&
|
||||
<Flex align={'center'} justify={'center'}>
|
||||
<Text fw={'bolder'} fz={13} c={'gray'}>
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import { AttachmentType } from "./providers/ProtocolProvider/protocol/packets/packet.message";
|
||||
|
||||
export const CORE_VERSION = window.version || "1.0.0";
|
||||
|
||||
/**
|
||||
* Application directives
|
||||
*/
|
||||
export const APPLICATION_PLATFROM = window.platform || "unknown";
|
||||
export const APPLICATION_ARCH = window.arch || "unknown";
|
||||
export const APP_PATH = window.appPath || ".";
|
||||
export const SIZE_LOGIN_WIDTH_PX = 300;
|
||||
export const DEVTOOLS_CHEATCODE = "rosettadev1";
|
||||
export const AVATAR_PASSWORD_TO_ENCODE = "rosetta-a";
|
||||
@@ -62,5 +56,6 @@ export const ALLOWED_DOMAINS_ZONES = [
|
||||
'gg',
|
||||
'fm',
|
||||
'tv',
|
||||
'im'
|
||||
'im',
|
||||
'sc'
|
||||
];
|
||||
62
app/hooks/useCore.ts
Normal file
62
app/hooks/useCore.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
export function useCore() {
|
||||
const openExternal = (url: string) => {
|
||||
window.shell.openExternal(url);
|
||||
};
|
||||
|
||||
const showItemInFolder = (fullPath: string) => {
|
||||
window.shell.showItemInFolder(fullPath);
|
||||
};
|
||||
|
||||
const getCoreVersion = async () => {
|
||||
const version = await window.electron.ipcRenderer.invoke('ipcCore:getCoreVersion');
|
||||
return version;
|
||||
}
|
||||
|
||||
const getArch = async () => {
|
||||
const arch = await window.electron.ipcRenderer.invoke('ipcCore:getArch');
|
||||
return arch;
|
||||
}
|
||||
|
||||
const getUserDir = async () => {
|
||||
const userDir = await window.electron.ipcRenderer.invoke('ipcCore:getUserDir');
|
||||
return userDir;
|
||||
}
|
||||
|
||||
const getAppPath = async () => {
|
||||
const appPath = await window.electron.ipcRenderer.invoke('ipcCore:getAppPath');
|
||||
return appPath;
|
||||
}
|
||||
|
||||
const getDownloadsPath = async () => {
|
||||
const downloadsPath = await window.electron.ipcRenderer.invoke('ipcCore:getDownloadsPath');
|
||||
return downloadsPath;
|
||||
}
|
||||
|
||||
const getPlatform = async () => {
|
||||
const platform = await window.electron.ipcRenderer.invoke('ipcCore:getPlatform');
|
||||
return platform;
|
||||
}
|
||||
|
||||
const getDeviceName = async () => {
|
||||
const deviceName = await window.electron.ipcRenderer.invoke('device:name');
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
const getDeviceId = async () => {
|
||||
const deviceId = await window.electron.ipcRenderer.invoke('device:id');
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
return {
|
||||
openExternal,
|
||||
showItemInFolder,
|
||||
getCoreVersion,
|
||||
getArch,
|
||||
getUserDir,
|
||||
getAppPath,
|
||||
getDownloadsPath,
|
||||
getPlatform,
|
||||
getDeviceName,
|
||||
getDeviceId
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useDownloadStatus } from "../TransportProvider/useDownloadStatus";
|
||||
import { useUploadStatus } from "../TransportProvider/useUploadStatus";
|
||||
import { useFileStorage } from "../../hooks/useFileStorage";
|
||||
@@ -10,10 +10,11 @@ import { useDialogsCache } from "../DialogProvider/useDialogsCache";
|
||||
import { useConsoleLogger } from "../../hooks/useConsoleLogger";
|
||||
import { Attachment, AttachmentType } from "../ProtocolProvider/protocol/packets/packet.message";
|
||||
import { useMemory } from "../MemoryProvider/useMemory";
|
||||
import { DialogContext } from "../DialogProvider/DialogProvider";
|
||||
import { useSaveAvatar } from "../AvatarProvider/useSaveAvatar";
|
||||
import { AVATAR_PASSWORD_TO_ENCODE } from "@/app/constants";
|
||||
import { useDialog } from "../DialogProvider/useDialog";
|
||||
import { useCore } from "@/app/hooks/useCore";
|
||||
import { MessageProps } from "@/app/components/Messages/Message";
|
||||
|
||||
export enum DownloadStatus {
|
||||
DOWNLOADED,
|
||||
@@ -24,7 +25,7 @@ export enum DownloadStatus {
|
||||
ERROR
|
||||
}
|
||||
|
||||
export function useAttachment(attachment: Attachment, keyPlain: string) {
|
||||
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 downloadPercentage = useDownloadStatus(attachment.id);
|
||||
@@ -37,13 +38,8 @@ export function useAttachment(attachment: Attachment, keyPlain: string) {
|
||||
const {updateAttachmentInDialogCache} = useDialogsCache();
|
||||
const {info} = useConsoleLogger('useAttachment');
|
||||
const {updateAttachmentsInMessagesByAttachmentId} = useDialog();
|
||||
|
||||
const {getDownloadsPath} = useCore();
|
||||
|
||||
const context = useContext(DialogContext);
|
||||
if(!context) {
|
||||
throw new Error("useAttachment must be used within a DialogProvider");
|
||||
}
|
||||
const {dialog} = context;
|
||||
const saveAvatar = useSaveAvatar();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -85,7 +81,8 @@ 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 downloadsPath = await getDownloadsPath();
|
||||
let pathInDownloads = downloadsPath + "/Rosetta Downloads/" + filename;
|
||||
const exists = await fileExists(pathInDownloads, false);
|
||||
const existsLength = await size(pathInDownloads, false);
|
||||
if(exists && existsLength == filesize){
|
||||
@@ -152,7 +149,7 @@ export function useAttachment(attachment: Attachment, keyPlain: string) {
|
||||
}
|
||||
setDownloadStatus(DownloadStatus.DECRYPTING);
|
||||
//console.info("Decrypted attachment ", Buffer.from(keyPlain, 'binary').toString('hex'));
|
||||
const decrypted = await decodeWithPassword(keyPlain, downloadedBlob);
|
||||
const decrypted = await decodeWithPassword(parentMessage.chacha_key_plain, downloadedBlob);
|
||||
setDownloadTag("");
|
||||
if(attachment.type == AttachmentType.FILE) {
|
||||
/**
|
||||
@@ -161,8 +158,9 @@ export function useAttachment(attachment: Attachment, keyPlain: string) {
|
||||
*/
|
||||
const preview = getPreview();
|
||||
const filename = preview.split("::")[1];
|
||||
const downloadsPath = await getDownloadsPath();
|
||||
let buffer = Buffer.from(decrypted.split(",")[1], 'base64');
|
||||
let pathInDownloads = window.downloadsPath + "/Rosetta Downloads/" + filename;
|
||||
let pathInDownloads = downloadsPath + "/Rosetta Downloads/" + filename;
|
||||
/**
|
||||
* Пишем файл в загрузки, но перед этим выбираем ему название, если файл в загрузках
|
||||
* уже есть с таким названием то добавляем к названию (1), (2) и так далее, чтобы не перезаписать существующий файл
|
||||
@@ -170,7 +168,7 @@ export function useAttachment(attachment: Attachment, keyPlain: string) {
|
||||
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);
|
||||
finalPath = downloadsPath + "/Rosetta Downloads/" + filename.split(".").slice(0, -1).join(".") + ` (${fileIndex})` + "." + filename.split(".").slice(-1);
|
||||
fileIndex++;
|
||||
}
|
||||
await writeFile(finalPath, buffer, false);
|
||||
@@ -185,7 +183,10 @@ export function useAttachment(attachment: Attachment, keyPlain: string) {
|
||||
await writeFile(avatarPath,
|
||||
Buffer.from(await encodeWithPassword(AVATAR_PASSWORD_TO_ENCODE, decrypted)));
|
||||
setDownloadStatus(DownloadStatus.DOWNLOADED);
|
||||
saveAvatar(dialog, avatarPath, decrypted);
|
||||
/**
|
||||
* Устанавливаем аватарку тому, кто ее прислал.
|
||||
*/
|
||||
saveAvatar(parentMessage.from, avatarPath, decrypted);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,15 @@ import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
|
||||
import { useSystemAccounts } from "../SystemAccountsProvider/useSystemAccounts";
|
||||
import { AVATAR_PASSWORD_TO_ENCODE } from "@/app/constants";
|
||||
|
||||
export const AvatarContext = createContext({});
|
||||
export interface AvatarProviderContextValue {
|
||||
deliveredAvatars: string[];
|
||||
saveAvatar: (fromPublicKey: string, path : string, decryptedContent : string) => Promise<void>;
|
||||
loadAvatarsFromCacheByPublicKey: (publicKey : string, allDecode? : boolean) => Promise<void>;
|
||||
changeAvatar: (base64Image : string, entity : string) => Promise<void>;
|
||||
decodedAvatarsCache: AvatarCacheEntry[];
|
||||
}
|
||||
|
||||
export const AvatarContext = createContext<AvatarProviderContextValue | null>(null);
|
||||
|
||||
interface AvatarProviderProps {
|
||||
children: React.ReactNode;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useContext } from "react";
|
||||
import { AvatarContext } from "./AvatarProvider";
|
||||
|
||||
export function useSaveAvatar() {
|
||||
export function useSaveAvatar() : (fromPublicKey: string, path : string, decryptedContent : string) => Promise<void> {
|
||||
const context : any = useContext(AvatarContext);
|
||||
if(!context){
|
||||
if(!context){
|
||||
throw new Error("useSaveAvatar must be used within an AvatarProvider");
|
||||
}
|
||||
return context.saveAvatar;
|
||||
|
||||
33
app/providers/DeviceProvider/useCoreDevice.ts
Normal file
33
app/providers/DeviceProvider/useCoreDevice.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useCore } from "@/app/hooks/useCore";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function useCoreDevice() : {
|
||||
deviceId: string;
|
||||
deviceName: string;
|
||||
platform: string;
|
||||
} {
|
||||
const { getDeviceId, getDeviceName, getPlatform } = useCore();
|
||||
const [deviceId, setDeviceId] = useState<string>("");
|
||||
const [deviceName, setDeviceName] = useState<string>("");
|
||||
const [platform, setPlatform] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
fetchDeviceInfo();
|
||||
}, []);
|
||||
|
||||
const fetchDeviceInfo = async () => {
|
||||
const deviceId = await getDeviceId();
|
||||
const deviceName = await getDeviceName();
|
||||
const platform = await getPlatform();
|
||||
setDeviceId(deviceId);
|
||||
setDeviceName(deviceName);
|
||||
setPlatform(platform);
|
||||
console.info("Device info - ID:", deviceId, "Name:", deviceName);
|
||||
}
|
||||
|
||||
return {
|
||||
deviceId,
|
||||
deviceName,
|
||||
platform
|
||||
}
|
||||
}
|
||||
@@ -840,10 +840,13 @@ export function DialogProvider(props: DialogProviderProps) {
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const decrypted = await decodeWithPassword(privatePlain, Buffer.from(fileData, 'binary').toString());
|
||||
let blob = "";
|
||||
if(meta.type != AttachmentType.IMAGE){
|
||||
blob = await decodeWithPassword(privatePlain, Buffer.from(fileData, 'binary').toString());
|
||||
}
|
||||
attachments.push({
|
||||
id: meta.id,
|
||||
blob: decrypted,
|
||||
blob: blob,
|
||||
type: meta.type,
|
||||
preview: meta.preview
|
||||
});
|
||||
|
||||
@@ -445,7 +445,7 @@ export function useDialogFiber() {
|
||||
* чтобы когда приходит пачка сообщений с сервера в момент того как
|
||||
* пользователь был неактивен, не слать уведомления по всем этим сообщениям
|
||||
*/
|
||||
if (!muted.includes(fromPublicKey) || protocolState == ProtocolState.SYNCHRONIZATION) {
|
||||
if (!muted.includes(fromPublicKey) || protocolState != ProtocolState.SYNCHRONIZATION) {
|
||||
/**
|
||||
* Если пользователь в муте или сейчас идет синхронизация - не отправляем уведомление
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@ import { decodeWithPassword, encodeWithPassword } from "@/app/workers/crypto/cry
|
||||
import { useFileStorage } from "@/app/hooks/useFileStorage";
|
||||
import { generateRandomKey } from "@/app/utils/utils";
|
||||
import { createContext, useEffect, useState } from "react";
|
||||
import { useCore } from "@/app/hooks/useCore";
|
||||
|
||||
interface SystemProviderContextValue {
|
||||
id: string;
|
||||
@@ -21,7 +22,10 @@ export interface SystemProviderProps {
|
||||
*/
|
||||
export function SystemProvider(props: SystemProviderProps) {
|
||||
const [deviceId, setDeviceId] = useState<string>("");
|
||||
const [deviceName, setDeviceName] = useState<string>("");
|
||||
const [deviceOs, setDeviceOs] = useState<string>("");
|
||||
const {writeFile, readFile} = useFileStorage();
|
||||
const { getDeviceId, getDeviceName, getPlatform } = useCore();
|
||||
|
||||
useEffect(() => {
|
||||
fetchDeviceId();
|
||||
@@ -29,6 +33,10 @@ export function SystemProvider(props: SystemProviderProps) {
|
||||
|
||||
const fetchDeviceId = async () => {
|
||||
const device = await readFile("device");
|
||||
const name = await getDeviceName();
|
||||
const platform = await getPlatform();
|
||||
setDeviceName(name);
|
||||
setDeviceOs(platform);
|
||||
if(device){
|
||||
const decoded = await decodeDevice(Buffer.from(device).toString('utf-8'));
|
||||
if(decoded){
|
||||
@@ -47,12 +55,11 @@ export function SystemProvider(props: SystemProviderProps) {
|
||||
}
|
||||
|
||||
const decodeDevice = async (data: string) => {
|
||||
const hwid = window.deviceId;
|
||||
const platform = window.deviceName;
|
||||
const hwid = await getDeviceId();
|
||||
const deviceName = await getDeviceName();
|
||||
const salt = "rosetta-device-salt";
|
||||
|
||||
try {
|
||||
const decoded = await decodeWithPassword(hwid + platform + salt, data);
|
||||
const decoded = await decodeWithPassword(hwid + deviceName + salt, data);
|
||||
return decoded;
|
||||
} catch (e) {
|
||||
console.error("Failed to decode device data:", e);
|
||||
@@ -61,29 +68,24 @@ export function SystemProvider(props: SystemProviderProps) {
|
||||
}
|
||||
|
||||
const encodeDevice = async (data: string) => {
|
||||
const hwid = window.deviceId;
|
||||
const platform = window.deviceName;
|
||||
const hwid = await getDeviceId();
|
||||
const deviceName = await getDeviceName();
|
||||
const salt = "rosetta-device-salt";
|
||||
|
||||
try {
|
||||
const encoded = await encodeWithPassword(hwid + platform + salt, data);
|
||||
const encoded = await encodeWithPassword(hwid + deviceName + salt, data);
|
||||
return encoded;
|
||||
} catch (e) {
|
||||
console.error("Failed to encode device data:", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const systemName = window.deviceName || "Unknown Device";
|
||||
const systemOs = window.platform || "Unknown OS";
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<SystemProviderContext.Provider value={{
|
||||
id: deviceId,
|
||||
name: systemName,
|
||||
os: systemOs
|
||||
name: deviceName || "Unknown Device",
|
||||
os: deviceOs || "Unknown OS"
|
||||
}}>
|
||||
{props.children}
|
||||
</SystemProviderContext.Provider>
|
||||
|
||||
@@ -4,8 +4,8 @@ import { useSender } from "../ProtocolProvider/useSender";
|
||||
import { usePacket } from "../ProtocolProvider/usePacket";
|
||||
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
|
||||
import { useFileStorage } from "@/app/hooks/useFileStorage";
|
||||
import { APPLICATION_ARCH, APPLICATION_PLATFROM, CORE_VERSION } from "@/app/constants";
|
||||
import { APP_VERSION } from "@/app/version";
|
||||
import { useCore } from "@/app/hooks/useCore";
|
||||
|
||||
export interface UpdateProviderProps {
|
||||
children: React.ReactNode;
|
||||
@@ -58,6 +58,7 @@ export function UpdateProvider(props: UpdateProviderProps) {
|
||||
const [appUpdateUrl, setAppUpdateUrl] = useState<string>("");
|
||||
const [appActualVersion, setAppActualVersion] = useState<string>("");
|
||||
const {writeFile} = useFileStorage();
|
||||
const {getCoreVersion, getArch, getPlatform} = useCore();
|
||||
|
||||
useEffect(() => {
|
||||
let packet = new PacketRequestUpdate();
|
||||
@@ -75,6 +76,9 @@ export function UpdateProvider(props: UpdateProviderProps) {
|
||||
}, []);
|
||||
|
||||
const checkForUpdates = async () => {
|
||||
const coreVersion = await getCoreVersion();
|
||||
const arch = await getArch();
|
||||
const platform = await getPlatform();
|
||||
if(updateServerRef.current == null){
|
||||
/**
|
||||
* SDU еще не определен
|
||||
@@ -85,7 +89,7 @@ export function UpdateProvider(props: UpdateProviderProps) {
|
||||
* Запрашиваем обновления с SDU сервера
|
||||
*/
|
||||
let response = await fetch
|
||||
(`${updateServerRef.current}/updates/get?app=${APP_VERSION}&kernel=${CORE_VERSION}&arch=${APPLICATION_ARCH}&platform=${APPLICATION_PLATFROM}`).catch((e) => {
|
||||
(`${updateServerRef.current}/updates/get?app=${APP_VERSION}&kernel=${coreVersion}&arch=${arch}&platform=${platform}`).catch((e) => {
|
||||
error("Failed to check for updates: " + e.message);
|
||||
});
|
||||
if(!response || response.status != 200){
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
export const APP_VERSION = "1.0.4";
|
||||
export const CORE_MIN_REQUIRED_VERSION = "1.4.9";
|
||||
export const APP_VERSION = "1.0.5";
|
||||
export const CORE_MIN_REQUIRED_VERSION = "1.5.0";
|
||||
|
||||
export const RELEASE_NOTICE = `
|
||||
**Обновление v1.0.4** :emoji_1f631:
|
||||
- Улучшеный UI для взаимодействия с отправкой изображений
|
||||
- Исправлена блокировка потока при отправке изображений большого размера
|
||||
- Исправлены проблемы с утечками памяти
|
||||
- Исправлен вылет из приложения при попытке переслать сообщение
|
||||
- Исправлены проблемы со скроллам в групповых чатах
|
||||
- Исправлены проблемы с дерганием скролла в личных сообщениях
|
||||
- Улучшен наблюдатель за изменениями размера в контенте
|
||||
- Исправлена проблема с отображением аватара в упоминаниях
|
||||
- Множественные исправления мелких багов и улучшения производительности
|
||||
**Обновление v1.0.5** :emoji_1f631:
|
||||
- Оптимизирован код ядра
|
||||
- Исправление ошибки с системой обновления в результате гонки потоков в ядре
|
||||
- Исправление уведомлений при синхронизации
|
||||
- Анимация перемещения диалогов
|
||||
- Оптимизирован код вложений
|
||||
- Исправлен скролл при подгрузке сообщений сверху
|
||||
- Ускорена загрузка диалогов при большом количестве тяжелых изображений
|
||||
`;
|
||||
@@ -10,11 +10,14 @@ import { AnimatedButton } from "@/app/components/AnimatedButton/AnimatedButton";
|
||||
import { useLogout } from "@/app/providers/AccountProvider/useLogout";
|
||||
import { usePacket } from "@/app/providers/ProtocolProvider/usePacket";
|
||||
import { PacketDeviceResolve, Solution } from "@/app/providers/ProtocolProvider/protocol/packets/packet.device.resolve";
|
||||
import { useCoreDevice } from "@/app/providers/DeviceProvider/useCoreDevice";
|
||||
|
||||
export function DeviceConfirm() {
|
||||
const [protocolState] = useProtocolState();
|
||||
const navigate = useNavigate();
|
||||
const logout = useLogout();
|
||||
const {deviceName} = useCoreDevice();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if(protocolState == ProtocolState.CONNECTED) {
|
||||
@@ -60,7 +63,7 @@ export function DeviceConfirm() {
|
||||
<Flex justify={'center'} mt={'xl'} px={'lg'} align={'center'}>
|
||||
<Flex justify={'center'} gap={'sm'} align={'center'}>
|
||||
<Text ta={'center'} c={'dimmed'} fz={12}>
|
||||
Confirm device <strong>{window.deviceName}</strong> on your first device to loading your chats.
|
||||
Confirm device <strong>{deviceName}</strong> on your first device to loading your chats.
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -4,15 +4,26 @@ import { RosettaPower } from "@/app/components/RosettaPower/RosettaPower";
|
||||
import { SettingsAlert } from "@/app/components/SettingsAlert/SettingsAlert";
|
||||
import { SettingsInput } from "@/app/components/SettingsInput/SettingsInput";
|
||||
import { UpdateAlert } from "@/app/components/UpdateAlert/UpdateAlert";
|
||||
import { CORE_VERSION } from "@/app/constants";
|
||||
import { useCore } from "@/app/hooks/useCore";
|
||||
import { UpdateStatus } from "@/app/providers/UpdateProvider/UpdateProvider";
|
||||
import { useUpdater } from "@/app/providers/UpdateProvider/useUpdater";
|
||||
import { APP_VERSION } from "@/app/version";
|
||||
import { Box, Text } from "@mantine/core";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function Update() {
|
||||
const {updateStatus} = useUpdater();
|
||||
|
||||
const {getCoreVersion} = useCore();
|
||||
const [coreVersion, setCoreVersion] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCoreVersion = async () => {
|
||||
const version = await getCoreVersion();
|
||||
setCoreVersion(version);
|
||||
}
|
||||
fetchCoreVersion();
|
||||
}, [getCoreVersion]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Breadcrumbs text="Updates"></Breadcrumbs>
|
||||
@@ -23,7 +34,7 @@ export function Update() {
|
||||
<Box mt={'sm'}>
|
||||
<UpdateAlert radius={'sm'}></UpdateAlert>
|
||||
</Box>
|
||||
<SettingsInput.Copy mt={'sm'} hit="Kernel" value={CORE_VERSION}></SettingsInput.Copy>
|
||||
<SettingsInput.Copy mt={'sm'} hit="Kernel" value={coreVersion}></SettingsInput.Copy>
|
||||
<Text fz={10} mt={3} c={'gray'} pl={'xs'} pr={'xs'}>
|
||||
If the kernel version is outdated, you need to reinstall the application so that this kernel continues to receive current updates.
|
||||
</Text>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BrowserWindow, shell, app, ipcMain, nativeTheme, screen, powerMonitor } from 'electron'
|
||||
import { BrowserWindow, shell, ipcMain, nativeTheme, screen, powerMonitor } from 'electron'
|
||||
import { join } from 'path'
|
||||
import fs from 'fs'
|
||||
import { WORKING_DIR } from './constants';
|
||||
@@ -79,11 +79,6 @@ export function foundationIpcRegistration(mainWindow: BrowserWindow) {
|
||||
ipcMain.removeAllListeners("write-file");
|
||||
ipcMain.removeAllListeners("read-file");
|
||||
ipcMain.removeAllListeners("mkdir");
|
||||
ipcMain.removeHandler("get-core-version");
|
||||
ipcMain.removeHandler("get-arch");
|
||||
ipcMain.removeAllListeners("get-user-dir");
|
||||
ipcMain.removeHandler("get-downloads-path")
|
||||
ipcMain.removeHandler("get-app-path");
|
||||
ipcMain.removeHandler('open-dev-tools');
|
||||
ipcMain.removeHandler('window-state');
|
||||
ipcMain.removeHandler('window-toggle');
|
||||
@@ -92,14 +87,6 @@ export function foundationIpcRegistration(mainWindow: BrowserWindow) {
|
||||
ipcMain.removeHandler('showItemInFolder');
|
||||
ipcMain.removeHandler('openExternal');
|
||||
|
||||
ipcMain.handle('showItemInFolder', (_, fullPath: string) => {
|
||||
shell.showItemInFolder(fullPath);
|
||||
});
|
||||
|
||||
ipcMain.handle('openExternal', (_, url: string) => {
|
||||
shell.openExternal(url);
|
||||
});
|
||||
|
||||
ipcMain.handle('open-dev-tools', () => {
|
||||
if (mainWindow.webContents.isDevToolsOpened()) {
|
||||
return;
|
||||
@@ -208,27 +195,4 @@ export function foundationIpcRegistration(mainWindow: BrowserWindow) {
|
||||
mainWindow.webContents.send("mkdir-reply");
|
||||
});
|
||||
});
|
||||
/**
|
||||
* Change to get-core-version
|
||||
*/
|
||||
ipcMain.handle("get-core-version", () => {
|
||||
return app.getVersion();
|
||||
});
|
||||
|
||||
ipcMain.handle("get-arch", () => {
|
||||
return process.arch;
|
||||
})
|
||||
|
||||
ipcMain.on("get-user-dir", () => {
|
||||
const userDir = app.getPath("userData");
|
||||
mainWindow.webContents.send("get-user-dir-reply", userDir);
|
||||
});
|
||||
|
||||
ipcMain.handle("get-app-path", () => {
|
||||
return app.getAppPath();
|
||||
});
|
||||
|
||||
ipcMain.handle("get-downloads-path", () => {
|
||||
return app.getPath("downloads");
|
||||
});
|
||||
}
|
||||
|
||||
34
lib/main/ipcs/ipcCore.ts
Normal file
34
lib/main/ipcs/ipcCore.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { app, ipcMain, shell } from "electron";
|
||||
|
||||
ipcMain.handle("ipcCore:getCoreVersion", () => {
|
||||
return app.getVersion();
|
||||
});
|
||||
|
||||
ipcMain.handle("ipcCore:getArch", () => {
|
||||
return process.arch;
|
||||
})
|
||||
|
||||
ipcMain.handle("ipcCore:getUserDir", () => {
|
||||
const userDir = app.getPath("userData");
|
||||
return userDir;
|
||||
});
|
||||
|
||||
ipcMain.handle("ipcCore:getAppPath", () => {
|
||||
return app.getAppPath();
|
||||
});
|
||||
|
||||
ipcMain.handle("ipcCore:getDownloadsPath", () => {
|
||||
return app.getPath("downloads");
|
||||
});
|
||||
|
||||
ipcMain.handle('ipcCore:showItemInFolder', (_, fullPath: string) => {
|
||||
shell.showItemInFolder(fullPath);
|
||||
});
|
||||
|
||||
ipcMain.handle('ipcCore:openExternal', (_, url: string) => {
|
||||
shell.openExternal(url);
|
||||
});
|
||||
|
||||
ipcMain.handle('ipcCore:getPlatform', () => {
|
||||
return process.platform;
|
||||
});
|
||||
@@ -7,6 +7,7 @@ import './ipcs/ipcFilestorage'
|
||||
import './ipcs/ipcUpdate'
|
||||
import './ipcs/ipcNotification'
|
||||
import './ipcs/ipcDevice'
|
||||
import './ipcs/ipcCore'
|
||||
import { Tray } from 'electron/main'
|
||||
import { join } from 'path'
|
||||
import { Logger } from './logger'
|
||||
|
||||
@@ -4,46 +4,25 @@ import api from './api'
|
||||
|
||||
|
||||
const exposeContext = async () => {
|
||||
let version = await ipcRenderer.invoke("get-core-version");
|
||||
let appPath = await ipcRenderer.invoke("get-app-path");
|
||||
let arch = await ipcRenderer.invoke("get-arch");
|
||||
let deviceName = await ipcRenderer.invoke("device:name");
|
||||
let deviceId = await ipcRenderer.invoke("device:id");
|
||||
|
||||
let downloadsPath = await ipcRenderer.invoke("get-downloads-path");
|
||||
if (process.contextIsolated) {
|
||||
try {
|
||||
contextBridge.exposeInMainWorld('electron', electronAPI)
|
||||
contextBridge.exposeInMainWorld('api', api)
|
||||
contextBridge.exposeInMainWorld('version', version);
|
||||
contextBridge.exposeInMainWorld('platform', process.platform);
|
||||
contextBridge.exposeInMainWorld('appPath', appPath);
|
||||
contextBridge.exposeInMainWorld('arch', arch);
|
||||
contextBridge.exposeInMainWorld('deviceName', deviceName);
|
||||
contextBridge.exposeInMainWorld('deviceId', deviceId);
|
||||
contextBridge.exposeInMainWorld('shell', {
|
||||
openExternal: (url: string) => {
|
||||
ipcRenderer.invoke('openExternal', url);
|
||||
ipcRenderer.invoke('ipcCore:openExternal', url);
|
||||
},
|
||||
showItemInFolder: (fullPath: string) => {
|
||||
ipcRenderer.invoke('showItemInFolder', fullPath);
|
||||
ipcRenderer.invoke('ipcCore:showItemInFolder', fullPath);
|
||||
}
|
||||
});
|
||||
contextBridge.exposeInMainWorld('downloadsPath', downloadsPath)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
} else {
|
||||
window.electron = electronAPI
|
||||
window.api = api
|
||||
window.version = version;
|
||||
window.platform = process.platform;
|
||||
window.appPath = appPath;
|
||||
window.arch = arch;
|
||||
window.api = api;
|
||||
window.shell = shell;
|
||||
window.downloadsPath = downloadsPath;
|
||||
window.deviceName = deviceName;
|
||||
window.deviceId = deviceId;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Rosetta",
|
||||
"version": "1.4.9",
|
||||
"version": "1.5.0",
|
||||
"description": "Rosetta Messenger",
|
||||
"main": "./out/main/main.js",
|
||||
"license": "MIT",
|
||||
|
||||
Reference in New Issue
Block a user