From bd1b013706a34433879346e87454b00e25a43f05 Mon Sep 17 00:00:00 2001 From: Rodney G Date: Wed, 4 Nov 2026 22:44:28 -0500 Subject: [PATCH 1/7] Fix login autostart so Windows starts the app and macOS stays in the tray. The auto-launch package registered a disabled Windows Run value named nw and kept adding macOS login items that opened the window and extra Dock icons. Co-authored-by: Cursor --- desktopBg.cjs | 167 ++-- desktopInject.js | 9 +- docs/desktop-tray.md | 4 +- index.html | 3 +- loginAutostart.cjs | 831 ++++++++++++++++++ package-lock.json | 59 +- package.json | 1 - resources/windows/installer.nsi | 97 ++ scripts/release.mjs | 17 +- tests/desktop/bootstrap.static.test.mjs | 2 + tests/desktop/desktopBg.unit.test.mjs | 38 +- tests/desktop/loginAutostart.unit.test.mjs | 315 +++++++ .../desktop/release-packaging.static.test.mjs | 6 +- 13 files changed, 1335 insertions(+), 214 deletions(-) create mode 100644 loginAutostart.cjs create mode 100644 tests/desktop/loginAutostart.unit.test.mjs diff --git a/desktopBg.cjs b/desktopBg.cjs index 25d344ea..d2ca90b2 100644 --- a/desktopBg.cjs +++ b/desktopBg.cjs @@ -11,6 +11,7 @@ const fs = require('fs'); const path = require('path'); const os = require('os'); const userPreferences = require('./userPreferences.cjs'); +const { createLoginAutostart } = require('./loginAutostart.cjs'); const SUPPRESS_SHOW_KEY = 'onlykeySuppressShow'; @@ -190,6 +191,7 @@ const state = { stopped: false, pendingTimers: [], autoLaunch: null, + userRevealedWindow: false, }; const osx = os.platform() === 'darwin'; @@ -447,6 +449,7 @@ function resolveTrayIconPath() { } function revealWindow(win) { + state.userRevealedWindow = true; setSuppressShow(false); try { if (win.isMinimized) win.restore(); @@ -466,6 +469,7 @@ function executeShowMainWindow() { const reveal = () => { if (state.quitting || state.stopped || !hasNw()) return; state.quitting = false; + state.userRevealedWindow = true; setSuppressShow(false); trayLog('executeShowMainWindow', { hasMainRef: !!state.mainAppWindow, @@ -674,14 +678,42 @@ function collectArgv() { return values; } -/** True when this process was started by the OS login entry (--hidden). */ +function hasHiddenLaunchArg(arg) { + return arg === '--hidden' || arg === '--onlykey-autostart' || arg.startsWith('--hidden='); +} + +/** True when this process was started by the OS login entry. */ function launchedHiddenToTray() { - return collectArgv().some((arg) => arg === '--hidden' || arg.startsWith('--hidden=')); + try { + if (process.env.ONLYKEY_LAUNCHED_HIDDEN === '1') return true; + } catch { + // ignore + } + return collectArgv().some(hasHiddenLaunchArg); } function isHiddenLaunchCommand(cmdline) { const line = Array.isArray(cmdline) ? cmdline.map(String).join(' ') : String(cmdline || ''); - return /(?:^|[\s"'=])--hidden(?:\s|"|'|=|$)/.test(line); + return /(?:^|[\s"'=])--(?:hidden|onlykey-autostart)(?:\s|"|'|=|$)/.test(line); +} + +function localSuppressShow() { + try { + return localStorage.getItem(SUPPRESS_SHOW_KEY) === '1'; + } catch { + return false; + } +} + +/** + * Autostart must not paint the window, including when a focus event arrives + * before the suppress flag is put back. After the user asks for the window, + * only an explicit hide-to-tray keeps it hidden. + */ +function shouldKeepWindowHidden() { + if (state.userRevealedWindow) return readSuppressShow() || localSuppressShow(); + if (launchedHiddenToTray()) return true; + return readSuppressShow() || localSuppressShow(); } /** @@ -690,6 +722,7 @@ function isHiddenLaunchCommand(cmdline) { */ function applyLaunchVisibility(win) { if (!launchedHiddenToTray()) return false; + state.userRevealedWindow = false; setSuppressShow(true); if (!win) return true; try { @@ -845,21 +878,22 @@ function buildTrayMenu(autoLaunch) { checked: !!userPreferences.autoLaunch, click: function () { if (!autoLaunch) { - // Keep checkbox in sync with reality when auto-launch is unavailable. + // Keep checkbox in sync with reality when autostart is unavailable. autoLaunchMenuItem.checked = false; userPreferences.autoLaunch = false; return; } const next = !userPreferences.autoLaunch; - userPreferences.autoLaunch = next; - autoLaunch - .isEnabled() - .then((isEnabled) => { - if (isEnabled && !next) return autoLaunch.disable(); - if (!isEnabled && next) return autoLaunch.enable(); - return undefined; - }) + const op = next ? autoLaunch.enable() : autoLaunch.disable(); + op .catch(console.error) + .then(() => autoLaunch.isEnabled()) + .then((enabled) => { + userPreferences.autoLaunch = !!enabled; + }) + .catch(() => { + userPreferences.autoLaunch = next; + }) .finally(() => { // Rebuild so Linux tray picks up the new checked state. assignTrayMenu(buildTrayMenu(autoLaunch)); @@ -927,81 +961,14 @@ function buildTrayMenu(autoLaunch) { return settingsMenu; } -/** - * Login autostart used to open the window (and, on macOS, a visible login - * item). Rewrite an already-enabled entry once so the next login stays in - * the tray. Never call this from desktop unit tests — the dev execPath can - * resolve to a real HKCU Run value. - */ -async function adoptHiddenAutostart(autoLaunch, launchPath, appName) { - const marker = path.join(tmpDir(), 'autostart-hidden-synced'); - - if (osx && !fs.existsSync(marker)) { - try { - const AutoLaunch = require('auto-launch'); - const legacy = new AutoLaunch({ - name: appName, - path: launchPath, - isHidden: true, - mac: { useLaunchAgent: false }, - }); - if (await legacy.isEnabled()) { - await legacy.disable(); - await autoLaunch.enable(); - try { - fs.writeFileSync(marker, '1'); - } catch { - // ignore - } - return; - } - } catch (error) { - console.error('Legacy login-item migration failed:', error); - } - } - - let enabled = false; - try { - enabled = !!(await autoLaunch.isEnabled()); - } catch { - return; - } - - if (!enabled) { - try { - if (!fs.existsSync(marker)) fs.writeFileSync(marker, '1'); - } catch { - // ignore - } - return; - } - - if (fs.existsSync(marker)) return; - - try { - await autoLaunch.disable(); - await autoLaunch.enable(); - fs.writeFileSync(marker, '1'); - } catch (error) { - console.error('Could not update autostart to launch in the tray:', error); - try { - await autoLaunch.enable(); - } catch { - // leave the previous attempt's error - } - } -} - /** * macOS: the path a login entry must launch. * * process.execPath in NW.js's node context is a Chromium HELPER, not the app: * …/OnlyKey App.app/Contents/Frameworks/nwjs Framework.framework/Versions// * Helpers/nwjs Helper (Renderer).app/Contents/MacOS/nwjs Helper (Renderer). - * auto-launch only knows Electron's helper layout, so it kept that path - and - * named the LaunchAgent after it - and at login macOS started the renderer - * helper with --hidden instead of the app. Use the outer bundle's own - * executable (Contents/MacOS/). + * Login autostart does not launch that helper. It `open`s the outer bundle. + * This resolves the bundle's own executable (Contents/MacOS/). */ function macBundleRoot(execPath) { const match = /^(.+?\.app)(?:\/|$)/.exec(String(execPath || '')); @@ -1059,45 +1026,28 @@ async function initTray() { if (state.tray || !trayInitActive()) return; const appPath = process.execPath; - const appName = appPath.includes('node_modules') ? 'OnlyKey-dev' : 'OnlyKey'; - // Prefer the launcher wrapper when installed under /opt so login restart - // goes through the same path as the desktop entry. - let launchPath = fs.existsSync(path.join(path.dirname(appPath), 'onlykey-launch')) - ? path.join(path.dirname(appPath), 'onlykey-launch') - : appPath; - if (osx) launchPath = macLaunchPath(appPath); let autoLaunch = null; try { - const AutoLaunch = require('auto-launch'); - autoLaunch = new AutoLaunch({ - name: appName, - path: launchPath, - // --hidden on Windows/Linux; LaunchAgent ProgramArguments on macOS. - // The app honors --hidden by staying in the tray. - isHidden: true, - mac: { useLaunchAgent: true }, + autoLaunch = createLoginAutostart({ + execPath: appPath, + allowWrites: !isDesktopTestMode(), }); } catch (error) { - console.error('AutoLaunch init skipped:', error); + console.error('Autostart init skipped:', error); } - // Resolve OS auto-launch state BEFORE first menu paint. Doing it afterward and + // Resolve OS autostart state BEFORE first menu paint. Doing it afterward and // calling remove()/insert() emptied the Linux tray menu (Cinnamon/Mint). - if (autoLaunch) { + // Desktop unit tests must not read or write the developer machine's login items. + if (autoLaunch && !isDesktopTestMode()) { try { - if (!isDesktopTestMode()) { - if (osx && removeHelperLaunchAgents(appPath)) { - // Autostart was on but pointed at a helper: register the app itself. - await autoLaunch.enable(); - } - await adoptHiddenAutostart(autoLaunch, launchPath, appName); - } + await autoLaunch.repair(); if (!trayInitActive()) return; const enabled = await autoLaunch.isEnabled(); if (!trayInitActive()) return; userPreferences.autoLaunch = !!enabled; - } catch { - // auto-launch may not be supported on all platforms + } catch (error) { + console.error('Autostart repair failed:', error); } } @@ -1310,6 +1260,7 @@ module.exports = { showMainWindow, quitApp, launchedHiddenToTray, + shouldKeepWindowHidden, applyLaunchVisibility, dispatchTrayCommand, processTrayCommand, diff --git a/desktopInject.js b/desktopInject.js index 34b7d8d2..3c7a389c 100644 --- a/desktopInject.js +++ b/desktopInject.js @@ -31,12 +31,11 @@ var showWin = function () { try { - if (localStorage.getItem('onlykeySuppressShow') === '1') return; - var fs = require('fs'); - var path = require('path'); - if (fs.existsSync(path.join(nw.App.startPath, 'tmp', 'suppress-show.json'))) return; + if (desktop && typeof desktop.shouldKeepWindowHidden === 'function' && desktop.shouldKeepWindowHidden()) { + return; + } } catch { - // suppress-show probe is best-effort + // show the window if the hidden-launch check itself fails } if (win.isMinimized && win.restore) win.restore(); if (!win.isVisible) win.show(true); diff --git a/docs/desktop-tray.md b/docs/desktop-tray.md index 9c0ca282..783a436b 100644 --- a/docs/desktop-tray.md +++ b/docs/desktop-tray.md @@ -35,7 +35,7 @@ Do **not** pass `title: undefined` into `nw.Tray` — macOS draws that string ne 1. `package.json` → `window.inject_js_start: desktopInject.js` calls `desktopBg.cjs` → `start()`. 2. `window.show` is **false**. `desktopInject.js` shows the window at document-start, except an autostart launch (`--hidden`), which stays in the tray. Do not set `show` back to `true` — that flashes a window on login. 3. `start()` clears stale `tmp/tray-*`, binds close-to-tray, calls `initTray()` in the **main renderer**, polls `tmp/tray-command.json` (for tests / fallback). It also polls `~/.config/OnlyKey/app-tmp/tray-command.json`, which the Linux `onlykey-launch` wrapper writes. -4. Tray menu `click` handlers call `dispatchTrayCommand()` → `processTrayCommand()` in the same renderer. The autostart checkbox label is **Autostart to tray** (`isHidden: true`, macOS LaunchAgent). +4. Tray menu `click` handlers call `dispatchTrayCommand()` → `processTrayCommand()` in the same renderer. The autostart checkbox label is **Autostart to tray**. `loginAutostart.cjs` registers one login entry that passes `--hidden --onlykey-autostart`: Windows `HKCU\...\Run\OnlyKey` (and clears a disabled StartupApproved flag; the old value was named `nw` and Task Manager left it off), macOS LaunchAgent `to.crp.onlykey` via `open -gj` on the app bundle (legacy Login Items and helper agents are removed so Dock icons do not accumulate), Linux `~/.config/autostart/OnlyKey.desktop` through `onlykey-launch`. 5. A second launch while the window is hidden (Start menu, Dock, Applications, taskbar pin, CLI — anything that hits the same executable) is Chromium single-instance. `nw.App` `open` (Windows/Linux) and `reopen` (macOS) call `executeShowMainWindow()`. A second `--hidden` autostart does not. 6. `scripts/start-desktop.mjs` clears tray tmp files before launch and `taskkill`s stale `nw.exe` on exit. @@ -54,7 +54,7 @@ On Cinnamon/Mint/GNOME StatusNotifier, **never** mutate an already-assigned tray Correct pattern (see `buildTrayMenu` / `assignTrayMenu` in `desktopBg.cjs`): -1. Resolve auto-launch enabled state **before** first menu paint. +1. Resolve autostart enabled state **before** first menu paint. 2. Build a full `nw.Menu` with final checkbox values. 3. Assign `tray.menu` once (re-assign on Linux after ~250ms). 4. On preference toggles, **rebuild** a new menu and reassign — do not remove/insert in place. diff --git a/index.html b/index.html index ec6f2c7c..b4be3cd1 100644 --- a/index.html +++ b/index.html @@ -60,8 +60,7 @@ } var showWin = function () { try { - if (localStorage.getItem('onlykeySuppressShow') === '1') return; - if (fs.existsSync(path.join(nw.App.startPath, 'tmp', 'suppress-show.json'))) return; + if (desktopApi.shouldKeepWindowHidden && desktopApi.shouldKeepWindowHidden()) return; } catch (e) {} if (win.isMinimized && win.restore) win.restore(); if (!win.isVisible) win.show(true); diff --git a/loginAutostart.cjs b/loginAutostart.cjs new file mode 100644 index 00000000..92993dcb --- /dev/null +++ b/loginAutostart.cjs @@ -0,0 +1,831 @@ +/** + * Login autostart for the packaged app. + * + * The `auto-launch` package is not used. It renamed the Windows Run value to + * the executable (`nw`), ignored Task Manager's StartupApproved flag, and on + * macOS it launched the binary inside the bundle (and, on older builds, added + * another Login Item). That left Windows logged-in-but-not-started, and Mac + * with a visible window plus a new Dock icon for every release candidate. + * + * One entry, named for the product: + * Windows HKCU\...\Run\OnlyKey → nw.exe "" --hidden --onlykey-autostart + * plus StartupApproved enabled (02 00 …). A disabled `nw` value that + * points at this install is migrated once; a disabled `OnlyKey` + * value is left off. + * macOS ~/Library/LaunchAgents/to.crp.onlykey.plist + * runs `open -gj --args --hidden --onlykey-autostart` + * so Launch Services owns a single Dock tile. Legacy agents and + * Login Items that start this app are removed. + * Linux ~/.config/autostart/OnlyKey.desktop via onlykey-launch --hidden. + */ +'use strict'; + +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const WINDOWS_RUN = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'; +const WINDOWS_APPROVED = + 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run'; +const ENABLED_APPROVAL = new Set([0x02, 0x06, 0x08]); +const MAC_LABEL = 'to.crp.onlykey'; +const MAC_LABEL_DEV = 'to.crp.onlykey.dev'; + +const LINUX_LEGACY_DESKTOPS = new Set([ + 'nw.desktop', + 'nwjs.desktop', + 'onlykey-launch.desktop', + 'OnlyKey App.desktop', + 'OnlyKey-dev.desktop', +]); + +function registrationName(execPath) { + const normalized = String(execPath || '').replace(/\\/g, '/').toLowerCase(); + return normalized.includes('/node_modules/') ? 'OnlyKey-dev' : 'OnlyKey'; +} + +function quoteWindowsArg(value) { + return `"${String(value).replace(/"/g, '')}"`; +} + +/** Command stored in the Run key. The directory argument is the app root. */ +function windowsRunCommand(exePath) { + const exe = path.win32.normalize(String(exePath || '')); + const dir = path.win32.dirname(exe); + return `${quoteWindowsArg(exe)} ${quoteWindowsArg(dir)} --hidden --onlykey-autostart`; +} + +function normalizeWinPath(value) { + return String(value || '').replace(/\//g, '\\').toLowerCase(); +} + +function commandTargetsExe(command, exePath) { + if (!command || !exePath) return false; + return normalizeWinPath(command).includes(normalizeWinPath(exePath)); +} + +function parseApproved(value) { + if (value == null || value === '') return null; + const parts = String(value).trim().split(/[-\s]+/).filter(Boolean); + if (!parts.length || parts.some((part) => !/^[0-9a-fA-F]{2}$/.test(part))) return null; + return Buffer.from(parts.map((part) => Number.parseInt(part, 16))); +} + +/** Missing StartupApproved means Windows will run the Run value. */ +function approvalEnabled(bytes) { + if (bytes == null || !Buffer.isBuffer(bytes) || bytes.length === 0) return true; + return ENABLED_APPROVAL.has(bytes[0]); +} + +/** + * @param {{ name: string, exePath: string, runValues: object, approvals: object, force?: boolean }} opts + * force is the tray checkbox turning autostart on. Repair must not pass it: + * a StartupApproved disable of the OnlyKey value is the user (or Task Manager) + * turning that entry off, and the next launch has to leave it off. + */ +function planWindowsUpdate(opts) { + const name = opts.name; + const exePath = opts.exePath; + const run = opts.runValues || {}; + const approved = opts.approvals || {}; + const desired = windowsRunCommand(exePath); + const legacy = Object.keys(run).filter((key) => key !== name && commandTargetsExe(run[key], exePath)); + const hasCanonical = Object.prototype.hasOwnProperty.call(run, name); + const canonical = hasCanonical ? run[name] : null; + + if (opts.force) { + return { enabled: true, write: desired, enableApproved: true, deleteRun: legacy }; + } + + if (hasCanonical && !approvalEnabled(parseApproved(approved[name]))) { + return { enabled: false, write: null, enableApproved: false, deleteRun: legacy }; + } + + if (!hasCanonical && legacy.length === 0) { + return { enabled: false, write: null, enableApproved: false, deleteRun: [] }; + } + + const commandOk = canonical === desired; + const approvalOk = hasCanonical && approvalEnabled(parseApproved(approved[name])); + if (commandOk && approvalOk && legacy.length === 0) { + return { enabled: true, write: null, enableApproved: false, deleteRun: [] }; + } + + return { enabled: true, write: desired, enableApproved: true, deleteRun: legacy }; +} + +function planWindowsDisable(name, exePath, runValues) { + const run = runValues || {}; + const legacy = Object.keys(run).filter((key) => key !== name && commandTargetsExe(run[key], exePath)); + const deleteRun = Object.prototype.hasOwnProperty.call(run, name) ? [name, ...legacy] : legacy; + return { enabled: false, write: null, enableApproved: false, deleteRun }; +} + +function macBundleRoot(execPath) { + const match = /^(.+?\.app)(?:\/|$)/.exec(String(execPath || '').replace(/\\/g, '/')); + return match ? match[1] : null; +} + +function macLabelFor(execPath) { + return registrationName(execPath) === 'OnlyKey-dev' ? MAC_LABEL_DEV : MAC_LABEL; +} + +function xmlEscape(value) { + return String(value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} + +/** `open -gj` keeps Launch Services on the bundle, so Dock tiles do not multiply. */ +function macLaunchAgentPlist(bundlePath, label) { + const args = ['/usr/bin/open', '-gj', bundlePath, '--args', '--hidden', '--onlykey-autostart']; + const body = args.map((arg) => ` ${xmlEscape(arg)}`).join('\n'); + return ` + + + + Label + ${xmlEscape(label)} + ProgramArguments + +${body} + + RunAtLoad + + LimitLoadToSessionType + Aqua + + +`; +} + +function macAgentIsCurrent(text, bundlePath) { + const body = String(text || ''); + if (!body.includes('--hidden') && !body.includes('--onlykey-autostart')) return false; + if (bundlePath && !body.includes(bundlePath)) return false; + return body.includes('/usr/bin/open'); +} + +function shouldRemoveMacAgent(filename, text, opts) { + if (!filename || !String(filename).endsWith('.plist')) return false; + if (filename === `${opts.label}.plist`) return false; + const body = String(text || ''); + if (opts.devMode) return !!(opts.bundlePath && body.includes(opts.bundlePath)); + if (opts.bundlePath && body.includes(opts.bundlePath)) return true; + if (body.includes('OnlyKey App.app')) return true; + if (body.includes(MAC_LABEL)) return true; + return false; +} + +function shouldRemoveLoginItem(item, opts) { + const itemPath = String((item && item.itemPath) || ''); + if (!itemPath) return false; + if (opts.devMode) return !!(opts.bundlePath && itemPath.includes(opts.bundlePath)); + if (opts.bundlePath && itemPath.includes(opts.bundlePath)) return true; + return itemPath.includes('OnlyKey App.app'); +} + +function planMac(opts) { + const want = !!opts.canonicalText || opts.legacyNames.length > 0 || opts.loginItems.length > 0; + if (!want) { + return { + enabled: false, + write: false, + deleteAgents: opts.legacyNames, + deleteLoginItems: opts.loginItems, + }; + } + return { + enabled: true, + write: opts.canonicalText !== opts.desiredText, + deleteAgents: opts.legacyNames, + deleteLoginItems: opts.loginItems, + }; +} + +function normalizeMacPath(value) { + let text = String(value || '').trim(); + if (text.startsWith('file://')) { + text = text.slice('file://'.length); + try { + text = decodeURI(text); + } catch { + // keep the raw path + } + } + if (text.length > 1 && text.endsWith('/')) text = text.slice(0, -1); + return text; +} + +function isOnlyKeyDockTile(tile) { + if (!tile) return false; + if (tile.bundleId === MAC_LABEL || tile.bundleId === MAC_LABEL_DEV) return true; + const url = normalizeMacPath(tile.url); + if (url.includes('/OnlyKey App.app')) return true; + const label = tile.label || ''; + return (label === 'OnlyKey App' || label === 'OnlyKey') && url.includes('OnlyKey'); +} + +/** + * Collapse extra OnlyKey Dock tiles. A single live tile is left alone so a + * normal launch does not restart the Dock. + */ +function dockTilesToDrop(tiles, bundlePath, exists) { + const current = normalizeMacPath(bundlePath); + const onlyKey = []; + (tiles || []).forEach((tile, index) => { + if (!isOnlyKeyDockTile(tile)) return; + onlyKey.push({ index, path: normalizeMacPath(tile.url) }); + }); + const currentOnes = onlyKey.filter((entry) => entry.path === current); + const drop = []; + currentOnes.slice(1).forEach((entry) => drop.push(entry.index)); + for (const entry of onlyKey) { + if (entry.path === current) continue; + const missing = !entry.path || (typeof exists === 'function' && !exists(entry.path)); + if (missing || currentOnes.length > 0) drop.push(entry.index); + } + return drop; +} + +function quoteDesktopExec(value) { + const text = String(value); + if (!/[\s"\\]/.test(text)) return text; + return `"${text.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`; +} + +function linuxExecCommand(exePath, existsSync) { + const dir = path.dirname(exePath); + const wrapper = path.join(dir, 'onlykey-launch'); + if (existsSync(wrapper)) { + return `${quoteDesktopExec(wrapper)} --hidden --onlykey-autostart`; + } + return `${quoteDesktopExec(exePath)} ${quoteDesktopExec(dir)} --hidden --onlykey-autostart`; +} + +function linuxDesktopContents(execLine) { + return [ + '[Desktop Entry]', + 'Type=Application', + 'Version=1.0', + 'Name=OnlyKey', + 'Comment=OnlyKey startup script', + `Exec=${execLine}`, + 'StartupNotify=false', + 'Terminal=false', + 'X-GNOME-Autostart-enabled=true', + '', + ].join('\n'); +} + +function linuxAutostartDisabled(text) { + const body = String(text || ''); + return /^Hidden=true\s*$/m.test(body) || /^X-GNOME-Autostart-enabled=false\s*$/m.test(body); +} + +function desktopExecLine(text) { + const match = String(text || '').match(/^Exec=(.*)$/m); + return match ? match[1].trim() : ''; +} + +function shouldRemoveLinuxAutostart(filename, text, opts) { + if (filename === `${opts.name}.desktop`) return false; + if (opts.devMode) return false; + if (!LINUX_LEGACY_DESKTOPS.has(filename)) return false; + const body = String(text || ''); + return body.includes('onlykey-launch') || body.includes('/opt/OnlyKey/') || body.includes('OnlyKey'); +} + +function planLinux(opts) { + const disabled = !!(opts.canonicalText && linuxAutostartDisabled(opts.canonicalText)); + if (disabled || (!opts.canonicalText && opts.legacyNames.length === 0)) { + return { enabled: false, write: false, deleteLegacy: opts.legacyNames }; + } + const execOk = opts.canonicalText && desktopExecLine(opts.canonicalText) === desktopExecLine(opts.desiredText); + return { enabled: true, write: !execOk, deleteLegacy: opts.legacyNames }; +} + +function parseLoginItems(output) { + const items = []; + const chunks = String(output || '').split(/\r?\n---\r?\n/); + for (const chunk of chunks) { + const lines = chunk.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0); + if (lines.length < 2) continue; + items.push({ name: lines[0], itemPath: lines[1] }); + } + return items; +} + +function appleString(value) { + return `"${String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`; +} + +function defaultRun(file, args, options = {}) { + // Resolved at call time so tests can spy on child_process.spawnSync. + const runner = require('child_process').spawnSync; + return runner(file, args, { + encoding: 'utf8', + timeout: options.timeout || 20000, + windowsHide: true, + input: options.input, + env: options.env ? { ...process.env, ...options.env } : process.env, + }); +} + +function assertRan(result, label) { + if (!result || result.error) { + throw result?.error || new Error(`${label} failed`); + } + if (result.status !== 0) { + const detail = `${result.stderr || ''}\n${result.stdout || ''}`.trim(); + throw new Error(detail || `${label} exited ${result.status}`); + } + return result.stdout || ''; +} + +function powershellExe() { + const root = process.env.SystemRoot || process.env.windir || 'C:\\Windows'; + return path.join(root, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'); +} + +const READ_WINDOWS_SCRIPT = ` +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 +function Read-Key([string]$path) { + $map = @{} + $item = Get-Item -LiteralPath $path -ErrorAction SilentlyContinue + if (-not $item) { return $map } + foreach ($n in $item.GetValueNames()) { + if ([string]::IsNullOrEmpty($n)) { continue } + $v = $item.GetValue($n) + if ($v -is [byte[]]) { $map[$n] = [System.BitConverter]::ToString($v) } + else { $map[$n] = [string]$v } + } + return $map +} +@{ + run = (Read-Key $env:ONLYKEY_RUN_KEY) + approved = (Read-Key $env:ONLYKEY_APPROVED_KEY) +} | ConvertTo-Json -Compress -Depth 4 +`; + +const APPLY_WINDOWS_SCRIPT = ` +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' +$run = $env:ONLYKEY_RUN_KEY +$approved = $env:ONLYKEY_APPROVED_KEY +if ($env:ONLYKEY_WRITE_VALUE) { + if (-not (Test-Path -LiteralPath $run)) { New-Item -Path $run -Force | Out-Null } + New-ItemProperty -LiteralPath $run -Name $env:ONLYKEY_WRITE_NAME -PropertyType String -Value $env:ONLYKEY_WRITE_VALUE -Force | Out-Null +} +if ($env:ONLYKEY_ENABLE_APPROVED -eq '1') { + if (-not (Test-Path -LiteralPath $approved)) { New-Item -Path $approved -Force | Out-Null } + $bytes = [byte[]](2,0,0,0,0,0,0,0,0,0,0,0) + New-ItemProperty -LiteralPath $approved -Name $env:ONLYKEY_WRITE_NAME -PropertyType Binary -Value $bytes -Force | Out-Null +} +if ($env:ONLYKEY_DELETE_RUN) { + foreach ($n in $env:ONLYKEY_DELETE_RUN.Split('|')) { + if (-not $n) { continue } + Remove-ItemProperty -LiteralPath $run -Name $n -ErrorAction SilentlyContinue + if (Test-Path -LiteralPath $approved) { + Remove-ItemProperty -LiteralPath $approved -Name $n -ErrorAction SilentlyContinue + } + } +} +`; + +function parseJsonObject(text) { + const cleaned = String(text || '').replace(/^\uFEFF/, '').trim(); + if (!cleaned) return {}; + const parsed = JSON.parse(cleaned); + return parsed && typeof parsed === 'object' ? parsed : {}; +} + +function createLoginAutostart(options = {}) { + const execPath = options.execPath || process.execPath; + const platform = options.platform || process.platform; + const fileSystem = options.fs || fs; + const home = options.homedir || os.homedir(); + const run = options.run || defaultRun; + const allowWrites = options.allowWrites !== false; + const name = registrationName(execPath); + const devMode = name === 'OnlyKey-dev'; + const keys = { + run: (options.registry && options.registry.run) || WINDOWS_RUN, + approved: (options.registry && options.registry.approved) || WINDOWS_APPROVED, + }; + + function refuse() { + return { changed: false, enabled: false, skipped: true }; + } + + function runPowershell(script, env) { + const encoded = Buffer.from(script, 'utf16le').toString('base64'); + const stdout = assertRan( + run(powershellExe(), ['-NoProfile', '-NonInteractive', '-EncodedCommand', encoded], { env }), + 'powershell' + ); + return stdout; + } + + function readWindows() { + const stdout = runPowershell(READ_WINDOWS_SCRIPT, { + ONLYKEY_RUN_KEY: keys.run, + ONLYKEY_APPROVED_KEY: keys.approved, + }); + const parsed = parseJsonObject(stdout); + return { + run: parsed.run && typeof parsed.run === 'object' ? parsed.run : {}, + approved: parsed.approved && typeof parsed.approved === 'object' ? parsed.approved : {}, + }; + } + + function applyWindows(plan) { + const changed = !!(plan.write || plan.enableApproved || (plan.deleteRun && plan.deleteRun.length)); + if (!changed) return { changed: false, enabled: plan.enabled }; + runPowershell(APPLY_WINDOWS_SCRIPT, { + ONLYKEY_RUN_KEY: keys.run, + ONLYKEY_APPROVED_KEY: keys.approved, + ONLYKEY_WRITE_NAME: name, + ONLYKEY_WRITE_VALUE: plan.write || '', + ONLYKEY_ENABLE_APPROVED: plan.enableApproved ? '1' : '0', + ONLYKEY_DELETE_RUN: (plan.deleteRun || []).join('|'), + }); + return { changed: true, enabled: plan.enabled, removed: plan.deleteRun || [] }; + } + + function windowsEnabled(state) { + const command = state.run[name]; + if (!commandTargetsExe(command, execPath)) return false; + return approvalEnabled(parseApproved(state.approved[name])); + } + + function repairWindows() { + const state = readWindows(); + const plan = planWindowsUpdate({ + name, + exePath: execPath, + runValues: state.run, + approvals: state.approved, + }); + return applyWindows(plan); + } + + function enableWindows() { + const state = readWindows(); + return applyWindows(planWindowsUpdate({ + name, + exePath: execPath, + runValues: state.run, + approvals: state.approved, + force: true, + })); + } + + function disableWindows() { + const state = readWindows(); + return applyWindows(planWindowsDisable(name, execPath, state.run)); + } + + function readText(filePath) { + try { + if (!fileSystem.existsSync(filePath)) return ''; + return fileSystem.readFileSync(filePath, 'utf8'); + } catch { + return ''; + } + } + + function listFiles(dir) { + try { + return fileSystem.readdirSync(dir); + } catch { + return []; + } + } + + function macPaths() { + const bundlePath = macBundleRoot(execPath); + const label = macLabelFor(execPath); + const dir = path.join(home, 'Library', 'LaunchAgents'); + return { bundlePath, label, dir, canonical: path.join(dir, `${label}.plist`) }; + } + + function macLegacy(paths) { + return listFiles(paths.dir).filter((filename) => { + const text = readText(path.join(paths.dir, filename)); + return shouldRemoveMacAgent(filename, text, { + label: paths.label, + bundlePath: paths.bundlePath, + devMode, + }); + }); + } + + function listLoginItems() { + const script = [ + 'tell application "System Events"', + ' set output to ""', + ' set totalCount to count of login items', + ' repeat with i from 1 to totalCount', + ' set itemName to name of login item i', + ' set itemPath to path of login item i', + ' set output to output & itemName & linefeed & itemPath & linefeed & "---" & linefeed', + ' end repeat', + ' return output', + 'end tell', + ].join('\n'); + const stdout = assertRan(run('osascript', [], { input: script, timeout: 15000 }), 'osascript'); + const paths = macPaths(); + return parseLoginItems(stdout).filter((item) => shouldRemoveLoginItem(item, { + bundlePath: paths.bundlePath, + devMode, + })); + } + + function deleteLoginItems(items) { + for (const item of items) { + const script = `tell application "System Events" to delete login item ${appleString(item.name)}`; + try { + assertRan(run('osascript', [], { input: script, timeout: 15000 }), 'osascript'); + } catch (error) { + console.error('Could not remove login item', item.name, error); + } + } + } + + function writeCanonical(paths, text) { + fileSystem.mkdirSync(paths.dir, { recursive: true }); + fileSystem.writeFileSync(paths.canonical, text, { mode: 0o644 }); + } + + function deleteAgents(paths, names) { + for (const filename of names) { + try { + fileSystem.unlinkSync(path.join(paths.dir, filename)); + } catch (error) { + if (error && error.code !== 'ENOENT') console.error('Could not remove LaunchAgent', filename, error); + } + } + } + + function pruneDock(bundlePath) { + if (devMode || !bundlePath) return false; + const plistPath = path.join(home, 'Library', 'Preferences', 'com.apple.dock.plist'); + if (!fileSystem.existsSync(plistPath)) return false; + let raw; + try { + raw = assertRan(run('plutil', ['-convert', 'json', '-o', '-', plistPath], { timeout: 10000 }), 'plutil'); + } catch (error) { + console.error('Could not read the Dock layout:', error); + return false; + } + let data; + try { + data = JSON.parse(raw); + } catch { + return false; + } + const persistent = data['persistent-apps']; + if (!Array.isArray(persistent)) return false; + const tiles = persistent.map((entry) => { + const tile = entry && entry['tile-data'] ? entry['tile-data'] : {}; + const fileData = tile['file-data'] || {}; + return { + bundleId: tile['bundle-identifier'] || '', + label: tile['file-label'] || '', + url: fileData._CFURLString || '', + }; + }); + const drop = new Set(dockTilesToDrop(tiles, bundlePath, (target) => { + try { + return fileSystem.existsSync(target); + } catch { + return false; + } + })); + if (drop.size === 0) return false; + data['persistent-apps'] = persistent.filter((_, index) => !drop.has(index)); + const tmp = path.join(os.tmpdir(), `onlykey-dock-${process.pid}.json`); + fileSystem.writeFileSync(tmp, JSON.stringify(data)); + try { + assertRan(run('plutil', ['-convert', 'binary1', tmp], { timeout: 10000 }), 'plutil'); + fileSystem.copyFileSync(tmp, plistPath); + } catch (error) { + console.error('Could not update the Dock layout:', error); + return false; + } finally { + try { + fileSystem.unlinkSync(tmp); + } catch { + // ignore + } + } + try { + run('killall', ['Dock'], { timeout: 5000 }); + } catch (error) { + console.error('Could not restart the Dock:', error); + } + return true; + } + + function repairMac() { + const paths = macPaths(); + if (!paths.bundlePath) return { changed: false, enabled: false }; + const desired = macLaunchAgentPlist(paths.bundlePath, paths.label); + const canonicalText = readText(paths.canonical); + const legacyNames = macLegacy(paths); + let loginItems = []; + try { + loginItems = listLoginItems(); + } catch (error) { + console.error('Could not list login items:', error); + } + const plan = planMac({ + canonicalText, + desiredText: desired, + legacyNames, + loginItems, + }); + if (plan.write) writeCanonical(paths, desired); + deleteAgents(paths, plan.deleteAgents); + deleteLoginItems(plan.deleteLoginItems); + const dockChanged = pruneDock(paths.bundlePath); + const changed = !!(plan.write || plan.deleteAgents.length || plan.deleteLoginItems.length || dockChanged); + return { changed, enabled: plan.enabled, removed: plan.deleteAgents }; + } + + function enableMac() { + const paths = macPaths(); + if (!paths.bundlePath) throw new Error('macOS autostart needs the app bundle'); + const desired = macLaunchAgentPlist(paths.bundlePath, paths.label); + writeCanonical(paths, desired); + deleteAgents(paths, macLegacy(paths)); + try { + deleteLoginItems(listLoginItems()); + } catch (error) { + console.error('Could not remove login items:', error); + } + pruneDock(paths.bundlePath); + return { changed: true, enabled: true }; + } + + function disableMac() { + const paths = macPaths(); + deleteAgents(paths, [path.basename(paths.canonical), ...macLegacy(paths)]); + try { + deleteLoginItems(listLoginItems()); + } catch (error) { + console.error('Could not remove login items:', error); + } + return { changed: true, enabled: false }; + } + + function macEnabled() { + const paths = macPaths(); + if (!paths.bundlePath) return false; + return macAgentIsCurrent(readText(paths.canonical), paths.bundlePath); + } + + function linuxDir() { + return path.join(home, '.config', 'autostart'); + } + + function linuxDesired() { + const execLine = linuxExecCommand(execPath, (target) => fileSystem.existsSync(target)); + return linuxDesktopContents(execLine); + } + + function linuxLegacy() { + return listFiles(linuxDir()).filter((filename) => { + const text = readText(path.join(linuxDir(), filename)); + return shouldRemoveLinuxAutostart(filename, text, { name, devMode }); + }); + } + + function repairLinux() { + const dir = linuxDir(); + const canonical = path.join(dir, `${name}.desktop`); + const desired = linuxDesired(); + const plan = planLinux({ + canonicalText: readText(canonical), + desiredText: desired, + legacyNames: linuxLegacy(), + }); + if (plan.write) { + fileSystem.mkdirSync(dir, { recursive: true }); + fileSystem.writeFileSync(canonical, desired, { mode: 0o644 }); + } + for (const filename of plan.deleteLegacy) { + try { + fileSystem.unlinkSync(path.join(dir, filename)); + } catch (error) { + if (error && error.code !== 'ENOENT') console.error('Could not remove autostart entry', filename, error); + } + } + return { changed: !!(plan.write || plan.deleteLegacy.length), enabled: plan.enabled }; + } + + function linuxEnabled() { + const text = readText(path.join(linuxDir(), `${name}.desktop`)); + if (!text || linuxAutostartDisabled(text)) return false; + return desktopExecLine(text).includes('--hidden'); + } + + function repair() { + if (!allowWrites) return Promise.resolve(refuse()); + return Promise.resolve().then(() => { + if (platform === 'win32') return repairWindows(); + if (platform === 'darwin') return repairMac(); + if (platform === 'linux') return repairLinux(); + return refuse(); + }); + } + + function enableLinux() { + const dir = linuxDir(); + fileSystem.mkdirSync(dir, { recursive: true }); + fileSystem.writeFileSync(path.join(dir, `${name}.desktop`), linuxDesired(), { mode: 0o644 }); + for (const filename of linuxLegacy()) { + try { + fileSystem.unlinkSync(path.join(dir, filename)); + } catch (error) { + if (error && error.code !== 'ENOENT') console.error('Could not remove autostart entry', filename, error); + } + } + return { changed: true, enabled: true }; + } + + function enable() { + if (!allowWrites) return Promise.resolve(refuse()); + return Promise.resolve().then(() => { + if (platform === 'win32') return enableWindows(); + if (platform === 'darwin') return enableMac(); + if (platform === 'linux') return enableLinux(); + return refuse(); + }); + } + + function disable() { + if (!allowWrites) return Promise.resolve(refuse()); + return Promise.resolve().then(() => { + if (platform === 'win32') return disableWindows(); + if (platform === 'darwin') return disableMac(); + if (platform === 'linux') { + const dir = linuxDir(); + const names = new Set([`${name}.desktop`, ...linuxLegacy()]); + for (const filename of names) { + try { + fileSystem.unlinkSync(path.join(dir, filename)); + } catch (error) { + if (error && error.code !== 'ENOENT') console.error('Could not remove autostart entry', filename, error); + } + } + return { changed: true, enabled: false }; + } + return refuse(); + }); + } + + function isEnabled() { + if (!allowWrites) return Promise.resolve(false); + return Promise.resolve().then(() => { + if (platform === 'win32') return windowsEnabled(readWindows()); + if (platform === 'darwin') return macEnabled(); + if (platform === 'linux') return linuxEnabled(); + return false; + }); + } + + return { repair, enable, disable, isEnabled, name }; +} + +module.exports = { + createLoginAutostart, + registrationName, + windowsRunCommand, + commandTargetsExe, + parseApproved, + approvalEnabled, + planWindowsUpdate, + planWindowsDisable, + macBundleRoot, + macLabelFor, + macLaunchAgentPlist, + macAgentIsCurrent, + shouldRemoveMacAgent, + shouldRemoveLoginItem, + planMac, + parseLoginItems, + normalizeMacPath, + isOnlyKeyDockTile, + dockTilesToDrop, + linuxExecCommand, + linuxDesktopContents, + linuxAutostartDisabled, + shouldRemoveLinuxAutostart, + planLinux, + desktopExecLine, +}; diff --git a/package-lock.json b/package-lock.json index 0cea046d..a62a0486 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "6.0.0", "license": "Apache-2.0", "dependencies": { - "auto-launch": "^5.0.5", "js-sha256": "0.9.0", "openpgp": "^6.3.1", "react": "^19.2.4", @@ -2928,11 +2927,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/applescript": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/applescript/-/applescript-1.0.0.tgz", - "integrity": "sha512-yvtNHdWvtbYEiIazXAdp/NY+BBb65/DAseqlNiJQjOx9DynuzOYDbVLBJvuc0ve0VL9x6B3OHF6eH52y9hCBtQ==" - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3004,22 +2998,6 @@ "dev": true, "license": "MIT" }, - "node_modules/auto-launch": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/auto-launch/-/auto-launch-5.0.6.tgz", - "integrity": "sha512-OgxiAm4q9EBf9EeXdPBiVNENaWE3jUZofwrhAkWjHDYGezu1k3FRZHU8V2FBxGuSJOHzKmTJEd0G7L7/0xDGFA==", - "license": "MIT", - "dependencies": { - "applescript": "^1.0.0", - "mkdirp": "^0.5.1", - "path-is-absolute": "^1.0.0", - "untildify": "^3.0.2", - "winreg": "1.2.4" - }, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/axios": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", @@ -4964,6 +4942,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4992,18 +4971,6 @@ "node": ">= 18" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -5203,15 +5170,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -5872,15 +5830,6 @@ "dev": true, "license": "MIT" }, - "node_modules/untildify": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", - "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/update-browserslist-db": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", @@ -6152,12 +6101,6 @@ "node": ">=8" } }, - "node_modules/winreg": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.4.tgz", - "integrity": "sha512-IHpzORub7kYlb8A43Iig3reOvlcBJGX9gZ0WycHhghHtA65X0LYnMRuJs+aH1abVnMJztQkvQNlltnbPi5aGIA==", - "license": "BSD-2-Clause" - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index e2f7cd1e..8ae7cc95 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "node-remote": "http://localhost:5173/*", "chromium-args": "--disable-features=CalculateNativeWinOcclusion --disable-renderer-backgrounding --disable-background-timer-throttling", "dependencies": { - "auto-launch": "^5.0.5", "js-sha256": "0.9.0", "openpgp": "^6.3.1", "react": "^19.2.4", diff --git a/resources/windows/installer.nsi b/resources/windows/installer.nsi index a5646526..78d32714 100755 --- a/resources/windows/installer.nsi +++ b/resources/windows/installer.nsi @@ -103,6 +103,27 @@ Section "Install" ; Create start menu shortcut CreateShortCut "$SMPROGRAMS\${name}.lnk" "$INSTDIR\${exec}" "" "$INSTDIR\icon.ico" + + ; Migrate the old auto-launch value named "nw" (Task Manager shows that name, + ; and a disabled StartupApproved flag keeps it from running). A disabled + ; OnlyKey value is left alone. + ReadRegStr $0 HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "OnlyKey" + ${If} $0 == "" + ReadRegStr $0 HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "nw" + ${If} $0 != "" + StrCpy $1 "$INSTDIR\nw.exe" + Push $0 + Push $1 + Call StrContains + Pop $2 + ${If} $2 == "1" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "OnlyKey" "$\"$INSTDIR\nw.exe$\" $\"$INSTDIR$\" --hidden --onlykey-autostart" + WriteRegBin HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run" "OnlyKey" 020000000000000000000000 + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "nw" + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run" "nw" + ${EndIf} + ${EndIf} + ${EndIf} WriteUninstaller "${uninstaller}" @@ -117,6 +138,65 @@ SectionEnd ShowUninstDetails hide +; $0 = 1 when the string contains the substring. +; Push string, push substring, Call StrContains, Pop result. +Function StrContains + Exch $R0 + Exch + Exch $R1 + Push $R2 + Push $R3 + Push $R4 + StrLen $R2 $R0 + StrCpy $R3 0 + inst_str_loop: + StrCpy $R4 $R1 $R2 $R3 + StrCmp $R4 "" inst_str_notfound + StrCmp $R4 $R0 inst_str_found + IntOp $R3 $R3 + 1 + Goto inst_str_loop + inst_str_found: + StrCpy $R0 1 + Goto inst_str_done + inst_str_notfound: + StrCpy $R0 0 + inst_str_done: + Pop $R4 + Pop $R3 + Pop $R2 + Pop $R1 + Exch $R0 +FunctionEnd + +; Push string, push substring, Call un.StrContains, Pop result. +Function un.StrContains + Exch $R0 + Exch + Exch $R1 + Push $R2 + Push $R3 + Push $R4 + StrLen $R2 $R0 + StrCpy $R3 0 + un_str_loop: + StrCpy $R4 $R1 $R2 $R3 + StrCmp $R4 "" un_str_notfound + StrCmp $R4 $R0 un_str_found + IntOp $R3 $R3 + 1 + Goto un_str_loop + un_str_found: + StrCpy $R0 1 + Goto un_str_done + un_str_notfound: + StrCpy $R0 0 + un_str_done: + Pop $R4 + Pop $R3 + Pop $R2 + Pop $R1 + Exch $R0 +FunctionEnd + UninstallCaption "Uninstall ${productName}" UninstallText "Follow the prompts to remove ${productName} from this computer." UninstallIcon "${icon}" @@ -153,6 +233,23 @@ Section "Uninstall" DeleteRegKey HKLM "${uninstkey}" DeleteRegKey HKLM "${regkey}" + + ; Per-user login entry. Also drop the legacy value auto-launch named after nw.exe + ; when it points at this install. + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "OnlyKey" + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run" "OnlyKey" + ReadRegStr $0 HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "nw" + ${If} $0 != "" + StrCpy $1 "$INSTDIR\nw.exe" + Push $0 + Push $1 + Call un.StrContains + Pop $2 + ${If} $2 == "1" + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "nw" + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run" "nw" + ${EndIf} + ${EndIf} Delete "$SMPROGRAMS\${name}.lnk" diff --git a/scripts/release.mjs b/scripts/release.mjs index fc0bbe10..9201204d 100644 --- a/scripts/release.mjs +++ b/scripts/release.mjs @@ -180,6 +180,7 @@ export const DESKTOP_SHELL_FILES = [ 'userPreferences.cjs', 'desktopBg.cjs', 'desktopInject.js', + 'loginAutostart.cjs', ]; /** @@ -198,20 +199,20 @@ export const APP_NW_ALLOWLIST = new Set([ /** * Vite bundles react, react-dom, zustand, openpgp, and js-sha256 into dist/. - * Only these two are loaded via require() at runtime (sshpk is externalized - * in vite.config.ts; auto-launch is required by desktopBg.cjs). + * Only sshpk is loaded via require() at runtime (externalized in vite.config.ts). + * Login autostart is loginAutostart.cjs in the desktop shell; it does not use + * the auto-launch package (that package registered Windows startup as "nw" + * and piled up macOS login items). */ -export const RUNTIME_DEPS = ['sshpk', 'auto-launch']; +export const RUNTIME_DEPS = ['sshpk']; /** * Every package the RUNTIME_DEPS load, as package-lock.json `packages` keys * (`node_modules/x`, or a nested `node_modules/a/node_modules/x`). * - * Copying the two names alone shipped them WITHOUT their own dependencies: - * auto-launch needs applescript, untildify, mkdirp and winreg, so - * `require('auto-launch')` threw inside the packaged app, autostart was never - * registered, and the tray's "Autostart to tray" box could be unticked but - * never ticked again. sshpk was missing all of its own the same way. + * Copying the package name alone ships it WITHOUT its own dependencies. + * sshpk needs asn1 and the rest of its tree; a missing one throws inside the + * packaged app. * * Resolution follows node's: the nearest node_modules above the requiring * package first, then the top level. A dependency the lock does not have is diff --git a/tests/desktop/bootstrap.static.test.mjs b/tests/desktop/bootstrap.static.test.mjs index 43f85d20..1ad578d5 100644 --- a/tests/desktop/bootstrap.static.test.mjs +++ b/tests/desktop/bootstrap.static.test.mjs @@ -11,6 +11,7 @@ describe('desktop bootstrap static checks', () => { expect(html).toContain("require(require('path').join(appRoot, 'desktopBg.cjs'))"); expect(html).toContain('desktopApi.start()'); expect(html).toContain('launchedHiddenToTray'); + expect(html).toContain('shouldKeepWindowHidden'); expect(html).toContain('NW desktop bootstrap'); }); @@ -19,6 +20,7 @@ describe('desktop bootstrap static checks', () => { expect(inject).toContain('desktopBg.cjs'); expect(inject).toContain('desktop.start'); expect(inject).toContain('applyLaunchVisibility'); + expect(inject).toContain('shouldKeepWindowHidden'); }); it('desktopInject.js prefers a relative require so Finder launches still boot', () => { diff --git a/tests/desktop/desktopBg.unit.test.mjs b/tests/desktop/desktopBg.unit.test.mjs index 8b6638a2..81e07c6e 100644 --- a/tests/desktop/desktopBg.unit.test.mjs +++ b/tests/desktop/desktopBg.unit.test.mjs @@ -375,38 +375,22 @@ describe('desktopBg.cjs unit', () => { expect(win.show).not.toHaveBeenCalled(); }); - it('registers autostart as a hidden to-tray launch', async () => { - const autoLaunchPath = require.resolve('auto-launch'); - const previous = require.cache[autoLaunchPath]; - function FakeAutoLaunch(opts) { - this.opts = opts; - FakeAutoLaunch.instances.push(this); - this.isEnabled = vi.fn(() => Promise.resolve(false)); - this.enable = vi.fn(() => Promise.resolve()); - this.disable = vi.fn(() => Promise.resolve()); - } - FakeAutoLaunch.instances = []; - require.cache[autoLaunchPath] = { - id: autoLaunchPath, - filename: autoLaunchPath, - loaded: true, - exports: FakeAutoLaunch, - }; - + it('registers autostart outside the test process and stays hidden for --onlykey-autostart', async () => { + const childProcess = require('child_process'); + const spy = vi.spyOn(childProcess, 'spawnSync'); try { - createNwMock(); + createNwMock({ argv: ['--onlykey-desktop-test', '--onlykey-autostart'] }); const desktop = loadDesktopModule(); + expect(desktop.launchedHiddenToTray()).toBe(true); + expect(desktop.shouldKeepWindowHidden()).toBe(true); desktop.start(); await vi.waitFor(() => expect(desktop.getTestState().ownsTray).toBe(true), { timeout: 2000 }); - - expect(FakeAutoLaunch.instances.length).toBeGreaterThan(0); - expect(FakeAutoLaunch.instances[0].opts.isHidden).toBe(true); - expect(FakeAutoLaunch.instances[0].opts.mac).toEqual({ useLaunchAgent: true }); - expect(FakeAutoLaunch.instances[0].enable).not.toHaveBeenCalled(); - expect(FakeAutoLaunch.instances[0].disable).not.toHaveBeenCalled(); + const loginCalls = spy.mock.calls.filter((args) => /powershell|osascript|plutil|reg.exe/i.test(String(args[0]))); + expect(loginCalls).toEqual([]); + desktop.dispatchTrayCommand('show'); + expect(desktop.shouldKeepWindowHidden()).toBe(false); } finally { - if (previous) require.cache[autoLaunchPath] = previous; - else delete require.cache[autoLaunchPath]; + spy.mockRestore(); } }); diff --git a/tests/desktop/loginAutostart.unit.test.mjs b/tests/desktop/loginAutostart.unit.test.mjs new file mode 100644 index 00000000..de5cd14a --- /dev/null +++ b/tests/desktop/loginAutostart.unit.test.mjs @@ -0,0 +1,315 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { createRequire } from 'node:module'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const require = createRequire(import.meta.url); +const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); +const autostart = require(path.join(rootDir, 'loginAutostart.cjs')); + +const EXE = 'C:\\Program Files (x86)\\OnlyKey\\nw.exe'; +const BUNDLE = '/Applications/OnlyKey App.app'; +const HELPER = `${BUNDLE}/Contents/Frameworks/nwjs Framework.framework/Helpers/nwjs Helper (Renderer).app/Contents/MacOS/nwjs Helper (Renderer)`; + +function tempHome() { + return fs.mkdtempSync(path.join(os.tmpdir(), 'ok-autostart-')); +} + +describe('login autostart plans', () => { + it('builds a Windows Run command that passes the app directory and stays hidden', () => { + expect(autostart.windowsRunCommand(EXE)).toBe( + '"C:\\Program Files (x86)\\OnlyKey\\nw.exe" "C:\\Program Files (x86)\\OnlyKey" --hidden --onlykey-autostart' + ); + expect(autostart.registrationName(EXE)).toBe('OnlyKey'); + expect(autostart.registrationName('C:\\src\\OnlyKey-App\\node_modules\\nw\\nwjs\\nw.exe')).toBe('OnlyKey-dev'); + }); + + it('treats StartupApproved 01 as off and 02/06 as on', () => { + expect(autostart.approvalEnabled(null)).toBe(true); + expect(autostart.approvalEnabled(autostart.parseApproved('02-00-00-00-00-00-00-00-00-00-00-00'))).toBe(true); + expect(autostart.approvalEnabled(autostart.parseApproved('06-00-00-00-00-00-00-00-00-00-00-00'))).toBe(true); + expect(autostart.approvalEnabled(autostart.parseApproved('01-00-00-00-C9-2C-7F-F8-B8-A3-DC-01'))).toBe(false); + expect(autostart.approvalEnabled(autostart.parseApproved('03-00-00-00-00-00-00-00-00-00-00-00'))).toBe(false); + }); + + it('migrates a disabled nw Run value that points at this install', () => { + const plan = autostart.planWindowsUpdate({ + name: 'OnlyKey', + exePath: EXE, + runValues: { + OneDrive: '"C:\\Users\\me\\OneDrive.exe" /background', + nw: '"C:\\Program Files (x86)\\OnlyKey\\nw.exe" --hidden', + }, + approvals: { nw: '01-00-00-00-C9-2C-7F-F8-B8-A3-DC-01' }, + }); + expect(plan.enabled).toBe(true); + expect(plan.write).toBe(autostart.windowsRunCommand(EXE)); + expect(plan.enableApproved).toBe(true); + expect(plan.deleteRun).toEqual(['nw']); + }); + + it('does not turn a disabled OnlyKey entry back on', () => { + const command = autostart.windowsRunCommand(EXE); + const plan = autostart.planWindowsUpdate({ + name: 'OnlyKey', + exePath: EXE, + runValues: { OnlyKey: command, nw: `"${EXE}" --hidden` }, + approvals: { OnlyKey: '03-00-00-00-11-22-33-44-55-66-77-88' }, + }); + expect(plan.enabled).toBe(false); + expect(plan.write).toBeNull(); + expect(plan.deleteRun).toEqual(['nw']); + }); + + it('leaves a correct enabled entry untouched', () => { + const command = autostart.windowsRunCommand(EXE); + const plan = autostart.planWindowsUpdate({ + name: 'OnlyKey', + exePath: EXE, + runValues: { OnlyKey: command }, + approvals: { OnlyKey: '02-00-00-00-00-00-00-00-00-00-00-00' }, + }); + expect(plan).toEqual({ enabled: true, write: null, enableApproved: false, deleteRun: [] }); + }); + + it('checking the box re-enables an entry Task Manager turned off', () => { + const plan = autostart.planWindowsUpdate({ + name: 'OnlyKey', + exePath: EXE, + runValues: { OnlyKey: autostart.windowsRunCommand(EXE) }, + approvals: { OnlyKey: '01-00-00-00-00-00-00-00-00-00-00-00' }, + force: true, + }); + expect(plan.enabled).toBe(true); + expect(plan.enableApproved).toBe(true); + }); + + it('does not treat another app\'s nw Run value as ours', () => { + const plan = autostart.planWindowsUpdate({ + name: 'OnlyKey', + exePath: EXE, + runValues: { nw: '"C:\\Tools\\Other\\nw.exe" --hidden' }, + approvals: {}, + }); + expect(plan.enabled).toBe(false); + expect(plan.deleteRun).toEqual([]); + }); + + it('launches the macOS bundle through open -gj, not the helper binary', () => { + expect(autostart.macBundleRoot(HELPER)).toBe(BUNDLE); + const plist = autostart.macLaunchAgentPlist(BUNDLE, 'to.crp.onlykey'); + expect(plist).toContain('to.crp.onlykey'); + expect(plist).toContain('/usr/bin/open'); + expect(plist).toContain('-gj'); + expect(plist).toContain(`${BUNDLE}`); + expect(plist).toContain('--hidden'); + expect(plist).toContain('--onlykey-autostart'); + expect(plist).not.toContain('Helper'); + expect(autostart.macAgentIsCurrent(plist, BUNDLE)).toBe(true); + }); + + it('removes legacy Mac agents and login items that start OnlyKey, and keeps other apps', () => { + const opts = { label: 'to.crp.onlykey', bundlePath: BUNDLE, devMode: false }; + expect(autostart.shouldRemoveMacAgent('to.crp.onlykey.plist', `${BUNDLE}`, opts)).toBe(false); + expect(autostart.shouldRemoveMacAgent( + 'nwjs Helper (Renderer).plist', + `${HELPER}--hidden`, + opts + )).toBe(true); + expect(autostart.shouldRemoveMacAgent( + 'OnlyKey App.plist', + `${BUNDLE}/Contents/MacOS/OnlyKey App`, + opts + )).toBe(true); + expect(autostart.shouldRemoveMacAgent('other.plist', '/Applications/Other.app', opts)).toBe(false); + expect(autostart.shouldRemoveLoginItem({ name: 'OnlyKey App', itemPath: BUNDLE }, opts)).toBe(true); + expect(autostart.shouldRemoveLoginItem({ name: 'Mail', itemPath: '/System/Applications/Mail.app' }, opts)).toBe(false); + expect(autostart.shouldRemoveMacAgent( + 'OnlyKey App.plist', + `${BUNDLE}`, + { ...opts, devMode: true } + )).toBe(true); + expect(autostart.shouldRemoveMacAgent( + 'OnlyKey App.plist', + '/Applications/OnlyKey App.app', + { label: 'to.crp.onlykey.dev', bundlePath: '/tmp/dev/nwjs.app', devMode: true } + )).toBe(false); + }); + + it('keeps one live Dock tile and drops stale OnlyKey icons', () => { + const tiles = [ + { bundleId: 'to.crp.onlykey', label: 'OnlyKey App', url: 'file:///Applications/OnlyKey%20App.app/' }, + { bundleId: 'to.crp.onlykey', label: 'OnlyKey App', url: 'file:///Applications/OnlyKey%20App.app/' }, + { bundleId: 'com.apple.mail', label: 'Mail', url: 'file:///System/Applications/Mail.app/' }, + { bundleId: 'to.crp.onlykey', label: 'OnlyKey App', url: 'file:///Users/me/Downloads/OnlyKey%20App.app/' }, + ]; + const exists = (target) => target === BUNDLE || target === '/Users/me/Downloads/OnlyKey App.app'; + expect(autostart.dockTilesToDrop(tiles, BUNDLE, exists).sort()).toEqual([1, 3]); + expect(autostart.dockTilesToDrop([tiles[0]], BUNDLE, exists)).toEqual([]); + expect(autostart.dockTilesToDrop( + [{ bundleId: 'to.crp.onlykey', label: 'OnlyKey App', url: 'file:///missing/OnlyKey%20App.app/' }], + BUNDLE, + () => false + )).toEqual([0]); + }); + + it('parses login item listings', () => { + const items = autostart.parseLoginItems('OnlyKey App\n/Applications/OnlyKey App.app\n---\nMail\n/System/Applications/Mail.app\n---\n'); + expect(items).toEqual([ + { name: 'OnlyKey App', itemPath: '/Applications/OnlyKey App.app' }, + { name: 'Mail', itemPath: '/System/Applications/Mail.app' }, + ]); + }); + + it('writes a Linux autostart file through onlykey-launch and honors a desktop disable', async () => { + const home = tempHome(); + try { + const wrapper = path.join(home, 'opt', 'onlykey-launch'); + fs.mkdirSync(path.dirname(wrapper), { recursive: true }); + fs.writeFileSync(wrapper, '#!/bin/sh\n'); + const execLine = autostart.linuxExecCommand(path.join(home, 'opt', 'nw'), (target) => target === wrapper); + expect(execLine).toContain('--hidden --onlykey-autostart'); + expect(execLine).toContain('onlykey-launch'); + const desired = autostart.linuxDesktopContents(execLine); + expect(autostart.linuxAutostartDisabled(desired)).toBe(false); + expect(autostart.linuxAutostartDisabled('Hidden=true\n')).toBe(true); + expect(autostart.linuxAutostartDisabled('X-GNOME-Autostart-enabled=false\n')).toBe(true); + + const legacy = path.join(home, '.config', 'autostart'); + fs.mkdirSync(legacy, { recursive: true }); + fs.writeFileSync(path.join(legacy, 'nw.desktop'), '[Desktop Entry]\nExec=/opt/OnlyKey/nw --hidden\n'); + fs.writeFileSync(path.join(legacy, 'keep.desktop'), '[Desktop Entry]\nExec=/usr/bin/keep\n'); + + const auto = autostart.createLoginAutostart({ + platform: 'linux', + execPath: path.join(home, 'opt', 'nw'), + homedir: home, + fs, + }); + const result = await auto.repair(); + expect(result.enabled).toBe(true); + const canonical = fs.readFileSync(path.join(legacy, 'OnlyKey.desktop'), 'utf8'); + expect(canonical).toContain(`Exec=${execLine}`); + expect(fs.existsSync(path.join(legacy, 'nw.desktop'))).toBe(false); + expect(fs.existsSync(path.join(legacy, 'keep.desktop'))).toBe(true); + expect(await auto.isEnabled()).toBe(true); + + fs.writeFileSync(path.join(legacy, 'OnlyKey.desktop'), 'Hidden=true\nExec=/opt/OnlyKey/nw\n'); + const held = await auto.repair(); + expect(held.enabled).toBe(false); + expect(fs.readFileSync(path.join(legacy, 'OnlyKey.desktop'), 'utf8')).toContain('Hidden=true'); + } finally { + fs.rmSync(home, { recursive: true, force: true }); + } + }); +}); + +describe('macOS autostart files', () => { + const homes = []; + afterEach(() => { + for (const home of homes.splice(0)) fs.rmSync(home, { recursive: true, force: true }); + }); + + it('replaces helper agents and login items with one open -gj agent', async () => { + const home = tempHome(); + homes.push(home); + const agents = path.join(home, 'Library', 'LaunchAgents'); + fs.mkdirSync(agents, { recursive: true }); + fs.writeFileSync( + path.join(agents, 'nwjs Helper (Renderer).plist'), + `${HELPER}--hidden` + ); + fs.writeFileSync(path.join(agents, 'other.plist'), '/Applications/Other.app'); + + const calls = []; + const auto = autostart.createLoginAutostart({ + platform: 'darwin', + execPath: HELPER, + homedir: home, + fs, + run(file, args, opts) { + calls.push({ file, args, input: opts && opts.input }); + if (file === 'osascript' && opts && opts.input && opts.input.includes('count of login items')) { + return { status: 0, stdout: `OnlyKey App\n${BUNDLE}\n---\n`, stderr: '' }; + } + if (file === 'osascript') return { status: 0, stdout: '', stderr: '' }; + if (file === 'plutil' && args.includes('-o')) { + return { status: 0, stdout: JSON.stringify({ 'persistent-apps': [] }), stderr: '' }; + } + return { status: 0, stdout: '', stderr: '' }; + }, + }); + + const result = await auto.repair(); + expect(result.enabled).toBe(true); + expect(fs.existsSync(path.join(agents, 'nwjs Helper (Renderer).plist'))).toBe(false); + expect(fs.existsSync(path.join(agents, 'other.plist'))).toBe(true); + const plist = fs.readFileSync(path.join(agents, 'to.crp.onlykey.plist'), 'utf8'); + expect(plist).toContain('/usr/bin/open'); + expect(plist).toContain(BUNDLE); + expect(await auto.isEnabled()).toBe(true); + expect(calls.some((call) => call.file === 'osascript' && call.input && call.input.includes('delete login item'))).toBe(true); + expect(calls.some((call) => call.file === 'killall')).toBe(false); + }); +}); + +describe('windows autostart registry', () => { + const keyRoot = 'HKCU:\\Software\\OnlyKeyAutostartTest'; + const registry = { + run: `${keyRoot}\\Run`, + approved: `${keyRoot}\\Approved`, + }; + + afterEach(() => { + if (process.platform !== 'win32') return; + const { spawnSync } = require('child_process'); + const exe = path.join(process.env.SystemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'); + const script = `Remove-Item -LiteralPath '${keyRoot}' -Recurse -Force -ErrorAction SilentlyContinue`; + const encoded = Buffer.from(script, 'utf16le').toString('base64'); + spawnSync(exe, ['-NoProfile', '-NonInteractive', '-EncodedCommand', encoded], { windowsHide: true }); + }); + + it.skipIf(process.platform !== 'win32')('migrates a disabled nw value into an enabled OnlyKey entry', async () => { + const auto = autostart.createLoginAutostart({ + platform: 'win32', + execPath: EXE, + registry, + }); + + const { spawnSync } = require('child_process'); + const exe = path.join(process.env.SystemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'); + const plant = ` + $ErrorActionPreference = 'Stop' + New-Item -Path '${registry.run}' -Force | Out-Null + New-ItemProperty -LiteralPath '${registry.run}' -Name 'nw' -PropertyType String -Value '"${EXE}" --hidden' -Force | Out-Null + New-Item -Path '${registry.approved}' -Force | Out-Null + New-ItemProperty -LiteralPath '${registry.approved}' -Name 'nw' -PropertyType Binary -Value ([byte[]](1,0,0,0,1,2,3,4,5,6,7,8)) -Force | Out-Null + `; + const planted = spawnSync(exe, ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(plant, 'utf16le').toString('base64')], { windowsHide: true, encoding: 'utf8' }); + expect(planted.status, planted.stderr).toBe(0); + + const repaired = await auto.repair(); + expect(repaired.enabled).toBe(true); + expect(await auto.isEnabled()).toBe(true); + + const read = spawnSync(exe, ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(` + $run = Get-Item -LiteralPath '${registry.run}' + $approved = Get-Item -LiteralPath '${registry.approved}' + @{ + command = [string]$run.GetValue('OnlyKey') + nw = [string]$run.GetValue('nw') + flag = [System.BitConverter]::ToString([byte[]]$approved.GetValue('OnlyKey')) + } | ConvertTo-Json -Compress + `, 'utf16le').toString('base64')], { windowsHide: true, encoding: 'utf8' }); + expect(read.status, read.stderr).toBe(0); + const state = JSON.parse(read.stdout); + expect(state.command).toBe(autostart.windowsRunCommand(EXE)); + expect(state.nw).toBe(''); + expect(state.flag.startsWith('02-00-00-00')).toBe(true); + + await auto.disable(); + expect(await auto.isEnabled()).toBe(false); + }, 30000); +}); diff --git a/tests/desktop/release-packaging.static.test.mjs b/tests/desktop/release-packaging.static.test.mjs index 7c1b0bfc..13a6e8e4 100644 --- a/tests/desktop/release-packaging.static.test.mjs +++ b/tests/desktop/release-packaging.static.test.mjs @@ -385,12 +385,12 @@ describe('ensureOfficialNwjsApp', () => { describe('runtime node_modules in the app payload', () => { const lock = JSON.parse(fs.readFileSync(path.join(rootDir, 'package-lock.json'), 'utf8')); - it('ships what auto-launch and sshpk require, not just the two packages', () => { + it('ships what sshpk requires, not just the package itself', () => { const closure = runtimeDependencyClosure(lock).map((k) => k.replace(/^.*node_modules\//, '')); - for (const dep of ['auto-launch', 'applescript', 'untildify', 'mkdirp', 'winreg', - 'sshpk', 'asn1', 'bcrypt-pbkdf', 'ecc-jsbn', 'jsbn', 'safer-buffer', 'tweetnacl']) { + for (const dep of ['sshpk', 'asn1', 'bcrypt-pbkdf', 'ecc-jsbn', 'jsbn', 'safer-buffer', 'tweetnacl']) { expect(closure).toContain(dep); } + expect(closure).not.toContain('auto-launch'); }); it('refuses a dependency the lock does not have', () => { From 4545dd940ab23cf5b1da66f0b7cdc66b066a724b Mon Sep 17 00:00:00 2001 From: Rodney G Date: Fri, 6 Nov 2026 00:54:34 -0500 Subject: [PATCH 2/7] Explain why macOS asks to control System Events before that prompt appears. Hidden login still registers the LaunchAgent and skips that prompt until the window is open. A stored autostart off is not turned back on. Co-authored-by: Cursor --- desktopBg.cjs | 294 +++++++++++++++++++-- docs/desktop-tray.md | 4 +- loginAutostart.cjs | 85 ++++-- resources/osx/Info.plist | 2 + tests/desktop/bootstrap.static.test.mjs | 11 + tests/desktop/desktopBg.unit.test.mjs | 18 ++ tests/desktop/loginAutostart.unit.test.mjs | 67 +++++ 7 files changed, 437 insertions(+), 44 deletions(-) diff --git a/desktopBg.cjs b/desktopBg.cjs index d2ca90b2..438bc58a 100644 --- a/desktopBg.cjs +++ b/desktopBg.cjs @@ -14,6 +14,9 @@ const userPreferences = require('./userPreferences.cjs'); const { createLoginAutostart } = require('./loginAutostart.cjs'); const SUPPRESS_SHOW_KEY = 'onlykeySuppressShow'; +const LOGIN_ITEM_EXPLAINED_KEY = 'onlykey.loginItemAccessExplained'; +const LOGIN_ITEM_ACCESS_DETAIL = + 'OnlyKey uses this once to remove login items left by older versions, so the app starts once in the tray.'; function isLikelyAppRoot(dir) { try { @@ -192,6 +195,14 @@ const state = { pendingTimers: [], autoLaunch: null, userRevealedWindow: false, + macLoginItemsDeferred: false, + macLoginItemCleanupStarted: false, + macLoginItemsReady: false, + macPreferenceBeforeLoginItems: null, + autostartUserSet: false, + loginItemPromptDismissed: false, + macAutostartChain: Promise.resolve(), + loginItemExplainPromise: null, }; const osx = os.platform() === 'darwin'; @@ -201,6 +212,231 @@ function hasNw() { return typeof nw !== 'undefined' && !!nw?.Window; } +/** + * Hidden autostart must not call System Events. Login Item cleanup waits + * until the user has opened the window and seen why macOS will ask. + */ +function shouldDeferMacLoginItems(platform, hiddenLaunch, userRevealed) { + return platform === 'darwin' && !!hiddenLaunch && !userRevealed; +} + +/** + * After the note, delete leftover Login Items. Migrate them into the + * LaunchAgent only when autostart is still wanted and no agent exists yet. + * A stored "off", or a checkbox change during the note, must not be undone. + */ +function macLoginItemFollowUp(userSet, preferenceOn, agentEnabled) { + if (userSet || agentEnabled || !preferenceOn) return 'remove-items'; + return 'migrate'; +} + +function loginItemAccessExplained() { + try { + return localStorage.getItem(LOGIN_ITEM_EXPLAINED_KEY) === '1'; + } catch { + return false; + } +} + +function markLoginItemAccessExplained() { + try { + localStorage.setItem(LOGIN_ITEM_EXPLAINED_KEY, '1'); + } catch { + // ignore + } +} + +function loginItemAppleScriptDeferred() { + return osx && !state.macLoginItemsReady; +} + +function enqueueMacAutostart(task) { + const run = state.macAutostartChain.then(task, task); + state.macAutostartChain = run.then( + () => undefined, + () => undefined, + ); + return run; +} + +function presentLoginItemAccessDialog() { + return new Promise((resolve) => { + const show = () => { + if (loginItemAccessExplained()) { + resolve(true); + return; + } + if (!document.body) { + resolve(false); + return; + } + const overlay = document.createElement('div'); + overlay.setAttribute('role', 'dialog'); + overlay.setAttribute('aria-modal', 'true'); + overlay.setAttribute('aria-labelledby', 'onlykey-login-item-title'); + overlay.setAttribute('data-testid', 'login-item-access-dialog'); + overlay.style.cssText = [ + 'position:fixed', + 'inset:0', + 'z-index:400', + 'display:flex', + 'align-items:center', + 'justify-content:center', + 'padding:16px', + 'background:rgba(0,0,0,0.7)', + 'font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif', + ].join(';'); + + const card = document.createElement('div'); + card.style.cssText = [ + 'width:100%', + 'max-width:28rem', + 'background:var(--ok-surface,#2d2d2d)', + 'color:var(--ok-text,#fff)', + 'border:1px solid var(--ok-border,rgba(255,255,255,0.1))', + 'border-radius:16px', + 'padding:24px', + 'box-shadow:0 25px 50px rgba(0,0,0,0.45)', + ].join(';'); + + const title = document.createElement('h2'); + title.id = 'onlykey-login-item-title'; + title.textContent = 'Allow System Events'; + title.style.cssText = 'margin:0 0 12px;font-size:20px;font-weight:700'; + + const lead = document.createElement('p'); + lead.textContent = 'macOS will ask OnlyKey to control System Events. Choose Allow.'; + lead.style.cssText = 'margin:0 0 8px;font-size:14px;line-height:1.5;color:var(--ok-text-secondary,#d1d5db)'; + + const detail = document.createElement('p'); + detail.textContent = LOGIN_ITEM_ACCESS_DETAIL; + detail.style.cssText = 'margin:0;font-size:14px;line-height:1.5;color:var(--ok-text-muted,#a8b0bc)'; + + const actions = document.createElement('div'); + actions.style.cssText = 'display:flex;justify-content:flex-end;gap:12px;margin-top:20px'; + + const later = document.createElement('button'); + later.type = 'button'; + later.textContent = 'Not now'; + later.style.cssText = [ + 'background:transparent', + 'color:var(--ok-text,#fff)', + 'border:0', + 'border-radius:12px', + 'padding:10px 20px', + 'font-weight:600', + 'cursor:pointer', + ].join(';'); + later.addEventListener('click', () => { + state.loginItemPromptDismissed = true; + overlay.remove(); + resolve('later'); + }); + + const button = document.createElement('button'); + button.type = 'button'; + button.textContent = 'Continue'; + button.style.cssText = [ + 'background:#0056b3', + 'color:#fff', + 'border:0', + 'border-radius:12px', + 'padding:10px 20px', + 'font-weight:700', + 'cursor:pointer', + ].join(';'); + button.addEventListener('click', () => { + overlay.remove(); + resolve(true); + }); + + actions.append(later, button); + card.append(title, lead, detail, actions); + overlay.appendChild(card); + document.body.appendChild(overlay); + try { + const win = nw.Window.get(); + if (win && win.isVisible) button.focus(); + } catch { + // ignore + } + }; + + if (typeof document === 'undefined') { + resolve(false); + return; + } + if (document.body) { + show(); + return; + } + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', show, { once: true }); + return; + } + resolve(false); + }); +} + +/** Resolves true when it is safe to call System Events. */ +function explainLoginItemAccess() { + if (!osx || isDesktopTestMode()) return Promise.resolve(false); + if (loginItemAccessExplained()) return Promise.resolve(true); + if (state.loginItemExplainPromise) return state.loginItemExplainPromise; + const pending = presentLoginItemAccessDialog().then((shown) => { + if (!shown) state.loginItemExplainPromise = null; + return shown; + }); + state.loginItemExplainPromise = pending; + return pending; +} + +async function finishMacLoginItemAccess() { + let proceed = false; + try { + const answer = await explainLoginItemAccess(); + if (answer === 'later') { + state.loginItemPromptDismissed = true; + state.macLoginItemCleanupStarted = false; + return; + } + if (answer !== true) { + state.macLoginItemCleanupStarted = false; + return; + } + proceed = true; + if (state.stopped || !state.autoLaunch) return; + const followUp = macLoginItemFollowUp( + state.autostartUserSet, + state.macPreferenceBeforeLoginItems === true, + await state.autoLaunch.isEnabled(), + ); + if (followUp === 'remove-items') { + await state.autoLaunch.removeLegacyLoginItems(); + return; + } + const result = await state.autoLaunch.repair(); + if (state.autostartUserSet) return; + userPreferences.autoLaunch = !!result.enabled; + if (state.tray) assignTrayMenu(buildTrayMenu(state.autoLaunch)); + } catch (error) { + console.error('Login item cleanup failed:', error); + } finally { + if (!proceed) return; + markLoginItemAccessExplained(); + state.macLoginItemsReady = true; + } +} + +function scheduleMacLoginItemCleanup() { + if (!osx || isDesktopTestMode() || state.loginItemPromptDismissed) return; + if (state.macLoginItemCleanupStarted) return; + if (!state.autoLaunch) return; + if (state.macLoginItemsDeferred && !state.userRevealedWindow) return; + state.macLoginItemCleanupStarted = true; + enqueueMacAutostart(() => finishMacLoginItemAccess()); +} + function trayInitActive() { return !state.stopped && hasNw(); } @@ -463,6 +699,7 @@ function revealWindow(win) { } win.show(true); win.focus(); + scheduleMacLoginItemCleanup(); } function executeShowMainWindow() { @@ -884,20 +1121,24 @@ function buildTrayMenu(autoLaunch) { return; } const next = !userPreferences.autoLaunch; - const op = next ? autoLaunch.enable() : autoLaunch.disable(); - op - .catch(console.error) - .then(() => autoLaunch.isEnabled()) - .then((enabled) => { - userPreferences.autoLaunch = !!enabled; - }) - .catch(() => { - userPreferences.autoLaunch = next; - }) - .finally(() => { - // Rebuild so Linux tray picks up the new checked state. - assignTrayMenu(buildTrayMenu(autoLaunch)); - }); + state.autostartUserSet = true; + enqueueMacAutostart(() => { + const autostartOptions = loginItemAppleScriptDeferred() ? { skipLoginItems: true } : undefined; + const op = next ? autoLaunch.enable(autostartOptions) : autoLaunch.disable(autostartOptions); + return op + .catch(console.error) + .then(() => autoLaunch.isEnabled()) + .then((enabled) => { + userPreferences.autoLaunch = !!enabled; + }) + .catch(() => { + userPreferences.autoLaunch = next; + }) + .finally(() => { + // Rebuild so Linux tray picks up the new checked state. + assignTrayMenu(buildTrayMenu(autoLaunch)); + }); + }); }, }); @@ -1039,13 +1280,29 @@ async function initTray() { // Resolve OS autostart state BEFORE first menu paint. Doing it afterward and // calling remove()/insert() emptied the Linux tray menu (Cinnamon/Mint). // Desktop unit tests must not read or write the developer machine's login items. + // On Mac the note and System Events call happen after the tray exists, so + // closing the window before Continue still leaves a tray icon. + const deferMacLoginItems = shouldDeferMacLoginItems( + os.platform(), + launchedHiddenToTray(), + state.userRevealedWindow, + ); if (autoLaunch && !isDesktopTestMode()) { try { - await autoLaunch.repair(); + if (osx) { + state.macLoginItemsDeferred = deferMacLoginItems; + if (state.macPreferenceBeforeLoginItems == null && !state.autostartUserSet) { + state.macPreferenceBeforeLoginItems = !!userPreferences.autoLaunch; + } + } + await autoLaunch.repair(osx ? { skipLoginItems: true } : undefined); if (!trayInitActive()) return; - const enabled = await autoLaunch.isEnabled(); + const enabled = !!(await autoLaunch.isEnabled()); if (!trayInitActive()) return; - userPreferences.autoLaunch = !!enabled; + // Keep a stored "on" until the visible-window pass can see Login Items. + // Persisting "off" here would skip that migration if the note is dismissed. + const mayMigrate = osx && !enabled && state.macPreferenceBeforeLoginItems === true && !state.autostartUserSet; + if (!mayMigrate) userPreferences.autoLaunch = enabled; } catch (error) { console.error('Autostart repair failed:', error); } @@ -1073,6 +1330,7 @@ async function initTray() { assignTrayMenu(settingsMenu); trayLog('initTray complete', { iconPath, menuItems: state.menuLabels.length }); markTrayReady(true); + scheduleMacLoginItemCleanup(); // Re-assign once more on next tick — some Linux tray hosts register the icon // asynchronously and drop the first menu attachment. @@ -1260,6 +1518,8 @@ module.exports = { showMainWindow, quitApp, launchedHiddenToTray, + shouldDeferMacLoginItems, + macLoginItemFollowUp, shouldKeepWindowHidden, applyLaunchVisibility, dispatchTrayCommand, diff --git a/docs/desktop-tray.md b/docs/desktop-tray.md index 783a436b..2245d893 100644 --- a/docs/desktop-tray.md +++ b/docs/desktop-tray.md @@ -35,7 +35,9 @@ Do **not** pass `title: undefined` into `nw.Tray` — macOS draws that string ne 1. `package.json` → `window.inject_js_start: desktopInject.js` calls `desktopBg.cjs` → `start()`. 2. `window.show` is **false**. `desktopInject.js` shows the window at document-start, except an autostart launch (`--hidden`), which stays in the tray. Do not set `show` back to `true` — that flashes a window on login. 3. `start()` clears stale `tmp/tray-*`, binds close-to-tray, calls `initTray()` in the **main renderer**, polls `tmp/tray-command.json` (for tests / fallback). It also polls `~/.config/OnlyKey/app-tmp/tray-command.json`, which the Linux `onlykey-launch` wrapper writes. -4. Tray menu `click` handlers call `dispatchTrayCommand()` → `processTrayCommand()` in the same renderer. The autostart checkbox label is **Autostart to tray**. `loginAutostart.cjs` registers one login entry that passes `--hidden --onlykey-autostart`: Windows `HKCU\...\Run\OnlyKey` (and clears a disabled StartupApproved flag; the old value was named `nw` and Task Manager left it off), macOS LaunchAgent `to.crp.onlykey` via `open -gj` on the app bundle (legacy Login Items and helper agents are removed so Dock icons do not accumulate), Linux `~/.config/autostart/OnlyKey.desktop` through `onlykey-launch`. +4. Tray menu `click` handlers call `dispatchTrayCommand()` → `processTrayCommand()` in the same renderer. The autostart checkbox label is **Autostart to tray**. `loginAutostart.cjs` registers one login entry that passes `--hidden --onlykey-autostart`: Windows `HKCU\...\Run\OnlyKey` (and clears a disabled StartupApproved flag; the old value was named `nw` and Task Manager left it off), macOS LaunchAgent `to.crp.onlykey` via `open -gj` on the app bundle (legacy helper agents are removed so Dock icons do not accumulate; Login Items are removed later, while the window is visible), Linux `~/.config/autostart/OnlyKey.desktop` through `onlykey-launch`. + +The first time a visible window is about to ask System Events to list Login Items, the app shows a note. Continue, then choose Allow on the macOS prompt. Not now leaves those Login Items until the next time the window is opened. The tray is already up, so the note does not block it. A `--hidden --onlykey-autostart` launch does not show that note and does not run the AppleScript; the LaunchAgent is still registered. A stored autostart "off" is not turned back on. `NSAppleEventsUsageDescription` in `resources/osx/Info.plist` is the purpose line macOS can show on the prompt. 5. A second launch while the window is hidden (Start menu, Dock, Applications, taskbar pin, CLI — anything that hits the same executable) is Chromium single-instance. `nw.App` `open` (Windows/Linux) and `reopen` (macOS) call `executeShowMainWindow()`. A second `--hidden` autostart does not. 6. `scripts/start-desktop.mjs` clears tray tmp files before launch and `taskkill`s stale `nw.exe` on exit. diff --git a/loginAutostart.cjs b/loginAutostart.cjs index 92993dcb..e45131c4 100644 --- a/loginAutostart.cjs +++ b/loginAutostart.cjs @@ -14,8 +14,10 @@ * value is left off. * macOS ~/Library/LaunchAgents/to.crp.onlykey.plist * runs `open -gj --args --hidden --onlykey-autostart` - * so Launch Services owns a single Dock tile. Legacy agents and - * Login Items that start this app are removed. + * so Launch Services owns a single Dock tile. Legacy agents are + * removed immediately. Login Items are removed with System Events + * only while the window is visible. Pass skipLoginItems to leave + * them in place (a hidden autostart launch does this). * Linux ~/.config/autostart/OnlyKey.desktop via onlykey-launch --hidden. */ 'use strict'; @@ -631,18 +633,22 @@ function createLoginAutostart(options = {}) { return true; } - function repairMac() { + function readLoginItems() { + try { + return listLoginItems(); + } catch (error) { + console.error('Could not list login items:', error); + return []; + } + } + + function repairMac(opts = {}) { const paths = macPaths(); if (!paths.bundlePath) return { changed: false, enabled: false }; const desired = macLaunchAgentPlist(paths.bundlePath, paths.label); const canonicalText = readText(paths.canonical); const legacyNames = macLegacy(paths); - let loginItems = []; - try { - loginItems = listLoginItems(); - } catch (error) { - console.error('Could not list login items:', error); - } + const loginItems = opts.skipLoginItems ? [] : readLoginItems(); const plan = planMac({ canonicalText, desiredText: desired, @@ -651,36 +657,55 @@ function createLoginAutostart(options = {}) { }); if (plan.write) writeCanonical(paths, desired); deleteAgents(paths, plan.deleteAgents); - deleteLoginItems(plan.deleteLoginItems); + if (!opts.skipLoginItems) deleteLoginItems(plan.deleteLoginItems); const dockChanged = pruneDock(paths.bundlePath); const changed = !!(plan.write || plan.deleteAgents.length || plan.deleteLoginItems.length || dockChanged); return { changed, enabled: plan.enabled, removed: plan.deleteAgents }; } - function enableMac() { + function enableMac(opts = {}) { const paths = macPaths(); if (!paths.bundlePath) throw new Error('macOS autostart needs the app bundle'); const desired = macLaunchAgentPlist(paths.bundlePath, paths.label); writeCanonical(paths, desired); deleteAgents(paths, macLegacy(paths)); - try { - deleteLoginItems(listLoginItems()); - } catch (error) { - console.error('Could not remove login items:', error); + if (!opts.skipLoginItems) { + try { + deleteLoginItems(listLoginItems()); + } catch (error) { + console.error('Could not remove login items:', error); + } } pruneDock(paths.bundlePath); return { changed: true, enabled: true }; } - function disableMac() { + function disableMac(opts = {}) { const paths = macPaths(); deleteAgents(paths, [path.basename(paths.canonical), ...macLegacy(paths)]); + if (!opts.skipLoginItems) { + try { + deleteLoginItems(listLoginItems()); + } catch (error) { + console.error('Could not remove login items:', error); + } + } + return { changed: true, enabled: false }; + } + + function removeLegacyLoginItems() { + if (!allowWrites || platform !== 'darwin') return { removed: 0, skipped: true }; + const paths = macPaths(); + if (!paths.bundlePath) return { removed: 0, skipped: true }; + let loginItems = []; try { - deleteLoginItems(listLoginItems()); + loginItems = listLoginItems(); } catch (error) { - console.error('Could not remove login items:', error); + console.error('Could not list login items:', error); + return { removed: 0, failed: true }; } - return { changed: true, enabled: false }; + deleteLoginItems(loginItems); + return { removed: loginItems.length }; } function macEnabled() { @@ -734,11 +759,12 @@ function createLoginAutostart(options = {}) { return desktopExecLine(text).includes('--hidden'); } - function repair() { + function repair(options) { if (!allowWrites) return Promise.resolve(refuse()); + const skipLoginItems = !!(options && options.skipLoginItems); return Promise.resolve().then(() => { if (platform === 'win32') return repairWindows(); - if (platform === 'darwin') return repairMac(); + if (platform === 'darwin') return repairMac({ skipLoginItems }); if (platform === 'linux') return repairLinux(); return refuse(); }); @@ -758,21 +784,23 @@ function createLoginAutostart(options = {}) { return { changed: true, enabled: true }; } - function enable() { + function enable(options) { if (!allowWrites) return Promise.resolve(refuse()); + const skipLoginItems = !!(options && options.skipLoginItems); return Promise.resolve().then(() => { if (platform === 'win32') return enableWindows(); - if (platform === 'darwin') return enableMac(); + if (platform === 'darwin') return enableMac({ skipLoginItems }); if (platform === 'linux') return enableLinux(); return refuse(); }); } - function disable() { + function disable(options) { if (!allowWrites) return Promise.resolve(refuse()); + const skipLoginItems = !!(options && options.skipLoginItems); return Promise.resolve().then(() => { if (platform === 'win32') return disableWindows(); - if (platform === 'darwin') return disableMac(); + if (platform === 'darwin') return disableMac({ skipLoginItems }); if (platform === 'linux') { const dir = linuxDir(); const names = new Set([`${name}.desktop`, ...linuxLegacy()]); @@ -799,7 +827,12 @@ function createLoginAutostart(options = {}) { }); } - return { repair, enable, disable, isEnabled, name }; + function clearLegacyLoginItems() { + if (!allowWrites) return Promise.resolve({ removed: 0, skipped: true }); + return Promise.resolve().then(() => removeLegacyLoginItems()); + } + + return { repair, enable, disable, isEnabled, removeLegacyLoginItems: clearLegacyLoginItems, name }; } module.exports = { diff --git a/resources/osx/Info.plist b/resources/osx/Info.plist index 1a1979d3..f2dfe06b 100755 --- a/resources/osx/Info.plist +++ b/resources/osx/Info.plist @@ -30,5 +30,7 @@ NSApplication NSSupportsAutomaticGraphicsSwitching + NSAppleEventsUsageDescription + OnlyKey uses this once to remove login items left by older versions, so the app starts once in the tray. diff --git a/tests/desktop/bootstrap.static.test.mjs b/tests/desktop/bootstrap.static.test.mjs index 1ad578d5..67257f5f 100644 --- a/tests/desktop/bootstrap.static.test.mjs +++ b/tests/desktop/bootstrap.static.test.mjs @@ -34,6 +34,17 @@ describe('desktop bootstrap static checks', () => { expect(plist).not.toContain('com.intel.nw'); }); + it('explains the System Events prompt in the Mac plist and the desktop shell', () => { + const purpose = 'OnlyKey uses this once to remove login items left by older versions, so the app starts once in the tray.'; + const plist = fs.readFileSync(path.join(rootDir, 'resources', 'osx', 'Info.plist'), 'utf8'); + const shell = fs.readFileSync(path.join(rootDir, 'desktopBg.cjs'), 'utf8'); + expect(plist).toContain('NSAppleEventsUsageDescription'); + expect(plist).toContain(purpose); + expect(shell).toContain(purpose); + expect(shell).toContain('skipLoginItems'); + expect(shell).toContain('shouldDeferMacLoginItems'); + }); + it('linux launcher asks a running instance to show unless autostart is hidden', () => { const launch = fs.readFileSync(path.join(rootDir, 'resources', 'linux', 'onlykey-launch'), 'utf8'); expect(launch).toContain('tray-command.json'); diff --git a/tests/desktop/desktopBg.unit.test.mjs b/tests/desktop/desktopBg.unit.test.mjs index 81e07c6e..8bbc70f2 100644 --- a/tests/desktop/desktopBg.unit.test.mjs +++ b/tests/desktop/desktopBg.unit.test.mjs @@ -108,6 +108,24 @@ function loadDesktopModule() { } describe('desktopBg.cjs unit', () => { + it('defers macOS System Events only for a hidden launch before the window is shown', () => { + const desktop = loadDesktopModule(); + expect(desktop.shouldDeferMacLoginItems('darwin', true, false)).toBe(true); + expect(desktop.shouldDeferMacLoginItems('darwin', true, true)).toBe(false); + expect(desktop.shouldDeferMacLoginItems('darwin', false, false)).toBe(false); + expect(desktop.shouldDeferMacLoginItems('win32', true, false)).toBe(false); + expect(desktop.shouldDeferMacLoginItems('linux', true, false)).toBe(false); + }); + + it('does not migrate login items over an explicit autostart off', () => { + const desktop = loadDesktopModule(); + expect(desktop.macLoginItemFollowUp(false, true, false)).toBe('migrate'); + expect(desktop.macLoginItemFollowUp(false, true, true)).toBe('remove-items'); + expect(desktop.macLoginItemFollowUp(false, false, false)).toBe('remove-items'); + expect(desktop.macLoginItemFollowUp(true, true, false)).toBe('remove-items'); + expect(desktop.macLoginItemFollowUp(true, false, true)).toBe('remove-items'); + }); + beforeEach(() => { vi.useRealTimers(); vi.stubGlobal('localStorage', { diff --git a/tests/desktop/loginAutostart.unit.test.mjs b/tests/desktop/loginAutostart.unit.test.mjs index de5cd14a..3f868015 100644 --- a/tests/desktop/loginAutostart.unit.test.mjs +++ b/tests/desktop/loginAutostart.unit.test.mjs @@ -253,6 +253,73 @@ describe('macOS autostart files', () => { expect(calls.some((call) => call.file === 'osascript' && call.input && call.input.includes('delete login item'))).toBe(true); expect(calls.some((call) => call.file === 'killall')).toBe(false); }); + + it('skips System Events on a deferred repair and still replaces helper agents', async () => { + const home = tempHome(); + homes.push(home); + const agents = path.join(home, 'Library', 'LaunchAgents'); + fs.mkdirSync(agents, { recursive: true }); + fs.writeFileSync( + path.join(agents, 'nwjs Helper (Renderer).plist'), + `${HELPER}--hidden` + ); + + const calls = []; + const auto = autostart.createLoginAutostart({ + platform: 'darwin', + execPath: HELPER, + homedir: home, + fs, + run(file, args, opts) { + calls.push({ file, args, input: opts && opts.input }); + if (file === 'plutil' && args.includes('-o')) { + return { status: 0, stdout: JSON.stringify({ 'persistent-apps': [] }), stderr: '' }; + } + return { status: 0, stdout: '', stderr: '' }; + }, + }); + + const result = await auto.repair({ skipLoginItems: true }); + expect(result.enabled).toBe(true); + expect(fs.existsSync(path.join(agents, 'nwjs Helper (Renderer).plist'))).toBe(false); + expect(fs.readFileSync(path.join(agents, 'to.crp.onlykey.plist'), 'utf8')).toContain('/usr/bin/open'); + expect(calls.some((call) => call.file === 'osascript')).toBe(false); + expect(await auto.isEnabled()).toBe(true); + + await auto.disable({ skipLoginItems: true }); + expect(fs.existsSync(path.join(agents, 'to.crp.onlykey.plist'))).toBe(false); + expect(calls.some((call) => call.file === 'osascript')).toBe(false); + }); + + it('removes legacy login items without rewriting the LaunchAgent', async () => { + const home = tempHome(); + homes.push(home); + const agents = path.join(home, 'Library', 'LaunchAgents'); + fs.mkdirSync(agents, { recursive: true }); + const plist = autostart.macLaunchAgentPlist(BUNDLE, 'to.crp.onlykey'); + fs.writeFileSync(path.join(agents, 'to.crp.onlykey.plist'), plist); + + const calls = []; + const auto = autostart.createLoginAutostart({ + platform: 'darwin', + execPath: HELPER, + homedir: home, + fs, + run(file, args, opts) { + calls.push({ file, input: opts && opts.input }); + if (file === 'osascript' && opts && opts.input && opts.input.includes('count of login items')) { + return { status: 0, stdout: `OnlyKey App\n${BUNDLE}\n---\n`, stderr: '' }; + } + return { status: 0, stdout: '', stderr: '' }; + }, + }); + + const removed = await auto.removeLegacyLoginItems(); + expect(removed.removed).toBe(1); + expect(fs.readFileSync(path.join(agents, 'to.crp.onlykey.plist'), 'utf8')).toBe(plist); + expect(calls.some((call) => call.file === 'osascript' && call.input && call.input.includes('delete login item'))).toBe(true); + expect(calls.some((call) => call.file === 'plutil')).toBe(false); + }); }); describe('windows autostart registry', () => { From 19336f0f79914c70f9aaa46ea39bf265f4fd9397 Mon Sep 17 00:00:00 2001 From: Rodney G Date: Fri, 6 Nov 2026 21:55:36 -0500 Subject: [PATCH 3/7] Clear the release npm audit by moving dev tools onto patched releases. The app dependencies were already clean. npm run release audits the full tree, including vitest, happy-dom, and the nw, wait-on, and ESLint tooling. Co-authored-by: Cursor --- package-lock.json | 172 ++++++++++++++++++++++++---------------------- package.json | 14 ++-- 2 files changed, 98 insertions(+), 88 deletions(-) diff --git a/package-lock.json b/package-lock.json index a62a0486..7a4e6687 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,19 +27,19 @@ "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.4", - "@vitest/coverage-v8": "^4.1.11", - "@vitest/ui": "^4.1.11", + "@vitest/coverage-v8": "^4.1.12", + "@vitest/ui": "^4.1.12", "concurrently": "^9.2.1", "eslint": "^9.39.5", "eslint-plugin-react-hooks": "^7.1.1", "globals": "^17.11.0", - "happy-dom": "^20.10.6", + "happy-dom": "^21.0.4", "nw": "0.104.1", "tailwindcss": "^4.2.1", "typescript": "^5.9.3", "typescript-eslint": "^8.67.0", "vite": "^7.3.6", - "vitest": "^4.1.11", + "vitest": "^4.1.12", "wait-on": "^9.0.4" } }, @@ -1119,6 +1119,13 @@ "node": ">=18.0.0" } }, + "node_modules/@isaacs/nonrecursive-glob-match": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@isaacs/nonrecursive-glob-match/-/nonrecursive-glob-match-1.1.0.tgz", + "integrity": "sha512-GPm/Dbvk5RLmhRnxXIa7nTpK0xtl4E0wj0MslsuNiJ1FIROU+2+9lgUC/dgF7Ey4QCWhfEsgxilMRMCdHPfDfw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -2353,9 +2360,9 @@ "license": "MIT" }, "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==", "dev": true, "license": "MIT", "dependencies": { @@ -2575,16 +2582,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-11.0.5.tgz", + "integrity": "sha512-snPlVppFcYWfLLFQGhDxUyBcHNtMlWmn3jtj7pEaO+DP8pqL0IvAzXhMYmn1z9ASNtgmwLBdFxSf4tMUB0OTKA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.8" + "@isaacs/nonrecursive-glob-match": "^1.1.0", + "brace-expansion": "^5.0.12" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2680,15 +2688,15 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.11.tgz", - "integrity": "sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.12.tgz", + "integrity": "sha512-6S7/iVknKglLAxMFxQGNppBI0iqJS2NgM6FBxavitj7KaDq/vywOWGf6dGu7F2L1Qy0G6S3uTp7aGshHrzK8JA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.11", + "@vitest/utils": "4.1.12", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -2702,8 +2710,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.11", - "vitest": "4.1.11" + "@vitest/browser": "4.1.12", + "vitest": "4.1.12" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2712,16 +2720,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", - "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.12.tgz", + "integrity": "sha512-vFlTt+YEu9WBpNjdGayNmlMVzrWPi1+sv853/7t2FU7hpO5gAFBKHzQmkCq5Xjpr+evegF2994LQT3hF3mRlqQ==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", + "@vitest/spy": "4.1.12", + "@vitest/utils": "4.1.12", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -2730,13 +2738,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", - "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.12.tgz", + "integrity": "sha512-fKShNpKZzupWZuY0z7egydvL0sqW4INEZBEExMBMAa6izlENEVHwoXgdaTCaBZ5fwlr4xf2o1wXC83n9RpO+NA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.11", + "@vitest/spy": "4.1.12", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -2757,9 +2765,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", - "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.12.tgz", + "integrity": "sha512-XW9O8+Mxx4VpCtkYdMJLZQQwsVryP9wk7cPKDxtXFfwSak6yRh5+2fep/Y0XF/ElBvJHq8bcYdi6aeuai0opZA==", "dev": true, "license": "MIT", "dependencies": { @@ -2770,13 +2778,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", - "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.12.tgz", + "integrity": "sha512-NKlVKCr5QnCe8jijdMnmkpHHU+WTqiLFR6G0pHji9uRjXK9xJl1k1nTw6F3pOOj2PDcsDt2GKQMI7wIsaT491w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.11", + "@vitest/utils": "4.1.12", "pathe": "^2.0.3" }, "funding": { @@ -2784,14 +2792,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", - "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.12.tgz", + "integrity": "sha512-ko1y1Qgb/slzQ9mr+b89Cuqud8WG1TKtsPD8r7ZUBdvVOGt85Af2HwCqHujrhwJq95hs/9RM7fcAPg57o1vlzw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.11", - "@vitest/utils": "4.1.11", + "@vitest/pretty-format": "4.1.12", + "@vitest/utils": "4.1.12", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -2800,9 +2808,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", - "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.12.tgz", + "integrity": "sha512-RGq9x4JNGNTZHEJxvN0oSiJjeSCExm0n2gZKKJZHY9cX5AVA6M7VJILBT3ZJ/tJ/0J9uWT7fETV9/btoi//vSg==", "dev": true, "license": "MIT", "funding": { @@ -2810,14 +2818,14 @@ } }, "node_modules/@vitest/ui": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.11.tgz", - "integrity": "sha512-r/rwyKoev21mWdRGSEkZOqkQ2BYy68mwjihg9M90nNRbf4NGrgzZ4cj6JNCEwlOGJkbKeMgsjlykvwKUbRr7gw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.12.tgz", + "integrity": "sha512-eYUTHtsp52Fu6YGUw9T9173bba7YUr9j/Xj9CdhUaOb8AppLKcknHUaiZQeqJ8MiJ8C2VyKiZuUGSKdvPIKQKA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@vitest/utils": "4.1.11", + "@vitest/utils": "4.1.12", "fflate": "^0.8.2", "flatted": "^3.4.2", "pathe": "^2.0.3", @@ -2829,17 +2837,17 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "4.1.11" + "vitest": "4.1.12" } }, "node_modules/@vitest/utils": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", - "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.12.tgz", + "integrity": "sha512-Q3awXrwOfEV+qc3WmO5ONyE0yO7Q1gQ9/oyHxGLZ/pl7N3NO/ZztqQwKLuwMkV+CA1JYSIeO3gkHnRDIo4AVaA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.11", + "@vitest/pretty-format": "4.1.12", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -3910,9 +3918,9 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.1.tgz", - "integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.2.tgz", + "integrity": "sha512-/kVP2U8z596Ia0gwFrwyIeo74ta5aVmSeU1FfAUMAmcEP5EP7zydmag3SLiN64NaRKef3lxsq7i/g7ltJQDGow==", "dev": true, "funding": [ { @@ -4104,9 +4112,9 @@ "license": "ISC" }, "node_modules/happy-dom": { - "version": "20.11.0", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.11.0.tgz", - "integrity": "sha512-XogN4asPd1a56di9prVG6bZxteNcXsZxxKmAvcEfc5Px5Ca2hMyMgk8wvqK2K1V8zXg40j9VANXsDaJYh9DeNA==", + "version": "21.0.4", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-21.0.4.tgz", + "integrity": "sha512-tFm+GaLBgo6o0awHnvHaNoraVo27qJ+2tZEtZ+cIa08hXbeitrxAJajLI05hsUy/2/YTKcUjQYzHe5mCcraLBw==", "dev": true, "license": "MIT", "peer": true, @@ -4389,9 +4397,9 @@ } }, "node_modules/joi": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-18.3.0.tgz", - "integrity": "sha512-/RwAK4BAfzcdbdFg073tzq4/xhY6Q7lMd5AmjqTL6pu0QFJdEmtswgm8BvgwXvOKuPNYQ8XrdQrrW/gF65LiNw==", + "version": "18.3.2", + "resolved": "https://registry.npmjs.org/joi/-/joi-18.3.2.tgz", + "integrity": "sha512-iUivApKYw5GpZmq2Sy6El99j7noo8QSUFZlyQtr3HCkosC/b5wKF6/Y3teiHIlaM8WI38sAT3P+51rB0lhAhJA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5948,20 +5956,20 @@ } }, "node_modules/vitest": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", - "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.12.tgz", + "integrity": "sha512-5eWdz2yb3ajxWF3C8C4TjkXl7FGm3n2E/VlazALrQHzK7nYMSGFnCZto0bbPNl77AwkOZVLk0YnBAejUu0NiWA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@vitest/expect": "4.1.11", - "@vitest/mocker": "4.1.11", - "@vitest/pretty-format": "4.1.11", - "@vitest/runner": "4.1.11", - "@vitest/snapshot": "4.1.11", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", + "@vitest/expect": "4.1.12", + "@vitest/mocker": "4.1.12", + "@vitest/pretty-format": "4.1.12", + "@vitest/runner": "4.1.12", + "@vitest/snapshot": "4.1.12", + "@vitest/spy": "4.1.12", + "@vitest/utils": "4.1.12", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -5989,12 +5997,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.11", - "@vitest/browser-preview": "4.1.11", - "@vitest/browser-webdriverio": "4.1.11", - "@vitest/coverage-istanbul": "4.1.11", - "@vitest/coverage-v8": "4.1.11", - "@vitest/ui": "4.1.11", + "@vitest/browser-playwright": "4.1.12", + "@vitest/browser-preview": "4.1.12", + "@vitest/browser-webdriverio": "^4.1.11", + "@vitest/coverage-istanbul": "4.1.12", + "@vitest/coverage-v8": "4.1.12", + "@vitest/ui": "4.1.12", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -6130,9 +6138,9 @@ } }, "node_modules/ws": { - "version": "8.21.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", - "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "version": "8.22.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.1.tgz", + "integrity": "sha512-oyL6OPYme/CmiFvNuGzeoxCJ4nOrZE2O1FwM9MWM1AsGri6nLXB2pg+lNA8aLeXMSav0J6eIVS1j6vUPzUtK9g==", "dev": true, "license": "MIT", "engines": { @@ -6226,9 +6234,9 @@ } }, "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", "dev": true, "license": "MIT", "peer": true, diff --git a/package.json b/package.json index 8ae7cc95..03e244ae 100644 --- a/package.json +++ b/package.json @@ -77,25 +77,25 @@ "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.4", - "@vitest/coverage-v8": "^4.1.11", - "@vitest/ui": "^4.1.11", + "@vitest/coverage-v8": "^4.1.12", + "@vitest/ui": "^4.1.12", "concurrently": "^9.2.1", "eslint": "^9.39.5", "eslint-plugin-react-hooks": "^7.1.1", "globals": "^17.11.0", - "happy-dom": "^20.10.6", + "happy-dom": "^21.0.4", "nw": "0.104.1", "tailwindcss": "^4.2.1", "typescript": "^5.9.3", "typescript-eslint": "^8.67.0", "vite": "^7.3.6", - "vitest": "^4.1.11", + "vitest": "^4.1.12", "wait-on": "^9.0.4" }, "overrides": { "axios": "^1.18.1", "form-data": "^4.0.6", - "follow-redirects": "^1.16.0", + "follow-redirects": "^1.16.2", "lodash": "^4.18.1", "tar": "^7.5.22", "shell-quote": "^1.10.0", @@ -104,7 +104,9 @@ "nanoid": "^3.3.18", "picomatch": "^4.0.5", "@babel/core": "^7.29.7", - "joi": "^18.2.3" + "joi": "^18.3.1", + "minimatch@>=7.0.0 <11": "^11.0.1", + "zod": "^4.6.6" }, "repository": { "type": "git", From 0d722fabe7264fdb6b3116e2d53804b9fd662a30 Mon Sep 17 00:00:00 2001 From: Rodney G Date: Fri, 6 Nov 2026 21:55:36 -0500 Subject: [PATCH 4/7] Stop the System Events cleanup from returning inside a finally block. That return failed lint and could discard the cleanup result. Co-authored-by: Cursor --- desktopBg.cjs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/desktopBg.cjs b/desktopBg.cjs index 438bc58a..a1f495db 100644 --- a/desktopBg.cjs +++ b/desktopBg.cjs @@ -422,9 +422,10 @@ async function finishMacLoginItemAccess() { } catch (error) { console.error('Login item cleanup failed:', error); } finally { - if (!proceed) return; - markLoginItemAccessExplained(); - state.macLoginItemsReady = true; + if (proceed) { + markLoginItemAccessExplained(); + state.macLoginItemsReady = true; + } } } From 8e9be98c582f097772050e3a76dc47cbc81cd101 Mon Sep 17 00:00:00 2001 From: Rodney G Date: Fri, 6 Nov 2026 22:02:33 -0500 Subject: [PATCH 5/7] Show the System Events note with the shared confirm dialog. The desktop shell only waits for Continue or Not now. It no longer builds its own overlay. Co-authored-by: Cursor --- desktopBg.cjs | 136 +++--------------- docs/desktop-tray.md | 2 +- src/App.tsx | 2 + src/components/LoginItemAccessHost.tsx | 61 ++++++++ .../__tests__/LoginItemAccessHost.ui.test.tsx | 36 +++++ tests/desktop/bootstrap.static.test.mjs | 8 +- 6 files changed, 127 insertions(+), 118 deletions(-) create mode 100644 src/components/LoginItemAccessHost.tsx create mode 100644 src/components/__tests__/LoginItemAccessHost.ui.test.tsx diff --git a/desktopBg.cjs b/desktopBg.cjs index a1f495db..99be5864 100644 --- a/desktopBg.cjs +++ b/desktopBg.cjs @@ -15,8 +15,7 @@ const { createLoginAutostart } = require('./loginAutostart.cjs'); const SUPPRESS_SHOW_KEY = 'onlykeySuppressShow'; const LOGIN_ITEM_EXPLAINED_KEY = 'onlykey.loginItemAccessExplained'; -const LOGIN_ITEM_ACCESS_DETAIL = - 'OnlyKey uses this once to remove login items left by older versions, so the app starts once in the tray.'; +const LOGIN_ITEM_UI_READY = 'onlykey-login-item-ui-ready'; function isLikelyAppRoot(dir) { try { @@ -259,122 +258,29 @@ function enqueueMacAutostart(task) { return run; } -function presentLoginItemAccessDialog() { +/** The React confirm dialog owns the UI. This only waits for its answer. */ +function requestLoginItemDecision() { return new Promise((resolve) => { - const show = () => { - if (loginItemAccessExplained()) { - resolve(true); - return; - } - if (!document.body) { - resolve(false); - return; - } - const overlay = document.createElement('div'); - overlay.setAttribute('role', 'dialog'); - overlay.setAttribute('aria-modal', 'true'); - overlay.setAttribute('aria-labelledby', 'onlykey-login-item-title'); - overlay.setAttribute('data-testid', 'login-item-access-dialog'); - overlay.style.cssText = [ - 'position:fixed', - 'inset:0', - 'z-index:400', - 'display:flex', - 'align-items:center', - 'justify-content:center', - 'padding:16px', - 'background:rgba(0,0,0,0.7)', - 'font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif', - ].join(';'); - - const card = document.createElement('div'); - card.style.cssText = [ - 'width:100%', - 'max-width:28rem', - 'background:var(--ok-surface,#2d2d2d)', - 'color:var(--ok-text,#fff)', - 'border:1px solid var(--ok-border,rgba(255,255,255,0.1))', - 'border-radius:16px', - 'padding:24px', - 'box-shadow:0 25px 50px rgba(0,0,0,0.45)', - ].join(';'); - - const title = document.createElement('h2'); - title.id = 'onlykey-login-item-title'; - title.textContent = 'Allow System Events'; - title.style.cssText = 'margin:0 0 12px;font-size:20px;font-weight:700'; - - const lead = document.createElement('p'); - lead.textContent = 'macOS will ask OnlyKey to control System Events. Choose Allow.'; - lead.style.cssText = 'margin:0 0 8px;font-size:14px;line-height:1.5;color:var(--ok-text-secondary,#d1d5db)'; - - const detail = document.createElement('p'); - detail.textContent = LOGIN_ITEM_ACCESS_DETAIL; - detail.style.cssText = 'margin:0;font-size:14px;line-height:1.5;color:var(--ok-text-muted,#a8b0bc)'; - - const actions = document.createElement('div'); - actions.style.cssText = 'display:flex;justify-content:flex-end;gap:12px;margin-top:20px'; - - const later = document.createElement('button'); - later.type = 'button'; - later.textContent = 'Not now'; - later.style.cssText = [ - 'background:transparent', - 'color:var(--ok-text,#fff)', - 'border:0', - 'border-radius:12px', - 'padding:10px 20px', - 'font-weight:600', - 'cursor:pointer', - ].join(';'); - later.addEventListener('click', () => { - state.loginItemPromptDismissed = true; - overlay.remove(); - resolve('later'); - }); - - const button = document.createElement('button'); - button.type = 'button'; - button.textContent = 'Continue'; - button.style.cssText = [ - 'background:#0056b3', - 'color:#fff', - 'border:0', - 'border-radius:12px', - 'padding:10px 20px', - 'font-weight:700', - 'cursor:pointer', - ].join(';'); - button.addEventListener('click', () => { - overlay.remove(); - resolve(true); - }); - - actions.append(later, button); - card.append(title, lead, detail, actions); - overlay.appendChild(card); - document.body.appendChild(overlay); - try { - const win = nw.Window.get(); - if (win && win.isVisible) button.focus(); - } catch { - // ignore - } - }; - - if (typeof document === 'undefined') { + if (typeof window === 'undefined') { resolve(false); return; } - if (document.body) { - show(); - return; - } - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', show, { once: true }); - return; - } - resolve(false); + let settled = false; + const deliver = () => { + if (settled) return true; + const ask = window.onlykeyAskLoginItemAccess; + if (typeof ask !== 'function') return false; + settled = true; + Promise.resolve(ask()).then(resolve, () => resolve(false)); + return true; + }; + if (deliver()) return; + const onReady = () => { + window.removeEventListener(LOGIN_ITEM_UI_READY, onReady); + if (!deliver()) resolve(false); + }; + window.addEventListener(LOGIN_ITEM_UI_READY, onReady, { once: true }); + if (deliver()) window.removeEventListener(LOGIN_ITEM_UI_READY, onReady); }); } @@ -383,7 +289,7 @@ function explainLoginItemAccess() { if (!osx || isDesktopTestMode()) return Promise.resolve(false); if (loginItemAccessExplained()) return Promise.resolve(true); if (state.loginItemExplainPromise) return state.loginItemExplainPromise; - const pending = presentLoginItemAccessDialog().then((shown) => { + const pending = requestLoginItemDecision().then((shown) => { if (!shown) state.loginItemExplainPromise = null; return shown; }); diff --git a/docs/desktop-tray.md b/docs/desktop-tray.md index 2245d893..4c7e46f3 100644 --- a/docs/desktop-tray.md +++ b/docs/desktop-tray.md @@ -37,7 +37,7 @@ Do **not** pass `title: undefined` into `nw.Tray` — macOS draws that string ne 3. `start()` clears stale `tmp/tray-*`, binds close-to-tray, calls `initTray()` in the **main renderer**, polls `tmp/tray-command.json` (for tests / fallback). It also polls `~/.config/OnlyKey/app-tmp/tray-command.json`, which the Linux `onlykey-launch` wrapper writes. 4. Tray menu `click` handlers call `dispatchTrayCommand()` → `processTrayCommand()` in the same renderer. The autostart checkbox label is **Autostart to tray**. `loginAutostart.cjs` registers one login entry that passes `--hidden --onlykey-autostart`: Windows `HKCU\...\Run\OnlyKey` (and clears a disabled StartupApproved flag; the old value was named `nw` and Task Manager left it off), macOS LaunchAgent `to.crp.onlykey` via `open -gj` on the app bundle (legacy helper agents are removed so Dock icons do not accumulate; Login Items are removed later, while the window is visible), Linux `~/.config/autostart/OnlyKey.desktop` through `onlykey-launch`. -The first time a visible window is about to ask System Events to list Login Items, the app shows a note. Continue, then choose Allow on the macOS prompt. Not now leaves those Login Items until the next time the window is opened. The tray is already up, so the note does not block it. A `--hidden --onlykey-autostart` launch does not show that note and does not run the AppleScript; the LaunchAgent is still registered. A stored autostart "off" is not turned back on. `NSAppleEventsUsageDescription` in `resources/osx/Info.plist` is the purpose line macOS can show on the prompt. +The first time a visible window is about to ask System Events to list Login Items, `LoginItemAccessHost` shows the shared confirm dialog. The desktop shell only waits for Continue or Not now. Continue, then choose Allow on the macOS prompt. Not now leaves those Login Items until the next time the window is opened. The tray is already up, so the note does not block it. A `--hidden --onlykey-autostart` launch does not show that note and does not run the AppleScript; the LaunchAgent is still registered. A stored autostart "off" is not turned back on. `NSAppleEventsUsageDescription` in `resources/osx/Info.plist` is the purpose line macOS can show on the prompt. 5. A second launch while the window is hidden (Start menu, Dock, Applications, taskbar pin, CLI — anything that hits the same executable) is Chromium single-instance. `nw.App` `open` (Windows/Linux) and `reopen` (macOS) call `executeShowMainWindow()`. A second `--hidden` autostart does not. 6. `scripts/start-desktop.mjs` clears tray tmp files before launch and `taskkill`s stale `nw.exe` on exit. diff --git a/src/App.tsx b/src/App.tsx index bc3e969d..c4eb0b5b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -15,6 +15,7 @@ import DeviceDialogs from './components/DeviceDialogs'; import WorkingDialog from './components/dialogs/WorkingDialog'; import AppUpdateHost from './components/AppUpdateHost'; import FirmwareUpdateHost from './components/FirmwareUpdateHost'; +import LoginItemAccessHost from './components/LoginItemAccessHost'; import ThemeToggle from './components/ThemeToggle'; import DeviceMessages from './components/DeviceMessages'; import { HelpTip } from './components/ui/HelpTip'; @@ -54,6 +55,7 @@ const App: React.FC = () => { {/* Outside sessionEpoch so a lock/unplug does not abort an in-flight download. */} +
diff --git a/src/components/LoginItemAccessHost.tsx b/src/components/LoginItemAccessHost.tsx new file mode 100644 index 00000000..aca19a24 --- /dev/null +++ b/src/components/LoginItemAccessHost.tsx @@ -0,0 +1,61 @@ +import React, { useEffect, useRef, useState } from 'react'; +import ConfirmDialog from './dialogs/ConfirmDialog'; + +/** Same sentence as NSAppleEventsUsageDescription in resources/osx/Info.plist. */ +export const LOGIN_ITEM_ACCESS_PURPOSE = + 'OnlyKey uses this once to remove login items left by older versions, so the app starts once in the tray.'; + +const LOGIN_ITEM_ACCESS_MESSAGE = `macOS will ask OnlyKey to control System Events. Choose Allow. ${LOGIN_ITEM_ACCESS_PURPOSE}`; + +export const LOGIN_ITEM_UI_READY = 'onlykey-login-item-ui-ready'; + +export type LoginItemAccessAnswer = true | 'later'; + +declare global { + interface Window { + onlykeyAskLoginItemAccess?: () => Promise; + } +} + +/** + * macOS asks the desktop shell for this answer before it talks to System Events. + * The shell does not build UI; it calls window.onlykeyAskLoginItemAccess. + */ +const LoginItemAccessHost: React.FC = () => { + const [open, setOpen] = useState(false); + const resolveRef = useRef<((answer: LoginItemAccessAnswer) => void) | null>(null); + + useEffect(() => { + window.onlykeyAskLoginItemAccess = () => + new Promise((resolve) => { + resolveRef.current = resolve; + setOpen(true); + }); + window.dispatchEvent(new Event(LOGIN_ITEM_UI_READY)); + return () => { + delete window.onlykeyAskLoginItemAccess; + resolveRef.current = null; + }; + }, []); + + const answer = (value: LoginItemAccessAnswer) => { + setOpen(false); + const resolve = resolveRef.current; + resolveRef.current = null; + resolve?.(value); + }; + + return ( + answer(true)} + onCancel={() => answer('later')} + /> + ); +}; + +export default LoginItemAccessHost; diff --git a/src/components/__tests__/LoginItemAccessHost.ui.test.tsx b/src/components/__tests__/LoginItemAccessHost.ui.test.tsx new file mode 100644 index 00000000..ab26eb8d --- /dev/null +++ b/src/components/__tests__/LoginItemAccessHost.ui.test.tsx @@ -0,0 +1,36 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import LoginItemAccessHost from '../LoginItemAccessHost'; +import { renderWithProviders } from '../../test/render'; + +describe('LoginItemAccessHost', () => { + afterEach(() => { + delete window.onlykeyAskLoginItemAccess; + }); + + it('stays closed until the desktop shell asks', () => { + renderWithProviders(); + expect(screen.queryByRole('heading', { name: 'Allow System Events' })).not.toBeInTheDocument(); + expect(typeof window.onlykeyAskLoginItemAccess).toBe('function'); + }); + + it('resolves Continue and Not now for the desktop shell', async () => { + const user = userEvent.setup(); + renderWithProviders(); + const ask = window.onlykeyAskLoginItemAccess; + expect(ask).toBeTypeOf('function'); + + const first = ask!(); + expect(await screen.findByRole('heading', { name: 'Allow System Events' })).toBeInTheDocument(); + expect(screen.getByText(/remove login items left by older versions/i)).toBeInTheDocument(); + await user.click(screen.getByRole('button', { name: 'Continue' })); + await expect(first).resolves.toBe(true); + expect(screen.queryByRole('heading', { name: 'Allow System Events' })).not.toBeInTheDocument(); + + const second = ask!(); + await screen.findByRole('button', { name: 'Not now' }); + await user.click(screen.getByRole('button', { name: 'Not now' })); + await expect(second).resolves.toBe('later'); + }); +}); diff --git a/tests/desktop/bootstrap.static.test.mjs b/tests/desktop/bootstrap.static.test.mjs index 67257f5f..8b24133a 100644 --- a/tests/desktop/bootstrap.static.test.mjs +++ b/tests/desktop/bootstrap.static.test.mjs @@ -34,13 +34,17 @@ describe('desktop bootstrap static checks', () => { expect(plist).not.toContain('com.intel.nw'); }); - it('explains the System Events prompt in the Mac plist and the desktop shell', () => { + it('explains the System Events prompt in the Mac plist and the confirm dialog', () => { const purpose = 'OnlyKey uses this once to remove login items left by older versions, so the app starts once in the tray.'; const plist = fs.readFileSync(path.join(rootDir, 'resources', 'osx', 'Info.plist'), 'utf8'); + const dialog = fs.readFileSync(path.join(rootDir, 'src', 'components', 'LoginItemAccessHost.tsx'), 'utf8'); const shell = fs.readFileSync(path.join(rootDir, 'desktopBg.cjs'), 'utf8'); expect(plist).toContain('NSAppleEventsUsageDescription'); expect(plist).toContain(purpose); - expect(shell).toContain(purpose); + expect(dialog).toContain(purpose); + expect(dialog).toContain('ConfirmDialog'); + expect(shell).toContain('onlykeyAskLoginItemAccess'); + expect(shell).not.toContain('document.createElement'); expect(shell).toContain('skipLoginItems'); expect(shell).toContain('shouldDeferMacLoginItems'); }); From c83fbfbeff0ebe2ba53ae48e0faafd99adc5ae4a Mon Sep 17 00:00:00 2001 From: Rodney G Date: Sat, 7 Nov 2026 00:08:17 -0500 Subject: [PATCH 6/7] Ask the page window for the System Events note so the dialog can open. Co-authored-by: Cursor --- desktopBg.cjs | 77 +++++++++++++++---- docs/desktop-tray.md | 2 +- src/components/LoginItemAccessHost.tsx | 64 ++++++++++----- .../__tests__/LoginItemAccessHost.ui.test.tsx | 17 +++- tests/desktop/bootstrap.static.test.mjs | 1 + tests/desktop/desktopBg.unit.test.mjs | 31 ++++++++ 6 files changed, 154 insertions(+), 38 deletions(-) diff --git a/desktopBg.cjs b/desktopBg.cjs index 99be5864..2c6b7973 100644 --- a/desktopBg.cjs +++ b/desktopBg.cjs @@ -202,6 +202,7 @@ const state = { loginItemPromptDismissed: false, macAutostartChain: Promise.resolve(), loginItemExplainPromise: null, + loginItemPoll: null, }; const osx = os.platform() === 'darwin'; @@ -258,29 +259,69 @@ function enqueueMacAutostart(task) { return run; } +/** + * document-start inject scripts do not share the page's JavaScript globals. + * nw.Window.get().window is the page where LoginItemAccessHost registers. + */ +function appPageWindow() { + const seen = []; + const push = (candidate) => { + if (candidate && !seen.includes(candidate)) seen.push(candidate); + }; + try { + if (typeof nw !== 'undefined' && nw.Window && typeof nw.Window.get === 'function') { + const nwWin = nw.Window.get(); + if (nwWin) push(nwWin.window); + } + } catch { + // ignore + } + if (typeof window !== 'undefined') push(window); + return seen; +} + +function stopLoginItemPoll() { + if (state.loginItemPoll) { + clearInterval(state.loginItemPoll); + state.loginItemPoll = null; + } +} + /** The React confirm dialog owns the UI. This only waits for its answer. */ function requestLoginItemDecision() { return new Promise((resolve) => { - if (typeof window === 'undefined') { - resolve(false); - return; - } - let settled = false; - const deliver = () => { - if (settled) return true; - const ask = window.onlykeyAskLoginItemAccess; - if (typeof ask !== 'function') return false; - settled = true; - Promise.resolve(ask()).then(resolve, () => resolve(false)); - return true; + let finished = false; + let asked = false; + const finish = (value) => { + if (finished) return; + finished = true; + stopLoginItemPoll(); + resolve(value); + }; + const tryAsk = () => { + if (finished || asked) return asked; + for (const page of appPageWindow()) { + const ask = page && page.onlykeyAskLoginItemAccess; + if (typeof ask !== 'function') continue; + asked = true; + stopLoginItemPoll(); + Promise.resolve() + .then(() => ask()) + .then((answer) => finish(answer), () => finish(false)); + return true; + } + return false; }; - if (deliver()) return; const onReady = () => { - window.removeEventListener(LOGIN_ITEM_UI_READY, onReady); - if (!deliver()) resolve(false); + tryAsk(); }; - window.addEventListener(LOGIN_ITEM_UI_READY, onReady, { once: true }); - if (deliver()) window.removeEventListener(LOGIN_ITEM_UI_READY, onReady); + for (const page of appPageWindow()) { + if (page && typeof page.addEventListener === 'function') { + page.addEventListener(LOGIN_ITEM_UI_READY, onReady); + } + } + if (tryAsk()) return; + state.loginItemPoll = setInterval(tryAsk, 50); }); } @@ -1369,6 +1410,7 @@ function startWindowPoll(tick, maxPolls) { function stop() { state.stopped = true; + stopLoginItemPoll(); if (state.commandPoll) { clearInterval(state.commandPoll); state.commandPoll = null; @@ -1427,6 +1469,7 @@ module.exports = { launchedHiddenToTray, shouldDeferMacLoginItems, macLoginItemFollowUp, + requestLoginItemDecision, shouldKeepWindowHidden, applyLaunchVisibility, dispatchTrayCommand, diff --git a/docs/desktop-tray.md b/docs/desktop-tray.md index 4c7e46f3..82f5b509 100644 --- a/docs/desktop-tray.md +++ b/docs/desktop-tray.md @@ -37,7 +37,7 @@ Do **not** pass `title: undefined` into `nw.Tray` — macOS draws that string ne 3. `start()` clears stale `tmp/tray-*`, binds close-to-tray, calls `initTray()` in the **main renderer**, polls `tmp/tray-command.json` (for tests / fallback). It also polls `~/.config/OnlyKey/app-tmp/tray-command.json`, which the Linux `onlykey-launch` wrapper writes. 4. Tray menu `click` handlers call `dispatchTrayCommand()` → `processTrayCommand()` in the same renderer. The autostart checkbox label is **Autostart to tray**. `loginAutostart.cjs` registers one login entry that passes `--hidden --onlykey-autostart`: Windows `HKCU\...\Run\OnlyKey` (and clears a disabled StartupApproved flag; the old value was named `nw` and Task Manager left it off), macOS LaunchAgent `to.crp.onlykey` via `open -gj` on the app bundle (legacy helper agents are removed so Dock icons do not accumulate; Login Items are removed later, while the window is visible), Linux `~/.config/autostart/OnlyKey.desktop` through `onlykey-launch`. -The first time a visible window is about to ask System Events to list Login Items, `LoginItemAccessHost` shows the shared confirm dialog. The desktop shell only waits for Continue or Not now. Continue, then choose Allow on the macOS prompt. Not now leaves those Login Items until the next time the window is opened. The tray is already up, so the note does not block it. A `--hidden --onlykey-autostart` launch does not show that note and does not run the AppleScript; the LaunchAgent is still registered. A stored autostart "off" is not turned back on. `NSAppleEventsUsageDescription` in `resources/osx/Info.plist` is the purpose line macOS can show on the prompt. +The first time a visible window is about to ask System Events to list Login Items, `LoginItemAccessHost` shows the shared confirm dialog. The desktop shell waits for Continue or Not now by calling `onlykeyAskLoginItemAccess` on the page window (`nw.Window.get().window`). The document-start inject script does not share that page's JavaScript globals, so the shell keeps checking the page window until React registers the callback. Continue, then choose Allow on the macOS prompt. Not now leaves those Login Items until the next time the window is opened. The tray is already up, so the note does not block it. A `--hidden --onlykey-autostart` launch does not show that note and does not run the AppleScript; the LaunchAgent is still registered. A stored autostart "off" is not turned back on. `NSAppleEventsUsageDescription` in `resources/osx/Info.plist` is the purpose line macOS can show on the prompt. 5. A second launch while the window is hidden (Start menu, Dock, Applications, taskbar pin, CLI — anything that hits the same executable) is Chromium single-instance. `nw.App` `open` (Windows/Linux) and `reopen` (macOS) call `executeShowMainWindow()`. A second `--hidden` autostart does not. 6. `scripts/start-desktop.mjs` clears tray tmp files before launch and `taskkill`s stale `nw.exe` on exit. diff --git a/src/components/LoginItemAccessHost.tsx b/src/components/LoginItemAccessHost.tsx index aca19a24..dcfb3707 100644 --- a/src/components/LoginItemAccessHost.tsx +++ b/src/components/LoginItemAccessHost.tsx @@ -1,4 +1,5 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; import ConfirmDialog from './dialogs/ConfirmDialog'; /** Same sentence as NSAppleEventsUsageDescription in resources/osx/Info.plist. */ @@ -17,45 +18,70 @@ declare global { } } +/** + * The document-start inject script cannot see this page's globals. The pending + * request lives on the module so a remount still opens the note the shell asked for. + */ +let pendingResolve: ((answer: LoginItemAccessAnswer) => void) | null = null; +const subscribers = new Set<(open: boolean) => void>(); + +function publish(open: boolean) { + subscribers.forEach((notify) => notify(open)); +} + +function askLoginItemAccess(): Promise { + return new Promise((resolve) => { + pendingResolve = resolve; + publish(true); + }); +} + +function settleLoginItemAccess(answer: LoginItemAccessAnswer) { + const resolve = pendingResolve; + pendingResolve = null; + publish(false); + resolve?.(answer); +} + +export function resetLoginItemAccessPrompt() { + pendingResolve = null; + publish(false); +} + /** * macOS asks the desktop shell for this answer before it talks to System Events. * The shell does not build UI; it calls window.onlykeyAskLoginItemAccess. */ const LoginItemAccessHost: React.FC = () => { const [open, setOpen] = useState(false); - const resolveRef = useRef<((answer: LoginItemAccessAnswer) => void) | null>(null); useEffect(() => { - window.onlykeyAskLoginItemAccess = () => - new Promise((resolve) => { - resolveRef.current = resolve; - setOpen(true); - }); + window.onlykeyAskLoginItemAccess = askLoginItemAccess; + const notify = (next: boolean) => setOpen(next); + subscribers.add(notify); + notify(pendingResolve != null); window.dispatchEvent(new Event(LOGIN_ITEM_UI_READY)); return () => { - delete window.onlykeyAskLoginItemAccess; - resolveRef.current = null; + subscribers.delete(notify); }; }, []); - const answer = (value: LoginItemAccessAnswer) => { - setOpen(false); - const resolve = resolveRef.current; - resolveRef.current = null; - resolve?.(value); - }; - - return ( + const dialog = ( answer(true)} - onCancel={() => answer('later')} + onConfirm={() => settleLoginItemAccess(true)} + onCancel={() => settleLoginItemAccess('later')} /> ); + + if (typeof document !== 'undefined' && document.body) { + return createPortal(dialog, document.body); + } + return dialog; }; export default LoginItemAccessHost; diff --git a/src/components/__tests__/LoginItemAccessHost.ui.test.tsx b/src/components/__tests__/LoginItemAccessHost.ui.test.tsx index ab26eb8d..ff96cfc0 100644 --- a/src/components/__tests__/LoginItemAccessHost.ui.test.tsx +++ b/src/components/__tests__/LoginItemAccessHost.ui.test.tsx @@ -1,11 +1,12 @@ import { afterEach, describe, expect, it } from 'vitest'; import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import LoginItemAccessHost from '../LoginItemAccessHost'; +import LoginItemAccessHost, { resetLoginItemAccessPrompt } from '../LoginItemAccessHost'; import { renderWithProviders } from '../../test/render'; describe('LoginItemAccessHost', () => { afterEach(() => { + resetLoginItemAccessPrompt(); delete window.onlykeyAskLoginItemAccess; }); @@ -33,4 +34,18 @@ describe('LoginItemAccessHost', () => { await user.click(screen.getByRole('button', { name: 'Not now' })); await expect(second).resolves.toBe('later'); }); + + it('opens after a remount when the shell already asked', async () => { + const user = userEvent.setup(); + const first = renderWithProviders(); + const ask = window.onlykeyAskLoginItemAccess; + expect(ask).toBeTypeOf('function'); + first.unmount(); + + const pending = ask!(); + renderWithProviders(); + expect(await screen.findByRole('heading', { name: 'Allow System Events' })).toBeInTheDocument(); + await user.click(screen.getByRole('button', { name: 'Not now' })); + await expect(pending).resolves.toBe('later'); + }); }); diff --git a/tests/desktop/bootstrap.static.test.mjs b/tests/desktop/bootstrap.static.test.mjs index 8b24133a..dc42ce62 100644 --- a/tests/desktop/bootstrap.static.test.mjs +++ b/tests/desktop/bootstrap.static.test.mjs @@ -44,6 +44,7 @@ describe('desktop bootstrap static checks', () => { expect(dialog).toContain(purpose); expect(dialog).toContain('ConfirmDialog'); expect(shell).toContain('onlykeyAskLoginItemAccess'); + expect(shell).toContain('appPageWindow'); expect(shell).not.toContain('document.createElement'); expect(shell).toContain('skipLoginItems'); expect(shell).toContain('shouldDeferMacLoginItems'); diff --git a/tests/desktop/desktopBg.unit.test.mjs b/tests/desktop/desktopBg.unit.test.mjs index 8bbc70f2..8074bd8a 100644 --- a/tests/desktop/desktopBg.unit.test.mjs +++ b/tests/desktop/desktopBg.unit.test.mjs @@ -150,6 +150,37 @@ describe('desktopBg.cjs unit', () => { vi.unstubAllGlobals(); }); + it('asks the page window when the inject global cannot see the dialog', async () => { + const { win } = createNwMock(); + const injectWindow = { addEventListener: vi.fn() }; + vi.stubGlobal('window', injectWindow); + const desktop = loadDesktopModule(); + win.window.onlykeyAskLoginItemAccess = () => Promise.resolve(true); + + await expect(desktop.requestLoginItemDecision()).resolves.toBe(true); + expect(injectWindow.onlykeyAskLoginItemAccess).toBeUndefined(); + }); + + it('waits for the page to register the System Events dialog', async () => { + vi.useFakeTimers(); + const { win } = createNwMock(); + vi.stubGlobal('window', { addEventListener: vi.fn() }); + const desktop = loadDesktopModule(); + const pending = desktop.requestLoginItemDecision(); + let settled = false; + pending.then(() => { + settled = true; + }); + + await vi.advanceTimersByTimeAsync(50); + expect(settled).toBe(false); + + win.window.onlykeyAskLoginItemAccess = () => Promise.resolve('later'); + await vi.advanceTimersByTimeAsync(50); + await expect(pending).resolves.toBe('later'); + vi.useRealTimers(); + }); + it('binds close handler and creates tray on start() in main context', async () => { const { win } = createNwMock(); const desktop = loadDesktopModule(); From 321ae29dfb30c2fb5aa4950a1517646721d74054 Mon Sep 17 00:00:00 2001 From: Rodney G Date: Sat, 7 Nov 2026 14:19:42 -0500 Subject: [PATCH 7/7] Add macOS System Events screenshots for the login-autostart PR. Co-authored-by: Cursor --- .../allow-system-events-modal.jpg | Bin 0 -> 35311 bytes .../automation-permission-enabled.jpg | Bin 0 -> 85849 bytes .../macos-system-events-prompt.jpg | Bin 0 -> 61436 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/login-autostart/allow-system-events-modal.jpg create mode 100644 docs/login-autostart/automation-permission-enabled.jpg create mode 100644 docs/login-autostart/macos-system-events-prompt.jpg diff --git a/docs/login-autostart/allow-system-events-modal.jpg b/docs/login-autostart/allow-system-events-modal.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3247327a507cd8285de4ece3ff26ab5153011e85 GIT binary patch literal 35311 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 literal 0 HcmV?d00001 diff --git a/docs/login-autostart/automation-permission-enabled.jpg b/docs/login-autostart/automation-permission-enabled.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ae237928df8bba5778562a25cc41263c3e1a3217 GIT binary patch literal 85849 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 literal 0 HcmV?d00001 diff --git a/docs/login-autostart/macos-system-events-prompt.jpg b/docs/login-autostart/macos-system-events-prompt.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5048feb54370f30a1ed3c2cc2e73c57f0a57e356 GIT binary patch literal 61436 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