From 5c84e18c2b4d1b98246319f4963f3df3b0ed782a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mihai=20-=20Alexandru=20Chindri=C8=99?= Date: Tue, 10 Mar 2026 00:10:39 +0200 Subject: [PATCH] feat: add auto-refresh interval option Add a new --refresh-interval CLI option that reloads the packaged page on a fixed interval. The injected runtime defers refreshes while the page is hidden or while the user is focused in editable fields so dashboards and news pages can stay fresh without disrupting active input. --- bin/defaults.ts | 1 + bin/helpers/cli-program.ts | 14 +++++++++ bin/helpers/merge.ts | 2 ++ bin/types.ts | 4 +++ dist/cli.js | 13 +++++++- docs/cli-usage.md | 13 ++++++++ src-tauri/pake.json | 1 + src-tauri/src/app/config.rs | 2 ++ src-tauri/src/inject/event.js | 56 ++++++++++++++++++++++++++++++++++ tests/unit/cli-options.test.ts | 9 ++++++ 10 files changed, 114 insertions(+), 1 deletion(-) diff --git a/bin/defaults.ts b/bin/defaults.ts index e919f5da6..9f35fd351 100644 --- a/bin/defaults.ts +++ b/bin/defaults.ts @@ -49,6 +49,7 @@ export const DEFAULT_PAKE_OPTIONS: PakeCliOptions = { minWidth: 0, minHeight: 0, ignoreCertificateErrors: false, + refreshInterval: 0, newWindow: false, install: false, }; diff --git a/bin/helpers/cli-program.ts b/bin/helpers/cli-program.ts index ac1a1a63e..4a1c0d01d 100644 --- a/bin/helpers/cli-program.ts +++ b/bin/helpers/cli-program.ts @@ -221,6 +221,20 @@ ${green('|_| \\__,_|_|\\_\\___| can turn any webpage into a desktop app with .argParser(validateNumberInput) .hideHelp(), ) + .addOption( + new Option( + '--refresh-interval ', + 'Auto-refresh page interval in seconds (0 disables)', + ) + .default(DEFAULT.refreshInterval) + .argParser((value) => { + const interval = parseInt(value); + if (isNaN(interval) || interval < 0) { + throw new Error('--refresh-interval must be a number >= 0'); + } + return interval; + }), + ) .addOption( new Option( '--ignore-certificate-errors', diff --git a/bin/helpers/merge.ts b/bin/helpers/merge.ts index 54712ed2c..1736dc497 100644 --- a/bin/helpers/merge.ts +++ b/bin/helpers/merge.ts @@ -78,6 +78,7 @@ export async function mergeConfig( minWidth, minHeight, ignoreCertificateErrors, + refreshInterval, newWindow, } = options; @@ -108,6 +109,7 @@ export async function mergeConfig( min_width: minWidth, min_height: minHeight, ignore_certificate_errors: ignoreCertificateErrors, + refresh_interval: refreshInterval, new_window: newWindow, }; Object.assign(tauriConf.pake.windows[0], { url, ...tauriConfWindowOptions }); diff --git a/bin/types.ts b/bin/types.ts index b2cd86f38..8c1d70d8f 100644 --- a/bin/types.ts +++ b/bin/types.ts @@ -118,6 +118,9 @@ export interface PakeCliOptions { // Ignore certificate errors (for self-signed certs), default false ignoreCertificateErrors: boolean; + // Auto-refresh page interval in seconds, default 0 (disabled) + refreshInterval: number; + // Turn on rapid build mode (app only, no dmg/deb/msi), good for debugging iterativeBuild: boolean; @@ -163,6 +166,7 @@ export interface WindowConfig { min_width: number; min_height: number; ignore_certificate_errors: boolean; + refresh_interval: number; new_window: boolean; } diff --git a/dist/cli.js b/dist/cli.js index b2bf0fd2c..2a96737fd 100644 --- a/dist/cli.js +++ b/dist/cli.js @@ -484,7 +484,7 @@ async function mergeConfig(url, options, tauriConf) { await fsExtra.copy(sourcePath, destPath); } })); - const { width, height, fullscreen, maximize, hideTitleBar, alwaysOnTop, appVersion, darkMode, disabledWebShortcuts, activationShortcut, userAgent, showSystemTray, systemTrayIcon, useLocalFile, identifier, name = 'pake-app', resizable = true, inject, proxyUrl, installerLanguage, hideOnClose, incognito, title, wasm, enableDragDrop, multiInstance, multiWindow, startToTray, forceInternalNavigation, internalUrlRegex, zoom, minWidth, minHeight, ignoreCertificateErrors, newWindow, } = options; + const { width, height, fullscreen, maximize, hideTitleBar, alwaysOnTop, appVersion, darkMode, disabledWebShortcuts, activationShortcut, userAgent, showSystemTray, systemTrayIcon, useLocalFile, identifier, name = 'pake-app', resizable = true, inject, proxyUrl, installerLanguage, hideOnClose, incognito, title, wasm, enableDragDrop, multiInstance, multiWindow, startToTray, forceInternalNavigation, internalUrlRegex, zoom, minWidth, minHeight, ignoreCertificateErrors, refreshInterval, newWindow, } = options; const { platform } = process; const platformHideOnClose = hideOnClose ?? platform === 'darwin'; const tauriConfWindowOptions = { @@ -510,6 +510,7 @@ async function mergeConfig(url, options, tauriConf) { min_width: minWidth, min_height: minHeight, ignore_certificate_errors: ignoreCertificateErrors, + refresh_interval: refreshInterval, new_window: newWindow, }; Object.assign(tauriConf.pake.windows[0], { url, ...tauriConfWindowOptions }); @@ -2073,6 +2074,7 @@ const DEFAULT_PAKE_OPTIONS = { minWidth: 0, minHeight: 0, ignoreCertificateErrors: false, + refreshInterval: 0, newWindow: false, install: false, }; @@ -2225,6 +2227,15 @@ ${green('|_| \\__,_|_|\\_\\___| can turn any webpage into a desktop app with .default(DEFAULT_PAKE_OPTIONS.minHeight) .argParser(validateNumberInput) .hideHelp()) + .addOption(new Option('--refresh-interval ', 'Auto-refresh page interval in seconds (0 disables)') + .default(DEFAULT_PAKE_OPTIONS.refreshInterval) + .argParser((value) => { + const interval = parseInt(value); + if (isNaN(interval) || interval < 0) { + throw new Error('--refresh-interval must be a number >= 0'); + } + return interval; + })) .addOption(new Option('--ignore-certificate-errors', 'Ignore certificate errors (for self-signed certificates)') .default(DEFAULT_PAKE_OPTIONS.ignoreCertificateErrors) .hideHelp()) diff --git a/docs/cli-usage.md b/docs/cli-usage.md index 0360a75fe..3f24475aa 100644 --- a/docs/cli-usage.md +++ b/docs/cli-usage.md @@ -370,6 +370,19 @@ Set the window title bar text. macOS shows no title if not specified; Windows/Li --title "Google Translate" ``` +#### [refresh-interval] + +Auto-refresh the current page at a fixed interval in seconds. Default is `0` (disabled). + +When enabled, Pake defers the refresh while the page is hidden or while the user is actively focused in an input, textarea, select, or contenteditable field. + +```shell +--refresh-interval + +# Example: refresh every 5 minutes +pake https://news.ycombinator.com --name HackerNews --refresh-interval 300 +``` + #### [incognito] Launch the application in incognito/private browsing mode. Default is `false`. When enabled, the webview will run in private mode, which means it won't store cookies, local storage, or browsing history. This is useful for privacy-sensitive applications. diff --git a/src-tauri/pake.json b/src-tauri/pake.json index 1fd46d7a2..78e0009ff 100644 --- a/src-tauri/pake.json +++ b/src-tauri/pake.json @@ -20,6 +20,7 @@ "start_to_tray": false, "force_internal_navigation": false, "internal_url_regex": "", + "refresh_interval": 0, "new_window": false } ], diff --git a/src-tauri/src/app/config.rs b/src-tauri/src/app/config.rs index a40550f3e..9a927287f 100644 --- a/src-tauri/src/app/config.rs +++ b/src-tauri/src/app/config.rs @@ -34,6 +34,8 @@ pub struct WindowConfig { pub min_height: f64, #[serde(default)] pub ignore_certificate_errors: bool, + #[serde(default)] + pub refresh_interval: u32, } fn default_zoom() -> u32 { diff --git a/src-tauri/src/inject/event.js b/src-tauri/src/inject/event.js index b67a6f04b..81da21780 100644 --- a/src-tauri/src/inject/event.js +++ b/src-tauri/src/inject/event.js @@ -263,6 +263,8 @@ document.addEventListener("DOMContentLoaded", () => { const pakeConfig = window["pakeConfig"] || {}; const forceInternalNavigation = pakeConfig.force_internal_navigation === true; const internalUrlRegex = pakeConfig.internal_url_regex || ""; + const refreshIntervalSeconds = Number(pakeConfig.refresh_interval || 0); + let autoRefreshTimer = null; let internalUrlPattern = null; if (internalUrlRegex) { try { @@ -308,6 +310,60 @@ document.addEventListener("DOMContentLoaded", () => { }); } + function shouldDeferAutoRefresh() { + if (document.hidden) { + return true; + } + + const activeElement = document.activeElement; + if (!activeElement) { + return false; + } + + const tagName = activeElement.tagName; + return ( + activeElement.isContentEditable || + tagName === "INPUT" || + tagName === "TEXTAREA" || + tagName === "SELECT" + ); + } + + function scheduleAutoRefresh(delayMs = refreshIntervalSeconds * 1000) { + if (refreshIntervalSeconds <= 0) { + return; + } + + if (autoRefreshTimer) { + clearTimeout(autoRefreshTimer); + } + + autoRefreshTimer = window.setTimeout(() => { + if (shouldDeferAutoRefresh()) { + scheduleAutoRefresh(5000); + return; + } + + window.location.reload(); + }, delayMs); + } + + if (refreshIntervalSeconds > 0) { + scheduleAutoRefresh(); + + document.addEventListener("visibilitychange", () => { + if (!document.hidden) { + scheduleAutoRefresh(); + } + }); + + window.addEventListener("beforeunload", () => { + if (autoRefreshTimer) { + clearTimeout(autoRefreshTimer); + } + }); + } + document.addEventListener( "paste", (event) => { diff --git a/tests/unit/cli-options.test.ts b/tests/unit/cli-options.test.ts index 756d2a4a5..6ad345b8f 100644 --- a/tests/unit/cli-options.test.ts +++ b/tests/unit/cli-options.test.ts @@ -29,4 +29,13 @@ describe('CLI options', () => { expect(option?.defaultValue).toBe(false); expect(option?.hidden).toBe(false); }); + + it('registers --refresh-interval option', () => { + const option = program.options.find( + (item) => item.long === '--refresh-interval', + ); + + expect(option).toBeDefined(); + expect(option?.defaultValue).toBe(0); + }); });