UBERF-11085 (#9076)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2025-05-23 23:08:00 +07:00
committed by GitHub
parent bd6c8d6758
commit 47d47aefec
6 changed files with 37 additions and 204 deletions
+9 -9
View File
@@ -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
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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",
-105
View File
@@ -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<Client & BackupClient> {
// 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<void> {
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<void> => {
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
}
-4
View File
@@ -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') }
+26 -84
View File
@@ -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<void> => {
// 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<void> => {
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<void> => {
// 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<void> {
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()
})