From 47d47aefecec31545145a18f2f2bcafc29d7e605 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Fri, 23 May 2025 23:08:00 +0700 Subject: [PATCH] UBERF-11085 (#9076) Signed-off-by: Andrey Sobolev --- common/config/rush/pnpm-lock.yaml | 18 ++--- desktop-package/package.json | 2 +- desktop/package.json | 2 +- desktop/src/main/backup.ts | 105 ---------------------------- desktop/src/main/menu.ts | 4 -- desktop/src/main/start.ts | 110 +++++++----------------------- 6 files changed, 37 insertions(+), 204 deletions(-) delete mode 100644 desktop/src/main/backup.ts diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index b78fdd70a2..0bec0dad96 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -1736,8 +1736,8 @@ importers: specifier: ~22.8.7 version: 22.8.8 electron: - specifier: ^35.0.3 - version: 35.0.3 + specifier: ^36.3.1 + version: 36.3.1 electron-builder: specifier: ^25.1.8 version: 25.1.8 @@ -4172,7 +4172,7 @@ packages: version: 0.0.0 '@rush-temp/desktop-1@file:projects/desktop-1.tgz': - resolution: {integrity: sha512-rsHmRGdl06oTqUIcR9dPSRKa4Fr+aiBNUfdnskyM/yzol/YQcaOhrOmStUArrr+l0ey5qSVX40GNrRuDK5RQJA==, tarball: file:projects/desktop-1.tgz} + resolution: {integrity: sha512-+3xHE79pZH3p6fzQkqin/6vH7inebwonxQgQiODe0wVygulmUFPWwFFEI4rtraGZYiivKUmJ5zCd+dCckLu6KQ==, tarball: file:projects/desktop-1.tgz} version: 0.0.0 '@rush-temp/desktop-preferences-assets@file:projects/desktop-preferences-assets.tgz': @@ -4188,7 +4188,7 @@ packages: version: 0.0.0 '@rush-temp/desktop@file:projects/desktop.tgz': - resolution: {integrity: sha512-nfD+5Z3H2YZOhYd17GM4bArHHPFx7Jkxnf65ODiy05bVFUHbj3YaC0btFbq5l83NFWL94YL3yPvXe1P87MqI0w==, tarball: file:projects/desktop.tgz} + resolution: {integrity: sha512-ksJbenEj9NJb3b0bGinpUWssVjPWzaWfUEy+kRMqG1gBaFMqhtqBXNNuyLbtAywAaX9uMqSosGCE/E265qRfAw==, tarball: file:projects/desktop.tgz} version: 0.0.0 '@rush-temp/devmodel-resources@file:projects/devmodel-resources.tgz': @@ -8512,8 +8512,8 @@ packages: electron-windows-badge@1.1.0: resolution: {integrity: sha512-hWFbUk9u3fQHcKzTAcjZAN7XH9bL9oH9g20RRDU/DVDNqdMI03GzlBZfR/R8R1krYu9AT4biLqSCAxnt9LMAfA==} - electron@35.0.3: - resolution: {integrity: sha512-kjQAYEWXSr2TyK19IZoF85dzFIBaYuX7Yp/C+34b5Y/jmI2z270CGie+RjmEGMMitsy0G8YJKftukhYMuWlK6g==} + electron@36.3.1: + resolution: {integrity: sha512-LeOZ+tVahmctHaAssLCGRRUa2SAO09GXua3pKdG+WzkbSDMh+3iOPONNVPTqGp8HlWnzGj4r6mhsIbM2RgH+eQ==} engines: {node: '>= 12.20.55'} hasBin: true @@ -18113,7 +18113,7 @@ snapshots: '@vercel/webpack-asset-relocator-loader': 1.7.4 cross-env: 7.0.3 dotenv: 16.0.3 - electron: 35.0.3 + electron: 36.3.1 electron-builder: 25.1.8 electron-squirrel-startup: 1.0.1 node-loader: 2.0.0(webpack@5.97.1) @@ -18229,7 +18229,7 @@ snapshots: css-loader: 5.2.7(webpack@5.97.1) dotenv: 16.0.3 dotenv-webpack: 8.0.1(webpack@5.97.1) - electron: 35.0.3 + electron: 36.3.1 electron-context-menu: 4.0.4 electron-log: 5.1.7 electron-squirrel-startup: 1.0.1 @@ -31556,7 +31556,7 @@ snapshots: electron-windows-badge@1.1.0: {} - electron@35.0.3: + electron@36.3.1: dependencies: '@electron/get': 2.0.3 '@types/node': 22.13.12 diff --git a/desktop-package/package.json b/desktop-package/package.json index 57d892317f..454b4974c4 100644 --- a/desktop-package/package.json +++ b/desktop-package/package.json @@ -21,7 +21,7 @@ "node-loader": "~2.0.0", "cross-env": "~7.0.3", "typescript": "^5.3.3", - "electron": "^35.0.3", + "electron": "^36.3.1", "@types/node": "~20.11.16", "electron-builder": "^25.1.8", "@electron/notarize": "^2.3.2" diff --git a/desktop/package.json b/desktop/package.json index 2ed45c739d..cfa6c7e0f3 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -47,7 +47,7 @@ "typescript": "^5.3.3", "ts-node": "^10.8.0", "ts-node-dev": "^2.0.0", - "electron": "^35.0.3", + "electron": "^36.3.1", "@types/node": "~20.11.16", "copy-webpack-plugin": "^11.0.0", "@typescript-eslint/eslint-plugin": "^6.11.0", diff --git a/desktop/src/main/backup.ts b/desktop/src/main/backup.ts deleted file mode 100644 index 2a21bccf69..0000000000 --- a/desktop/src/main/backup.ts +++ /dev/null @@ -1,105 +0,0 @@ -import client, { clientId } from '@hcengineering/client' -import { MeasureMetricsContext, WorkspaceIds, type BackupClient, type Client } from '@hcengineering/core' -import { addLocation, getResource, setMetadata } from '@hcengineering/platform' -import WebSocket from 'ws' - -import { - backup, - createFileBackupStorage -} from '@hcengineering/server-backup' -import { dialog, type BrowserWindow } from 'electron' - -let runningBackup: { - notify: (command: string, ...args: any[]) => void -} | undefined - -export async function createClient ( - transactorUrl: string, - token: string -): Promise { - // We need to override default factory with 'ws' one. - // eslint-disable-next-line - setMetadata(client.metadata.UseBinaryProtocol, true) - setMetadata(client.metadata.UseProtocolCompression, true) - setMetadata(client.metadata.ConnectionTimeout, 0) - setMetadata(client.metadata.ClientSocketFactory, (url) => { - return new WebSocket(url) as any - }) - addLocation(clientId, () => import('@hcengineering/client-resources')) - - const clientFactory = await getResource(client.function.GetClient) - return (await clientFactory(token, transactorUrl)) as unknown as Client & BackupClient -} - -async function doBackup (dirName: string, token: string, endpoint: string, wsIds: WorkspaceIds, notify: (command: string, ...args: any[]) => void, backupHugeFiles: boolean): Promise { - notify('backup', 0) - const ctx = new MeasureMetricsContext('backup', {}) - - const storage = await createFileBackupStorage(dirName) - const client = await createClient(endpoint, token) - try { - ctx.info('do backup', { ...wsIds, endpoint }) - await backup(ctx, endpoint, wsIds, storage, { - force: true, - skipDomains: [], - timeout: 0, - connectTimeout: 60 * 1000, - blobDownloadLimit: backupHugeFiles ? 10240 : 50, - token, - skipBlobContentTypes: [], - keepSnapshots: 7 * 14, - isCanceled: (): boolean => { - return runningBackup == null - }, - progress: async (value: number): Promise => { - notify('backup', value) - }, - getConnection: async () => client - }) - } finally { - await client.close() - } - - notify('backup', 100) -} - -export function startBackup (window: BrowserWindow, token: string, endpoint: string, wsIds: WorkspaceIds, cmd: (command: string, ...args: any[]) => void): void { - void dialog - .showOpenDialog(window, { - properties: ['openDirectory'], - buttonLabel: 'Select a backup folder', - message: 'Select a folder for Huly incremental backup.' - }) - .then((response) => { - if (!response.canceled && response.filePaths.length > 0) { - void dialog - .showMessageBox({ - type: 'info', - buttons: ['Backup....', 'Cancel'], - defaultId: 0, - textWidth: 500, - title: 'Huly desktop need a confirmation for a backup process to be started...', - message: `Huly Desktop need a confirmation for a backup process to be started....\n\n - An incremental backup will be performed to folder:\n ${response.filePaths[0]}\n - Backup could be canceled and resumed laterly into same location.`, - checkboxLabel: 'Include files >= 50mb', - checkboxChecked: false - }) - .then((backupKind) => { - if (backupKind.response === 1) { - return - } - runningBackup = { - notify: cmd - } - void doBackup(response.filePaths[0], token, endpoint, wsIds, cmd, backupKind.checkboxChecked) - }) - } - }) -} -export function cancelBackup (): void { - if (runningBackup != null) { - runningBackup.notify('backup-cancel') - } - runningBackup = undefined -} diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts index edbf0d15b7..0c964ae510 100644 --- a/desktop/src/main/menu.ts +++ b/desktop/src/main/menu.ts @@ -32,10 +32,6 @@ export const addMenus = (getWindow: () => BrowserWindow, sendCommand: (cmd: stri label: 'Select workspace', click: () => { sendCommand('select-workspace') } }, - { - label: 'Backup...', - click: () => { sendCommand('start-backup') } - }, { label: 'Logout', click: () => { sendCommand('logout') } diff --git a/desktop/src/main/start.ts b/desktop/src/main/start.ts index f47e3b1cd1..d9ea7cd2e2 100644 --- a/desktop/src/main/start.ts +++ b/desktop/src/main/start.ts @@ -24,7 +24,6 @@ import * as path from 'path' import { Config, NotificationParams } from '../ui/types' import { getOptions } from './args' -import { cancelBackup, startBackup } from './backup' import { addMenus } from './menu' import { addPermissionHandlers } from './permissions' import autoUpdater from './updater' @@ -184,9 +183,13 @@ function handleAuthRedirects (window: BrowserWindow): void { } const createWindow = async (): Promise => { + // Restore window position if available + const restoredBounds: any = settings.get('windowBounds') mainWindow = new BrowserWindow({ - width: defaultWidth, - height: defaultHeight, + width: restoredBounds?.width ?? defaultWidth, + height: restoredBounds?.height ?? defaultHeight, + x: restoredBounds?.x ?? undefined, + y: restoredBounds?.y ?? undefined, titleBarStyle: isMac ? 'hidden' : 'default', trafficLightPosition: { x: 10, y: 10 }, roundedCorners: true, @@ -195,7 +198,7 @@ const createWindow = async (): Promise => { devTools: true, sandbox: false, nodeIntegration: true, - backgroundThrottling: false, + // backgroundThrottling: false, partition: sessionPartition, preload: path.join(app.getAppPath(), 'dist', 'main', 'preload.js') } @@ -213,6 +216,14 @@ const createWindow = async (): Promise => { // All other urls will be blocked. hookOpenWindow(mainWindow) + // Save window position on close + mainWindow.on('close', () => { + const bounds = mainWindow?.getBounds() + if (bounds !== undefined) { + settings.set('windowBounds', bounds) + } + }) + if (isMac) { mainWindow.on('close', (event) => { // Prevent the default behavior (which would quit the app) @@ -315,61 +326,12 @@ ipcMain.on('set-front-cookie', function (event, host: string, name: string, valu void win?.webContents?.session.cookies.set(cv) }) -const DEEP_LINKS_PROTOCOL = 'huly' -/* - Copy-paste from official tutorial for deep links - https://www.electronjs.org/docs/latest/tutorial/launch-app-from-url-in-another-app -*/ -if (process.defaultApp) { - if (process.argv.length >= 2) { - app.setAsDefaultProtocolClient(DEEP_LINKS_PROTOCOL, process.execPath, [path.resolve(process.argv[1])]) - } -} else { - app.setAsDefaultProtocolClient(DEEP_LINKS_PROTOCOL) -} - const gotTheLock = app.requestSingleInstanceLock() if (!gotTheLock) { app.quit() } -let deepLink = '' -let haveDeepLinkHandler = false -function tryToOpenDeepLink (link?: string): void { - if (mainWindow == null || !haveDeepLinkHandler) { - if (link != null) { - deepLink = link - } - return - } - - const url = link ?? deepLink - const httpsUrl = url.replace(`${DEEP_LINKS_PROTOCOL}://`, 'https://') - mainWindow.webContents.send('handle-deep-link', httpsUrl) -} - -// Add windows / linux handler for second instance -// This is because Windows try to open second instance from the deep link -app.on('second-instance', (event, commandLine, workingDirectory) => { - if (mainWindow != null) { - if (mainWindow.isMinimized()) mainWindow.restore() - mainWindow.focus() - } - - const url = commandLine.pop() - console.log('Opening url', url) - - if (url != null) { - tryToOpenDeepLink(url) - } -}) - -ipcMain.on('on-deep-link-handler', () => { - haveDeepLinkHandler = true - tryToOpenDeepLink() -}) - ipcMain.handle('get-screen-access', () => systemPreferences.getMediaAccessStatus('screen') === 'granted') ipcMain.handle('get-screen-sources', () => { return desktopCapturer.getSources({ types: ['window', 'screen'], fetchWindowIcons: true, thumbnailSize: { width: 225, height: 135 } }).then(async sources => { @@ -383,21 +345,9 @@ ipcMain.handle('get-screen-sources', () => { }) }) -// MacOS implementation -// Handle the protocol. In this case, we choose to show an Error Box. -app.on('open-url', (event, url) => { - if (url == null) { - return - } - tryToOpenDeepLink(url) -}) - async function onReady (): Promise { await createWindow() - const customProtocolArg = process.argv.find((arg) => arg.startsWith(`${DEEP_LINKS_PROTOCOL}://`)) ?? '' - tryToOpenDeepLink(customProtocolArg) - if (serverChanged) { mainWindow?.webContents.send('logout') } @@ -419,15 +369,20 @@ if (isMac) { mainWindow.show() } }) +} +app.on('before-quit', () => { + if (mainWindow !== undefined && !mainWindow.isDestroyed()) { + const bounds = mainWindow.getBounds() + settings.set('windowBounds', bounds) + } + // Note: in case the app is exited by auto-updater all windows will be destroyed at this point + if (mainWindow === undefined || mainWindow.isDestroyed()) return - app.on('before-quit', () => { - // Note: in case the app is exited by auto-updater all windows will be destroyed at this point - if (mainWindow === undefined || mainWindow.isDestroyed()) return - + if (isMac) { mainWindow?.removeAllListeners('close') mainWindow?.close() - }) -} + } +}) // Note: it is reset when the app is relaunched after update let isUpdating = false @@ -471,16 +426,3 @@ autoUpdater.on('update-downloaded', (info) => { autoUpdater.quitAndInstall() }) - -ipcMain.on('start-backup', (event, token, endpoint, wsIds) => { - console.log('start backup', token, endpoint, wsIds) - if (mainWindow != null) { - startBackup(mainWindow, token, endpoint, wsIds, (cmd: string, ...args: any[]) => { - mainWindow?.webContents.send(cmd, ...args) - }) - } -}) - -ipcMain.on('cancel-backup', (event) => { - cancelBackup() -})