This commit is contained in:
rosetta
2026-01-30 05:01:05 +02:00
commit 83f38dc63f
327 changed files with 18725 additions and 0 deletions

BIN
lib/.DS_Store vendored Normal file

Binary file not shown.

227
lib/main/app.ts Normal file
View File

@@ -0,0 +1,227 @@
import { BrowserWindow, shell, app, ipcMain, nativeTheme, screen, powerMonitor } from 'electron'
import { join } from 'path'
import fs from 'fs'
import { WORKING_DIR } from './constants';
import { boot } from './boot/bootloader';
export async function startApplication() {
let preloaderWindow = createPreloaderWindow();
await fs.promises.mkdir(WORKING_DIR, { recursive: true });
createAppWindow(preloaderWindow);
}
export function createPreloaderWindow() {
let preloaderWindow = new BrowserWindow({
width: 150,
height: 150,
frame: false,
transparent: true,
center: true,
resizable: false,
alwaysOnTop: true
});
preloaderWindow.loadFile(join(__dirname, '../../resources/preload.html'));
return preloaderWindow;
}
export function createAppWindow(preloaderWindow?: BrowserWindow): void {
const mainWindow = new BrowserWindow({
width: 900,
height: 670,
minWidth: 385,
minHeight: 555,
show: false,
title: 'Rosetta Messager',
icon: join(__dirname, '../../resources/R.png'),
frame: false,
autoHideMenuBar: true,
backgroundColor: '#000',
webPreferences: {
preload: join(__dirname, '../preload/preload.js'),
sandbox: false,
nodeIntegration: true,
nodeIntegrationInSubFrames: true,
nodeIntegrationInWorker: true,
webSecurity: false,
allowRunningInsecureContent: true
}
});
powerMonitor.on('lock-screen', () => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.reload();
}
});
foundationIpcRegistration(mainWindow);
mainWindow.webContents.on('did-finish-load', () => {
if (preloaderWindow && !preloaderWindow.isDestroyed()) {
preloaderWindow.close();
}
mainWindow.show();
});
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
});
boot(mainWindow);
}
export function foundationIpcRegistration(mainWindow: BrowserWindow) {
ipcMain.removeAllListeners('window-resize');
ipcMain.removeAllListeners('window-resizeble');
ipcMain.removeAllListeners('window-theme');
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');
ipcMain.removeHandler('window-close');
ipcMain.removeHandler('window-minimize');
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;
}
mainWindow.webContents.openDevTools({ mode: 'detach' });
});
ipcMain.on('window-resize', (_, { width, height }) => {
const { x: currentX, y: currentY, width: currentWidth, height: currentHeight } = mainWindow.getBounds();
const newX = currentX + (currentWidth - width) / 2;
const newY = currentY + (currentHeight - height) / 2;
const { width: screenWidth, height: screenHeight } = screen.getPrimaryDisplay().workAreaSize;
const clampedX = Math.max(0, Math.min(newX, screenWidth - width));
//const clampedY = Math.max(0, Math.min(newY, screenHeight - height));
mainWindow.setBounds({
x: Math.round(clampedX),
//y: Math.round(clampedY),
width,
height,
});
});
ipcMain.on('window-resizeble', (_, isResizeble) => {
mainWindow.setResizable(isResizeble);
mainWindow.webContents.send('window-state-changed');
});
ipcMain.on('window-theme', (_, theme) => {
nativeTheme.themeSource = theme;
})
ipcMain.on("write-file", (_, filePath, content) => {
fs.writeFile(filePath, content, (err) => {
if (err) {
console.error(err);
return;
}
});
});
ipcMain.handle('window-state', () => {
return {
isMinimized: mainWindow.isMinimized(),
isMaximized: mainWindow.isMaximized(),
isFullScreen: mainWindow.isFullScreen(),
isVisible: mainWindow.isVisible(),
isFocused: mainWindow.isFocused(),
isResizable: mainWindow.isResizable(),
isClosable: mainWindow.isClosable(),
isDestroyed: mainWindow.isDestroyed(),
bounds: mainWindow.getBounds()
};
});
ipcMain.handle('window-toggle', () => {
if (mainWindow.isMaximized() || mainWindow.isFullScreen()) {
mainWindow.setFullScreen(false);
mainWindow.unmaximize();
} else {
mainWindow.setFullScreen(true);
}
setTimeout(() => {
/**
* Разобраться с этой хуйней
*/
mainWindow.webContents.send('window-state-changed');
}, 700);
});
ipcMain.handle('window-minimize', () => {
mainWindow.minimize();
mainWindow.webContents.send('window-state-changed');
});
ipcMain.handle('window-close', () => {
mainWindow.hide();
mainWindow.webContents.send('window-state-changed');
});
ipcMain.on("read-file", (_, filePath) => {
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}
mainWindow.webContents.send("read-file-reply", data);
});
});
ipcMain.on("mkdir", (_, dirPath) => {
fs.mkdir(dirPath, { recursive: true }, (err) => {
if (err) {
console.error(err);
return;
}
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");
});
}

View File

@@ -0,0 +1,67 @@
import { app, BrowserWindow, ipcMain } from "electron";
import fs from 'fs/promises'
import { WORKING_DIR } from "../constants";
import path from "path";
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');
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 окно
*/
export async function boot(window : BrowserWindow) {
if (!app.isPackaged && process.env['ELECTRON_RENDERER_URL']) {
await bootDevelopment(window);
// await bootProduction(window);
window.webContents.openDevTools({ mode: 'detach' });
//console.info(window.webContents);
return;
}
//window.webContents.openDevTools({ mode: 'detach' });
await bootProduction(window);
}
async function bootProduction(window : BrowserWindow) {
logger.log("Booting in production mode");
let html = path.join(WORKING_DIR, 'b', 'j.html');
if(await existsFile(html)){
logger.log(`Loading compiled file`);
window.loadFile(html);
} else {
logger.log(`Loading conatiner file`);
window.loadFile(path.join(__dirname, '../renderer/dev.html'));
}
}
async function bootDevelopment(window : BrowserWindow) {
logger.log("Booting in development mode");
window.loadURL(process.env['ELECTRON_RENDERER_URL'] + "/dev.html");
window.webContents.openDevTools({ mode: 'detach' });
}
async function existsFile(filePath: string): Promise<boolean> {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}

130
lib/main/boot/compiler.ts Normal file
View File

@@ -0,0 +1,130 @@
import path from "path";
import { WORKING_DIR } from "../constants";
import fs from 'fs/promises'
import JSZip from "jszip";
import crypto from "crypto";
import { Logger } from "../logger";
const logger = Logger('compiler');
export interface BundleFile {
file: string;
type: string;
main: boolean;
}
const binaryFileTypes = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'mp4', 'mp3', 'wav', 'ogg', 'pdf', 'zip', 'rar', '7z'];
export async function compileBundleFile(zip : JSZip) {
let timestart = Date.now();
logger.log("Starting compilation of bundle file");
let files : BundleFile[] = await getAllFilesInBundleForCompile(zip);
if(files.length == 0){
logger.log("No files to compile in bundle");
throw new Error("No files to compile in bundle");
}
let compileOutputDir = path.join(WORKING_DIR, 'b');
await fs.mkdir(compileOutputDir, { recursive: true });
let mainFiles = files.filter(f => f.main);
let otherFiles = files.filter(f => !f.main);
logger.log(`Compiling ${files.length} files, ${mainFiles.length} main files and ${otherFiles.length} other files`);
for (let file of files) {
let zipFile = zip.file(file.file);
if (zipFile) {
let content = await zipFile.async('nodebuffer');
let outputPath = path.join(compileOutputDir, await hashFileName(path.basename(file.file)));
await fs.writeFile(outputPath, content);
}
}
await compileHtmlFile(mainFiles);
await transitionFilenamesInFiles([...mainFiles, ...otherFiles]);
logger.log("Compilation done successfully in " + (Date.now() - timestart) + "ms");
}
async function transitionFilenamesInFiles(otherFiles: BundleFile[]) {
logger.log(`Transitioning filenames in ${otherFiles.length} files`);
for(let i = 0; i < otherFiles.length; i++){
let file = otherFiles[i];
let filePath = path.join(WORKING_DIR, 'b', await hashFileName(path.basename(file.file)));
let buffer = await fs.readFile(filePath, {
encoding: file.type && binaryFileTypes.includes(file.type) ? 'binary' : 'utf-8'
});
let content = buffer.toString();
logger.log(`Processing file ${path.basename(file.file)}`);
for(let j = 0; j < otherFiles.length; j++){
let targetFile = otherFiles[j];
let originalName = path.basename(targetFile.file);
let hashedName = await hashFileName(originalName);
let entries = content.split(originalName);
if(entries.length <= 0){
continue;
}
logger.log(`In file ${path.basename(file.file)} replacing ${originalName} with ${hashedName} (${entries.length - 1} occurrences)`);
content = entries.join(hashedName);
}
logger.log(`Flush transitioned file ${path.basename(file.file)}`);
await fs.writeFile(filePath, content, {
encoding: file.type && binaryFileTypes.includes(file.type) ? 'binary' : 'utf-8'
});
}
}
async function compileHtmlFile(mainFiles: BundleFile[]) {
let html = await constructHtmlTemplateToCompile();
html = html.replace(`<script type="module" src="/renderer.tsx"></script>`, '');
let scripts = mainFiles.filter(f => f.type === 'js').map(f => f.file);
let styles = mainFiles.filter(f => f.type === 'css').map(f => f.file);
let scriptTags : string = "";
for (let script of scripts) {
let hashedName = await hashFileName(path.basename(script));
scriptTags += `<script type="module" src="${hashedName}"></script>\n`;
}
let styleTags : string = "";
for (let style of styles) {
let hashedName = await hashFileName(path.basename(style));
styleTags += `<link rel="stylesheet" href="${hashedName}">\n`;
}
html = html.replace('<!--RR-->', `${scriptTags}\n${styleTags}`);
html = html.replace('\n', '');
let outputHtmlPath = path.join(WORKING_DIR, 'b', 'j.html');
await fs.writeFile(outputHtmlPath, html);
}
export async function getAllFilesInBundleForCompile(zip : JSZip) : Promise<BundleFile[]> {
const mainFilePrefixes = ['index-', 'main-'];
return Object.keys(zip.files)
.filter(filePath => filePath.indexOf('__MACOSX') == -1)
.map(filePath => {
const ext = path.extname(filePath).slice(1);
const isMain = mainFilePrefixes.some(prefix => path.basename(filePath).startsWith(prefix));
return {
file: filePath,
type: ext,
main: isMain
} as BundleFile;
});
}
async function constructHtmlTemplateToCompile() {
let buffer = await fs.readFile(
path.join(__dirname, '../../resources/prod.html')
);
let html = buffer.toString();
return html;
}
async function hashFileName(name : string) : Promise<string> {
return crypto.createHash('md5').update(name).digest('hex') + path.extname(name);
}

