From f7fc3bd84c6bde5c1c6e010712ced73672b22222 Mon Sep 17 00:00:00 2001 From: WINBIGFOX Date: Tue, 6 Jan 2026 12:57:02 +0100 Subject: [PATCH 1/2] style: apply Prettier configurations and improve code consistency Applied Prettier rules across project files to enforce consistent code formatting. Added `.prettierrc` and `.prettierignore` files, updated dependencies with `prettier-plugin-organize-imports`, and refactored imports, string quotes, and object formatting for uniformity. --- resources/electron/.prettierignore | 5 + resources/electron/.prettierrc | 8 + resources/electron/build/notarize.js | 30 +- resources/electron/electron-builder.mjs | 51 +- .../electron/electron-plugin/.stylelintrc | 27 +- .../electron/electron-plugin/babel.config.js | 5 +- .../electron/electron-plugin/src/index.ts | 469 ++++++++-------- .../src/libs/menubar/Menubar.ts | 521 +++++++++--------- .../src/libs/menubar/ambient.d.ts | 11 +- .../electron-plugin/src/libs/menubar/index.ts | 2 +- .../electron-plugin/src/libs/menubar/types.ts | 192 ++++--- .../src/libs/menubar/util/cleanOptions.ts | 86 ++- .../libs/menubar/util/getWindowPosition.ts | 131 +++-- .../src/libs/positioner/index.ts | 116 ++-- .../electron-plugin/src/preload/index.mts | 64 ++- .../src/preload/livewire-dispatcher.js | 17 +- .../src/server/ProcessResult.ts | 6 +- .../electron-plugin/src/server/api.ts | 132 ++--- .../electron-plugin/src/server/api/alert.ts | 10 +- .../electron-plugin/src/server/api/app.ts | 34 +- .../src/server/api/autoUpdater.ts | 44 +- .../src/server/api/broadcasting.ts | 10 +- .../src/server/api/childProcess.ts | 77 +-- .../src/server/api/clipboard.ts | 62 +-- .../src/server/api/contextMenu.ts | 8 +- .../electron-plugin/src/server/api/debug.ts | 10 +- .../electron-plugin/src/server/api/dialog.ts | 36 +- .../electron-plugin/src/server/api/dock.ts | 4 +- .../src/server/api/globalShortcut.ts | 28 +- .../src/server/api/helper/index.ts | 19 +- .../electron-plugin/src/server/api/menu.ts | 2 +- .../electron-plugin/src/server/api/menuBar.ts | 95 ++-- .../src/server/api/notification.ts | 21 +- .../src/server/api/powerMonitor.ts | 61 +- .../electron-plugin/src/server/api/process.ts | 4 +- .../src/server/api/progressBar.ts | 12 +- .../electron-plugin/src/server/api/screen.ts | 18 +- .../src/server/api/settings.ts | 24 +- .../electron-plugin/src/server/api/shell.ts | 60 +- .../electron-plugin/src/server/api/system.ts | 29 +- .../electron-plugin/src/server/api/window.ts | 88 ++- .../src/server/childProcess.ts | 12 +- .../electron-plugin/src/server/index.ts | 41 +- .../electron-plugin/src/server/php.ts | 323 ++++++----- .../electron-plugin/src/server/state.ts | 104 ++-- .../electron-plugin/src/server/utils.ts | 26 +- .../src/server/webPreferences.ts | 13 +- .../electron-plugin/tests/api.test.ts | 8 +- .../electron-plugin/tests/mocking.test.ts | 7 +- .../electron/electron-plugin/tests/setup.ts | 23 +- .../electron-plugin/tests/utils.test.ts | 20 +- .../electron/electron-plugin/tsconfig.json | 18 +- .../electron-plugin/vitest.config.mts | 6 +- resources/electron/electron.vite.config.mjs | 16 +- resources/electron/eslint.config.js | 31 +- resources/electron/package-lock.json | 55 +- resources/electron/package.json | 3 +- resources/electron/php.js | 23 +- resources/electron/src/main/index.js | 22 +- 59 files changed, 1660 insertions(+), 1720 deletions(-) create mode 100644 resources/electron/.prettierignore create mode 100644 resources/electron/.prettierrc diff --git a/resources/electron/.prettierignore b/resources/electron/.prettierignore new file mode 100644 index 00000000..52a51424 --- /dev/null +++ b/resources/electron/.prettierignore @@ -0,0 +1,5 @@ +out +electron-plugin/dist +package-lock.json +eslint.config.js +.prettierrc.json diff --git a/resources/electron/.prettierrc b/resources/electron/.prettierrc new file mode 100644 index 00000000..091b9ef0 --- /dev/null +++ b/resources/electron/.prettierrc @@ -0,0 +1,8 @@ +{ + "plugins": ["prettier-plugin-organize-imports"], + "trailingComma": "all", + "tabWidth": 4, + "semi": true, + "singleQuote": true, + "printWidth": 120 +} diff --git a/resources/electron/build/notarize.js b/resources/electron/build/notarize.js index 8f2b6ccd..74f7b493 100644 --- a/resources/electron/build/notarize.js +++ b/resources/electron/build/notarize.js @@ -7,18 +7,26 @@ export default async (context) => { // And the current build target is macOS if (context.packager.platform.name !== 'mac') return; - console.log('aftersign hook triggered, start to notarize app.') - - if (!('NATIVEPHP_APPLE_ID' in process.env && 'NATIVEPHP_APPLE_ID_PASS' in process.env && 'NATIVEPHP_APPLE_TEAM_ID' in process.env)) { - console.warn('skipping notarizing, NATIVEPHP_APPLE_ID, NATIVEPHP_APPLE_ID_PASS and NATIVEPHP_APPLE_TEAM_ID env variables must be set.') - return + console.log('aftersign hook triggered, start to notarize app.'); + + if ( + !( + 'NATIVEPHP_APPLE_ID' in process.env && + 'NATIVEPHP_APPLE_ID_PASS' in process.env && + 'NATIVEPHP_APPLE_TEAM_ID' in process.env + ) + ) { + console.warn( + 'skipping notarizing, NATIVEPHP_APPLE_ID, NATIVEPHP_APPLE_ID_PASS and NATIVEPHP_APPLE_TEAM_ID env variables must be set.', + ); + return; } const appId = process.env.NATIVEPHP_APP_ID; - const {appOutDir} = context + const { appOutDir } = context; - const appName = context.packager.appInfo.productFilename + const appName = context.packager.appInfo.productFilename; try { await notarize({ @@ -28,10 +36,10 @@ export default async (context) => { appleIdPassword: process.env.NATIVEPHP_APPLE_ID_PASS, teamId: process.env.NATIVEPHP_APPLE_TEAM_ID, tool: 'notarytool', - }) + }); } catch (error) { - console.error(error) + console.error(error); } - console.log(`done notarizing ${appId}.`) -} + console.log(`done notarizing ${appId}.`); +}; diff --git a/resources/electron/electron-builder.mjs b/resources/electron/electron-builder.mjs index 8cb1558c..af28e71e 100644 --- a/resources/electron/electron-builder.mjs +++ b/resources/electron/electron-builder.mjs @@ -42,7 +42,7 @@ let updaterConfig = {}; try { updaterConfig = process.env.NATIVEPHP_UPDATER_CONFIG; updaterConfig = JSON.parse(updaterConfig); -} catch (e) { +} catch { updaterConfig = {}; } @@ -69,7 +69,7 @@ export default { beforePack: async (context) => { let arch = { 1: 'x64', - 3: 'arm64' + 3: 'arm64', }[context.arch]; if (arch === undefined) { @@ -83,14 +83,16 @@ export default { afterSign: 'build/notarize.js', win: { executableName: fileName, - ...(azurePublisherName && azureEndpoint && azureCertificateProfileName && azureCodeSigningAccountName ? { - azureSignOptions: { - publisherName: azurePublisherName, - endpoint: azureEndpoint, - certificateProfileName: azureCertificateProfileName, - codeSigningAccountName: azureCodeSigningAccountName - } - } : {}), + ...(azurePublisherName && azureEndpoint && azureCertificateProfileName && azureCodeSigningAccountName + ? { + azureSignOptions: { + publisherName: azurePublisherName, + endpoint: azureEndpoint, + certificateProfileName: azureCertificateProfileName, + codeSigningAccountName: azureCodeSigningAccountName, + }, + } + : {}), }, nsis: { artifactName: appName + '-${version}-setup.${ext}', @@ -106,14 +108,10 @@ export default { entitlementsInherit: 'build/entitlements.mac.plist', artifactName: appName + '-${version}-${arch}.${ext}', extendInfo: { - NSCameraUsageDescription: - "Application requests access to the device's camera.", - NSMicrophoneUsageDescription: - "Application requests access to the device's microphone.", - NSDocumentsFolderUsageDescription: - "Application requests access to the user's Documents folder.", - NSDownloadsFolderUsageDescription: - "Application requests access to the user's Downloads folder.", + NSCameraUsageDescription: "Application requests access to the device's camera.", + NSMicrophoneUsageDescription: "Application requests access to the device's microphone.", + NSDocumentsFolderUsageDescription: "Application requests access to the user's Documents folder.", + NSDownloadsFolderUsageDescription: "Application requests access to the user's Downloads folder.", }, }, dmg: { @@ -138,22 +136,15 @@ export default { { from: process.env.NATIVEPHP_BUILD_PATH, to: 'build', - filter: [ - '**/*', - '!{.git}', - ] - } + filter: ['**/*', '!{.git}'], + }, ], extraFiles: [ { from: join(process.env.APP_PATH, 'extras'), to: 'extras', - filter: [ - '**/*' - ] - } + filter: ['**/*'], + }, ], - ...updaterEnabled - ? { publish: updaterConfig } - : {} + ...(updaterEnabled ? { publish: updaterConfig } : {}), }; diff --git a/resources/electron/electron-plugin/.stylelintrc b/resources/electron/electron-plugin/.stylelintrc index fcc73d56..727392ed 100644 --- a/resources/electron/electron-plugin/.stylelintrc +++ b/resources/electron/electron-plugin/.stylelintrc @@ -1,18 +1,15 @@ { - "extends": [ - "stylelint-config-recommended", - "stylelint-config-sass-guidelines" - ], - "overrides": [ - { - "files": ["**/*.scss"], - "customSyntax": "postcss-scss" + "extends": ["stylelint-config-recommended", "stylelint-config-sass-guidelines"], + "overrides": [ + { + "files": ["**/*.scss"], + "customSyntax": "postcss-scss" + } + ], + "rules": { + "function-parentheses-space-inside": null, + "no-descending-specificity": null, + "max-nesting-depth": 2, + "selector-max-id": 1 } - ], - "rules": { - "function-parentheses-space-inside": null, - "no-descending-specificity": null, - "max-nesting-depth": 2, - "selector-max-id": 1 - } } diff --git a/resources/electron/electron-plugin/babel.config.js b/resources/electron/electron-plugin/babel.config.js index 8dc63557..a4845bef 100644 --- a/resources/electron/electron-plugin/babel.config.js +++ b/resources/electron/electron-plugin/babel.config.js @@ -1,6 +1,3 @@ export default { - presets: [ - ['@babel/preset-env', {targets: {node: 'current'}}], - '@babel/preset-typescript', - ], + presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'], }; diff --git a/resources/electron/electron-plugin/src/index.ts b/resources/electron/electron-plugin/src/index.ts index bfd0ea82..121214be 100644 --- a/resources/electron/electron-plugin/src/index.ts +++ b/resources/electron/electron-plugin/src/index.ts @@ -1,181 +1,168 @@ -import CrossProcessExports from "electron"; -import { app, session, powerMonitor } from "electron"; -import { ChildProcessWithoutNullStreams } from "child_process"; -import { initialize } from "@electron/remote/main/index.js"; -import state from "./server/state.js"; -import { electronApp, optimizer } from "@electron-toolkit/utils"; +import { electronApp, optimizer } from '@electron-toolkit/utils'; +import { initialize } from '@electron/remote/main/index.js'; +import { ChildProcessWithoutNullStreams } from 'child_process'; +import CrossProcessExports, { app, powerMonitor, session } from 'electron'; +import killSync from 'kill-sync'; +import { resolve } from 'path'; +import ps from 'ps-node'; +import { stopAllProcesses } from './server/api/childProcess.js'; import { - retrieveNativePHPConfig, - retrievePhpIniSettings, - runScheduler, - killScheduler, - startAPI, - startPhpApp, -} from "./server/index.js"; -import { notifyLaravel } from "./server/utils.js"; -import { resolve } from "path"; -import { stopAllProcesses } from "./server/api/childProcess.js"; -import ps from "ps-node"; -import killSync from "kill-sync"; + killScheduler, + retrieveNativePHPConfig, + retrievePhpIniSettings, + runScheduler, + startAPI, + startPhpApp, +} from './server/index.js'; +import state from './server/state.js'; +import { notifyLaravel } from './server/utils.js'; // Workaround for CommonJS module import electronUpdater from 'electron-updater'; const { autoUpdater } = electronUpdater; class NativePHP { - processes: ChildProcessWithoutNullStreams[] = []; - mainWindow = null; - schedulerInterval = undefined; - - public bootstrap( - app: CrossProcessExports.App, - icon: string, - phpBinary: string, - cert: string, - appPath: string - ) { - - initialize(); - - state.icon = icon; - state.php = phpBinary; - state.caCert = cert; - state.appPath = appPath; - - this.bootstrapApp(app); - this.addEventListeners(app); - } - - private addEventListeners(app: Electron.CrossProcessExports.App) { - app.on("open-url", (event, url) => { - notifyLaravel("events", { - event: "\\Native\\Desktop\\Events\\App\\OpenedFromURL", - payload: [url], - }); - }); - - app.on("open-file", (event, path) => { - notifyLaravel("events", { - event: "\\Native\\Desktop\\Events\\App\\OpenFile", - payload: [path], - }); - }); - - app.on("window-all-closed", () => { - if (process.platform !== "darwin") { - app.quit(); - } - }); - - app.on("before-quit", () => { - if (this.schedulerInterval) { - clearInterval(this.schedulerInterval); - } - - // close all child processes from the app - stopAllProcesses(); - - this.killChildProcesses(); - }); - - // 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 (event, hasVisibleWindows) { - // On macOS it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (!hasVisibleWindows) { - notifyLaravel("booted"); - } - - event.preventDefault(); - }); - } - - private async bootstrapApp(app: Electron.CrossProcessExports.App) { - await app.whenReady(); - - const config = await this.loadConfig(); - - this.setDockIcon(); - this.setAppUserModelId(config); - this.setDeepLinkHandler(config); - this.startAutoUpdater(config); - - await this.startElectronApi(); - - state.phpIni = await this.loadPhpIni(); - - await this.startPhpApp(); - this.startScheduler(); - - powerMonitor.on("suspend", () => { - this.stopScheduler(); - }); - - powerMonitor.on("resume", () => { - this.stopScheduler(); - this.startScheduler(); - }); - - const filter = { - urls: [`http://127.0.0.1:${state.phpPort}/*`] - }; - - session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => { - details.requestHeaders['X-NativePHP-Secret'] = state.randomSecret; - - callback({ requestHeaders: details.requestHeaders }); - }); - - await notifyLaravel("booted"); - } + processes: ChildProcessWithoutNullStreams[] = []; + mainWindow = null; + schedulerInterval = undefined; - private async loadConfig() { - let config = {}; - - try { - const result = await retrieveNativePHPConfig(); - - config = JSON.parse(result.stdout); - } catch (error) { - console.error(error); + public bootstrap(app: CrossProcessExports.App, icon: string, phpBinary: string, cert: string, appPath: string) { + initialize(); + + state.icon = icon; + state.php = phpBinary; + state.caCert = cert; + state.appPath = appPath; + + this.bootstrapApp(app); + this.addEventListeners(app); + } + + private addEventListeners(app: Electron.CrossProcessExports.App) { + app.on('open-url', (event, url) => { + notifyLaravel('events', { + event: '\\Native\\Desktop\\Events\\App\\OpenedFromURL', + payload: [url], + }); + }); + + app.on('open-file', (event, path) => { + notifyLaravel('events', { + event: '\\Native\\Desktop\\Events\\App\\OpenFile', + payload: [path], + }); + }); + + app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } + }); + + app.on('before-quit', () => { + if (this.schedulerInterval) { + clearInterval(this.schedulerInterval); + } + + // close all child processes from the app + stopAllProcesses(); + + this.killChildProcesses(); + }); + + // 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 (event, hasVisibleWindows) { + // On macOS it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (!hasVisibleWindows) { + notifyLaravel('booted'); + } + + event.preventDefault(); + }); } - return config; - } + private async bootstrapApp(app: Electron.CrossProcessExports.App) { + await app.whenReady(); - private setDockIcon() { - // Only run this on macOS - if ( - process.platform === "darwin" && - process.env.NODE_ENV === "development" - ) { - app.dock.setIcon(state.icon); + const config = await this.loadConfig(); + + this.setDockIcon(); + this.setAppUserModelId(config); + this.setDeepLinkHandler(config); + this.startAutoUpdater(config); + + await this.startElectronApi(); + + state.phpIni = await this.loadPhpIni(); + + await this.startPhpApp(); + this.startScheduler(); + + powerMonitor.on('suspend', () => { + this.stopScheduler(); + }); + + powerMonitor.on('resume', () => { + this.stopScheduler(); + this.startScheduler(); + }); + + const filter = { + urls: [`http://127.0.0.1:${state.phpPort}/*`], + }; + + session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => { + details.requestHeaders['X-NativePHP-Secret'] = state.randomSecret; + + callback({ requestHeaders: details.requestHeaders }); + }); + + await notifyLaravel('booted'); } - } - private setAppUserModelId(config) { - electronApp.setAppUserModelId(config?.app_id); - } + private async loadConfig() { + let config = {}; + + try { + const result = await retrieveNativePHPConfig(); + + config = JSON.parse(result.stdout); + } catch (error) { + console.error(error); + } - private setDeepLinkHandler(config) { - const deepLinkProtocol = config?.deeplink_scheme; + return config; + } - if (deepLinkProtocol) { - if (process.defaultApp) { - if (process.argv.length >= 2) { - app.setAsDefaultProtocolClient(deepLinkProtocol, process.execPath, [ - resolve(process.argv[1]), - ]); + private setDockIcon() { + // Only run this on macOS + if (process.platform === 'darwin' && process.env.NODE_ENV === 'development') { + app.dock.setIcon(state.icon); } - } else { - app.setAsDefaultProtocolClient(deepLinkProtocol); - } + } + + private setAppUserModelId(config) { + electronApp.setAppUserModelId(config?.app_id); + } + + private setDeepLinkHandler(config) { + const deepLinkProtocol = config?.deeplink_scheme; + + if (deepLinkProtocol) { + if (process.defaultApp) { + if (process.argv.length >= 2) { + app.setAsDefaultProtocolClient(deepLinkProtocol, process.execPath, [resolve(process.argv[1])]); + } + } else { + app.setAsDefaultProtocolClient(deepLinkProtocol); + } /** * Handle protocol url for windows and linux @@ -184,116 +171,108 @@ class NativePHP { * than the open-url event and Windows requiring additional code in order to * open the contents of the protocol link within the same Electron instance. */ - if (process.platform !== "darwin") { + if (process.platform !== 'darwin') { const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) { app.quit(); return; } else { - app.on( - "second-instance", - (event, commandLine, workingDirectory) => { - // Someone tried to run a second instance, we should focus our window. - if (this.mainWindow) { - if (this.mainWindow.isMinimized()) - this.mainWindow.restore(); - this.mainWindow.focus(); - } - - // the commandLine is array of strings in which last element is deep link url - notifyLaravel("events", { - event: "\\Native\\Desktop\\Events\\App\\OpenedFromURL", - payload: { - url: commandLine[commandLine.length - 1], - }, - }); - }, - ); + app.on('second-instance', (event, commandLine) => { + // Someone tried to run a second instance, we should focus our window. + if (this.mainWindow) { + if (this.mainWindow.isMinimized()) this.mainWindow.restore(); + this.mainWindow.focus(); + } + + // the commandLine is array of strings in which last element is deep link url + notifyLaravel('events', { + event: '\\Native\\Desktop\\Events\\App\\OpenedFromURL', + payload: { + url: commandLine[commandLine.length - 1], + }, + }); + }); } } } } - private startAutoUpdater(config) { - if (config?.updater?.enabled === true) { - autoUpdater.checkForUpdatesAndNotify(); + private startAutoUpdater(config) { + if (config?.updater?.enabled === true) { + autoUpdater.checkForUpdatesAndNotify(); + } } - } - - private async startElectronApi() { - // Start an Express server so that the Electron app can be controlled from PHP via API - const electronApi = await startAPI(); - - state.electronApiPort = electronApi.port; - - console.log("Electron API server started on port", electronApi.port); - } - private async loadPhpIni() { - let config = {}; + private async startElectronApi() { + // Start an Express server so that the Electron app can be controlled from PHP via API + const electronApi = await startAPI(); - try { - const result = await retrievePhpIniSettings(); + state.electronApiPort = electronApi.port; - config = JSON.parse(result.stdout); - } catch (error) { - console.error(error); + console.log('Electron API server started on port', electronApi.port); } - return config; - } + private async loadPhpIni() { + let config = {}; - private async startPhpApp() { - this.processes.push(await startPhpApp()); - } - - - private stopScheduler() { - if (this.schedulerInterval) { - clearInterval(this.schedulerInterval); - this.schedulerInterval = null; - } - killScheduler(); - } + try { + const result = await retrievePhpIniSettings(); - private startScheduler() { - const now = new Date(); - const delay = - (60 - now.getSeconds()) * 1000 + (1000 - now.getMilliseconds()); + config = JSON.parse(result.stdout); + } catch (error) { + console.error(error); + } - setTimeout(() => { - console.log("Running scheduler..."); + return config; + } - runScheduler(); + private async startPhpApp() { + this.processes.push(await startPhpApp()); + } - this.schedulerInterval = setInterval(() => { - console.log("Running scheduler..."); + private stopScheduler() { + if (this.schedulerInterval) { + clearInterval(this.schedulerInterval); + this.schedulerInterval = null; + } + killScheduler(); + } - runScheduler(); - }, 60 * 1000); - }, delay); - } + private startScheduler() { + const now = new Date(); + const delay = (60 - now.getSeconds()) * 1000 + (1000 - now.getMilliseconds()); - private killChildProcesses() { - this.stopScheduler(); + setTimeout(() => { + console.log('Running scheduler...'); - this.processes - .filter((p) => p !== undefined) - .forEach((process) => { - if (!process || !process.pid) return; - if (process.killed && process.exitCode !== null) return; + runScheduler(); - try { - // @ts-ignore - killSync(process.pid, 'SIGTERM', true); // Kill tree - ps.kill(process.pid); // Sometimes does not kill the subprocess of php server + this.schedulerInterval = setInterval(() => { + console.log('Running scheduler...'); - } catch (err) { - console.error(err); - } + runScheduler(); + }, 60 * 1000); + }, delay); + } - }); - } + private killChildProcesses() { + this.stopScheduler(); + + this.processes + .filter((p) => p !== undefined) + .forEach((process) => { + if (!process || !process.pid) return; + if (process.killed && process.exitCode !== null) return; + + try { + // @ts-ignore + killSync(process.pid, 'SIGTERM', true); // Kill tree + ps.kill(process.pid); // Sometimes does not kill the subprocess of php server + } catch (err) { + console.error(err); + } + }); + } } export default new NativePHP(); diff --git a/resources/electron/electron-plugin/src/libs/menubar/Menubar.ts b/resources/electron/electron-plugin/src/libs/menubar/Menubar.ts index 71fb39d5..8e2cb848 100644 --- a/resources/electron/electron-plugin/src/libs/menubar/Menubar.ts +++ b/resources/electron/electron-plugin/src/libs/menubar/Menubar.ts @@ -1,7 +1,7 @@ +import { BrowserWindow, Tray } from 'electron'; import { EventEmitter } from 'events'; import fs from 'fs'; import path from 'path'; -import { BrowserWindow, Tray } from 'electron'; import Positioner from '../positioner/index.js'; import type { Options } from './types.js'; @@ -14,313 +14,290 @@ import { getWindowPosition } from './util/getWindowPosition.js'; * @noInheritDoc */ export class Menubar extends EventEmitter { - private _app: Electron.App; - private _browserWindow?: BrowserWindow; - private _blurTimeout: NodeJS.Timeout | null = null; // track blur events with timeout - private _isVisible: boolean; // track visibility - private _cachedBounds?: Electron.Rectangle; // _cachedBounds are needed for double-clicked event - private _options: Options; - private _positioner: Positioner | undefined; - private _tray?: Tray; - - constructor(app: Electron.App, options?: Partial) { - super(); - this._app = app; - this._options = cleanOptions(options); - this._isVisible = false; - - if (app.isReady()) { - // See https://github.com/maxogden/menubar/pull/151 - process.nextTick(() => - this.appReady().catch((err) => console.error('menubar: ', err)), - ); - } else { - app.on('ready', () => { - this.appReady().catch((err) => console.error('menubar: ', err)); - }); - } - } - - /** - * The Electron [App](https://electronjs.org/docs/api/app) - * instance. - */ - get app(): Electron.App { - return this._app; - } - - /** - * The [electron-positioner](https://github.com/jenslind/electron-positioner) - * instance. - */ - get positioner(): Positioner { - if (!this._positioner) { - throw new Error( - 'Please access `this.positioner` after the `after-create-window` event has fired.', - ); + private _app: Electron.App; + private _browserWindow?: BrowserWindow; + private _blurTimeout: NodeJS.Timeout | null = null; // track blur events with timeout + private _isVisible: boolean; // track visibility + private _cachedBounds?: Electron.Rectangle; // _cachedBounds are needed for double-clicked event + private _options: Options; + private _positioner: Positioner | undefined; + private _tray?: Tray; + + constructor(app: Electron.App, options?: Partial) { + super(); + this._app = app; + this._options = cleanOptions(options); + this._isVisible = false; + + if (app.isReady()) { + // See https://github.com/maxogden/menubar/pull/151 + process.nextTick(() => this.appReady().catch((err) => console.error('menubar: ', err))); + } else { + app.on('ready', () => { + this.appReady().catch((err) => console.error('menubar: ', err)); + }); + } } - return this._positioner; - } - - /** - * The Electron [Tray](https://electronjs.org/docs/api/tray) instance. - */ - get tray(): Tray { - if (!this._tray) { - throw new Error( - 'Please access `this.tray` after the `ready` event has fired.', - ); + /** + * The Electron [App](https://electronjs.org/docs/api/app) + * instance. + */ + get app(): Electron.App { + return this._app; } - return this._tray; - } - - /** - * The Electron [BrowserWindow](https://electronjs.org/docs/api/browser-window) - * instance, if it's present. - */ - get window(): BrowserWindow | undefined { - return this._browserWindow; - } - - /** - * Retrieve a menubar option. - * - * @param key - The option key to retrieve, see {@link Options}. - */ - getOption(key: K): Options[K] { - return this._options[key]; - } - - /** - * Hide the menubar window. - */ - hideWindow(): void { - if (!this._browserWindow || !this._isVisible) { - return; - } - this.emit('hide'); - this._browserWindow.hide(); - this.emit('after-hide'); - this._isVisible = false; - if (this._blurTimeout) { - clearTimeout(this._blurTimeout); - this._blurTimeout = null; - } - } - - /** - * Change an option after menubar is created. - * - * @param key - The option key to modify, see {@link Options}. - * @param value - The value to set. - */ - setOption(key: K, value: Options[K]): void { - this._options[key] = value; - } - - /** - * Show the menubar window. - * - * @param trayPos - The bounds to show the window in. - */ - async showWindow(trayPos?: Electron.Rectangle): Promise { - if (!this.tray) { - throw new Error('Tray should have been instantiated by now'); - } + /** + * The [electron-positioner](https://github.com/jenslind/electron-positioner) + * instance. + */ + get positioner(): Positioner { + if (!this._positioner) { + throw new Error('Please access `this.positioner` after the `after-create-window` event has fired.'); + } - if (!this._browserWindow) { - await this.createWindow(); + return this._positioner; } - // Use guard for TypeScript, to avoid ! everywhere - if (!this._browserWindow) { - throw new Error('Window has been initialized just above. qed.'); + /** + * The Electron [Tray](https://electronjs.org/docs/api/tray) instance. + */ + get tray(): Tray { + if (!this._tray) { + throw new Error('Please access `this.tray` after the `ready` event has fired.'); + } + + return this._tray; } - // 'Windows' taskbar: sync windows position each time before showing - // https://github.com/maxogden/menubar/issues/232 - if (['win32', 'linux'].includes(process.platform)) { - // Fill in this._options.windowPosition when taskbar position is available - this._options.windowPosition = getWindowPosition(this.tray); + /** + * The Electron [BrowserWindow](https://electronjs.org/docs/api/browser-window) + * instance, if it's present. + */ + get window(): BrowserWindow | undefined { + return this._browserWindow; } - this.emit('show'); - - if (trayPos && trayPos.x !== 0) { - // Cache the bounds - this._cachedBounds = trayPos; - } else if (this._cachedBounds) { - // Cached value will be used if showWindow is called without bounds data - trayPos = this._cachedBounds; - } else if (this.tray.getBounds) { - // Get the current tray bounds - trayPos = this.tray.getBounds(); + /** + * Retrieve a menubar option. + * + * @param key - The option key to retrieve, see {@link Options}. + */ + getOption(key: K): Options[K] { + return this._options[key]; } - // Default the window to the right if `trayPos` bounds are undefined or null. - let noBoundsPosition = undefined; - if ( - (trayPos === undefined || trayPos.x === 0) && - this._options.windowPosition && - this._options.windowPosition.startsWith('tray') - ) { - noBoundsPosition = - process.platform === 'win32' ? 'bottomRight' : 'topRight'; + /** + * Hide the menubar window. + */ + hideWindow(): void { + if (!this._browserWindow || !this._isVisible) { + return; + } + this.emit('hide'); + this._browserWindow.hide(); + this.emit('after-hide'); + this._isVisible = false; + if (this._blurTimeout) { + clearTimeout(this._blurTimeout); + this._blurTimeout = null; + } } - const position = this.positioner.calculate( - this._options.windowPosition || noBoundsPosition, - trayPos, - ) as { x: number; y: number }; - - // Not using `||` because x and y can be zero. - const x = - this._options.browserWindow.x !== undefined - ? this._options.browserWindow.x - : position.x; - const y = - this._options.browserWindow.y !== undefined - ? this._options.browserWindow.y - : position.y; - - // `.setPosition` crashed on non-integers - // https://github.com/maxogden/menubar/issues/233 - this._browserWindow.setPosition(Math.round(x), Math.round(y)); - this._browserWindow.show(); - this._isVisible = true; - this.emit('after-show'); - return; - } - - private async appReady(): Promise { - if (this.app.dock && !this._options.showDockIcon) { - this.app.dock.hide(); + /** + * Change an option after menubar is created. + * + * @param key - The option key to modify, see {@link Options}. + * @param value - The value to set. + */ + setOption(key: K, value: Options[K]): void { + this._options[key] = value; } - if (this._options.activateWithApp) { - this.app.on('activate', (_event, hasVisibleWindows) => { - if (!hasVisibleWindows) { - this.showWindow().catch(console.error); + /** + * Show the menubar window. + * + * @param trayPos - The bounds to show the window in. + */ + async showWindow(trayPos?: Electron.Rectangle): Promise { + if (!this.tray) { + throw new Error('Tray should have been instantiated by now'); } - }); - } - let trayImage = - this._options.icon || path.join(this._options.dir, 'IconTemplate.png'); - if (typeof trayImage === 'string' && !fs.existsSync(trayImage)) { - trayImage = path.join(__dirname, '..', 'assets', 'IconTemplate.png'); // Default cat icon - } + if (!this._browserWindow) { + await this.createWindow(); + } - const defaultClickEvent = this._options.showOnRightClick - ? 'right-click' - : 'click'; + // Use guard for TypeScript, to avoid ! everywhere + if (!this._browserWindow) { + throw new Error('Window has been initialized just above. qed.'); + } - this._tray = this._options.tray || new Tray(trayImage); - // Type guards for TS not to complain - if (!this.tray) { - throw new Error('Tray has been initialized above'); - } - this.tray.on( - defaultClickEvent as Parameters[0], - this.clicked.bind(this), - ); - this.tray.on('double-click', this.clicked.bind(this)); - this.tray.setToolTip(this._options.tooltip); - - if (!this._options.windowPosition) { - this._options.windowPosition = getWindowPosition(this.tray); - } + // 'Windows' taskbar: sync windows position each time before showing + // https://github.com/maxogden/menubar/issues/232 + if (['win32', 'linux'].includes(process.platform)) { + // Fill in this._options.windowPosition when taskbar position is available + this._options.windowPosition = getWindowPosition(this.tray); + } - if (this._options.preloadWindow) { - await this.createWindow(); - } + this.emit('show'); + + if (trayPos && trayPos.x !== 0) { + // Cache the bounds + this._cachedBounds = trayPos; + } else if (this._cachedBounds) { + // Cached value will be used if showWindow is called without bounds data + trayPos = this._cachedBounds; + } else if (this.tray.getBounds) { + // Get the current tray bounds + trayPos = this.tray.getBounds(); + } - this.emit('ready'); - } - - /** - * Callback on tray icon click or double-click. - * - * @param e - * @param bounds - */ - private async clicked( - event?: Electron.KeyboardEvent, - bounds?: Electron.Rectangle, - ): Promise { - if (event && (event.shiftKey || event.ctrlKey || event.metaKey)) { - return this.hideWindow(); - } + // Default the window to the right if `trayPos` bounds are undefined or null. + let noBoundsPosition = undefined; + if ( + (trayPos === undefined || trayPos.x === 0) && + this._options.windowPosition && + this._options.windowPosition.startsWith('tray') + ) { + noBoundsPosition = process.platform === 'win32' ? 'bottomRight' : 'topRight'; + } - // if blur was invoked clear timeout - if (this._blurTimeout) { - clearInterval(this._blurTimeout); + const position = this.positioner.calculate(this._options.windowPosition || noBoundsPosition, trayPos) as { + x: number; + y: number; + }; + + // Not using `||` because x and y can be zero. + const x = this._options.browserWindow.x !== undefined ? this._options.browserWindow.x : position.x; + const y = this._options.browserWindow.y !== undefined ? this._options.browserWindow.y : position.y; + + // `.setPosition` crashed on non-integers + // https://github.com/maxogden/menubar/issues/233 + this._browserWindow.setPosition(Math.round(x), Math.round(y)); + this._browserWindow.show(); + this._isVisible = true; + this.emit('after-show'); + return; } - if (this._browserWindow && this._isVisible) { - return this.hideWindow(); - } + private async appReady(): Promise { + if (this.app.dock && !this._options.showDockIcon) { + this.app.dock.hide(); + } - this._cachedBounds = bounds || this._cachedBounds; - await this.showWindow(this._cachedBounds); - } + if (this._options.activateWithApp) { + this.app.on('activate', (_event, hasVisibleWindows) => { + if (!hasVisibleWindows) { + this.showWindow().catch(console.error); + } + }); + } - private async createWindow(): Promise { - this.emit('create-window'); + let trayImage = this._options.icon || path.join(this._options.dir, 'IconTemplate.png'); + if (typeof trayImage === 'string' && !fs.existsSync(trayImage)) { + trayImage = path.join(__dirname, '..', 'assets', 'IconTemplate.png'); // Default cat icon + } - // We add some default behavior for menubar's browserWindow, to make it - // look like a menubar - const defaults = { - show: false, // Don't show it at first - frame: false, // Remove window frame - }; + const defaultClickEvent = this._options.showOnRightClick ? 'right-click' : 'click'; - this._browserWindow = new BrowserWindow({ - ...defaults, - ...this._options.browserWindow, - }); + this._tray = this._options.tray || new Tray(trayImage); + // Type guards for TS not to complain + if (!this.tray) { + throw new Error('Tray has been initialized above'); + } + this.tray.on(defaultClickEvent as Parameters[0], this.clicked.bind(this)); + this.tray.on('double-click', this.clicked.bind(this)); + this.tray.setToolTip(this._options.tooltip); - this._positioner = new Positioner(this._browserWindow); + if (!this._options.windowPosition) { + this._options.windowPosition = getWindowPosition(this.tray); + } - this._browserWindow.on('blur', () => { - if (!this._browserWindow) { - return; - } - - // hack to close if icon clicked when open - this._browserWindow.isAlwaysOnTop() - ? this.emit('focus-lost') - : (this._blurTimeout = setTimeout(() => { - this.hideWindow(); - }, 100)); - }); - - if (this._options.showOnAllWorkspaces !== false) { - // https://github.com/electron/electron/issues/37832#issuecomment-1497882944 - this._browserWindow.setVisibleOnAllWorkspaces(true, { - skipTransformProcessType: true, // Avoid damaging the original visible state of app.dock - }); + if (this._options.preloadWindow) { + await this.createWindow(); + } + + this.emit('ready'); + } + + /** + * Callback on tray icon click or double-click. + * + * @param event + * @param bounds + */ + private async clicked(event?: Electron.KeyboardEvent, bounds?: Electron.Rectangle): Promise { + if (event && (event.shiftKey || event.ctrlKey || event.metaKey)) { + return this.hideWindow(); + } + + // if blur was invoked clear timeout + if (this._blurTimeout) { + clearInterval(this._blurTimeout); + } + + if (this._browserWindow && this._isVisible) { + return this.hideWindow(); + } + + this._cachedBounds = bounds || this._cachedBounds; + await this.showWindow(this._cachedBounds); } - this._browserWindow.on('close', this.windowClear.bind(this)); + private async createWindow(): Promise { + this.emit('create-window'); + + // We add some default behavior for menubar's browserWindow, to make it + // look like a menubar + const defaults = { + show: false, // Don't show it at first + frame: false, // Remove window frame + }; + + this._browserWindow = new BrowserWindow({ + ...defaults, + ...this._options.browserWindow, + }); + + this._positioner = new Positioner(this._browserWindow); + + this._browserWindow.on('blur', () => { + if (!this._browserWindow) { + return; + } + + // hack to close if icon clicked when open + if (this._browserWindow.isAlwaysOnTop()) { + this.emit('focus-lost'); + } else { + this._blurTimeout = setTimeout(() => { + this.hideWindow(); + }, 100); + } + }); + + if (this._options.showOnAllWorkspaces !== false) { + // https://github.com/electron/electron/issues/37832#issuecomment-1497882944 + this._browserWindow.setVisibleOnAllWorkspaces(true, { + skipTransformProcessType: true, // Avoid damaging the original visible state of app.dock + }); + } + + this._browserWindow.on('close', this.windowClear.bind(this)); - this.emit('before-load'); + this.emit('before-load'); - // If the user explicity set options.index to false, we don't loadURL - // https://github.com/maxogden/menubar/issues/255 - if (this._options.index !== false) { - await this._browserWindow.loadURL( - this._options.index, - this._options.loadUrlOptions, - ); + // If the user explicity set options.index to false, we don't loadURL + // https://github.com/maxogden/menubar/issues/255 + if (this._options.index !== false) { + await this._browserWindow.loadURL(this._options.index, this._options.loadUrlOptions); + } + this.emit('after-create-window'); } - this.emit('after-create-window'); - } - private windowClear(): void { - this._browserWindow = undefined; - this.emit('after-close'); - } + private windowClear(): void { + this._browserWindow = undefined; + this.emit('after-close'); + } } diff --git a/resources/electron/electron-plugin/src/libs/menubar/ambient.d.ts b/resources/electron/electron-plugin/src/libs/menubar/ambient.d.ts index 2ff6872e..4d71f6b5 100644 --- a/resources/electron/electron-plugin/src/libs/menubar/ambient.d.ts +++ b/resources/electron/electron-plugin/src/libs/menubar/ambient.d.ts @@ -1,11 +1,8 @@ // TODO https://github.com/jenslind/electron-positioner/issues/15 declare module 'electron-positioner' { - export default class { - constructor(window: Electron.BrowserWindow); + export default class { + constructor(window: Electron.BrowserWindow); - calculate( - position?: string, - rectangle?: Electron.Rectangle, - ): { x: number; y: number }; - } + calculate(position?: string, rectangle?: Electron.Rectangle): { x: number; y: number }; + } } diff --git a/resources/electron/electron-plugin/src/libs/menubar/index.ts b/resources/electron/electron-plugin/src/libs/menubar/index.ts index 6eba24dd..6e32030d 100644 --- a/resources/electron/electron-plugin/src/libs/menubar/index.ts +++ b/resources/electron/electron-plugin/src/libs/menubar/index.ts @@ -23,5 +23,5 @@ export { Menubar }; * {@link Options} */ export function menubar(options?: Partial): Menubar { - return new Menubar(app, options); + return new Menubar(app, options); } diff --git a/resources/electron/electron-plugin/src/libs/menubar/types.ts b/resources/electron/electron-plugin/src/libs/menubar/types.ts index cf37f654..51225240 100644 --- a/resources/electron/electron-plugin/src/libs/menubar/types.ts +++ b/resources/electron/electron-plugin/src/libs/menubar/types.ts @@ -1,104 +1,100 @@ -import type { - BrowserWindowConstructorOptions, - LoadURLOptions, - Tray, -} from 'electron'; +import type { BrowserWindowConstructorOptions, LoadURLOptions, Tray } from 'electron'; /** * Options for creating a menubar application */ export interface Options { - /** - * Listen on `app.on('activate')` to open menubar when app is activated. - * @default `true` - */ - activateWithApp?: boolean; - /** - * An Electron BrowserWindow instance, or an options object to be passed into - * the BrowserWindow constructor. - * @example - * ```typescript - * const options = { height: 640, width: 480 }; - * - * const mb = new Menubar({ - * browserWindow: options - * }); - * ``` - */ - browserWindow: BrowserWindowConstructorOptions; - /** - * The app source directory. - */ - dir: string; - /** - * The png icon to use for the menubar. A good size to start with is 20x20. - * To support retina, supply a 2x sized image (e.g. 40x40) with @2x added to - * the end of the name, so icon.png and icon@2x.png and Electron will - * automatically use your @2x version on retina screens. - */ - icon?: string | Electron.NativeImage; - /** - * The URL to load the menubar's browserWindow with. The url can be a remote - * address (e.g. `http://`) or a path to a local HTML file using the - * `file://` protocol. If false, then menubar won't call `loadURL` on - * start. - * @default `file:// + options.dir + index.html` - * @see https://electronjs.org/docs/api/browser-window#winloadurlurl-options - */ - index: string | false; - /** - * The options passed when loading the index URL in the menubar's - * browserWindow. Everything browserWindow.loadURL supports is supported; - * this object is simply passed onto browserWindow.loadURL - * @default `{}` - * @see https://electronjs.org/docs/api/browser-window#winloadurlurl-options - */ - loadUrlOptions?: LoadURLOptions; - /** - * Create BrowserWindow instance before it is used -- increasing resource - * usage, but making the click on the menubar load faster. - */ - preloadWindow?: boolean; - /** - * Configure the visibility of the application dock icon, macOS only. Calls - * [`app.dock.hide`](https://electronjs.org/docs/api/app#appdockhide-macos). - */ - showDockIcon?: boolean; - /** - * Makes the window available on all OS X workspaces. Calls - * [`setVisibleOnAllWorkspaces`](https://electronjs.org/docs/api/browser-window#winsetvisibleonallworkspacesvisible-options). - */ - showOnAllWorkspaces?: boolean; - /** - * Show the window on 'right-click' event instead of regular 'click'. - */ - showOnRightClick?: boolean; - /** - * Menubar tray icon tooltip text. Calls [`tray.setTooltip`](https://electronjs.org/docs/api/tray#traysettooltiptooltip). - */ - tooltip: string; - /** - * An electron Tray instance. If provided, `options.icon` will be ignored. - */ - tray?: Tray; - /** - * Sets the window position (x and y will still override this), check - * electron-positioner docs for valid values. - */ - windowPosition?: - | 'trayLeft' - | 'trayBottomLeft' - | 'trayRight' - | 'trayBottomRight' - | 'trayCenter' - | 'trayBottomCenter' - | 'topLeft' - | 'topRight' - | 'bottomLeft' - | 'bottomRight' - | 'topCenter' - | 'bottomCenter' - | 'leftCenter' - | 'rightCenter' - | 'center'; + /** + * Listen on `app.on('activate')` to open menubar when app is activated. + * @default `true` + */ + activateWithApp?: boolean; + /** + * An Electron BrowserWindow instance, or an options object to be passed into + * the BrowserWindow constructor. + * @example + * ```typescript + * const options = { height: 640, width: 480 }; + * + * const mb = new Menubar({ + * browserWindow: options + * }); + * ``` + */ + browserWindow: BrowserWindowConstructorOptions; + /** + * The app source directory. + */ + dir: string; + /** + * The png icon to use for the menubar. A good size to start with is 20x20. + * To support retina, supply a 2x sized image (e.g. 40x40) with @2x added to + * the end of the name, so icon.png and icon@2x.png and Electron will + * automatically use your @2x version on retina screens. + */ + icon?: string | Electron.NativeImage; + /** + * The URL to load the menubar's browserWindow with. The url can be a remote + * address (e.g. `http://`) or a path to a local HTML file using the + * `file://` protocol. If false, then menubar won't call `loadURL` on + * start. + * @default `file:// + options.dir + index.html` + * @see https://electronjs.org/docs/api/browser-window#winloadurlurl-options + */ + index: string | false; + /** + * The options passed when loading the index URL in the menubar's + * browserWindow. Everything browserWindow.loadURL supports is supported; + * this object is simply passed onto browserWindow.loadURL + * @default `{}` + * @see https://electronjs.org/docs/api/browser-window#winloadurlurl-options + */ + loadUrlOptions?: LoadURLOptions; + /** + * Create BrowserWindow instance before it is used -- increasing resource + * usage, but making the click on the menubar load faster. + */ + preloadWindow?: boolean; + /** + * Configure the visibility of the application dock icon, macOS only. Calls + * [`app.dock.hide`](https://electronjs.org/docs/api/app#appdockhide-macos). + */ + showDockIcon?: boolean; + /** + * Makes the window available on all OS X workspaces. Calls + * [`setVisibleOnAllWorkspaces`](https://electronjs.org/docs/api/browser-window#winsetvisibleonallworkspacesvisible-options). + */ + showOnAllWorkspaces?: boolean; + /** + * Show the window on 'right-click' event instead of regular 'click'. + */ + showOnRightClick?: boolean; + /** + * Menubar tray icon tooltip text. Calls [`tray.setTooltip`](https://electronjs.org/docs/api/tray#traysettooltiptooltip). + */ + tooltip: string; + /** + * An electron Tray instance. If provided, `options.icon` will be ignored. + */ + tray?: Tray; + /** + * Sets the window position (x and y will still override this), check + * electron-positioner docs for valid values. + */ + windowPosition?: + | 'trayLeft' + | 'trayBottomLeft' + | 'trayRight' + | 'trayBottomRight' + | 'trayCenter' + | 'trayBottomCenter' + | 'topLeft' + | 'topRight' + | 'bottomLeft' + | 'bottomRight' + | 'topCenter' + | 'bottomCenter' + | 'leftCenter' + | 'rightCenter' + | 'center'; } diff --git a/resources/electron/electron-plugin/src/libs/menubar/util/cleanOptions.ts b/resources/electron/electron-plugin/src/libs/menubar/util/cleanOptions.ts index d677340d..746b67ff 100644 --- a/resources/electron/electron-plugin/src/libs/menubar/util/cleanOptions.ts +++ b/resources/electron/electron-plugin/src/libs/menubar/util/cleanOptions.ts @@ -4,9 +4,9 @@ /** */ +import { app } from 'electron'; import path from 'path'; import url from 'url'; -import { app } from 'electron'; import type { Options } from '../types.js'; @@ -20,48 +20,44 @@ const DEFAULT_WINDOW_WIDTH = 400; * @ignore */ export function cleanOptions(opts?: Partial): Options { - const options: Partial = { ...opts }; - - if (options.activateWithApp === undefined) { - options.activateWithApp = true; - } - if (!options.dir) { - options.dir = app.getAppPath(); - } - if (!path.isAbsolute(options.dir)) { - options.dir = path.resolve(options.dir); - } - // Note: options.index can be `false` - if (options.index === undefined) { - options.index = url.format({ - pathname: path.join(options.dir, 'index.html'), - protocol: 'file:', - slashes: true, - }); - } - options.loadUrlOptions = options.loadUrlOptions || {}; - - options.tooltip = options.tooltip || ''; - - // `icon`, `preloadWindow`, `showDockIcon`, `showOnAllWorkspaces`, - // `showOnRightClick` don't need any special treatment - - // Now we take care of `browserWindow` - if (!options.browserWindow) { - options.browserWindow = {}; - } - - // Set width/height on options to be usable before the window is created - options.browserWindow.width = - // Note: not using `options.browserWindow.width || DEFAULT_WINDOW_WIDTH` so - // that users can put a 0 width - options.browserWindow.width !== undefined - ? options.browserWindow.width - : DEFAULT_WINDOW_WIDTH; - options.browserWindow.height = - options.browserWindow.height !== undefined - ? options.browserWindow.height - : DEFAULT_WINDOW_HEIGHT; - - return options as Options; + const options: Partial = { ...opts }; + + if (options.activateWithApp === undefined) { + options.activateWithApp = true; + } + if (!options.dir) { + options.dir = app.getAppPath(); + } + if (!path.isAbsolute(options.dir)) { + options.dir = path.resolve(options.dir); + } + // Note: options.index can be `false` + if (options.index === undefined) { + options.index = url.format({ + pathname: path.join(options.dir, 'index.html'), + protocol: 'file:', + slashes: true, + }); + } + options.loadUrlOptions = options.loadUrlOptions || {}; + + options.tooltip = options.tooltip || ''; + + // `icon`, `preloadWindow`, `showDockIcon`, `showOnAllWorkspaces`, + // `showOnRightClick` don't need any special treatment + + // Now we take care of `browserWindow` + if (!options.browserWindow) { + options.browserWindow = {}; + } + + // Set width/height on options to be usable before the window is created + options.browserWindow.width = + // Note: not using `options.browserWindow.width || DEFAULT_WINDOW_WIDTH` so + // that users can put a 0 width + options.browserWindow.width !== undefined ? options.browserWindow.width : DEFAULT_WINDOW_WIDTH; + options.browserWindow.height = + options.browserWindow.height !== undefined ? options.browserWindow.height : DEFAULT_WINDOW_HEIGHT; + + return options as Options; } diff --git a/resources/electron/electron-plugin/src/libs/menubar/util/getWindowPosition.ts b/resources/electron/electron-plugin/src/libs/menubar/util/getWindowPosition.ts index a4010a6b..64546c5a 100644 --- a/resources/electron/electron-plugin/src/libs/menubar/util/getWindowPosition.ts +++ b/resources/electron/electron-plugin/src/libs/menubar/util/getWindowPosition.ts @@ -9,15 +9,13 @@ import { type Rectangle, type Tray, screen as electronScreen } from 'electron'; const isLinux = process.platform === 'linux'; const trayToScreenRects = (tray: Tray): [Rectangle, Rectangle] => { - // There may be more than one screen, so we need to figure out on which screen our tray icon lives. - const { workArea, bounds: screenBounds } = electronScreen.getDisplayMatching( - tray.getBounds(), - ); + // There may be more than one screen, so we need to figure out on which screen our tray icon lives. + const { workArea, bounds: screenBounds } = electronScreen.getDisplayMatching(tray.getBounds()); - workArea.x -= screenBounds.x; - workArea.y -= screenBounds.y; + workArea.x -= screenBounds.x; + workArea.y -= screenBounds.y; - return [screenBounds, workArea]; + return [screenBounds, workArea]; }; type TaskbarLocation = 'top' | 'bottom' | 'left' | 'right'; @@ -30,42 +28,37 @@ type TaskbarLocation = 'top' | 'bottom' | 'left' | 'right'; * @param tray - The Electron Tray instance. */ export function taskbarLocation(tray: Tray): TaskbarLocation { - const [screenBounds, workArea] = trayToScreenRects(tray); - - // TASKBAR LEFT - if (workArea.x > 0) { - // Most likely Ubuntu hence assuming the window should be on top - if (isLinux && workArea.y > 0) return 'top'; - // The workspace starts more on the right - return 'left'; - } - - // TASKBAR TOP - if (workArea.y > 0) { - return 'top'; - } - - // TASKBAR RIGHT - // Here both workArea.y and workArea.x are 0 so we can no longer leverage them. - // We can use the workarea and display width though. - // Determine taskbar location - if (workArea.width < screenBounds.width) { - // The taskbar is either on the left or right, but since the LEFT case was handled above, - // we can be sure we're dealing with a right taskbar - return 'right'; - } - - // TASKBAR BOTTOM - // Since all the other cases were handled, we can be sure we're dealing with a bottom taskbar - return 'bottom'; + const [screenBounds, workArea] = trayToScreenRects(tray); + + // TASKBAR LEFT + if (workArea.x > 0) { + // Most likely Ubuntu hence assuming the window should be on top + if (isLinux && workArea.y > 0) return 'top'; + // The workspace starts more on the right + return 'left'; + } + + // TASKBAR TOP + if (workArea.y > 0) { + return 'top'; + } + + // TASKBAR RIGHT + // Here both workArea.y and workArea.x are 0 so we can no longer leverage them. + // We can use the workarea and display width though. + // Determine taskbar location + if (workArea.width < screenBounds.width) { + // The taskbar is either on the left or right, but since the LEFT case was handled above, + // we can be sure we're dealing with a right taskbar + return 'right'; + } + + // TASKBAR BOTTOM + // Since all the other cases were handled, we can be sure we're dealing with a bottom taskbar + return 'bottom'; } -type WindowPosition = - | 'trayCenter' - | 'topRight' - | 'trayBottomCenter' - | 'bottomLeft' - | 'bottomRight'; +type WindowPosition = 'trayCenter' | 'topRight' | 'trayBottomCenter' | 'bottomLeft' | 'bottomRight'; /** * Depending on where the taskbar is, determine where the window should be @@ -74,34 +67,34 @@ type WindowPosition = * @param tray - The Electron Tray instance. */ export function getWindowPosition(tray: Tray): WindowPosition { - switch (process.platform) { - // macOS - // Supports top taskbars - case 'darwin': - return 'trayCenter'; - // Linux - // Windows - // Supports top/bottom/left/right taskbar - case 'linux': - case 'win32': { - const traySide = taskbarLocation(tray); - - // Assign position for menubar - if (traySide === 'top') { - return isLinux ? 'topRight' : 'trayCenter'; - } - if (traySide === 'bottom') { - return 'bottomRight'; - } - if (traySide === 'left') { - return 'bottomLeft'; - } - if (traySide === 'right') { - return 'bottomRight'; - } + switch (process.platform) { + // macOS + // Supports top taskbars + case 'darwin': + return 'trayCenter'; + // Linux + // Windows + // Supports top/bottom/left/right taskbar + case 'linux': + case 'win32': { + const traySide = taskbarLocation(tray); + + // Assign position for menubar + if (traySide === 'top') { + return isLinux ? 'topRight' : 'trayCenter'; + } + if (traySide === 'bottom') { + return 'bottomRight'; + } + if (traySide === 'left') { + return 'bottomLeft'; + } + if (traySide === 'right') { + return 'bottomRight'; + } + } } - } - // When we really don't know, we just show the menubar on the top-right - return 'topRight'; + // When we really don't know, we just show the menubar on the top-right + return 'topRight'; } diff --git a/resources/electron/electron-plugin/src/libs/positioner/index.ts b/resources/electron/electron-plugin/src/libs/positioner/index.ts index eacd4c31..b1014b06 100644 --- a/resources/electron/electron-plugin/src/libs/positioner/index.ts +++ b/resources/electron/electron-plugin/src/libs/positioner/index.ts @@ -1,133 +1,92 @@ -"use strict"; +'use strict'; +import type { Screen } from 'electron'; +import { BrowserWindow, screen } from 'electron'; class Positioner { - browserWindow: any; - electronScreen: any; + browserWindow?: BrowserWindow; + electronScreen: Screen; - constructor(browserWindow: any) { + constructor(browserWindow?: BrowserWindow) { this.browserWindow = browserWindow; - this.electronScreen = require("electron").screen; + this.electronScreen = screen; } _getCoords(position, trayPosition) { - let screenSize = this._getScreenSize(trayPosition); - let windowSize = this._getWindowSize(); + const screenSize = this._getScreenSize(trayPosition); + const windowSize = this._getWindowSize(); if (trayPosition === undefined) trayPosition = {}; // Positions - let positions = { + const positions = { trayLeft: { x: Math.floor(trayPosition.x), y: screenSize.y, }, trayBottomLeft: { x: Math.floor(trayPosition.x), - y: Math.floor( - screenSize.height - (windowSize[1] - screenSize.y), - ), + y: Math.floor(screenSize.height - (windowSize[1] - screenSize.y)), }, trayRight: { - x: Math.floor( - trayPosition.x - windowSize[0] + trayPosition.width, - ), + x: Math.floor(trayPosition.x - windowSize[0] + trayPosition.width), y: screenSize.y, }, trayBottomRight: { - x: Math.floor( - trayPosition.x - windowSize[0] + trayPosition.width, - ), - y: Math.floor( - screenSize.height - (windowSize[1] - screenSize.y), - ), + x: Math.floor(trayPosition.x - windowSize[0] + trayPosition.width), + y: Math.floor(screenSize.height - (windowSize[1] - screenSize.y)), }, trayCenter: { - x: Math.floor( - trayPosition.x - windowSize[0] / 2 + trayPosition.width / 2, - ), + x: Math.floor(trayPosition.x - windowSize[0] / 2 + trayPosition.width / 2), y: screenSize.y, }, trayBottomCenter: { - x: Math.floor( - trayPosition.x - windowSize[0] / 2 + trayPosition.width / 2, - ), - y: Math.floor( - screenSize.height - (windowSize[1] - screenSize.y), - ), + x: Math.floor(trayPosition.x - windowSize[0] / 2 + trayPosition.width / 2), + y: Math.floor(screenSize.height - (windowSize[1] - screenSize.y)), }, topLeft: { x: screenSize.x, y: screenSize.y, }, topRight: { - x: Math.floor( - screenSize.x + (screenSize.width - windowSize[0]), - ), + x: Math.floor(screenSize.x + (screenSize.width - windowSize[0])), y: screenSize.y, }, bottomLeft: { x: screenSize.x, - y: Math.floor( - screenSize.height - (windowSize[1] - screenSize.y), - ), + y: Math.floor(screenSize.height - (windowSize[1] - screenSize.y)), }, bottomRight: { - x: Math.floor( - screenSize.x + (screenSize.width - windowSize[0]), - ), - y: Math.floor( - screenSize.height - (windowSize[1] - screenSize.y), - ), + x: Math.floor(screenSize.x + (screenSize.width - windowSize[0])), + y: Math.floor(screenSize.height - (windowSize[1] - screenSize.y)), }, topCenter: { - x: Math.floor( - screenSize.x + (screenSize.width / 2 - windowSize[0] / 2), - ), + x: Math.floor(screenSize.x + (screenSize.width / 2 - windowSize[0] / 2)), y: screenSize.y, }, bottomCenter: { - x: Math.floor( - screenSize.x + (screenSize.width / 2 - windowSize[0] / 2), - ), - y: Math.floor( - screenSize.height - (windowSize[1] - screenSize.y), - ), + x: Math.floor(screenSize.x + (screenSize.width / 2 - windowSize[0] / 2)), + y: Math.floor(screenSize.height - (windowSize[1] - screenSize.y)), }, leftCenter: { x: screenSize.x, - y: - screenSize.y + - Math.floor(screenSize.height / 2) - - Math.floor(windowSize[1] / 2), + y: screenSize.y + Math.floor(screenSize.height / 2) - Math.floor(windowSize[1] / 2), }, rightCenter: { - x: Math.floor( - screenSize.x + (screenSize.width - windowSize[0]), - ), - y: - screenSize.y + - Math.floor(screenSize.height / 2) - - Math.floor(windowSize[1] / 2), + x: Math.floor(screenSize.x + (screenSize.width - windowSize[0])), + y: screenSize.y + Math.floor(screenSize.height / 2) - Math.floor(windowSize[1] / 2), }, center: { - x: Math.floor( - screenSize.x + (screenSize.width / 2 - windowSize[0] / 2), - ), - y: Math.floor( - (screenSize.height + screenSize.y) / 2 - windowSize[1] / 2, - ), + x: Math.floor(screenSize.x + (screenSize.width / 2 - windowSize[0] / 2)), + y: Math.floor((screenSize.height + screenSize.y) / 2 - windowSize[1] / 2), }, }; // Default to right if the window is bigger than the space left. // Because on Windows the window might get out of bounds and dissappear. - if (position.substr(0, 4) === "tray") { - if ( - positions[position].x + windowSize[0] > - screenSize.width + screenSize.x - ) { + if (position.substr(0, 4) === 'tray') { + if (positions[position].x + windowSize[0] > screenSize.width + screenSize.x) { return { - x: positions["topRight"].x, + x: positions['topRight'].x, y: positions[position].y, }; } @@ -142,18 +101,15 @@ class Positioner { _getScreenSize(trayPosition) { if (trayPosition) { - return this.electronScreen.getDisplayMatching(trayPosition) - .workArea; + return this.electronScreen.getDisplayMatching(trayPosition).workArea; } else { - return this.electronScreen.getDisplayNearestPoint( - this.electronScreen.getCursorScreenPoint(), - ).workArea; + return this.electronScreen.getDisplayNearestPoint(this.electronScreen.getCursorScreenPoint()).workArea; } } move(position, trayPos) { // Get positions coords - var coords = this._getCoords(position, trayPos); + const coords = this._getCoords(position, trayPos); // Set the windows position this.browserWindow.setPosition(coords.x, coords.y); @@ -161,7 +117,7 @@ class Positioner { calculate(position, trayPos) { // Get positions coords - var coords = this._getCoords(position, trayPos); + const coords = this._getCoords(position, trayPos); return { x: coords.x, diff --git a/resources/electron/electron-plugin/src/preload/index.mts b/resources/electron/electron-plugin/src/preload/index.mts index 66064510..71d787ed 100644 --- a/resources/electron/electron-plugin/src/preload/index.mts +++ b/resources/electron/electron-plugin/src/preload/index.mts @@ -1,5 +1,5 @@ -import remote from "@electron/remote"; -import {ipcRenderer, contextBridge} from "electron"; +import remote from '@electron/remote'; +import { contextBridge, ipcRenderer } from 'electron'; // ------------------------------------------------------------------- // The Native helper @@ -14,12 +14,12 @@ const Native = { if (event === data.event) { return callback(data.payload, event); } - }) + }); }, contextMenu: (template) => { - let menu = remote.Menu.buildFromTemplate(template); + const menu = remote.Menu.buildFromTemplate(template); menu.popup({ window: remote.getCurrentWindow() }); - } + }, }; contextBridge.exposeInMainWorld('Native', Native); @@ -27,50 +27,54 @@ contextBridge.exposeInMainWorld('Native', Native); // ------------------------------------------------------------------- // Log events // ------------------------------------------------------------------- -ipcRenderer.on('log', (event, {level, message, context}) => { +ipcRenderer.on('log', (event, { level, message, context }) => { if (level === 'error') { - console.error(`[${level}] ${message}`, context) + console.error(`[${level}] ${message}`, context); } else if (level === 'warn') { - console.warn(`[${level}] ${message}`, context) + console.warn(`[${level}] ${message}`, context); } else { - console.log(`[${level}] ${message}`, context) + console.log(`[${level}] ${message}`, context); } }); - // ------------------------------------------------------------------- // Livewire event listeners // ------------------------------------------------------------------- ipcRenderer.on('native-event', (event, data) => { - // Strip leading slashes data.event = data.event.replace(/^(\\)+/, ''); // Forward event to renderer context // Handler injected via Events\LivewireDispatcher - window.postMessage({ - type: 'native-event', - event: data.event, - payload: data.payload - }, '*'); -}) + window.postMessage( + { + type: 'native-event', + event: data.event, + payload: data.payload, + }, + '*', + ); +}); // ------------------------------------------------------------------- // Let the client know preload is fully evaluated // ------------------------------------------------------------------- -contextBridge.exposeInMainWorld('native:initialized', (function() { - // This is admittedly a bit hacky. Due to context isolation - // we don't have direct access to the renderer window object, - // but by assigning a bridge function that executes itself inside - // the renderer context we can hack around it. +contextBridge.exposeInMainWorld( + 'native:initialized', + (function () { + // This is admittedly a bit hacky. Due to context isolation + // we don't have direct access to the renderer window object, + // but by assigning a bridge function that executes itself inside + // the renderer context we can hack around it. - // It's recommended to use window.postMessage & dispatch an - // event from the renderer itself, but we're loading webcontent - // from localhost. We don't have a renderer process we can access. - // Though this is hacky it works well and is the simplest way to do this - // without sprinkling additional logic all over the place. + // It's recommended to use window.postMessage & dispatch an + // event from the renderer itself, but we're loading webcontent + // from localhost. We don't have a renderer process we can access. + // Though this is hacky it works well and is the simplest way to do this + // without sprinkling additional logic all over the place. - window.dispatchEvent(new CustomEvent('native:init')); + window.dispatchEvent(new CustomEvent('native:init')); - return true; -})()) + return true; + })(), +); diff --git a/resources/electron/electron-plugin/src/preload/livewire-dispatcher.js b/resources/electron/electron-plugin/src/preload/livewire-dispatcher.js index 3b4edff1..779134a0 100644 --- a/resources/electron/electron-plugin/src/preload/livewire-dispatcher.js +++ b/resources/electron/electron-plugin/src/preload/livewire-dispatcher.js @@ -1,5 +1,5 @@ -window.addEventListener("message", (event) => { - if (event.data.type === "native-event") { +window.addEventListener('message', (event) => { + if (event.data.type === 'native-event') { const { event: eventName, payload } = event.data; LivewireDispatcher.handle(eventName, payload); @@ -10,7 +10,7 @@ const LivewireDispatcher = { handle: function (eventName, payload) { // Livewire 3 if (window.Livewire) { - window.Livewire.dispatch("native:" + eventName, payload); + window.Livewire.dispatch('native:' + eventName, payload); } // Livewire 2 @@ -18,14 +18,11 @@ const LivewireDispatcher = { window.livewire.components.components().forEach((component) => { if (Array.isArray(component.listeners)) { component.listeners.forEach((event) => { - - if (event.startsWith("native")) { - let event_parts = event.split( - /(native:|native-)|:|,/, - ); + if (event.startsWith('native')) { + let event_parts = event.split(/(native:|native-)|:|,/); - if (event_parts[1] == "native:") { - event_parts.splice(2, 0, "private", undefined, "nativephp", undefined); + if (event_parts[1] === 'native:') { + event_parts.splice(2, 0, 'private', undefined, 'nativephp', undefined); } let [s1, signature, channel_type, s2, channel, s3, event_name] = event_parts; diff --git a/resources/electron/electron-plugin/src/server/ProcessResult.ts b/resources/electron/electron-plugin/src/server/ProcessResult.ts index d4c1df30..3df1271d 100644 --- a/resources/electron/electron-plugin/src/server/ProcessResult.ts +++ b/resources/electron/electron-plugin/src/server/ProcessResult.ts @@ -1,6 +1,6 @@ -import { ChildProcessWithoutNullStreams } from "child_process"; +import { ChildProcessWithoutNullStreams } from 'child_process'; export interface ProcessResult { - process: ChildProcessWithoutNullStreams; - port: number; + process: ChildProcessWithoutNullStreams; + port: number; } diff --git a/resources/electron/electron-plugin/src/server/api.ts b/resources/electron/electron-plugin/src/server/api.ts index 5ff685e5..57f0942b 100644 --- a/resources/electron/electron-plugin/src/server/api.ts +++ b/resources/electron/electron-plugin/src/server/api.ts @@ -1,79 +1,79 @@ -import express from "express"; -import bodyParser from "body-parser"; -import getPort, {portNumbers} from "get-port"; -import middleware from "./api/middleware.js"; +import bodyParser from 'body-parser'; +import express from 'express'; +import getPort, { portNumbers } from 'get-port'; +import middleware from './api/middleware.js'; -import clipboardRoutes from "./api/clipboard.js"; -import alertRoutes from "./api/alert.js"; -import appRoutes from "./api/app.js"; -import autoUpdaterRoutes from "./api/autoUpdater.js"; -import screenRoutes from "./api/screen.js"; -import dialogRoutes from "./api/dialog.js"; -import debugRoutes from "./api/debug.js"; -import broadcastingRoutes from "./api/broadcasting.js"; -import systemRoutes from "./api/system.js"; -import globalShortcutRoutes from "./api/globalShortcut.js"; -import notificationRoutes from "./api/notification.js"; -import dockRoutes from "./api/dock.js"; -import menuRoutes from "./api/menu.js"; -import menuBarRoutes from "./api/menuBar.js"; -import windowRoutes from "./api/window.js"; -import processRoutes from "./api/process.js"; -import contextMenuRoutes from "./api/contextMenu.js"; -import settingsRoutes from "./api/settings.js"; -import shellRoutes from "./api/shell.js"; -import progressBarRoutes from "./api/progressBar.js"; -import powerMonitorRoutes from "./api/powerMonitor.js"; -import childProcessRoutes from "./api/childProcess.js"; -import { Server } from "net"; +import { Server } from 'net'; +import alertRoutes from './api/alert.js'; +import appRoutes from './api/app.js'; +import autoUpdaterRoutes from './api/autoUpdater.js'; +import broadcastingRoutes from './api/broadcasting.js'; +import childProcessRoutes from './api/childProcess.js'; +import clipboardRoutes from './api/clipboard.js'; +import contextMenuRoutes from './api/contextMenu.js'; +import debugRoutes from './api/debug.js'; +import dialogRoutes from './api/dialog.js'; +import dockRoutes from './api/dock.js'; +import globalShortcutRoutes from './api/globalShortcut.js'; +import menuRoutes from './api/menu.js'; +import menuBarRoutes from './api/menuBar.js'; +import notificationRoutes from './api/notification.js'; +import powerMonitorRoutes from './api/powerMonitor.js'; +import processRoutes from './api/process.js'; +import progressBarRoutes from './api/progressBar.js'; +import screenRoutes from './api/screen.js'; +import settingsRoutes from './api/settings.js'; +import shellRoutes from './api/shell.js'; +import systemRoutes from './api/system.js'; +import windowRoutes from './api/window.js'; export interface APIProcess { - server: Server; - port: number; + server: Server; + port: number; } async function startAPIServer(randomSecret: string): Promise { - const port = await getPort({ - port: portNumbers(4000, 5000), - }); + const port = await getPort({ + port: portNumbers(4000, 5000), + }); - return new Promise((resolve, reject) => { - const httpServer = express(); - httpServer.use(middleware(randomSecret)); - httpServer.use(bodyParser.json()); - httpServer.use("/api/clipboard", clipboardRoutes); - httpServer.use("/api/alert", alertRoutes); - httpServer.use("/api/app", appRoutes); - httpServer.use("/api/auto-updater", autoUpdaterRoutes); - httpServer.use("/api/screen", screenRoutes); - httpServer.use("/api/dialog", dialogRoutes); - httpServer.use("/api/system", systemRoutes); - httpServer.use("/api/global-shortcuts", globalShortcutRoutes); - httpServer.use("/api/notification", notificationRoutes); - httpServer.use("/api/dock", dockRoutes); - httpServer.use("/api/menu", menuRoutes); - httpServer.use("/api/window", windowRoutes); - httpServer.use("/api/process", processRoutes); - httpServer.use("/api/settings", settingsRoutes); - httpServer.use("/api/shell", shellRoutes); - httpServer.use("/api/context", contextMenuRoutes); - httpServer.use("/api/menu-bar", menuBarRoutes); - httpServer.use("/api/progress-bar", progressBarRoutes); - httpServer.use("/api/power-monitor", powerMonitorRoutes); - httpServer.use("/api/child-process", childProcessRoutes); - httpServer.use("/api/broadcast", broadcastingRoutes); + return new Promise((resolve) => { + const httpServer = express(); + httpServer.use(middleware(randomSecret)); + httpServer.use(bodyParser.json()); + httpServer.use('/api/clipboard', clipboardRoutes); + httpServer.use('/api/alert', alertRoutes); + httpServer.use('/api/app', appRoutes); + httpServer.use('/api/auto-updater', autoUpdaterRoutes); + httpServer.use('/api/screen', screenRoutes); + httpServer.use('/api/dialog', dialogRoutes); + httpServer.use('/api/system', systemRoutes); + httpServer.use('/api/global-shortcuts', globalShortcutRoutes); + httpServer.use('/api/notification', notificationRoutes); + httpServer.use('/api/dock', dockRoutes); + httpServer.use('/api/menu', menuRoutes); + httpServer.use('/api/window', windowRoutes); + httpServer.use('/api/process', processRoutes); + httpServer.use('/api/settings', settingsRoutes); + httpServer.use('/api/shell', shellRoutes); + httpServer.use('/api/context', contextMenuRoutes); + httpServer.use('/api/menu-bar', menuBarRoutes); + httpServer.use('/api/progress-bar', progressBarRoutes); + httpServer.use('/api/power-monitor', powerMonitorRoutes); + httpServer.use('/api/child-process', childProcessRoutes); + httpServer.use('/api/broadcast', broadcastingRoutes); - if (process.env.NODE_ENV === "development") { - httpServer.use("/api/debug", debugRoutes); - } + if (process.env.NODE_ENV === 'development') { + httpServer.use('/api/debug', debugRoutes); + } - const server = httpServer.listen(port, () => { - resolve({ - server, - port, - }); + const server = httpServer.listen(port, () => { + resolve({ + server, + port, + }); + }); }); - }); } export default startAPIServer; diff --git a/resources/electron/electron-plugin/src/server/api/alert.ts b/resources/electron/electron-plugin/src/server/api/alert.ts index 30f31d09..2e3e0881 100644 --- a/resources/electron/electron-plugin/src/server/api/alert.ts +++ b/resources/electron/electron-plugin/src/server/api/alert.ts @@ -1,5 +1,5 @@ -import express from 'express' -import { dialog } from 'electron' +import { dialog } from 'electron'; +import express from 'express'; const router = express.Router(); router.post('/message', (req, res) => { @@ -11,10 +11,10 @@ router.post('/message', (req, res) => { detail: detail ?? undefined, buttons: buttons ?? undefined, defaultId: defaultId ?? undefined, - cancelId: cancelId ?? undefined + cancelId: cancelId ?? undefined, }); res.json({ - result + result, }); }); @@ -24,7 +24,7 @@ router.post('/error', (req, res) => { dialog.showErrorBox(title, message); res.json({ - result: true + result: true, }); }); diff --git a/resources/electron/electron-plugin/src/server/api/app.ts b/resources/electron/electron-plugin/src/server/api/app.ts index f33ad652..0e8d69eb 100644 --- a/resources/electron/electron-plugin/src/server/api/app.ts +++ b/resources/electron/electron-plugin/src/server/api/app.ts @@ -1,79 +1,79 @@ -import express from 'express' -import { app } from 'electron' +import { app } from 'electron'; +import express from 'express'; const router = express.Router(); router.post('/quit', (req, res) => { - app.quit() + app.quit(); res.sendStatus(200); }); -router.post('/relaunch', (req, res) => { - app.relaunch() - app.quit() +router.post('/relaunch', () => { + app.relaunch(); + app.quit(); }); router.post('/show', (req, res) => { - app.show() + app.show(); res.sendStatus(200); }); router.post('/hide', (req, res) => { - app.hide() + app.hide(); res.sendStatus(200); }); router.get('/is-hidden', (req, res) => { res.json({ is_hidden: app.isHidden(), - }) + }); }); router.get('/locale', (req, res) => { res.json({ locale: app.getLocale(), - }) + }); }); router.get('/locale-country-code', (req, res) => { res.json({ locale_country_code: app.getLocaleCountryCode(), - }) + }); }); router.get('/system-locale', (req, res) => { res.json({ system_locale: app.getSystemLocale(), - }) + }); }); router.get('/app-path', (req, res) => { res.json({ path: app.getAppPath(), - }) + }); }); router.get('/path/:name', (req, res) => { res.json({ // @ts-ignore path: app.getPath(req.params.name), - }) + }); }); router.get('/version', (req, res) => { res.json({ version: app.getVersion(), - }) + }); }); router.post('/badge-count', (req, res) => { - app.setBadgeCount(req.body.count) + app.setBadgeCount(req.body.count); res.sendStatus(200); }); router.get('/badge-count', (req, res) => { res.json({ count: app.getBadgeCount(), - }) + }); }); router.post('/recent-documents', (req, res) => { diff --git a/resources/electron/electron-plugin/src/server/api/autoUpdater.ts b/resources/electron/electron-plugin/src/server/api/autoUpdater.ts index 6f65e8e1..d7c8c782 100644 --- a/resources/electron/electron-plugin/src/server/api/autoUpdater.ts +++ b/resources/electron/electron-plugin/src/server/api/autoUpdater.ts @@ -1,38 +1,34 @@ -import express from "express"; +import type { ProgressInfo, UpdateDownloadedEvent, UpdateInfo } from 'electron-updater'; import electronUpdater from 'electron-updater'; +import express from 'express'; +import { notifyLaravel } from '../utils.js'; const { autoUpdater } = electronUpdater; -import type { - ProgressInfo, - UpdateDownloadedEvent, - UpdateInfo, -} from "electron-updater"; -import { notifyLaravel } from "../utils.js"; const router = express.Router(); -router.post("/check-for-updates", (req, res) => { +router.post('/check-for-updates', (req, res) => { autoUpdater.checkForUpdates(); res.sendStatus(200); }); -router.post("/download-update", (req, res) => { +router.post('/download-update', (req, res) => { autoUpdater.downloadUpdate(); res.sendStatus(200); }); -router.post("/quit-and-install", (req, res) => { +router.post('/quit-and-install', (req, res) => { autoUpdater.quitAndInstall(); res.sendStatus(200); }); -autoUpdater.addListener("checking-for-update", () => { - notifyLaravel("events", { +autoUpdater.addListener('checking-for-update', () => { + notifyLaravel('events', { event: `\\Native\\Desktop\\Events\\AutoUpdater\\CheckingForUpdate`, }); }); -autoUpdater.addListener("update-available", (event: UpdateInfo) => { - notifyLaravel("events", { +autoUpdater.addListener('update-available', (event: UpdateInfo) => { + notifyLaravel('events', { event: `\\Native\\Desktop\\Events\\AutoUpdater\\UpdateAvailable`, payload: { version: event.version, @@ -46,8 +42,8 @@ autoUpdater.addListener("update-available", (event: UpdateInfo) => { }); }); -autoUpdater.addListener("update-not-available", (event: UpdateInfo) => { - notifyLaravel("events", { +autoUpdater.addListener('update-not-available', (event: UpdateInfo) => { + notifyLaravel('events', { event: `\\Native\\Desktop\\Events\\AutoUpdater\\UpdateNotAvailable`, payload: { version: event.version, @@ -61,8 +57,8 @@ autoUpdater.addListener("update-not-available", (event: UpdateInfo) => { }); }); -autoUpdater.addListener("error", (error: Error) => { - notifyLaravel("events", { +autoUpdater.addListener('error', (error: Error) => { + notifyLaravel('events', { event: `\\Native\\Desktop\\Events\\AutoUpdater\\Error`, payload: { name: error.name, @@ -72,8 +68,8 @@ autoUpdater.addListener("error", (error: Error) => { }); }); -autoUpdater.addListener("download-progress", (progressInfo: ProgressInfo) => { - notifyLaravel("events", { +autoUpdater.addListener('download-progress', (progressInfo: ProgressInfo) => { + notifyLaravel('events', { event: `\\Native\\Desktop\\Events\\AutoUpdater\\DownloadProgress`, payload: { total: progressInfo.total, @@ -85,8 +81,8 @@ autoUpdater.addListener("download-progress", (progressInfo: ProgressInfo) => { }); }); -autoUpdater.addListener("update-downloaded", (event: UpdateDownloadedEvent) => { - notifyLaravel("events", { +autoUpdater.addListener('update-downloaded', (event: UpdateDownloadedEvent) => { + notifyLaravel('events', { event: `\\Native\\Desktop\\Events\\AutoUpdater\\UpdateDownloaded`, payload: { downloadedFile: event.downloadedFile, @@ -101,8 +97,8 @@ autoUpdater.addListener("update-downloaded", (event: UpdateDownloadedEvent) => { }); }); -autoUpdater.addListener("update-cancelled", (event: UpdateInfo) => { - notifyLaravel("events", { +autoUpdater.addListener('update-cancelled', (event: UpdateInfo) => { + notifyLaravel('events', { event: `\\Native\\Desktop\\Events\\AutoUpdater\\UpdateCancelled`, payload: { version: event.version, diff --git a/resources/electron/electron-plugin/src/server/api/broadcasting.ts b/resources/electron/electron-plugin/src/server/api/broadcasting.ts index d0e3d844..a1e2d4a5 100644 --- a/resources/electron/electron-plugin/src/server/api/broadcasting.ts +++ b/resources/electron/electron-plugin/src/server/api/broadcasting.ts @@ -1,13 +1,13 @@ -import express from 'express' +import express from 'express'; import { broadcastToWindows } from '../utils.js'; const router = express.Router(); router.post('/', (req, res) => { - const {event, payload} = req.body; + const { event, payload } = req.body; - broadcastToWindows("native-event", { event, payload }); + broadcastToWindows('native-event', { event, payload }); - res.sendStatus(200) -}) + res.sendStatus(200); +}); export default router; diff --git a/resources/electron/electron-plugin/src/server/api/childProcess.ts b/resources/electron/electron-plugin/src/server/api/childProcess.ts index bcf0b5d3..87be31cc 100644 --- a/resources/electron/electron-plugin/src/server/api/childProcess.ts +++ b/resources/electron/electron-plugin/src/server/api/childProcess.ts @@ -1,16 +1,16 @@ +import { utilityProcess, UtilityProcess } from 'electron'; import express from 'express'; -import {utilityProcess, UtilityProcess} from 'electron'; +import killSync from 'kill-sync'; +import { join } from 'path'; +import { fileURLToPath } from 'url'; +import { getAppPath, getDefaultEnvironmentVariables, getDefaultPhpIniSettings, runningSecureBuild } from '../php.js'; import state from '../state.js'; -import {notifyLaravel} from "../utils.js"; -import {getAppPath, getDefaultEnvironmentVariables, getDefaultPhpIniSettings, runningSecureBuild} from "../php.js"; -import killSync from "kill-sync"; -import {fileURLToPath} from "url"; -import {join} from "path"; +import { notifyLaravel } from '../utils.js'; const router = express.Router(); function startProcess(settings, useNodeRuntime = false) { - const {alias, cmd, cwd, env, persistent, spawnTimeout = 30000} = settings; + const { alias, cmd, cwd, env, spawnTimeout = 30000 } = settings; if (getProcess(alias) !== undefined) { return state.processes[alias]; @@ -27,9 +27,9 @@ function startProcess(settings, useNodeRuntime = false) { env: { ...process.env, ...env, - USE_NODE_RUNTIME: useNodeRuntime ? '1' : '0' - } - } + USE_NODE_RUNTIME: useNodeRuntime ? '1' : '0', + }, + }, ); // Set timeout to detect if process never spawns @@ -40,7 +40,7 @@ function startProcess(settings, useNodeRuntime = false) { // Attempt to clean up try { proc.kill(); - } catch (e) { + } catch { // Ignore kill errors } @@ -49,7 +49,7 @@ function startProcess(settings, useNodeRuntime = false) { payload: { alias, error: 'Startup timeout exceeded', - } + }, }); } }, spawnTimeout); @@ -60,7 +60,7 @@ function startProcess(settings, useNodeRuntime = false) { payload: { alias, data: data.toString(), - } + }, }); }); @@ -72,7 +72,7 @@ function startProcess(settings, useNodeRuntime = false) { payload: { alias, data: data.toString(), - } + }, }); }); @@ -99,12 +99,12 @@ function startProcess(settings, useNodeRuntime = false) { state.processes[alias] = { pid: proc.pid, proc, - settings + settings, }; notifyLaravel('events', { event: 'Native\\Desktop\\Events\\ChildProcess\\ProcessSpawned', - payload: [alias, proc.pid] + payload: [alias, proc.pid], }); }); @@ -117,10 +117,10 @@ function startProcess(settings, useNodeRuntime = false) { payload: { alias, code, - } + }, }); - const settings = {...getSettings(alias)}; + const settings = { ...getSettings(alias) }; delete state.processes[alias]; if (settings?.persistent) { @@ -133,7 +133,7 @@ function startProcess(settings, useNodeRuntime = false) { return { pid: null, proc, - settings + settings, }; } catch (error) { console.error(`Failed to create process [${alias}]: ${error.message}`); @@ -143,30 +143,33 @@ function startProcess(settings, useNodeRuntime = false) { payload: { alias, error: error.toString(), - } + }, }); return { pid: null, proc: null, settings, - error: error.message + error: error.message, }; } } function startPhpProcess(settings) { - const defaultEnv = getDefaultEnvironmentVariables( - state.randomSecret, - state.electronApiPort - ); + const defaultEnv = getDefaultEnvironmentVariables(state.randomSecret, state.electronApiPort); // Construct command args from ini settings const customIniSettings = settings.iniSettings || {}; - const iniSettings = {...getDefaultPhpIniSettings(), ...state.phpIni, ...customIniSettings}; - const iniArgs = Object.keys(iniSettings).map(key => { - return ['-d', `${key}=${iniSettings[key]}`]; - }).flat(); + const iniSettings = { + ...getDefaultPhpIniSettings(), + ...state.phpIni, + ...customIniSettings, + }; + const iniArgs = Object.keys(iniSettings) + .map((key) => { + return ['-d', `${key}=${iniSettings[key]}`]; + }) + .flat(); if (settings.cmd[0] === 'artisan' && runningSecureBuild()) { settings.cmd.unshift(join(getAppPath(), 'build', '__nativephp_app_bundle')); @@ -177,7 +180,7 @@ function startPhpProcess(settings) { // Prepend cmd with php executable path & ini settings cmd: [state.php, ...iniArgs, ...settings.cmd], // Mix in the internal NativePHP env - env: {...settings.env, ...defaultEnv} + env: { ...settings.env, ...defaultEnv }, }; return startProcess(settings); @@ -224,7 +227,7 @@ router.post('/start-node', (req, res) => { const proc = startProcess(req.body, true); res.json(proc); -}) +}); router.post('/start-php', (req, res) => { const proc = startPhpProcess(req.body); @@ -233,7 +236,7 @@ router.post('/start-php', (req, res) => { }); router.post('/stop', (req, res) => { - const {alias} = req.body; + const { alias } = req.body; stopProcess(alias); @@ -241,9 +244,9 @@ router.post('/stop', (req, res) => { }); router.post('/restart', async (req, res) => { - const {alias} = req.body; + const { alias } = req.body; - const settings = {...getSettings(alias)}; + const settings = { ...getSettings(alias) }; stopProcess(alias); @@ -259,7 +262,7 @@ router.post('/restart', async (req, res) => { if (Date.now() - start > timeout) { return; } - await new Promise(resolve => setTimeout(resolve, retry)); + await new Promise((resolve) => setTimeout(resolve, retry)); } }; @@ -271,7 +274,7 @@ router.post('/restart', async (req, res) => { }); router.get('/get/:alias', (req, res) => { - const {alias} = req.params; + const { alias } = req.params; const proc = state.processes[alias]; @@ -288,7 +291,7 @@ router.get('/', (req, res) => { }); router.post('/message', (req, res) => { - const {alias, message} = req.body; + const { alias, message } = req.body; const proc = getProcess(alias); diff --git a/resources/electron/electron-plugin/src/server/api/clipboard.ts b/resources/electron/electron-plugin/src/server/api/clipboard.ts index c823d1d3..37a66e0a 100644 --- a/resources/electron/electron-plugin/src/server/api/clipboard.ts +++ b/resources/electron/electron-plugin/src/server/api/clipboard.ts @@ -1,83 +1,83 @@ +import { clipboard, nativeImage } from 'electron'; import * as express from 'express'; const router = express.Router(); -import { clipboard, nativeImage } from 'electron' -const DEFAULT_TYPE = 'clipboard' +const DEFAULT_TYPE = 'clipboard'; router.get('/text', (req, res) => { - const {type} = req.query + const { type } = req.query; res.json({ // @ts-ignore - text: clipboard.readText(type || DEFAULT_TYPE) - }) + text: clipboard.readText(type || DEFAULT_TYPE), + }); }); router.post('/text', (req, res) => { - const {text} = req.body - const {type} = req.query + const { text } = req.body; + const { type } = req.query; // @ts-ignore - clipboard.writeText(text, type || DEFAULT_TYPE) + clipboard.writeText(text, type || DEFAULT_TYPE); res.json({ text, - }) + }); }); router.get('/html', (req, res) => { - const {type} = req.query + const { type } = req.query; res.json({ // @ts-ignore - html: clipboard.readHTML(type || DEFAULT_TYPE) - }) + html: clipboard.readHTML(type || DEFAULT_TYPE), + }); }); router.post('/html', (req, res) => { - const {html} = req.body - const {type} = req.query + const { html } = req.body; + const { type } = req.query; // @ts-ignore - clipboard.writeHTML(html, type || DEFAULT_TYPE) + clipboard.writeHTML(html, type || DEFAULT_TYPE); res.json({ - html, - }) + html, + }); }); router.get('/image', (req, res) => { - const {type} = req.query + const { type } = req.query; // @ts-ignore const image = clipboard.readImage(type || DEFAULT_TYPE); res.json({ - image: image.isEmpty() ? null : image.toDataURL() - }) + image: image.isEmpty() ? null : image.toDataURL(), + }); }); router.post('/image', (req, res) => { - const {image} = req.body - const {type} = req.query + const { image } = req.body; + const { type } = req.query; try { - const _nativeImage = nativeImage.createFromDataURL(image) - // @ts-ignore - clipboard.writeImage(_nativeImage, type || DEFAULT_TYPE) + const _nativeImage = nativeImage.createFromDataURL(image); + // @ts-ignore + clipboard.writeImage(_nativeImage, type || DEFAULT_TYPE); } catch (e) { - res.status(400).json({ - error: e.message, - }) - return + res.status(400).json({ + error: e.message, + }); + return; } res.sendStatus(200); }); router.delete('/', (req, res) => { - const {type} = req.query + const { type } = req.query; // @ts-ignore - clipboard.clear(type || DEFAULT_TYPE) + clipboard.clear(type || DEFAULT_TYPE); res.sendStatus(200); }); diff --git a/resources/electron/electron-plugin/src/server/api/contextMenu.ts b/resources/electron/electron-plugin/src/server/api/contextMenu.ts index 8f723c9c..2c167cc2 100644 --- a/resources/electron/electron-plugin/src/server/api/contextMenu.ts +++ b/resources/electron/electron-plugin/src/server/api/contextMenu.ts @@ -1,10 +1,10 @@ +import contextMenu from 'electron-context-menu'; import express from 'express'; -import { compileMenu } from "./helper/index.js"; -import contextMenu from "electron-context-menu"; +import { compileMenu } from './helper/index.js'; const router = express.Router(); -let contextMenuDisposable = null +let contextMenuDisposable = null; router.delete('/', (req, res) => { res.sendStatus(200); @@ -27,7 +27,7 @@ router.post('/', (req, res) => { showLookUpSelection: false, showSearchWithGoogle: false, showInspectElement: false, - prepend: (defaultActions, parameters, browserWindow) => { + prepend: () => { return req.body.entries.map(compileMenu); }, }); diff --git a/resources/electron/electron-plugin/src/server/api/debug.ts b/resources/electron/electron-plugin/src/server/api/debug.ts index 20504631..8c1a9ed8 100644 --- a/resources/electron/electron-plugin/src/server/api/debug.ts +++ b/resources/electron/electron-plugin/src/server/api/debug.ts @@ -1,13 +1,13 @@ -import express from 'express' +import express from 'express'; import { broadcastToWindows } from '../utils.js'; const router = express.Router(); router.post('/log', (req, res) => { - const {level, message, context} = req.body + const { level, message, context } = req.body; - broadcastToWindows('log', {level, message, context}); + broadcastToWindows('log', { level, message, context }); - res.sendStatus(200) -}) + res.sendStatus(200); +}); export default router; diff --git a/resources/electron/electron-plugin/src/server/api/dialog.ts b/resources/electron/electron-plugin/src/server/api/dialog.ts index 74cb2a55..d52f20ac 100644 --- a/resources/electron/electron-plugin/src/server/api/dialog.ts +++ b/resources/electron/electron-plugin/src/server/api/dialog.ts @@ -1,11 +1,11 @@ -import express from 'express' -import {dialog} from 'electron' -import state from '../state.js' -import {trimOptions} from '../utils.js' +import { dialog } from 'electron'; +import express from 'express'; +import state from '../state.js'; +import { trimOptions } from '../utils.js'; const router = express.Router(); router.post('/open', (req, res) => { - const {title, buttonLabel, filters, properties, defaultPath, message, windowReference} = req.body + const { title, buttonLabel, filters, properties, defaultPath, message, windowReference } = req.body; let options = { title, @@ -13,27 +13,27 @@ router.post('/open', (req, res) => { buttonLabel, filters, message, - properties + properties, }; options = trimOptions(options); let result; - let browserWindow = state.findWindow(windowReference); + const browserWindow = state.findWindow(windowReference); if (browserWindow) { - result = dialog.showOpenDialogSync(browserWindow, options) + result = dialog.showOpenDialogSync(browserWindow, options); } else { - result = dialog.showOpenDialogSync(options) + result = dialog.showOpenDialogSync(options); } res.json({ - result - }) + result, + }); }); router.post('/save', (req, res) => { - const {title, buttonLabel, filters, properties, defaultPath, message, windowReference} = req.body + const { title, buttonLabel, filters, properties, defaultPath, message, windowReference } = req.body; let options = { title, @@ -41,23 +41,23 @@ router.post('/save', (req, res) => { buttonLabel, filters, message, - properties + properties, }; options = trimOptions(options); let result; - let browserWindow = state.findWindow(windowReference); + const browserWindow = state.findWindow(windowReference); if (browserWindow) { - result = dialog.showSaveDialogSync(browserWindow, options) + result = dialog.showSaveDialogSync(browserWindow, options); } else { - result = dialog.showSaveDialogSync(options) + result = dialog.showSaveDialogSync(options); } res.json({ - result - }) + result, + }); }); export default router; diff --git a/resources/electron/electron-plugin/src/server/api/dock.ts b/resources/electron/electron-plugin/src/server/api/dock.ts index 3e1f15fb..37f6c1bd 100644 --- a/resources/electron/electron-plugin/src/server/api/dock.ts +++ b/resources/electron/electron-plugin/src/server/api/dock.ts @@ -1,7 +1,7 @@ -import express from 'express'; import { app, Menu } from 'electron'; -import { compileMenu } from './helper/index.js'; +import express from 'express'; import state from '../state.js'; +import { compileMenu } from './helper/index.js'; const router = express.Router(); diff --git a/resources/electron/electron-plugin/src/server/api/globalShortcut.ts b/resources/electron/electron-plugin/src/server/api/globalShortcut.ts index 5ff74c2e..b4414d08 100644 --- a/resources/electron/electron-plugin/src/server/api/globalShortcut.ts +++ b/resources/electron/electron-plugin/src/server/api/globalShortcut.ts @@ -1,34 +1,34 @@ -import express from 'express' -import {globalShortcut} from 'electron' -import {notifyLaravel} from "../utils.js"; +import { globalShortcut } from 'electron'; +import express from 'express'; +import { notifyLaravel } from '../utils.js'; const router = express.Router(); router.post('/', (req, res) => { - const {key, event} = req.body + const { key, event } = req.body; globalShortcut.register(key, () => { notifyLaravel('events', { event, - payload: [key] - }) - }) + payload: [key], + }); + }); - res.sendStatus(200) -}) + res.sendStatus(200); +}); router.delete('/', (req, res) => { - const {key} = req.body + const { key } = req.body; - globalShortcut.unregister(key) + globalShortcut.unregister(key); - res.sendStatus(200) + res.sendStatus(200); }); router.get('/:key', (req, res) => { - const {key} = req.params + const { key } = req.params; res.json({ - isRegistered: globalShortcut.isRegistered(key) + isRegistered: globalShortcut.isRegistered(key), }); }); diff --git a/resources/electron/electron-plugin/src/server/api/helper/index.ts b/resources/electron/electron-plugin/src/server/api/helper/index.ts index 8a80facf..177cf818 100644 --- a/resources/electron/electron-plugin/src/server/api/helper/index.ts +++ b/resources/electron/electron-plugin/src/server/api/helper/index.ts @@ -1,6 +1,6 @@ import { shell } from 'electron'; -import { notifyLaravel, goToUrl } from '../../utils.js'; import state from '../../state.js'; +import { goToUrl, notifyLaravel } from '../../utils.js'; function triggerMenuItemEvent(menuItem, combo) { notifyLaravel('events', { @@ -16,7 +16,7 @@ function triggerMenuItemEvent(menuItem, combo) { }); } -export function compileMenu (item) { +export function compileMenu(item) { if (item.submenu) { if (Array.isArray(item.submenu)) { item.submenu = item.submenu?.map(compileMenu); @@ -36,16 +36,15 @@ export function compileMenu (item) { return; } - if (! focusedWindow) { + if (!focusedWindow) { // TODO: Bring a window to the front? return; } - const id = Object.keys(state.windows) - .find(key => state.windows[key] === focusedWindow); + const id = Object.keys(state.windows).find((key) => state.windows[key] === focusedWindow); goToUrl(item.url, id); - } + }; return item; } @@ -60,8 +59,8 @@ export function compileMenu (item) { } if (item.type === 'role') { - let menuItem = { - role: item.role + const menuItem = { + role: item.role, }; if (item.label) { @@ -72,10 +71,10 @@ export function compileMenu (item) { } // Default click event - if (! item.click) { + if (!item.click) { item.click = (menuItem, focusedWindow, combo) => { triggerMenuItemEvent(item, combo); - } + }; } return item; diff --git a/resources/electron/electron-plugin/src/server/api/menu.ts b/resources/electron/electron-plugin/src/server/api/menu.ts index 47b55596..699a1a1a 100644 --- a/resources/electron/electron-plugin/src/server/api/menu.ts +++ b/resources/electron/electron-plugin/src/server/api/menu.ts @@ -1,5 +1,5 @@ -import express from 'express'; import { Menu } from 'electron'; +import express from 'express'; import { compileMenu } from './helper/index.js'; const router = express.Router(); diff --git a/resources/electron/electron-plugin/src/server/api/menuBar.ts b/resources/electron/electron-plugin/src/server/api/menuBar.ts index 770853fa..1c2711e4 100644 --- a/resources/electron/electron-plugin/src/server/api/menuBar.ts +++ b/resources/electron/electron-plugin/src/server/api/menuBar.ts @@ -1,16 +1,15 @@ -import express from "express"; -import { app, Menu, Tray } from "electron"; -import { compileMenu } from "./helper/index.js"; -import state from "../state.js"; -import { menubar } from "../../libs/menubar/index.js"; -import { notifyLaravel } from "../utils.js"; -import { fileURLToPath } from 'url' -import { enable } from "@electron/remote/main/index.js"; -import mergePreferences from "../webPreferences.js"; +import { enable } from '@electron/remote/main/index.js'; +import { app, Menu, Tray } from 'electron'; +import express from 'express'; +import { menubar } from '../../libs/menubar/index.js'; +import state from '../state.js'; +import { notifyLaravel } from '../utils.js'; +import mergePreferences from '../webPreferences.js'; +import { compileMenu } from './helper/index.js'; const router = express.Router(); -router.post("/label", (req, res) => { +router.post('/label', (req, res) => { res.sendStatus(200); const { label } = req.body; @@ -18,7 +17,7 @@ router.post("/label", (req, res) => { state.tray?.setTitle(label); }); -router.post("/tooltip", (req, res) => { +router.post('/tooltip', (req, res) => { res.sendStatus(200); const { tooltip } = req.body; @@ -26,7 +25,7 @@ router.post("/tooltip", (req, res) => { state.tray?.setToolTip(tooltip); }); -router.post("/icon", (req, res) => { +router.post('/icon', (req, res) => { res.sendStatus(200); const { icon } = req.body; @@ -34,7 +33,7 @@ router.post("/icon", (req, res) => { state.tray?.setImage(icon); }); -router.post("/context-menu", (req, res) => { +router.post('/context-menu', (req, res) => { res.sendStatus(200); const { contextMenu } = req.body; @@ -42,19 +41,19 @@ router.post("/context-menu", (req, res) => { state.tray?.setContextMenu(buildMenu(contextMenu)); }); -router.post("/show", (req, res) => { +router.post('/show', (req, res) => { res.sendStatus(200); state.activeMenuBar.showWindow(); }); -router.post("/hide", (req, res) => { +router.post('/hide', (req, res) => { res.sendStatus(200); state.activeMenuBar.hideWindow(); }); -router.post("/resize", (req, res) => { +router.post('/resize', (req, res) => { res.sendStatus(200); const { width, height } = req.body; @@ -62,7 +61,7 @@ router.post("/resize", (req, res) => { state.activeMenuBar.window.setSize(width, height); }); -router.post("/create", (req, res) => { +router.post('/create', (req, res) => { res.sendStatus(200); let shouldSendCreatedEvent = true; @@ -90,13 +89,11 @@ router.post("/create", (req, res) => { tooltip, resizable, webPreferences, - event, } = req.body; - if (onlyShowContextMenu) { // Create a tray icon - const tray = new Tray(icon || state.icon.replace("icon.png", "IconTemplate.png")); + const tray = new Tray(icon || state.icon.replace('icon.png', 'IconTemplate.png')); // Set the context menu tray.setContextMenu(buildMenu(contextMenu)); @@ -112,16 +109,15 @@ router.post("/create", (req, res) => { if (!showDockIcon) { app.dock.hide(); } - } else { state.activeMenuBar = menubar({ - icon: icon || state.icon.replace("icon.png", "IconTemplate.png"), + icon: icon || state.icon.replace('icon.png', 'IconTemplate.png'), preloadWindow: true, tooltip, index: url, showDockIcon, showOnAllWorkspaces: showOnAllWorkspaces ?? false, - windowPosition: windowPosition ?? "trayCenter", + windowPosition: windowPosition ?? 'trayCenter', activateWithApp: false, browserWindow: { width, @@ -131,15 +127,15 @@ router.post("/create", (req, res) => { vibrancy, backgroundColor, transparent: transparency, - webPreferences: mergePreferences(webPreferences) - } + webPreferences: mergePreferences(webPreferences), + }, }); - state.activeMenuBar.on("after-create-window", () => { + state.activeMenuBar.on('after-create-window', () => { enable(state.activeMenuBar.window.webContents); }); - state.activeMenuBar.on("ready", () => { + state.activeMenuBar.on('ready', () => { // Set the event listeners eventsForTray(state.activeMenuBar.tray, onlyShowContextMenu, contextMenu, shouldSendCreatedEvent); @@ -149,45 +145,38 @@ router.post("/create", (req, res) => { // Set the title state.tray.setTitle(label); - state.activeMenuBar.on("hide", () => { - notifyLaravel("events", { - event: "\\Native\\Desktop\\Events\\MenuBar\\MenuBarHidden" + state.activeMenuBar.on('hide', () => { + notifyLaravel('events', { + event: '\\Native\\Desktop\\Events\\MenuBar\\MenuBarHidden', }); }); - state.activeMenuBar.on("show", () => { - notifyLaravel("events", { - event: "\\Native\\Desktop\\Events\\MenuBar\\MenuBarShown" + state.activeMenuBar.on('show', () => { + notifyLaravel('events', { + event: '\\Native\\Desktop\\Events\\MenuBar\\MenuBarShown', }); }); - }); } - }); - - function eventsForTray(tray, onlyShowContextMenu, contextMenu, shouldSendCreatedEvent) { - if (shouldSendCreatedEvent) { - notifyLaravel("events", { - event: "\\Native\\Desktop\\Events\\MenuBar\\MenuBarCreated" + notifyLaravel('events', { + event: '\\Native\\Desktop\\Events\\MenuBar\\MenuBarCreated', }); } - tray.on("drop-files", (event, files) => { - notifyLaravel("events", { - event: "\\Native\\Desktop\\Events\\MenuBar\\MenuBarDroppedFiles", - payload: [ - files - ] + tray.on('drop-files', (event, files) => { + notifyLaravel('events', { + event: '\\Native\\Desktop\\Events\\MenuBar\\MenuBarDroppedFiles', + payload: [files], }); }); tray.on('click', (combo, bounds, position) => { notifyLaravel('events', { - event: "\\Native\\Desktop\\Events\\MenuBar\\MenuBarClicked", + event: '\\Native\\Desktop\\Events\\MenuBar\\MenuBarClicked', payload: { combo, bounds, @@ -196,13 +185,13 @@ function eventsForTray(tray, onlyShowContextMenu, contextMenu, shouldSendCreated }); }); - tray.on("right-click", (combo, bounds) => { - notifyLaravel("events", { - event: "\\Native\\Desktop\\Events\\MenuBar\\MenuBarRightClicked", + tray.on('right-click', (combo, bounds) => { + notifyLaravel('events', { + event: '\\Native\\Desktop\\Events\\MenuBar\\MenuBarRightClicked', payload: { combo, bounds, - } + }, }); if (!onlyShowContextMenu) { @@ -213,7 +202,7 @@ function eventsForTray(tray, onlyShowContextMenu, contextMenu, shouldSendCreated tray.on('double-click', (combo, bounds) => { notifyLaravel('events', { - event: "\\Native\\Desktop\\Events\\MenuBar\\MenuBarDoubleClicked", + event: '\\Native\\Desktop\\Events\\MenuBar\\MenuBarDoubleClicked', payload: { combo, bounds, @@ -223,7 +212,7 @@ function eventsForTray(tray, onlyShowContextMenu, contextMenu, shouldSendCreated } function buildMenu(contextMenu) { - let menu = Menu.buildFromTemplate([{ role: "quit" }]); + let menu = Menu.buildFromTemplate([{ role: 'quit' }]); if (contextMenu) { const menuEntries = contextMenu.map(compileMenu); diff --git a/resources/electron/electron-plugin/src/server/api/notification.ts b/resources/electron/electron-plugin/src/server/api/notification.ts index 0fc69f35..6c752393 100644 --- a/resources/electron/electron-plugin/src/server/api/notification.ts +++ b/resources/electron/electron-plugin/src/server/api/notification.ts @@ -1,9 +1,8 @@ -import express from 'express'; import { Notification } from 'electron'; -import {notifyLaravel, broadcastToWindows} from "../utils.js"; -declare const require: any; -import playSoundLib from 'play-sound'; +import express from 'express'; import fs from 'fs'; +import playSoundLib from 'play-sound'; +import { broadcastToWindows, notifyLaravel } from '../utils.js'; const isLocalFile = (sound: unknown) => { if (typeof sound !== 'string') return false; @@ -34,7 +33,7 @@ router.post('/', (req, res) => { const eventName = customEvent ?? '\\Native\\Desktop\\Events\\Notifications\\NotificationClicked'; - const notificationReference = reference ?? (Date.now() + '.' + Math.random().toString(36).slice(2, 9)); + const notificationReference = reference ?? Date.now() + '.' + Math.random().toString(36).slice(2, 9); const usingLocalFile = isLocalFile(sound); @@ -51,7 +50,7 @@ router.post('/', (req, res) => { urgency, actions, closeButtonText, - toastXml + toastXml, }); if (usingLocalFile && !silent) { @@ -60,7 +59,7 @@ router.post('/', (req, res) => { broadcastToWindows('log', { level: 'error', message: `Sound file not found: ${sound}`, - context: { sound } + context: { sound }, }); return; } @@ -69,7 +68,7 @@ router.post('/', (req, res) => { }); } - notification.on("click", (event) => { + notification.on('click', (event) => { notifyLaravel('events', { event: eventName || '\\Native\\Desktop\\Events\\Notifications\\NotificationClicked', payload: { @@ -79,7 +78,7 @@ router.post('/', (req, res) => { }); }); - notification.on("action", (event, index) => { + notification.on('action', (event, index) => { notifyLaravel('events', { event: '\\Native\\Desktop\\Events\\Notifications\\NotificationActionClicked', payload: { @@ -90,7 +89,7 @@ router.post('/', (req, res) => { }); }); - notification.on("reply", (event, reply) => { + notification.on('reply', (event, reply) => { notifyLaravel('events', { event: '\\Native\\Desktop\\Events\\Notifications\\NotificationReply', payload: { @@ -101,7 +100,7 @@ router.post('/', (req, res) => { }); }); - notification.on("close", (event) => { + notification.on('close', (event) => { notifyLaravel('events', { event: '\\Native\\Desktop\\Events\\Notifications\\NotificationClosed', payload: { diff --git a/resources/electron/electron-plugin/src/server/api/powerMonitor.ts b/resources/electron/electron-plugin/src/server/api/powerMonitor.ts index 8b7c81a5..bed3f781 100644 --- a/resources/electron/electron-plugin/src/server/api/powerMonitor.ts +++ b/resources/electron/electron-plugin/src/server/api/powerMonitor.ts @@ -1,108 +1,105 @@ -import express from 'express' -import { powerMonitor } from 'electron' +import { powerMonitor } from 'electron'; +import express from 'express'; import { notifyLaravel } from '../utils.js'; const router = express.Router(); router.get('/get-system-idle-state', (req, res) => { - let threshold = Number(req.query.threshold) || 60; + const threshold = Number(req.query.threshold) || 60; res.json({ result: powerMonitor.getSystemIdleState(threshold), - }) + }); }); router.get('/get-system-idle-time', (req, res) => { res.json({ result: powerMonitor.getSystemIdleTime(), - }) + }); }); router.get('/get-current-thermal-state', (req, res) => { res.json({ result: powerMonitor.getCurrentThermalState(), - }) + }); }); router.get('/is-on-battery-power', (req, res) => { res.json({ result: powerMonitor.isOnBatteryPower(), - }) + }); }); powerMonitor.addListener('on-ac', () => { - notifyLaravel("events", { + notifyLaravel('events', { event: `\\Native\\Desktop\\Events\\PowerMonitor\\PowerStateChanged`, payload: { - state: 'on-ac' - } + state: 'on-ac', + }, }); -}) +}); powerMonitor.addListener('on-battery', () => { - notifyLaravel("events", { + notifyLaravel('events', { event: `\\Native\\Desktop\\Events\\PowerMonitor\\PowerStateChanged`, payload: { - state: 'on-battery' - } + state: 'on-battery', + }, }); -}) +}); // @ts-ignore powerMonitor.addListener('thermal-state-change', (details) => { - notifyLaravel("events", { + notifyLaravel('events', { event: `\\Native\\Desktop\\Events\\PowerMonitor\\ThermalStateChanged`, payload: { state: details.state, }, }); -}) +}); // @ts-ignore powerMonitor.addListener('speed-limit-change', (details) => { - notifyLaravel("events", { + notifyLaravel('events', { event: `\\Native\\Desktop\\Events\\PowerMonitor\\SpeedLimitChanged`, payload: { limit: details.limit, }, }); -}) +}); // @ts-ignore powerMonitor.addListener('lock-screen', () => { - notifyLaravel("events", { + notifyLaravel('events', { event: `\\Native\\Desktop\\Events\\PowerMonitor\\ScreenLocked`, }); -}) +}); // @ts-ignore powerMonitor.addListener('unlock-screen', () => { - notifyLaravel("events", { + notifyLaravel('events', { event: `\\Native\\Desktop\\Events\\PowerMonitor\\ScreenUnlocked`, }); -}) - +}); // @ts-ignore powerMonitor.addListener('shutdown', () => { - notifyLaravel("events", { + notifyLaravel('events', { event: `\\Native\\Desktop\\Events\\PowerMonitor\\Shutdown`, }); -}) - +}); // @ts-ignore powerMonitor.addListener('user-did-become-active', () => { - notifyLaravel("events", { + notifyLaravel('events', { event: `\\Native\\Desktop\\Events\\PowerMonitor\\UserDidBecomeActive`, }); -}) - +}); // @ts-ignore powerMonitor.addListener('user-did-resign-active', () => { - notifyLaravel("events", { + notifyLaravel('events', { event: `\\Native\\Desktop\\Events\\PowerMonitor\\UserDidResignActive`, }); -}) +}); export default router; diff --git a/resources/electron/electron-plugin/src/server/api/process.ts b/resources/electron/electron-plugin/src/server/api/process.ts index dc80a8e4..1b1e7c9f 100644 --- a/resources/electron/electron-plugin/src/server/api/process.ts +++ b/resources/electron/electron-plugin/src/server/api/process.ts @@ -7,8 +7,8 @@ router.get('/', (req, res) => { pid: process.pid, platform: process.platform, arch: process.arch, - uptime: process.uptime() - }) + uptime: process.uptime(), + }); }); export default router; diff --git a/resources/electron/electron-plugin/src/server/api/progressBar.ts b/resources/electron/electron-plugin/src/server/api/progressBar.ts index fc455d73..48c0152a 100644 --- a/resources/electron/electron-plugin/src/server/api/progressBar.ts +++ b/resources/electron/electron-plugin/src/server/api/progressBar.ts @@ -1,15 +1,15 @@ -import express from 'express' -import state from "../state.js"; +import express from 'express'; +import state from '../state.js'; const router = express.Router(); router.post('/update', (req, res) => { - const {percent} = req.body + const { percent } = req.body; Object.values(state.windows).forEach((window) => { - window.setProgressBar(percent) + window.setProgressBar(percent); }); - res.sendStatus(200) -}) + res.sendStatus(200); +}); export default router; diff --git a/resources/electron/electron-plugin/src/server/api/screen.ts b/resources/electron/electron-plugin/src/server/api/screen.ts index ba853f23..cfd54911 100644 --- a/resources/electron/electron-plugin/src/server/api/screen.ts +++ b/resources/electron/electron-plugin/src/server/api/screen.ts @@ -1,28 +1,28 @@ -import express from 'express' -import { screen } from 'electron' +import { screen } from 'electron'; +import express from 'express'; const router = express.Router(); router.get('/displays', (req, res) => { res.json({ - displays: screen.getAllDisplays() - }) + displays: screen.getAllDisplays(), + }); }); router.get('/primary-display', (req, res) => { res.json({ - primaryDisplay: screen.getPrimaryDisplay() - }) + primaryDisplay: screen.getPrimaryDisplay(), + }); }); router.get('/cursor-position', (req, res) => { - res.json(screen.getCursorScreenPoint()) + res.json(screen.getCursorScreenPoint()); }); router.get('/active', (req, res) => { - const cursor = screen.getCursorScreenPoint() + const cursor = screen.getCursorScreenPoint(); - res.json(screen.getDisplayNearestPoint(cursor)) + res.json(screen.getDisplayNearestPoint(cursor)); }); export default router; diff --git a/resources/electron/electron-plugin/src/server/api/settings.ts b/resources/electron/electron-plugin/src/server/api/settings.ts index 84286349..05adb9c4 100644 --- a/resources/electron/electron-plugin/src/server/api/settings.ts +++ b/resources/electron/electron-plugin/src/server/api/settings.ts @@ -4,34 +4,34 @@ import state from '../state.js'; const router = express.Router(); router.get('/:key', (req, res) => { - const key = req.params.key; + const key = req.params.key; - const value = state.store.get(key, null); + const value = state.store.get(key, null); - res.json({value}); + res.json({ value }); }); router.post('/:key', (req, res) => { - const key = req.params.key; - const value = req.body.value; + const key = req.params.key; + const value = req.body.value; - state.store.set(key, value); + state.store.set(key, value); - res.sendStatus(200) + res.sendStatus(200); }); router.delete('/:key', (req, res) => { - const key = req.params.key; + const key = req.params.key; - state.store.delete(key); + state.store.delete(key); - res.sendStatus(200) + res.sendStatus(200); }); router.delete('/', (req, res) => { - state.store.clear(); + state.store.clear(); - res.sendStatus(200) + res.sendStatus(200); }); export default router; diff --git a/resources/electron/electron-plugin/src/server/api/shell.ts b/resources/electron/electron-plugin/src/server/api/shell.ts index a39b94ea..7be4b7d4 100644 --- a/resources/electron/electron-plugin/src/server/api/shell.ts +++ b/resources/electron/electron-plugin/src/server/api/shell.ts @@ -1,50 +1,50 @@ -import express from "express"; -import { shell } from "electron"; +import { shell } from 'electron'; +import express from 'express'; const router = express.Router(); -router.post("/show-item-in-folder", (req, res) => { - const { path } = req.body; +router.post('/show-item-in-folder', (req, res) => { + const { path } = req.body; - shell.showItemInFolder(path); + shell.showItemInFolder(path); - res.sendStatus(200); + res.sendStatus(200); }); -router.post("/open-item", async (req, res) => { - const { path } = req.body; +router.post('/open-item', async (req, res) => { + const { path } = req.body; - let result = await shell.openPath(path); + const result = await shell.openPath(path); - res.json({ - result - }) + res.json({ + result, + }); }); -router.post("/open-external", async (req, res) => { - const { url } = req.body; +router.post('/open-external', async (req, res) => { + const { url } = req.body; - try { - await shell.openExternal(url); + try { + await shell.openExternal(url); - res.sendStatus(200); - } catch (e) { - res.status(500).json({ - error: e - }); - } + res.sendStatus(200); + } catch (e) { + res.status(500).json({ + error: e, + }); + } }); -router.delete("/trash-item", async (req, res) => { - const { path } = req.body; +router.delete('/trash-item', async (req, res) => { + const { path } = req.body; - try { - await shell.trashItem(path); + try { + await shell.trashItem(path); - res.sendStatus(200); - } catch (e) { - res.status(400).json(); - } + res.sendStatus(200); + } catch { + res.status(400).json(); + } }); export default router; diff --git a/resources/electron/electron-plugin/src/server/api/system.ts b/resources/electron/electron-plugin/src/server/api/system.ts index 4c74e0a0..fd4d60ed 100644 --- a/resources/electron/electron-plugin/src/server/api/system.ts +++ b/resources/electron/electron-plugin/src/server/api/system.ts @@ -1,5 +1,5 @@ +import { BrowserWindow, nativeTheme, safeStorage, systemPreferences } from 'electron'; import express from 'express'; -import {BrowserWindow, systemPreferences, safeStorage, nativeTheme} from 'electron'; const router = express.Router(); @@ -11,7 +11,7 @@ router.get('/can-prompt-touch-id', (req, res) => { router.post('/prompt-touch-id', async (req, res) => { try { - await systemPreferences.promptTouchID(req.body.reason) + await systemPreferences.promptTouchID(req.body.reason); res.sendStatus(200); } catch (e) { @@ -60,7 +60,7 @@ router.get('/printers', async (req, res) => { }); router.post('/print', async (req, res) => { - const {printer, html, settings} = req.body; + const { printer, html, settings } = req.body; let printWindow = new BrowserWindow({ show: false, @@ -87,7 +87,7 @@ router.post('/print', async (req, res) => { } if (printWindow) { printWindow.close(); // Close the window and the process - printWindow = null; // Free memory + printWindow = null; // Free memory } }); }); @@ -96,25 +96,28 @@ router.post('/print', async (req, res) => { }); router.post('/print-to-pdf', async (req, res) => { - const {html, settings} = req.body; + const { html, settings } = req.body; - let printWindow = new BrowserWindow({ + const printWindow = new BrowserWindow({ show: false, }); printWindow.webContents.on('did-finish-load', () => { - printWindow.webContents.printToPDF(settings ?? {}).then(data => { - printWindow.close(); + printWindow.webContents + .printToPDF(settings ?? {}) + .then((data) => { + printWindow.close(); res.json({ result: data.toString('base64'), }); - }).catch(e => { - printWindow.close(); + }) + .catch((e) => { + printWindow.close(); - res.status(400).json({ - error: e.message, + res.status(400).json({ + error: e.message, + }); }); - }); }); await printWindow.loadURL(`data:text/html;charset=UTF-8,${html}`); diff --git a/resources/electron/electron-plugin/src/server/api/window.ts b/resources/electron/electron-plugin/src/server/api/window.ts index b168bb63..73575ca0 100644 --- a/resources/electron/electron-plugin/src/server/api/window.ts +++ b/resources/electron/electron-plugin/src/server/api/window.ts @@ -1,17 +1,16 @@ -import express from 'express'; import { BrowserWindow } from 'electron'; -import state from '../state.js'; -import { fileURLToPath } from 'url' -import { notifyLaravel, goToUrl, appendWindowIdToUrl } from '../utils.js'; import windowStateKeeper from 'electron-window-state'; -import mergePreferences from '../webPreferences.js' +import express from 'express'; +import state from '../state.js'; +import { appendWindowIdToUrl, goToUrl, notifyLaravel } from '../utils.js'; +import mergePreferences from '../webPreferences.js'; -import {enable} from "@electron/remote/main/index.js"; +import { enable } from '@electron/remote/main/index.js'; const router = express.Router(); router.post('/maximize', (req, res) => { - const {id} = req.body; + const { id } = req.body; state.windows[id]?.maximize(); @@ -19,7 +18,7 @@ router.post('/maximize', (req, res) => { }); router.post('/minimize', (req, res) => { - const {id} = req.body; + const { id } = req.body; state.windows[id]?.minimize(); @@ -27,7 +26,7 @@ router.post('/minimize', (req, res) => { }); router.post('/resize', (req, res) => { - const {id, width, height} = req.body; + const { id, width, height } = req.body; state.windows[id]?.setSize(parseInt(width), parseInt(height)); @@ -35,7 +34,7 @@ router.post('/resize', (req, res) => { }); router.post('/title', (req, res) => { - const {id, title} = req.body; + const { id, title } = req.body; state.windows[id]?.setTitle(title); @@ -43,7 +42,7 @@ router.post('/title', (req, res) => { }); router.post('/url', (req, res) => { - const {id, url} = req.body; + const { id, url } = req.body; goToUrl(url, id); @@ -51,7 +50,7 @@ router.post('/url', (req, res) => { }); router.post('/closable', (req, res) => { - const {id, closable} = req.body; + const { id, closable } = req.body; state.windows[id]?.setClosable(closable); @@ -59,7 +58,7 @@ router.post('/closable', (req, res) => { }); router.post('/show-dev-tools', (req, res) => { - const {id} = req.body; + const { id } = req.body; state.windows[id]?.webContents.openDevTools(); @@ -67,7 +66,7 @@ router.post('/show-dev-tools', (req, res) => { }); router.post('/hide-dev-tools', (req, res) => { - const {id} = req.body; + const { id } = req.body; state.windows[id]?.webContents.closeDevTools(); @@ -75,7 +74,7 @@ router.post('/hide-dev-tools', (req, res) => { }); router.post('/set-zoom-factor', (req, res) => { - const {id, zoomFactor} = req.body; + const { id, zoomFactor } = req.body; state.windows[id]?.webContents.setZoomFactor(parseFloat(zoomFactor)); @@ -83,7 +82,7 @@ router.post('/set-zoom-factor', (req, res) => { }); router.post('/position', (req, res) => { - const {id, x, y, animate} = req.body; + const { id, x, y, animate } = req.body; state.windows[id]?.setPosition(parseInt(x), parseInt(y), animate); @@ -91,7 +90,7 @@ router.post('/position', (req, res) => { }); router.post('/reload', (req, res) => { - const {id} = req.body; + const { id } = req.body; state.windows[id]?.reload(); @@ -99,7 +98,7 @@ router.post('/reload', (req, res) => { }); router.post('/close', (req, res) => { - const {id} = req.body; + const { id } = req.body; if (state.windows[id]) { state.windows[id].close(); @@ -110,7 +109,7 @@ router.post('/close', (req, res) => { }); router.post('/hide', (req, res) => { - const {id} = req.body; + const { id } = req.body; if (state.windows[id]) { state.windows[id].hide(); @@ -130,7 +129,7 @@ router.post('/show', (req, res) => { }); router.post('/always-on-top', (req, res) => { - const {id, alwaysOnTop} = req.body; + const { id, alwaysOnTop } = req.body; state.windows[id]?.setAlwaysOnTop(alwaysOnTop); @@ -139,22 +138,22 @@ router.post('/always-on-top', (req, res) => { router.get('/current', (req, res) => { // Find the current window object - const currentWindow = Object.values(state.windows).find(window => window.id === BrowserWindow.getFocusedWindow().id); + const currentWindow = Object.values(state.windows).find( + (window) => window.id === BrowserWindow.getFocusedWindow().id, + ); // Get the developer-assigned id for that window - const id = Object.keys(state.windows).find(key => state.windows[key] === currentWindow); + const id = Object.keys(state.windows).find((key) => state.windows[key] === currentWindow); res.json(getWindowData(id)); }); router.get('/all', (req, res) => { - res.json( - Object.keys(state.windows).map(id => getWindowData(id)) - ); + res.json(Object.keys(state.windows).map((id) => getWindowData(id))); }); router.get('/get/:id', (req, res) => { - const {id} = req.params; + const { id } = req.params; if (state.windows[id] === undefined) { res.sendStatus(404); @@ -200,7 +199,7 @@ function getWindowData(id) { } router.post('/open', (req, res) => { - let { + const { id, x, y, @@ -215,7 +214,6 @@ router.post('/open', (req, res) => { skipTaskbar, hiddenInMissionControl, hasShadow, - url, resizable, movable, minimizable, @@ -259,12 +257,8 @@ router.post('/open', (req, res) => { } const window = new BrowserWindow({ - width: resizable - ? windowState?.width || parseInt(width) - : parseInt(width), - height: resizable - ? windowState?.height || parseInt(height) - : parseInt(height), + width: resizable ? windowState?.width || parseInt(width) : parseInt(width), + height: resizable ? windowState?.height || parseInt(height) : parseInt(height), frame: frame !== undefined ? frame : true, x: windowState?.x || x, y: windowState?.y || y, @@ -290,7 +284,7 @@ router.post('/open', (req, res) => { skipTaskbar, hiddenInMissionControl, autoHideMenuBar, - ...(process.platform === 'linux' ? {icon: state.icon} : {}), + ...(process.platform === 'linux' ? { icon: state.icon } : {}), webPreferences: mergePreferences(webPreferences), fullscreen, fullscreenable, @@ -309,49 +303,49 @@ router.post('/open', (req, res) => { if (suppressNewWindows) { window.webContents.setWindowOpenHandler(() => { - return { action: "deny" }; + return { action: 'deny' }; }); } window.on('blur', () => { notifyLaravel('events', { event: 'Native\\Desktop\\Events\\Windows\\WindowBlurred', - payload: [id] + payload: [id], }); }); window.on('focus', () => { notifyLaravel('events', { event: 'Native\\Desktop\\Events\\Windows\\WindowFocused', - payload: [id] + payload: [id], }); }); window.on('minimize', () => { notifyLaravel('events', { event: 'Native\\Desktop\\Events\\Windows\\WindowMinimized', - payload: [id] + payload: [id], }); }); window.on('maximize', () => { notifyLaravel('events', { event: 'Native\\Desktop\\Events\\Windows\\WindowMaximized', - payload: [id] + payload: [id], }); }); window.on('show', () => { notifyLaravel('events', { event: 'Native\\Desktop\\Events\\Windows\\WindowShown', - payload: [id] + payload: [id], }); }); window.on('resized', () => { notifyLaravel('events', { event: 'Native\\Desktop\\Events\\Windows\\WindowResized', - payload: [id, window.getSize()[0], window.getSize()[1]] + payload: [id, window.getSize()[0], window.getSize()[1]], }); }); @@ -359,26 +353,26 @@ router.post('/open', (req, res) => { evt.preventDefault(); }); - window.on('close', (evt) => { + window.on('close', () => { if (state.windows[id]) { delete state.windows[id]; } notifyLaravel('events', { event: 'Native\\Desktop\\Events\\Windows\\WindowClosed', - payload: [id] + payload: [id], }); }); // @ts-ignore - window.on('hide', (evt) => { + window.on('hide', () => { notifyLaravel('events', { event: 'Native\\Desktop\\Events\\Windows\\WindowHidden', - payload: [id] + payload: [id], }); }); - url = appendWindowIdToUrl(url, id); + const url = appendWindowIdToUrl(req.body.url, id); window.loadURL(url); diff --git a/resources/electron/electron-plugin/src/server/childProcess.ts b/resources/electron/electron-plugin/src/server/childProcess.ts index 5105cad5..33cbb01c 100644 --- a/resources/electron/electron-plugin/src/server/childProcess.ts +++ b/resources/electron/electron-plugin/src/server/childProcess.ts @@ -1,4 +1,4 @@ -import {spawn, fork} from "child_process"; +import { fork, spawn } from 'child_process'; const useNodeRuntime = process.env.USE_NODE_RUNTIME === '1'; const [command, ...args] = process.argv.slice(2); @@ -7,13 +7,13 @@ const [command, ...args] = process.argv.slice(2); // to use utilityProcess.fork. Otherwise, we can use utilityProcess.spawn const proc = useNodeRuntime ? fork(command, args, { - stdio: ['pipe', 'pipe', 'pipe', 'ipc'], - execPath: process.execPath - }) + stdio: ['pipe', 'pipe', 'pipe', 'ipc'], + execPath: process.execPath, + }) : spawn(command, args); process.parentPort.on('message', (message) => { - proc.stdin.write(message.data) + proc.stdin.write(message.data); }); // Handle normal output @@ -28,5 +28,5 @@ proc.stderr.on('data', (data) => { // Handle process exit proc.on('close', (code) => { - process.exit(code) + process.exit(code); }); diff --git a/resources/electron/electron-plugin/src/server/index.ts b/resources/electron/electron-plugin/src/server/index.ts index 58be7dfd..9714c758 100644 --- a/resources/electron/electron-plugin/src/server/index.ts +++ b/resources/electron/electron-plugin/src/server/index.ts @@ -1,44 +1,35 @@ -import startAPIServer, { APIProcess } from "./api.js"; -import { - retrieveNativePHPConfig, - retrievePhpIniSettings, - serveApp, - startScheduler, -} from "./php.js"; -import { appendCookie } from "./utils.js"; -import state from "./state.js"; -import { ChildProcess } from "child_process"; +import { ChildProcess } from 'child_process'; +import startAPIServer, { APIProcess } from './api.js'; +import { retrieveNativePHPConfig, retrievePhpIniSettings, serveApp, startScheduler } from './php.js'; +import state from './state.js'; +import { appendCookie } from './utils.js'; let schedulerProcess: ChildProcess | null = null; export async function startPhpApp() { - const result = await serveApp( - state.randomSecret, - state.electronApiPort, - state.phpIni - ); + const result = await serveApp(state.randomSecret, state.electronApiPort, state.phpIni); - state.phpPort = result.port; + state.phpPort = result.port; - await appendCookie(); + await appendCookie(); - return result.process; + return result.process; } export function runScheduler() { - killScheduler(); - schedulerProcess = startScheduler(state.randomSecret, state.electronApiPort, state.phpIni); + killScheduler(); + schedulerProcess = startScheduler(state.randomSecret, state.electronApiPort, state.phpIni); } export function killScheduler() { - if (schedulerProcess && !schedulerProcess.killed) { - schedulerProcess.kill(); - schedulerProcess = null; - } + if (schedulerProcess && !schedulerProcess.killed) { + schedulerProcess.kill(); + schedulerProcess = null; + } } export function startAPI(): Promise { - return startAPIServer(state.randomSecret); + return startAPIServer(state.randomSecret); } export { retrieveNativePHPConfig, retrievePhpIniSettings }; diff --git a/resources/electron/electron-plugin/src/server/php.ts b/resources/electron/electron-plugin/src/server/php.ts index 5463fdca..9e2e25ff 100644 --- a/resources/electron/electron-plugin/src/server/php.ts +++ b/resources/electron/electron-plugin/src/server/php.ts @@ -1,23 +1,23 @@ -import {mkdirSync, statSync, writeFileSync, existsSync} from 'fs' +import { existsSync, mkdirSync, statSync, writeFileSync } from 'fs'; import fs_extra from 'fs-extra'; -const {copySync, mkdirpSync} = fs_extra; +const { copySync, mkdirpSync } = fs_extra; -import Store from 'electron-store' -import {promisify} from 'util' -import {join, resolve} from 'path' -import {app} from 'electron' -import {execFile, spawn, spawnSync} from 'child_process' -import {createServer} from 'net' -import state from "./state.js"; -import getPort, {portNumbers} from 'get-port'; -import {ProcessResult} from "./ProcessResult.js"; +import { execFile, spawn, spawnSync } from 'child_process'; +import { app } from 'electron'; +import Store from 'electron-store'; +import getPort, { portNumbers } from 'get-port'; +import { createServer } from 'net'; +import { join } from 'path'; +import { promisify } from 'util'; +import { ProcessResult } from './ProcessResult.js'; +import state from './state.js'; // TODO: maybe in dev, don't go to the userData folder and stay in the Laravel app folder -const storagePath = join(app.getPath('userData'), 'storage') -const databasePath = join(app.getPath('userData'), 'database') -const databaseFile = join(databasePath, 'database.sqlite') -const bootstrapCache = join(app.getPath('userData'), 'bootstrap', 'cache') +const storagePath = join(app.getPath('userData'), 'storage'); +const databasePath = join(app.getPath('userData'), 'database'); +const databaseFile = join(databasePath, 'database.sqlite'); +const bootstrapCache = join(app.getPath('userData'), 'bootstrap', 'cache'); const argumentEnv = getArgumentEnv(); mkdirpSync(bootstrapCache); @@ -28,16 +28,14 @@ mkdirpSync(join(storagePath, 'framework', 'views')); mkdirpSync(join(storagePath, 'framework', 'testing')); function runningSecureBuild() { - return existsSync(join(getAppPath(), 'build', '__nativephp_app_bundle')) - && process.env.NODE_ENV !== 'development'; + return existsSync(join(getAppPath(), 'build', '__nativephp_app_bundle')) && process.env.NODE_ENV !== 'development'; } function shouldMigrateDatabase(store) { - return store.get('migrated_version') !== app.getVersion() - && process.env.NODE_ENV !== 'development'; + return store.get('migrated_version') !== app.getVersion() && process.env.NODE_ENV !== 'development'; } -function shouldOptimize(store) { +function shouldOptimize() { /* * For some weird reason, * the cached config is not picked up on subsequent launches, @@ -46,14 +44,13 @@ function shouldOptimize(store) { return process.env.NODE_ENV !== 'development'; // return runningSecureBuild(); - // return runningSecureBuild() && store.get('optimized_version') !== app.getVersion(); } async function getPhpPort() { // Try get-port first (fast path) const suggestedPort = await getPort({ host: '127.0.0.1', - port: portNumbers(8100, 9000) + port: portNumbers(8100, 9000), }); // Validate that we can actually bind to this port @@ -88,15 +85,19 @@ function canBindToPort(port: number): Promise { } async function retrievePhpIniSettings() { - const env = getDefaultEnvironmentVariables() as any; + const env: NodeJS.ProcessEnv = { + ...process.env, + ...getDefaultEnvironmentVariables(), + }; + const appPath = getAppPath(); const phpOptions = { cwd: appPath, - env + env, }; - let command = ['artisan', 'native:php-ini']; + const command = ['artisan', 'native:php-ini']; if (runningSecureBuild()) { command.unshift(join(appPath, 'build', '__nativephp_app_bundle')); @@ -106,15 +107,18 @@ async function retrievePhpIniSettings() { } async function retrieveNativePHPConfig() { - const env = getDefaultEnvironmentVariables() as any; - const appPath = getAppPath() + const env: NodeJS.ProcessEnv = { + ...process.env, + ...getDefaultEnvironmentVariables(), + }; + const appPath = getAppPath(); const phpOptions = { cwd: appPath, - env + env, }; - let command = ['artisan', 'native:config']; + const command = ['artisan', 'native:config']; if (runningSecureBuild()) { command.unshift(join(appPath, 'build', '__nativephp_app_bundle')); @@ -124,14 +128,13 @@ async function retrieveNativePHPConfig() { } function callPhp(args, options, phpIniSettings = {}) { - if (args[0] === 'artisan' && runningSecureBuild()) { args.unshift(join(getAppPath(), 'build', '__nativephp_app_bundle')); } - let iniSettings = Object.assign(getDefaultPhpIniSettings(), phpIniSettings); + const iniSettings = Object.assign(getDefaultPhpIniSettings(), phpIniSettings); - Object.keys(iniSettings).forEach(key => { + Object.keys(iniSettings).forEach((key) => { args.unshift('-d', `${key}=${iniSettings[key]}`); }); @@ -139,28 +142,23 @@ function callPhp(args, options, phpIniSettings = {}) { console.log('Calling PHP', state.php, args); } - return spawn( - state.php, - args, - { - cwd: options.cwd, - env: { - ...process.env, - ...options.env - }, - } - ); + return spawn(state.php, args, { + cwd: options.cwd, + env: { + ...process.env, + ...options.env, + }, + }); } function callPhpSync(args, options, phpIniSettings = {}) { - if (args[0] === 'artisan' && runningSecureBuild()) { args.unshift(join(getAppPath(), 'build', '__nativephp_app_bundle')); } - let iniSettings = Object.assign(getDefaultPhpIniSettings(), phpIniSettings); + const iniSettings = Object.assign(getDefaultPhpIniSettings(), phpIniSettings); - Object.keys(iniSettings).forEach(key => { + Object.keys(iniSettings).forEach((key) => { args.unshift('-d', `${key}=${iniSettings[key]}`); }); @@ -168,27 +166,23 @@ function callPhpSync(args, options, phpIniSettings = {}) { console.log('Calling PHP', state.php, args); } - return spawnSync( - state.php, - args, - { - cwd: options.cwd, - env: { - ...process.env, - ...options.env - } - } - ); + return spawnSync(state.php, args, { + cwd: options.cwd, + env: { + ...process.env, + ...options.env, + }, + }); } function getArgumentEnv() { - const envArgs = process.argv.filter(arg => arg.startsWith('--env.')); + const envArgs = process.argv.filter((arg) => arg.startsWith('--env.')); const env: { - TESTING?: number, - APP_PATH?: string + TESTING?: number; + APP_PATH?: string; } = {}; - envArgs.forEach(arg => { + envArgs.forEach((arg) => { const [key, value] = arg.slice(6).split('='); env[key] = value; }); @@ -197,7 +191,7 @@ function getArgumentEnv() { } function getAppPath() { - let appPath = state.appPath + let appPath = state.appPath; if (process.env.NODE_ENV === 'development' || argumentEnv.TESTING == 1) { appPath = process.env.APP_PATH || argumentEnv.APP_PATH; @@ -206,25 +200,24 @@ function getAppPath() { } function ensureAppFoldersAreAvailable() { - // if (!runningSecureBuild()) { console.log('Copying storage folder...'); console.log('Storage path:', storagePath); if (!existsSync(storagePath) || process.env.NODE_ENV === 'development') { const appPath = getAppPath(); - console.log("App path:", appPath); - copySync(join(appPath, 'storage'), storagePath) + console.log('App path:', appPath); + copySync(join(appPath, 'storage'), storagePath); } // } - mkdirSync(databasePath, {recursive: true}) + mkdirSync(databasePath, { recursive: true }); // Create a database file if it doesn't exist try { - statSync(databaseFile) - } catch (error) { - writeFileSync(databaseFile, '') + statSync(databaseFile); + } catch { + writeFileSync(databaseFile, ''); } } @@ -234,7 +227,7 @@ function startScheduler(secret, apiPort, phpIniSettings = {}) { const phpOptions = { cwd: appPath, - env + env, }; return callPhp(['artisan', 'schedule:run'], phpOptions, phpIniSettings); @@ -244,7 +237,7 @@ function getPath(name: string) { try { // @ts-ignore return app.getPath(name); - } catch (error) { + } catch { return ''; } } @@ -277,11 +270,13 @@ interface EnvironmentVariables { APP_ROUTES_CACHE?: string; APP_EVENTS_CACHE?: string; VIEW_COMPILED_PATH?: string; + + NIGHTWATCH_INGEST_URI?: string; } function getDefaultEnvironmentVariables(secret?: string, apiPort?: number): EnvironmentVariables { // Base variables with string values (no null values) - let variables: EnvironmentVariables = { + const variables: EnvironmentVariables = { APP_ENV: process.env.NODE_ENV === 'development' ? 'local' : 'production', APP_DEBUG: process.env.NODE_ENV === 'development' ? 'true' : 'false', LARAVEL_STORAGE_PATH: storagePath, @@ -324,95 +319,125 @@ function getDefaultEnvironmentVariables(secret?: string, apiPort?: number): Envi function getDefaultPhpIniSettings() { return { - 'memory_limit': '512M', + memory_limit: '512M', 'curl.cainfo': state.caCert, - 'openssl.cafile': state.caCert - } + 'openssl.cafile': state.caCert, + }; } -function serveApp(secret, apiPort, phpIniSettings): Promise { - return new Promise(async (resolve, reject) => { - const appPath = getAppPath(); - - console.log('Starting PHP server...', `${state.php} artisan serve`, appPath, phpIniSettings) - - ensureAppFoldersAreAvailable(); +async function serveApp(secret, apiPort, phpIniSettings): Promise { + const appPath = getAppPath(); - console.log('Making sure app folders are available') + console.log('Starting PHP server...', `${state.php} artisan serve`, appPath, phpIniSettings); - const env = getDefaultEnvironmentVariables(secret, apiPort); + ensureAppFoldersAreAvailable(); - const phpOptions = { - cwd: appPath, - env - }; + console.log('Making sure app folders are available'); - const store = new Store({ - name: 'nativephp', // So it doesn't conflict with settings of the app - }); + const env = getDefaultEnvironmentVariables(secret, apiPort); - // Cache the project - if (shouldOptimize(store)) { - console.log('Caching view and routes...'); + let phpNightWatchPort: number | undefined; + if (process.env.NIGHTWATCH_TOKEN) { + phpNightWatchPort = await getPhpPort(); + env.NIGHTWATCH_INGEST_URI = `127.0.0.1:${phpNightWatchPort}`; + } - let result = callPhpSync(['artisan', 'optimize'], phpOptions, phpIniSettings); + const phpOptions = { + cwd: appPath, + env, + }; - if (result.status !== 0) { - console.error('Failed to cache view and routes:', result.stderr.toString()); - } else { - store.set('optimized_version', app.getVersion()) - } - } + const store = new Store({ + name: 'nativephp', // So it doesn't conflict with settings of the app + }); - // Migrate the database - if (shouldMigrateDatabase(store)) { - console.log('Migrating database...'); + if (env.NIGHTWATCH_INGEST_URI && phpNightWatchPort) { + console.log('Starting Nightwatch server...'); + callPhp( + ['artisan', 'nightwatch:agent', `--listen-on=${env.NIGHTWATCH_INGEST_URI}`], + phpOptions, + phpIniSettings, + ); + console.log('Nightwatch server started on port:', phpNightWatchPort); + } - if(parseInt(process.env.SHELL_VERBOSITY) > 0) { - console.log('Database path:', databaseFile); - } + // Cache the project + if (shouldOptimize()) { + console.log('Caching view and routes...'); - let result = callPhpSync(['artisan', 'migrate', '--force'], phpOptions, phpIniSettings); + const result = callPhpSync(['artisan', 'optimize'], phpOptions, phpIniSettings); - if (result.status !== 0) { - console.error('Failed to migrate database:', result.stderr.toString()); - } else { - store.set('migrated_version', app.getVersion()) - } + if (result.status !== 0) { + console.error('Failed to cache view and routes:', result.stderr.toString()); + } else { + store.set('optimized_version', app.getVersion()); } + } - if (process.env.NODE_ENV === 'development') { - console.log('Skipping Database migration while in development.') - console.log('You may migrate manually by running: php artisan native:migrate') + // Migrate the database + if (shouldMigrateDatabase(store)) { + console.log('Migrating database...'); + + if (parseInt(process.env.SHELL_VERBOSITY) > 0) { + console.log('Database path:', databaseFile); } - let serverPath: string; - let cwd: string; + const result = callPhpSync(['artisan', 'migrate', '--force'], phpOptions, phpIniSettings); - if (runningSecureBuild()) { - serverPath = join(appPath, 'build', '__nativephp_app_bundle'); + if (result.status !== 0) { + console.error('Failed to migrate database:', result.stderr.toString()); } else { - console.log('* * * Running from source * * *'); - serverPath = join(appPath, 'vendor', 'laravel', 'framework', 'src', 'Illuminate', 'Foundation', 'resources', 'server.php'); - cwd = join(appPath, 'public'); + store.set('migrated_version', app.getVersion()); } + } + + if (process.env.NODE_ENV === 'development') { + console.log('Skipping Database migration while in development.'); + console.log('You may migrate manually by running: php artisan native:migrate'); + } - console.log('Starting PHP server...'); - const phpPort = await getPhpPort(); - const phpServer = callPhp(['-S', `127.0.0.1:${phpPort}`, serverPath], { + let serverPath: string; + let cwd: string; + + if (runningSecureBuild()) { + serverPath = join(appPath, 'build', '__nativephp_app_bundle'); + } else { + console.log('* * * Running from source * * *'); + serverPath = join( + appPath, + 'vendor', + 'laravel', + 'framework', + 'src', + 'Illuminate', + 'Foundation', + 'resources', + 'server.php', + ); + cwd = join(appPath, 'public'); + } + + console.log('Starting PHP server...'); + const phpPort = await getPhpPort(); + const phpServer = callPhp( + ['-S', `127.0.0.1:${phpPort}`, serverPath], + { cwd: cwd, - env - }, phpIniSettings) + env, + }, + phpIniSettings, + ); - const portRegex = /Development Server \(.*:([0-9]+)\) started/gm + const portRegex = /Development Server \(.*:([0-9]+)\) started/gm; + return new Promise((resolve, reject) => { // Show urls called phpServer.stdout.on('data', (data) => { // [Tue Jan 14 19:51:00 2025] 127.0.0.1:52779 [POST] URI: /_native/api/events if (parseInt(process.env.SHELL_VERBOSITY) > 0) { console.log(data.toString().trim()); } - }) + }); // Show PHP errors and indicate which port the server is running on phpServer.stderr.on('data', (data) => { @@ -421,44 +446,42 @@ function serveApp(secret, apiPort, phpIniSettings): Promise { if (match) { const port = parseInt(match[1]); - console.log("PHP Server started on port: ", port); + console.log('PHP Server started on port: ', port); resolve({ port, process: phpServer, }); - } else { - if (error.includes('[NATIVE_EXCEPTION]:')) { - let logFile = join(storagePath, 'logs'); - - console.log(); - console.error('Error in PHP:'); - console.error(' ' + error.split('[NATIVE_EXCEPTION]:')[1].trim()); - console.log('Please check your log files:'); - console.log(' ' + logFile); - console.log(); - } + } else if (error.includes('[NATIVE_EXCEPTION]:')) { + const logFile = join(storagePath, 'logs'); + + console.log(); + console.error('Error in PHP:'); + console.error(' ' + error.split('[NATIVE_EXCEPTION]:')[1].trim()); + console.log('Please check your log files:'); + console.log(' ' + logFile); + console.log(); } }); // Log when any error occurs (not started, not killed, couldn't send message, etc) phpServer.on('error', (error) => { - reject(error) + reject(error); }); // Log when the PHP server exits phpServer.on('close', (code) => { console.log(`PHP server exited with code ${code}`); }); - }) + }); } export { - startScheduler, - serveApp, getAppPath, - retrieveNativePHPConfig, - retrievePhpIniSettings, getDefaultEnvironmentVariables, getDefaultPhpIniSettings, - runningSecureBuild -} + retrieveNativePHPConfig, + retrievePhpIniSettings, + runningSecureBuild, + serveApp, + startScheduler, +}; diff --git a/resources/electron/electron-plugin/src/server/state.ts b/resources/electron/electron-plugin/src/server/state.ts index 503485e2..f2a7d725 100644 --- a/resources/electron/electron-plugin/src/server/state.ts +++ b/resources/electron/electron-plugin/src/server/state.ts @@ -1,69 +1,69 @@ -import {BrowserWindow, Tray, UtilityProcess} from "electron"; -import Store from "electron-store"; -import { notifyLaravel } from "./utils.js"; +import { BrowserWindow, Tray, UtilityProcess } from 'electron'; +import Store from 'electron-store'; +import type { Menubar } from '../libs/menubar/index.js'; +import { notifyLaravel } from './utils.js'; const settingsStore = new Store(); settingsStore.onDidAnyChange((newValue, oldValue) => { - // Only notify of the changed key/value pair - const changedKeys = Object.keys(newValue).filter((key) => newValue[key] !== oldValue[key]); + // Only notify of the changed key/value pair + const changedKeys = Object.keys(newValue).filter((key) => newValue[key] !== oldValue[key]); - changedKeys.forEach((key) => { - notifyLaravel("events", { - event: "Native\\Desktop\\Events\\Settings\\SettingChanged", - payload: { - key, - value: newValue[key] || null, - }, + changedKeys.forEach((key) => { + notifyLaravel('events', { + event: 'Native\\Desktop\\Events\\Settings\\SettingChanged', + payload: { + key, + value: newValue[key] || null, + }, + }); }); - }); }); interface State { - electronApiPort: number | null; - activeMenuBar: any; - tray: Tray | null; - php: string | null; - phpPort: number | null; - phpIni: any; - caCert: string | null; - appPath: string | null; - icon: string | null; - processes: Record}>; - windows: Record; - randomSecret: string; - store: Store; - findWindow: (id: string) => BrowserWindow | null; - dockBounce: number; + electronApiPort: number | null; + activeMenuBar: Menubar | null; + tray: Tray | null; + php: string | null; + phpPort: number | null; + phpIni: Record | null; + caCert: string | null; + appPath: string | null; + icon: string | null; + processes: Record }>; + windows: Record; + randomSecret: string; + store: Store; + findWindow: (id: string) => BrowserWindow | null; + dockBounce: number; } function generateRandomString(length: number) { - let result = ""; - const characters = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - const charactersLength = characters.length; + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const charactersLength = characters.length; - for (let i = 0; i < length; i += 1) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); - } + for (let i = 0; i < length; i += 1) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } - return result; + return result; } export default { - electronApiPort: null, - activeMenuBar: null, - tray: null, - php: null, - phpPort: null, - phpIni: null, - caCert: null, - appPath: null, - icon: null, - store: settingsStore, - randomSecret: generateRandomString(32), - processes: {}, - windows: {}, - findWindow(id: string) { - return this.windows[id] || null; - }, + electronApiPort: null, + activeMenuBar: null, + tray: null, + php: null, + phpPort: null, + phpIni: null, + caCert: null, + appPath: null, + icon: null, + store: settingsStore, + randomSecret: generateRandomString(32), + processes: {}, + windows: {}, + findWindow(id: string) { + return this.windows[id] || null; + }, } as State; diff --git a/resources/electron/electron-plugin/src/server/utils.ts b/resources/electron/electron-plugin/src/server/utils.ts index 57ed8964..97c771a5 100644 --- a/resources/electron/electron-plugin/src/server/utils.ts +++ b/resources/electron/electron-plugin/src/server/utils.ts @@ -1,11 +1,11 @@ +import axios from 'axios'; import { session } from 'electron'; import state from './state.js'; -import axios from 'axios'; export async function appendCookie() { const cookie = { url: `http://localhost:${state.phpPort}`, - name: "_php_native", + name: '_php_native', value: state.randomSecret, }; @@ -18,22 +18,18 @@ export async function notifyLaravel(endpoint: string, payload = {}) { } try { - await axios.post( - `http://127.0.0.1:${state.phpPort}/_native/api/${endpoint}`, - payload, - { - headers: { - "X-NativePHP-Secret": state.randomSecret, - }, - } - ); - } catch (e) { + await axios.post(`http://127.0.0.1:${state.phpPort}/_native/api/${endpoint}`, payload, { + headers: { + 'X-NativePHP-Secret': state.randomSecret, + }, + }); + } catch { // } } export function broadcastToWindows(event, payload) { - Object.values(state.windows).forEach(window => { + Object.values(state.windows).forEach((window) => { window.webContents.send(event, payload); }); @@ -45,8 +41,8 @@ export function broadcastToWindows(event, payload) { /** * Remove null and undefined values from an object */ -export function trimOptions(options: any): any { - Object.keys(options).forEach(key => options[key] == null && delete options[key]); +export function trimOptions>(options: T): T { + Object.keys(options).forEach((key) => options[key] == null && delete options[key]); return options; } diff --git a/resources/electron/electron-plugin/src/server/webPreferences.ts b/resources/electron/electron-plugin/src/server/webPreferences.ts index b53b631f..06cca45d 100644 --- a/resources/electron/electron-plugin/src/server/webPreferences.ts +++ b/resources/electron/electron-plugin/src/server/webPreferences.ts @@ -1,6 +1,6 @@ -import { fileURLToPath } from 'url' +import { fileURLToPath } from 'url'; -let preloadPath = fileURLToPath(new URL('../../electron-plugin/dist/preload/index.mjs', import.meta.url)); +const preloadPath = fileURLToPath(new URL('../../electron-plugin/dist/preload/index.mjs', import.meta.url)); const defaultWebPreferences = { spellcheck: false, @@ -12,13 +12,12 @@ const requiredWebPreferences = { sandbox: false, preload: preloadPath, contextIsolation: true, -} +}; -export default function(userWebPreferences: object = {}): object -{ +export default function (userWebPreferences: object = {}): object { return { ...defaultWebPreferences, ...userWebPreferences, - ...requiredWebPreferences - } + ...requiredWebPreferences, + }; } diff --git a/resources/electron/electron-plugin/tests/api.test.ts b/resources/electron/electron-plugin/tests/api.test.ts index 2bcb3b8c..826c4852 100644 --- a/resources/electron/electron-plugin/tests/api.test.ts +++ b/resources/electron/electron-plugin/tests/api.test.ts @@ -1,6 +1,6 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -import startAPIServer, { APIProcess } from "../src/server/api"; -import axios from "axios"; +import axios from 'axios'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import startAPIServer, { APIProcess } from '../src/server/api'; vi.mock('electron-updater', () => { return { @@ -56,7 +56,7 @@ describe('API test', () => { response = await axios.get('/api/process', { headers: { 'x-nativephp-secret': 'randomSecret', - } + }, }); } finally { expect(response.status).toBe(200); diff --git a/resources/electron/electron-plugin/tests/mocking.test.ts b/resources/electron/electron-plugin/tests/mocking.test.ts index 494a4cec..569ca11d 100644 --- a/resources/electron/electron-plugin/tests/mocking.test.ts +++ b/resources/electron/electron-plugin/tests/mocking.test.ts @@ -1,9 +1,8 @@ -import { describe, it, expect, vi } from 'vitest'; -import {mockForNodeRequire} from "vitest-mock-commonjs"; +import { describe, expect, it, vi } from 'vitest'; vi.mock('electron'); -import electron, { app, powerMonitor } from 'electron'; // Import the module you want to test +import { app, powerMonitor } from 'electron'; // Import the module you want to test describe('My Electron Module Tests', () => { // it('should return an object for electron', () => { @@ -16,7 +15,7 @@ describe('My Electron Module Tests', () => { }); it('should check if the app is packed', async () => { - const isPackaged = app.isPackaged; // Call the mocked method + const isPackaged = app.isPackaged; // Call the mocked method expect(isPackaged).toBe(false); // Assert the return value }); diff --git a/resources/electron/electron-plugin/tests/setup.ts b/resources/electron/electron-plugin/tests/setup.ts index 198961d2..1324b8ef 100644 --- a/resources/electron/electron-plugin/tests/setup.ts +++ b/resources/electron/electron-plugin/tests/setup.ts @@ -1,8 +1,9 @@ // vitest-setup.ts -import { vi } from 'vitest'; -import { mockForNodeRequire } from "vitest-mock-commonjs" +// @ts-ignore import express from 'express'; +import { vi } from 'vitest'; +import { mockForNodeRequire } from 'vitest-mock-commonjs'; // Mock electron mockForNodeRequire('electron', () => ({ @@ -64,13 +65,15 @@ mockForNodeRequire('electron', () => ({ scaleFactor: 1, rotation: 0, }), - getAllDisplays: vi.fn().mockReturnValue([{ - id: 1, - bounds: { x: 0, y: 0, width: 1920, height: 1080 }, - workArea: { x: 0, y: 0, width: 1920, height: 1040 }, - scaleFactor: 1, - rotation: 0, - }]), + getAllDisplays: vi.fn().mockReturnValue([ + { + id: 1, + bounds: { x: 0, y: 0, width: 1920, height: 1080 }, + workArea: { x: 0, y: 0, width: 1920, height: 1040 }, + scaleFactor: 1, + rotation: 0, + }, + ]), }, dialog: { @@ -172,7 +175,7 @@ vi.mock('electron-store', () => { has: vi.fn(), delete: vi.fn(), clear: vi.fn(), - onDidAnyChange: vi.fn().mockImplementation(callback => { + onDidAnyChange: vi.fn().mockImplementation(() => { // Return an unsubscribe function return () => {}; }), diff --git a/resources/electron/electron-plugin/tests/utils.test.ts b/resources/electron/electron-plugin/tests/utils.test.ts index 99d10547..c3c55629 100644 --- a/resources/electron/electron-plugin/tests/utils.test.ts +++ b/resources/electron/electron-plugin/tests/utils.test.ts @@ -1,28 +1,26 @@ -import {describe, expect, it, vi} from 'vitest'; -import {notifyLaravel} from "../src/server/utils"; -import state from "../src/server/state"; -import axios from "axios"; +import axios from 'axios'; +import { describe, expect, it, vi } from 'vitest'; +import state from '../src/server/state'; +import { notifyLaravel } from '../src/server/utils'; vi.mock('axios'); vi.mock('electron-store'); describe('Utils test', () => { - it('notifies laravel', async () => { state.phpPort = 8000; state.randomSecret = 'i-am-secret'; - await notifyLaravel('endpoint', {payload: 'payload'}); + await notifyLaravel('endpoint', { payload: 'payload' }); expect(axios.post).toHaveBeenCalledWith( `http://127.0.0.1:8000/_native/api/endpoint`, - {payload: 'payload'}, + { payload: 'payload' }, { headers: { - "X-NativePHP-Secret": 'i-am-secret', - } - } + 'X-NativePHP-Secret': 'i-am-secret', + }, + }, ); }); - }); diff --git a/resources/electron/electron-plugin/tsconfig.json b/resources/electron/electron-plugin/tsconfig.json index d6676c9b..3da2b4c5 100644 --- a/resources/electron/electron-plugin/tsconfig.json +++ b/resources/electron/electron-plugin/tsconfig.json @@ -10,19 +10,9 @@ "noImplicitAny": false, "preserveConstEnums": true, "removeComments": true, - "typeRoots": [ - "../node_modules/@types" - ], - "lib": [ - "es2019", - "dom" - ], - "types": [ - "node", - ] + "typeRoots": ["../node_modules/@types"], + "lib": ["es2019", "dom"], + "types": ["node"] }, - "include": [ - "./src/**/*.ts", - "./src/**/*.mts" - ] + "include": ["./src/**/*.ts", "./src/**/*.mts"] } diff --git a/resources/electron/electron-plugin/vitest.config.mts b/resources/electron/electron-plugin/vitest.config.mts index ee909ed3..2fe36b74 100644 --- a/resources/electron/electron-plugin/vitest.config.mts +++ b/resources/electron/electron-plugin/vitest.config.mts @@ -1,7 +1,7 @@ -import { defineConfig } from 'vitest/config' +import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { setupFiles: ['./tests/setup.ts'], - } -}) + }, +}); diff --git a/resources/electron/electron.vite.config.mjs b/resources/electron/electron.vite.config.mjs index 4af81f9c..45f9816f 100644 --- a/resources/electron/electron.vite.config.mjs +++ b/resources/electron/electron.vite.config.mjs @@ -1,5 +1,5 @@ -import { join } from 'path'; import { defineConfig, externalizeDepsPlugin } from 'electron-vite'; +import { join } from 'path'; export default defineConfig({ main: { @@ -9,12 +9,14 @@ export default defineConfig({ { name: 'watch-external', buildStart() { - this.addWatchFile(join(process.env.APP_PATH, 'app', 'Providers', 'NativeAppServiceProvider.php')); - } - } - ] + this.addWatchFile( + join(process.env.APP_PATH, 'app', 'Providers', 'NativeAppServiceProvider.php'), + ); + }, + }, + ], }, }, - plugins: [externalizeDepsPlugin()] - } + plugins: [externalizeDepsPlugin()], + }, }); diff --git a/resources/electron/eslint.config.js b/resources/electron/eslint.config.js index fb7840e3..fb072f7c 100644 --- a/resources/electron/eslint.config.js +++ b/resources/electron/eslint.config.js @@ -2,10 +2,16 @@ import globals from "globals"; import pluginJs from "@eslint/js"; import tseslint from "typescript-eslint"; import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; -import eslintPluginUnicorn from "eslint-plugin-unicorn"; +import { defineConfig, globalIgnores } from "eslint/config"; +// import eslintPluginUnicorn from "eslint-plugin-unicorn"; /** @type {import('eslint').Linter.Config[]} */ -export default [ +export default defineConfig([ + globalIgnores([ + "electron-plugin/dist/**/*", + "out/**/*", + 'electron-plugin/src/preload/livewire-dispatcher.js' + ]), { files: ["**/*.{js,mjs,cjs,ts}"], }, @@ -21,6 +27,25 @@ export default [ pluginJs.configs.recommended, ...tseslint.configs.recommended, eslintPluginPrettierRecommended, + { + rules: { + "@typescript-eslint/ban-ts-comment": [ + "error", + { + "ts-ignore": false, + "ts-expect-error": false, + "ts-nocheck": false, + "ts-check": false + } + ], + "prettier/prettier": [ + "error", + { + "singleQuote": true + } + ] + } + }, // { // languageOptions: { // globals: globals.builtin, @@ -32,4 +57,4 @@ export default [ // 'unicorn/prefer-module': 'error', // }, // }, -]; +]); diff --git a/resources/electron/package-lock.json b/resources/electron/package-lock.json index 8b163818..19990768 100644 --- a/resources/electron/package-lock.json +++ b/resources/electron/package-lock.json @@ -13,7 +13,7 @@ "@electron-toolkit/utils": "^4.0.0", "@electron/remote": "^2.1.2", "axios": "^1.7.9", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "electron-context-menu": "^4.0.4", "electron-store": "^10.0.0", "electron-updater": "^6.3.9", @@ -58,6 +58,7 @@ "globals": "^16.0.0", "less": "^4.2.1", "prettier": "^3.4.2", + "prettier-plugin-organize-imports": "^4.3", "rimraf": "^6.0.1", "stylelint": "^16.12.0", "stylelint-config-recommended": "^16.0.0", @@ -5160,23 +5161,43 @@ } }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/boolean": { @@ -8896,6 +8917,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -11062,6 +11084,23 @@ "node": ">=6.0.0" } }, + "node_modules/prettier-plugin-organize-imports": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.3.0.tgz", + "integrity": "sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": ">=2.0", + "typescript": ">=2.9", + "vue-tsc": "^2.1.0 || 3" + }, + "peerDependenciesMeta": { + "vue-tsc": { + "optional": true + } + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", diff --git a/resources/electron/package.json b/resources/electron/package.json index 481482e4..ee8f0e0a 100644 --- a/resources/electron/package.json +++ b/resources/electron/package.json @@ -42,7 +42,7 @@ "@electron-toolkit/utils": "^4.0.0", "@electron/remote": "^2.1.2", "axios": "^1.7.9", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "electron-context-menu": "^4.0.4", "electron-store": "^10.0.0", "electron-updater": "^6.3.9", @@ -87,6 +87,7 @@ "globals": "^16.0.0", "less": "^4.2.1", "prettier": "^3.4.2", + "prettier-plugin-organize-imports": "^4.3", "rimraf": "^6.0.1", "stylelint": "^16.12.0", "stylelint-config-recommended": "^16.0.0", diff --git a/resources/electron/php.js b/resources/electron/php.js index e9f3ed96..4efd272a 100644 --- a/resources/electron/php.js +++ b/resources/electron/php.js @@ -1,9 +1,8 @@ -import fs from "fs"; +import fs from 'fs'; import fs_extra from 'fs-extra'; -const { copySync, removeSync, ensureDirSync } = fs_extra; -import { join } from "path"; -import unzip from "yauzl"; - +import { join } from 'path'; +import unzip from 'yauzl'; +const { removeSync, ensureDirSync } = fs_extra; const isBuilding = Boolean(process.env.NATIVEPHP_BUILDING); const phpBinaryPath = process.env.NATIVEPHP_PHP_BINARY_PATH; @@ -11,15 +10,15 @@ const phpVersion = process.env.NATIVEPHP_PHP_BINARY_VERSION; // Differentiates for Serving and Building const isArm64 = isBuilding ? process.argv.includes('--arm64') : process.arch.includes('arm64'); -const isWindows = isBuilding ? process.argv.includes('--win') : process.platform.includes('win32'); -const isLinux = isBuilding ? process.argv.includes('--linux') : process.platform.includes('linux'); -const isDarwin = isBuilding ? process.argv.includes('--mac') : process.platform.includes('darwin'); +const isWindows = isBuilding ? process.argv.includes('--win') : process.platform.includes('win32'); +const isLinux = isBuilding ? process.argv.includes('--linux') : process.platform.includes('linux'); +const isDarwin = isBuilding ? process.argv.includes('--mac') : process.platform.includes('darwin'); // false because string mapping is done in is{OS} checks const platform = { os: false, arch: false, - phpBinary: 'php' + phpBinary: 'php', }; if (isWindows) { @@ -65,10 +64,10 @@ if (platform.phpBinary) { ensureDirSync(binaryDestDir); // Unzip the files - unzip.open(binarySrcDir, {lazyEntries: true}, function (err, zipfile) { + unzip.open(binarySrcDir, { lazyEntries: true }, function (err, zipfile) { if (err) throw err; zipfile.readEntry(); - zipfile.on("entry", function (entry) { + zipfile.on('entry', function (entry) { zipfile.openReadStream(entry, function (err, readStream) { if (err) throw err; @@ -77,7 +76,7 @@ if (platform.phpBinary) { readStream.pipe(writeStream); - writeStream.on("close", function() { + writeStream.on('close', function () { console.log('Copied PHP binary to ', binaryPath); // Add execute permissions diff --git a/resources/electron/src/main/index.js b/resources/electron/src/main/index.js index 309cc289..ac09bb69 100644 --- a/resources/electron/src/main/index.js +++ b/resources/electron/src/main/index.js @@ -1,22 +1,16 @@ -import {app} from 'electron' -import NativePHP from '#plugin' -import path from 'path' +import NativePHP from '#plugin'; +import { app } from 'electron'; +import path from 'path'; const buildPath = path.resolve(import.meta.dirname, import.meta.env.MAIN_VITE_NATIVEPHP_BUILD_PATH); -const defaultIcon = path.join(buildPath, 'icon.png') -const certificate = path.join(buildPath, 'cacert.pem') +const defaultIcon = path.join(buildPath, 'icon.png'); +const certificate = path.join(buildPath, 'cacert.pem'); const executable = process.platform === 'win32' ? 'php.exe' : 'php'; -const phpBinary = path.join(buildPath,'php', executable); -const appPath = path.join(buildPath, 'app') +const phpBinary = path.join(buildPath, 'php', executable); +const appPath = path.join(buildPath, 'app'); /** * Turn on the lights for the NativePHP app. */ -NativePHP.bootstrap( - app, - defaultIcon, - phpBinary, - certificate, - appPath -); +NativePHP.bootstrap(app, defaultIcon, phpBinary, certificate, appPath); From 372a78ab8c83c700271d1fdac8c26103bee6d4ee Mon Sep 17 00:00:00 2001 From: WINBIGFOX Date: Thu, 8 Jan 2026 12:13:07 +0100 Subject: [PATCH 2/2] merge main into branch and resolve conflicts --- .../electron/electron-plugin/src/index.ts | 38 ++-- .../electron-plugin/src/server/api/menuBar.ts | 2 +- .../electron-plugin/src/server/api/window.ts | 2 +- resources/electron/package-lock.json | 207 +++++++++++++++++- 4 files changed, 216 insertions(+), 33 deletions(-) diff --git a/resources/electron/electron-plugin/src/index.ts b/resources/electron/electron-plugin/src/index.ts index 7f878489..3c92d05e 100644 --- a/resources/electron/electron-plugin/src/index.ts +++ b/resources/electron/electron-plugin/src/index.ts @@ -26,14 +26,8 @@ class NativePHP { mainWindow = null; schedulerInterval = undefined; - public bootstrap( - app: CrossProcessExports.App, - icon: string, - phpBinary: string, - cert: string, - appPath: string - ) { - initialize(); + public bootstrap(app: CrossProcessExports.App, icon: string, phpBinary: string, cert: string, appPath: string) { + initialize(); state.icon = icon; state.php = phpBinary; @@ -203,22 +197,22 @@ class NativePHP { } } - private startAutoUpdater(config) { - if (config?.updater?.enabled === true) { - // If a public URL is configured for the current provider, use it for updates - const defaultProvider = config?.updater?.default; - const publicUrl = config?.updater?.providers?.[defaultProvider]?.public_url; - - if (publicUrl) { - autoUpdater.setFeedURL({ - provider: 'generic', - url: publicUrl - }); - } + private startAutoUpdater(config) { + if (config?.updater?.enabled === true) { + // If a public URL is configured for the current provider, use it for updates + const defaultProvider = config?.updater?.default; + const publicUrl = config?.updater?.providers?.[defaultProvider]?.public_url; + + if (publicUrl) { + autoUpdater.setFeedURL({ + provider: 'generic', + url: publicUrl, + }); + } - autoUpdater.checkForUpdatesAndNotify(); + autoUpdater.checkForUpdatesAndNotify(); + } } - } private async startElectronApi() { // Start an Express server so that the Electron app can be controlled from PHP via API diff --git a/resources/electron/electron-plugin/src/server/api/menuBar.ts b/resources/electron/electron-plugin/src/server/api/menuBar.ts index f27fbd04..853a6699 100644 --- a/resources/electron/electron-plugin/src/server/api/menuBar.ts +++ b/resources/electron/electron-plugin/src/server/api/menuBar.ts @@ -41,7 +41,7 @@ router.post('/context-menu', (req, res) => { state.tray?.setContextMenu(buildMenu(contextMenu)); }); -router.post("/show-context-menu", (req, res) => { +router.post('/show-context-menu', (req, res) => { res.sendStatus(200); state.tray?.popUpContextMenu(); diff --git a/resources/electron/electron-plugin/src/server/api/window.ts b/resources/electron/electron-plugin/src/server/api/window.ts index 55299cbf..eda824c5 100644 --- a/resources/electron/electron-plugin/src/server/api/window.ts +++ b/resources/electron/electron-plugin/src/server/api/window.ts @@ -58,7 +58,7 @@ router.post('/closable', (req, res) => { }); router.post('/window-button-visibility', (req, res) => { - const {id, windowButtonVisibility} = req.body; + const { id, windowButtonVisibility } = req.body; state.windows[id]?.setWindowButtonVisibility(windowButtonVisibility); diff --git a/resources/electron/package-lock.json b/resources/electron/package-lock.json index 19990768..7e26d947 100644 --- a/resources/electron/package-lock.json +++ b/resources/electron/package-lock.json @@ -19,6 +19,7 @@ "electron-updater": "^6.3.9", "electron-window-state": "^5.0.3", "express": "^5.1.0", + "fix-path": "^5.0.0", "fs-extra": "^11.2.0", "get-port": "^7.1.0", "kill-sync": "^1.0.3", @@ -6334,7 +6335,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -6458,6 +6458,18 @@ "dev": true, "license": "MIT" }, + "node_modules/default-shell": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/default-shell/-/default-shell-2.2.0.tgz", + "integrity": "sha512-sPpMZcVhRQ0nEMDtuMJ+RtCxt7iHPAMBU+I4tAlo5dU1sjRpNax0crj6nR3qKpvVnckaQ9U38enXcwW9nZJeCw==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -7690,6 +7702,41 @@ "node": ">= 0.6" } }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/expect-type": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", @@ -8021,6 +8068,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fix-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fix-path/-/fix-path-5.0.0.tgz", + "integrity": "sha512-erEWGGCN7RIu1bXTCfNVpVBdm0f5mwcbeja+4QXiEZzIQukP401sbpu8gd3Ny1vS34YNeswyMO0TdT2tP5OlHA==", + "license": "MIT", + "dependencies": { + "shell-path": "^3.1.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fix-path/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/fix-path/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -8816,6 +8906,15 @@ "node": ">= 14" } }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -9245,6 +9344,18 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -9290,7 +9401,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/jackspeak": { @@ -9918,6 +10028,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -9980,7 +10096,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -10502,6 +10617,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/npmlog": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", @@ -10566,7 +10693,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -10758,7 +10884,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11989,7 +12114,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -12002,12 +12126,70 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/shell-env": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/shell-env/-/shell-env-4.0.1.tgz", + "integrity": "sha512-w3oeZ9qg/P6Lu6qqwavvMnB/bwfsz67gPB3WXmLd/n6zuh7TWQZtGa3iMEdmua0kj8rivkwl+vUjgLWlqZOMPw==", + "license": "MIT", + "dependencies": { + "default-shell": "^2.0.0", + "execa": "^5.1.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shell-env/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/shell-env/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/shell-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/shell-path/-/shell-path-3.1.0.tgz", + "integrity": "sha512-s/9q9PEtcRmDTz69+cJ3yYBAe9yGrL7e46gm2bU4pQ9N48ecPK9QrGFnLwYgb4smOHskx4PL7wCNMktW2AoD+g==", + "license": "MIT", + "dependencies": { + "shell-env": "^4.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/shell-quote": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", @@ -12103,7 +12285,6 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, "license": "ISC" }, "node_modules/simple-update-notifier": { @@ -12480,6 +12661,15 @@ "node": ">=8" } }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/strip-indent": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.1.tgz", @@ -13853,7 +14043,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0"