diff --git a/.vscode/launch.json b/.vscode/launch.json index 3a9cc68..7c8e631 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,7 +17,7 @@ "runtimeExecutable": "${workspaceFolder}/node_modules/electron/dist/electron.exe" }, "args": [ - "dist/app/main/app-entry.cjs" + "dist/app/app-entry.cjs" ], "outputCapture": "std", "env": { diff --git a/rollup.config.js b/rollup.config.js index af1a9a2..e30ea90 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -111,7 +111,7 @@ const main = { * @type {import('rollup').RollupOptions} */ const app_entry = { - input: 'src/app/main/app-entry.cts', + input: 'src/app/app-entry.cts', output: { file: 'dist/bundle/app-entry.cjs', format: 'iife', @@ -121,7 +121,7 @@ const app_entry = { plugins: [ replace(replace_options), replace({ - include: ['src/app/main/app-entry.cts'], + include: ['src/app/app-entry.cts'], values: { '__NXAPI_BUNDLE_APP_MAIN__': JSON.stringify('./app-main-bundle.js'), }, @@ -147,7 +147,7 @@ const app_entry = { ], external: [ 'electron', - path.resolve(__dirname, 'src/app/main/app-main-bundle.js'), + path.resolve(__dirname, 'src/app/app-main-bundle.js'), ], watch, }; diff --git a/src/app/README.md b/src/app/README.md index 6b31072..4ba6040 100644 --- a/src/app/README.md +++ b/src/app/README.md @@ -3,7 +3,7 @@ Electron app The Electron app is bundled into ~4 files in `dist/app/bundle` using Rollup. The main process code is not bundled for development, but is when packaging the app at `dist/bundle` (with the command line executable at `dist/bundle/cli-bundle.js`). -[electron.ts](electron.ts) exports all Electron APIs used in the main process. This is because the `electron` module doesn't actually exist - Electron patches the `require` function (but not the module importer). Additionally Electron does not support using a standard JavaScript module as the app entrypoint, so [main/app-entry.cts](main/app-entry.cts) (a CommonJS module) is used to import the actual app entrypoint after the `ready` event. +[electron.ts](electron.ts) exports all Electron APIs used in the main process. This is because the `electron` module doesn't actually exist - Electron patches the `require` function (but not the module importer). Additionally Electron does not support using a standard JavaScript module as the app entrypoint, so [app-entry.cts](app-entry.cts) (a CommonJS module) is used to import the actual app entrypoint after the `ready` event. Electron APIs used in renderer processes should be imported directly from the `electron` module as they are always bundled into a CommonJS module. diff --git a/src/app/app-entry.cts b/src/app/app-entry.cts new file mode 100644 index 0000000..fd460e8 --- /dev/null +++ b/src/app/app-entry.cts @@ -0,0 +1,12 @@ +const electron = require('electron'); + +// Do anything that must be run before the app is ready... + +electron.app.whenReady() + // @ts-expect-error + .then(() => typeof __NXAPI_BUNDLE_APP_MAIN__ !== 'undefined' ? import(__NXAPI_BUNDLE_APP_MAIN__) : import('./main/index.js')) + .then(m => m.init.call(null)) + .catch(err => { + electron.dialog.showErrorBox('Error during startup', err?.stack ?? err?.message ?? err); + process.exit(1); + }); diff --git a/src/app/main/app-entry.cts b/src/app/main/app-entry.cts deleted file mode 100644 index 864fa0b..0000000 --- a/src/app/main/app-entry.cts +++ /dev/null @@ -1,8 +0,0 @@ -const electron = require('electron'); - -// Do anything that must be run before the app is ready... - -electron.app.whenReady() - // @ts-expect-error - .then(() => typeof __NXAPI_BUNDLE_APP_MAIN__ !== 'undefined' ? import(__NXAPI_BUNDLE_APP_MAIN__) : import('./index.js')) - .then(m => m.init.call(null)); diff --git a/src/app/main/index.ts b/src/app/main/index.ts index cc062c0..eca8a20 100644 --- a/src/app/main/index.ts +++ b/src/app/main/index.ts @@ -4,8 +4,7 @@ import * as path from 'node:path'; import { EventEmitter } from 'node:events'; import createDebug from 'debug'; import * as persist from 'node-persist'; -import dotenv from 'dotenv'; -import dotenvExpand from 'dotenv-expand'; +import { init as initGlobals } from '../../common/globals.js'; import MenuApp from './menu.js'; import { handleOpenWebServiceUri } from './webservices.js'; import { EmbeddedPresenceMonitor, PresenceMonitorManager } from './monitor.js'; @@ -26,7 +25,7 @@ const debug = createDebug('app:main'); export const protocol_registration_options = dev && process.platform === 'win32' ? { path: process.execPath, argv: [ - path.join(dir, 'dist', 'app', 'main', 'app-entry.cjs'), + path.join(dir, 'dist', 'app', 'app-entry.cjs'), ], } : null; export const login_item_options: LoginItemSettingsOptions = {}; @@ -107,15 +106,7 @@ export async function init() { return; } - dotenvExpand.expand(dotenv.config({ - path: path.join(paths.data, '.env'), - })); - if (process.env.NXAPI_DATA_PATH) dotenvExpand.expand(dotenv.config({ - path: path.join(process.env.NXAPI_DATA_PATH, '.env'), - })); - - if (process.env.DEBUG) createDebug.enable(process.env.DEBUG); - + initGlobals(); addUserAgent('nxapi-app (Chromium ' + process.versions.chrome + '; Electron ' + process.versions.electron + ')'); const storage = await initStorage(process.env.NXAPI_DATA_PATH ?? paths.data); @@ -145,6 +136,8 @@ export async function init() { app.on('open-url', (event, url) => { debug('Open URL', url); + event.preventDefault(); + if (!tryHandleUrl(appinstance, url)) { appinstance.showMainWindow(); } @@ -153,7 +146,9 @@ export async function init() { app.setAsDefaultProtocolClient('com.nintendo.znca', protocol_registration_options?.path, protocol_registration_options?.argv); - app.on('activate', () => { + app.on('activate', (event, has_visible_windows) => { + debug('activate', has_visible_windows); + if (BrowserWindow.getAllWindows().length === 0) appinstance.showMainWindow(); }); diff --git a/src/cli.ts b/src/cli.ts index 59d1011..923075e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,9 +1,6 @@ import process from 'node:process'; -import * as path from 'node:path'; import createDebug from 'debug'; import Yargs from 'yargs'; -import dotenv from 'dotenv'; -import dotenvExpand from 'dotenv-expand'; import * as commands from './cli/index.js'; import { checkUpdates } from './common/update.js'; import { dev } from './util/product.js'; @@ -11,17 +8,11 @@ import { paths } from './util/storage.js'; import { YargsArguments } from './util/yargs.js'; import { addUserAgent } from './util/useragent.js'; import { USER_AGENT_INFO_URL } from './common/constants.js'; +import { init as initGlobals } from './common/globals.js'; const debug = createDebug('cli'); -dotenvExpand.expand(dotenv.config({ - path: path.join(paths.data, '.env'), -})); -if (process.env.NXAPI_DATA_PATH) dotenvExpand.expand(dotenv.config({ - path: path.join(process.env.NXAPI_DATA_PATH, '.env'), -})); - -if (process.env.DEBUG) createDebug.enable(process.env.DEBUG); +initGlobals(); export function createYargs(argv: string[]) { const yargs = Yargs(argv).option('data-path', { diff --git a/src/cli/app.ts b/src/cli/app.ts index 383cb21..c403674 100644 --- a/src/cli/app.ts +++ b/src/cli/app.ts @@ -25,7 +25,7 @@ export async function handler(argv: ArgumentsCamelCase) { } execFileSync(electron, [ - 'dist/app/main/app-entry.cjs', + 'dist/app/app-entry.cjs', ], { stdio: 'inherit', env: { diff --git a/src/common/globals.ts b/src/common/globals.ts new file mode 100644 index 0000000..9a10568 --- /dev/null +++ b/src/common/globals.ts @@ -0,0 +1,24 @@ +import * as path from 'node:path'; +import createDebug from 'debug'; +import dotenv from 'dotenv'; +import dotenvExpand from 'dotenv-expand'; +import { paths } from '../util/storage.js'; + +let done = false; + +export function init() { + if (done) { + throw new Error('Attempted to initialise global data twice'); + } + + done = true; + + dotenvExpand.expand(dotenv.config({ + path: path.join(paths.data, '.env'), + })); + if (process.env.NXAPI_DATA_PATH) dotenvExpand.expand(dotenv.config({ + path: path.join(process.env.NXAPI_DATA_PATH, '.env'), + })); + + if (process.env.DEBUG) createDebug.enable(process.env.DEBUG); +}