10
lib/main/boot/updater.ts Normal file
View File

@@ -0,0 +1,10 @@
import fs from 'fs/promises'
import JSZip from "jszip";
import { compileBundleFile } from './compiler';
export async function installServiceUpdate(pathToUpdate : string) {
let data = await fs.readFile(pathToUpdate);
let zip = await JSZip.loadAsync(data);
await compileBundleFile(zip);
await fs.unlink(pathToUpdate);
}

8
lib/main/constants.ts Normal file
View File

@@ -0,0 +1,8 @@
import { app } from 'electron';
import path from 'path';
export const WORKING_DIR =
path.join(app.getPath('home'), 'rosed');
export const LOGFILE_PATH =
path.join(WORKING_DIR, 'rosetta.log');

51
lib/main/database.ts Normal file
View File

@@ -0,0 +1,51 @@
import path from 'path';
import { WORKING_DIR } from './constants';
import { promises as fs } from 'fs';
import sqlite3 from 'sqlite3'
let db : any = null;
export async function initializeDatabase(){
await fs.mkdir(WORKING_DIR, { recursive: true });
const dbPath = path.join(WORKING_DIR, 'r_d');
const dbLink = new sqlite3.Database(dbPath);
db = dbLink;
}
export function runQuery(query: string, params: any[] = []) : Promise<void> {
return new Promise((resolve, reject) => {
db.run(query, params, function (err) {
if (err) {
reject();
} else {
resolve();
}
});
});
}
export function getQuery(query: string, params: any[] = []): Promise<any> {
return new Promise((resolve, reject) => {
db.get(query, params, (err, row) => {
if (err) {
reject(err);
} else {
resolve(row);
}
});
});
}
export function allQuery(query: string, params: any[] = []): Promise<any[]> {
return new Promise((resolve, reject) => {
db.all(query, params, (err, rows) => {
if (err) {
reject(err);
} else {
resolve(rows);
}
});
});
}
initializeDatabase();

27
lib/main/index.d.ts vendored Normal file
View File

@@ -0,0 +1,27 @@
/// <reference types="electron-vite/node" />
declare module '*.png' {
const content: string
export default content
}
declare module '*.jpg' {
const content: string
export default content
}
declare module '*.jpeg' {
const content: string
export default content
}
declare module '*.svg' {
const content: string
export default content
}
declare module '*.web' {
const content: string
export default content
}

View File

@@ -0,0 +1,14 @@
import { ipcMain } from 'electron';
import { runQuery, getQuery, allQuery } from '../database';
ipcMain.handle('db:run', async (_, query: string, params: any[]) => {
return await runQuery(query, params);
});
ipcMain.handle('db:get', async (_, query: string, params: any[]) => {
return await getQuery(query, params);
});
ipcMain.handle('db:all', async (_, query: string, params: any[]) => {
return await allQuery(query, params);
});

View File

@@ -0,0 +1,36 @@
import { ipcMain } from "electron";
import os from "os";
import {machineId} from 'node-machine-id';
/**
* Consturct device name.
* Ex: Macbook Pro M3
*/
ipcMain.handle('device:name', () => {
const type = os.type(); // 'Darwin', 'Windows_NT', 'Linux'
let deviceName = "";
if (type === "Darwin") {
deviceName += "Mac";
} else if (type === "Windows_NT") {
deviceName += "Windows";
} else if (type === "Linux") {
deviceName += "Linux";
} else {
deviceName += type + " ";
}
const cpus = os.cpus();
if (cpus && cpus.length > 0) {
const cpuModel = cpus[0].model;
deviceName += cpuModel.replace("Apple", "").replace("Processor", "");
}
return deviceName.trim();
});
ipcMain.handle('device:id', async () => {
return await machineId();
});

View File

@@ -0,0 +1,22 @@
import { ipcMain } from "electron";
import { WORKING_DIR } from "../constants";
import fs from 'fs/promises'
import path from 'path'
ipcMain.handle('fileStorage:writeFile', async (_, file: string, data: string | Buffer, inWorkingDir : boolean = true) => {
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) => {
try{
const fullPath = path.join(inWorkingDir ? WORKING_DIR : '', file);
const data = await fs.readFile(fullPath);
return data;
}catch(e){
return null;
}
});

View File

@@ -0,0 +1,10 @@
import { ipcMain } from 'electron';
import { promises as fs } from 'fs';
import { LOGFILE_PATH } from '../constants';
ipcMain.handle('logger:log', async (_, logString) => {
console.log(logString);
await fs.appendFile(LOGFILE_PATH, logString + '\n');
});

View File

@@ -0,0 +1,16 @@
import { ipcMain, Notification } from "electron";
import { restoreApplicationAfterClickOnTrayOrDock } from "../main";
ipcMain.handle('notification:show', async (_, title: string, body: string) => {
let id = Math.random().toString(36).substring(2, 15);
let note = new Notification({
title: title,
body: body
});
note.on('click', () => {
restoreApplicationAfterClickOnTrayOrDock();
ipcMain.emit('notification:clicked', id);
});
note.show();
return id;
});

View File

@@ -0,0 +1,14 @@
import { app, ipcMain } from "electron";
import { installServiceUpdate } from "../boot/updater";
import path from "path";
import { WORKING_DIR } from "../constants";
ipcMain.handle('update:installServiceUpdate', async (_, bundleName: string) => {
await installServiceUpdate(path.join(WORKING_DIR, bundleName));
});
ipcMain.handle('update:restartApp', async () => {
app.relaunch();
app.exit(0);
});

16
lib/main/logger.ts Normal file
View File

@@ -0,0 +1,16 @@
import { promises as fs } from 'fs';
import { LOGFILE_PATH } from './constants';
export function Logger(component: string) {
const log = async (message: string) => {
const date = new Date().toISOString();
const logMessage = `[main_proc] [${date}] [${component}] ${message}`;
console.log(logMessage);
await fs.appendFile(LOGFILE_PATH, logMessage + '\n');
}
return {
log
};
}

102
lib/main/main.ts Normal file
View File

@@ -0,0 +1,102 @@
import { app, BrowserWindow, Menu, nativeImage } from 'electron'
import { electronApp, optimizer } from '@electron-toolkit/utils'
import { createAppWindow, startApplication } from './app'
import './ipcs/ipcDatabase'
import './ipcs/ipcLogger'
import './ipcs/ipcFilestorage'
import './ipcs/ipcUpdate'
import './ipcs/ipcNotification'
import './ipcs/ipcDevice'
import { Tray } from 'electron/main'
import { join } from 'path'
import { Logger } from './logger'
let lockInstance = app.requestSingleInstanceLock();
let tray : Tray | null = null;
const size = process.platform === 'darwin' ? 18 : 22;
const logger = Logger('main');
const icon = nativeImage.createFromPath(
join(__dirname, '../../resources/R.png')
).resize({ width: size, height: size });
if(!lockInstance){
app.quit();
process.exit(0);
}
process.on('unhandledRejection', (reason) => {
logger.log(`main thread error, reason: ${reason}`);
});
app.disableHardwareAcceleration();
app.on('second-instance', () => {
// Someone tried to run a second instance, we should focus our window.
const allWindows = BrowserWindow.getAllWindows();
if (allWindows.length) {
const mainWindow = allWindows[0];
if (mainWindow.isMinimized()) mainWindow.restore();
if (mainWindow.isVisible() === false) mainWindow.show();
mainWindow.focus();
}
});
export const restoreApplicationAfterClickOnTrayOrDock = () => {
const allWindows = BrowserWindow.getAllWindows();
if (allWindows.length > 0) {
const mainWindow = allWindows[0];
if (mainWindow.isMinimized()){
mainWindow.restore();
return;
}
if(mainWindow.isVisible() === false){
mainWindow.show();
}
mainWindow.focus();
} else {
createAppWindow();
}
}
//Menu.setApplicationMenu(null);
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(async () => {
electronApp.setAppUserModelId('Rosetta');
tray = new Tray(icon);
const contextMenu = Menu.buildFromTemplate([
{ label: 'Open App', click: () => restoreApplicationAfterClickOnTrayOrDock() },
{ label: 'Quit', click: () => app.quit() }
]);
tray.setContextMenu(contextMenu);
tray.setToolTip('Rosetta');
tray.on('click', () => {
restoreApplicationAfterClickOnTrayOrDock();
});
startApplication();
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
app.on('activate', function () {
restoreApplicationAfterClickOnTrayOrDock();
});
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform == 'darwin') {
app.hide();
}
})
// In this file, you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.

18
lib/preload/api.ts Normal file
View File

@@ -0,0 +1,18 @@
import { ipcRenderer } from 'electron';
const api = {
send: (channel: string, ...args: any[]) => {
ipcRenderer.send(channel, ...args);
},
receive: (channel: string, func: (...args: any[]) => void) => {
ipcRenderer.on(channel, (_, ...args) => func(...args));
},
invoke: (channel: string, ...args: any[]) => {
return ipcRenderer.invoke(channel, ...args);
},
removeAllListeners: (channel: string) => {
ipcRenderer.removeAllListeners(channel);
},
};
export default api;

17
lib/preload/index.d.ts vendored Normal file
View File

@@ -0,0 +1,17 @@
import { ElectronAPI } from '@electron-toolkit/preload'
import type api from './api'
declare global {
interface Window {
electron: ElectronAPI
api: typeof api,
version: string,
platform: string,
appPath: string,
arch: string,
shell: Electron.Shell;
downloadsPath: string;
deviceName: string;
deviceId: string;
}
}

137
lib/preload/preload.ts Normal file
View File

@@ -0,0 +1,137 @@
import { contextBridge, ipcRenderer, shell } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
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 () => {
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");
setTimeout(() => {
if(document.body.innerHTML.length < 100){
document.body.innerHTML = applicationError;
ipcRenderer.invoke("report-boot-process-failed");
}
}, 3000);
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);
},
showItemInFolder: (fullPath: string) => {
ipcRenderer.invoke('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.shell = shell;
window.downloadsPath = downloadsPath;
window.deviceName = deviceName;
window.deviceId = deviceId;
}
}
exposeContext();

35
lib/window/ipcEvents.ts Normal file
View File

@@ -0,0 +1,35 @@
import { type BrowserWindow, ipcMain } from 'electron'
import os from 'os'
const handleIPC = (channel: string, handler: (...args: any[]) => void) => {
ipcMain.handle(channel, handler)
}
export const registerWindowIPC = (mainWindow: BrowserWindow) => {
// Hide the menu bar
mainWindow.setMenuBarVisibility(false)
// Register window IPC
handleIPC('init-window', () => {
const { width, height } = mainWindow.getBounds()
const minimizable = mainWindow.isMinimizable()
const maximizable = mainWindow.isMaximizable()
const platform = os.platform()
return { width, height, minimizable, maximizable, platform }
})
handleIPC('is-window-minimizable', () => mainWindow.isMinimizable())
handleIPC('is-window-maximizable', () => mainWindow.isMaximizable())
handleIPC('window-minimize', () => mainWindow.minimize())
handleIPC('window-maximize', () => mainWindow.maximize())
handleIPC('window-close', () => mainWindow.close())
handleIPC('window-maximize-toggle', () => {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize()
} else {
mainWindow.maximize()
}
})
}