From b169653ab614938e33c2077d67221f5f206d9abe Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Nov 2025 02:21:52 +0000 Subject: [PATCH] Complete v0.9.9 improvements: Password protection, popup fixes, lockout independence Password Protection Enhancements: - Password prompt now appears on bootup if lockout enabled - Password prompt on system suspend/resume (via powerMonitor) - Password prompt on screen lock events - Ensures kiosk is always protected when password feature is enabled Critical Inactivity Popup Fix: - Fixed popup not appearing on rotation sites (major UX bug) - Auto-rotation now preserves userInteractedWithCurrentSite flag - Added isAutoRotation parameter to attachView() - Only resets interaction flag on manual navigation - Inactivity check moved BEFORE rotation check - Rotation pauses while inactivity prompt is displayed Lockout Timer Independence: - Lockout timer now uses lastLockoutCheck instead of lastUserInteraction - Responding to inactivity prompts no longer resets lockout timer - Added resetLockoutTimer parameter to markActivity() - Only actual user interactions (touches, swipes, keyboard) reset lockout - Ensures lockout fires even if user keeps responding to prompts Other Fixes: - Fixed script bailing on manual electron update (graceful error handling) - Changed "Nuclear reinstall" to "Reinstall complete! System is fresh." - Moved Session Lockout from Sites menu to Core Settings menu (option 7) All markActivity() calls updated with proper resetLockoutTimer flags --- install_kiosk_v0.9.9.sh | 145 ++++++++++++++++++++++++++++------------ 1 file changed, 104 insertions(+), 41 deletions(-) diff --git a/install_kiosk_v0.9.9.sh b/install_kiosk_v0.9.9.sh index 1ba5b81..e851968 100644 --- a/install_kiosk_v0.9.9.sh +++ b/install_kiosk_v0.9.9.sh @@ -9,15 +9,29 @@ # - Moved Session Lockout configuration from Sites menu to Core Settings menu # * Now accessible as option 7 in Core Settings # * Makes password protection easier to find and configure +# - Password protection now triggers on bootup, resume, and lockout timeout +# * Password prompt appears immediately on boot if lockout enabled +# * Password prompt appears after system suspend/resume +# * Password prompt appears when screen is locked +# * This ensures kiosk is always protected when password feature is enabled # - Fixed inactivity popup not appearing on rotation sites # * Popup now appears on ALL sites where user has interacted (not just manual sites) # * Inactivity check now happens BEFORE rotation check +# * Auto-rotation preserves user interaction flag (critical fix!) # * Prevents rotation from interrupting the inactivity prompt # * Rotation pauses while inactivity prompt is displayed # - Fixed session lockout being interrupted by rotation # * Rotation now pauses completely when session is locked # * Inactivity prompts won't appear while session is locked # * Password lockout screen stays visible until correct password entered +# - Fixed lockout timeout not firing independently +# * Lockout timer now separate from inactivity timer (uses lastLockoutCheck) +# * Responding to inactivity prompts no longer resets lockout timer +# * Only actual user interaction with content resets lockout timer +# * Ensures lockout fires even if user keeps responding to inactivity prompts +# - Fixed script bailing on manual electron update +# * Changed from hard exit to graceful error handling +# - Changed "Nuclear reinstall" message to "Reinstall complete! System is fresh." # # Previous features (v0.9.8): # - Added password-protected session lockout @@ -3426,7 +3440,7 @@ full_reinstall() { fi echo "" - log_success "Nuclear reinstall complete! System is FRESH." + log_success "Reinstall complete! System is fresh." echo "" pause } @@ -3528,7 +3542,7 @@ first_time_install() { ################################################################### echo "[13/27] Installing Electron..." sudo -u "$KIOSK_USER" tee "$KIOSK_DIR/main.js" > /dev/null <<'MAINJS' -const {app,BrowserWindow,BrowserView,globalShortcut,ipcMain,dialog}=require('electron'); +const {app,BrowserWindow,BrowserView,globalShortcut,ipcMain,dialog,powerMonitor}=require('electron'); const {exec}=require('child_process'); const fs=require('fs'); const path=require('path'); @@ -3612,7 +3626,7 @@ function loadConfig(){ } } -function markActivity(){ +function markActivity(resetLockoutTimer){ const now=Date.now(); const timeSinceLastActivity=now-lastUserInteraction; @@ -3622,7 +3636,13 @@ function markActivity(){ lastUserInteraction=now; userRecentlyActive=true; - lastLockoutCheck=now; // Reset lockout timer on activity + + // v0.9.9: Only reset lockout timer for actual user interaction (swipes, touches) + // NOT for inactivity prompt responses - this allows lockout to work independently + if(resetLockoutTimer){ + lastLockoutCheck=now; + console.log('[ACTIVITY] Lockout timer reset'); + } // v0.9.8: Mark that user has interacted with this site // This triggers inactivity prompt logic for ANY site (not just manual/home) @@ -3911,8 +3931,10 @@ function startMasterTimer(){ } // 8. LOCKOUT CHECK (session lock after extended inactivity) + // v0.9.9: Use lastLockoutCheck instead of lastUserInteraction + // This ensures lockout timer is independent from inactivity prompts if(lockoutEnabled&&!sessionLocked){ - const idleTime=now-lastUserInteraction; + const idleTime=now-lastLockoutCheck; // Log every 30 seconds when getting close to lockout if(idleTime>lockoutTimeout*0.75){ @@ -3941,12 +3963,12 @@ function stopMasterTimer(){ function rotateToNextSite(){ if(!views.length||showingHidden)return; - + let nextIdx=(currentIndex+1)%views.length; const startIdx=nextIdx; let found=false; let attempts=0; - + do{ const tabIdx=viewIndexToTabIndex(nextIdx); if(tabIdx>=0&&tabs[tabIdx]){ @@ -3959,41 +3981,44 @@ function rotateToNextSite(){ nextIdx=(nextIdx+1)%views.length; attempts++; }while(nextIdx!==startIdx&&attempts=0&&tabs[tabIdx]){ const configuredUrl=tabs[tabIdx].url; const currentUrl=views[i].webContents.getURL(); - + if(currentUrl&&!currentUrl.startsWith(configuredUrl)){ programmaticNavigation=true; views[i].webContents.loadURL(configuredUrl); } } - + views[i].webContents.focus(); siteStartTime=Date.now(); - // v0.9.8: Clear interaction flag when site changes - // Will be set back to true if user manually swiped here (markActivity called after) - // Will stay false if auto-rotated (no user interaction yet) - userInteractedWithCurrentSite=false; + // v0.9.9 CRITICAL FIX: Only reset interaction flag on MANUAL navigation + // Don't reset during auto-rotation - this allows inactivity prompt to work on rotation sites! + // Auto-rotation: user taps site A → rotates to site B after 30s → inactivity prompt can still appear + // Manual swipe: user deliberately navigated, reset the flag (will be set again by markActivity()) + if(!isAutoRotation){ + userInteractedWithCurrentSite=false; + } } function nextTab(){ @@ -4010,7 +4035,7 @@ function nextTab(){ manualNavigationMode=true; currentIndex=(currentIndex+1)%views.length; attachView(currentIndex); - markActivity(); + markActivity(true); // Actual user interaction - reset lockout timer } function prevTab(){ @@ -4027,7 +4052,7 @@ function prevTab(){ manualNavigationMode=true; currentIndex=(currentIndex-1+views.length)%views.length; attachView(currentIndex); - markActivity(); + markActivity(true); // Actual user interaction - reset lockout timer } function getHomeViewIndex(){ @@ -4066,11 +4091,9 @@ function returnToHome(){ attachView(currentIndex); - // CRITICAL FIX: Don't clear extensions unless explicitly requested - // Extensions are only cleared when user chooses "Return to Rotation" button - // or when the prompt times out (no response) - // Don't auto-clear here - let the extension logic handle expiration - markActivity(); + // v0.9.9: Don't reset lockout timer when returning to rotation + // This is a prompt response, not actual user interaction with content + markActivity(); // Resets inactivity timer but NOT lockout timer } function showInactivityPrompt(){ @@ -4116,11 +4139,13 @@ function showInactivityPrompt(){ inactivityExtensionUntil=0; returnToHome(); }else if(minutes===0){ - // User chose "I'm still here" - just mark activity, no extension + // v0.9.9: User chose "I'm still here" - don't reset lockout timer + // This is a prompt response, not actual interaction with content inactivityExtensionUntil=0; - markActivity(); + markActivity(); // Resets inactivity timer but NOT lockout timer }else{ // User chose a time extension - grant it! + // Don't reset lockout timer - they're just buying more time const now=Date.now(); inactivityExtensionUntil=now+(minutes*60*1000); lastUserInteraction=now; @@ -4307,8 +4332,8 @@ function returnToTabs(){ views[currentIndex].setBounds({x:0,y:0,width:w,height:h}); showingHidden=false; currentHiddenIndex=0; - - markActivity(); + + markActivity(true); // User toggled hidden - actual interaction } function showPinEntry(){ @@ -4407,9 +4432,9 @@ function createWindow(){ mainWindow.setMenu(null); mainWindow.show(); - - mainWindow.on('focus',()=>markActivity()); - mainWindow.webContents.on('before-input-event',()=>markActivity()); + + mainWindow.on('focus',()=>markActivity(true)); + mainWindow.webContents.on('before-input-event',()=>markActivity(true)); if(!tabs.length){ mainWindow.loadURL('data:text/html,No Sites Configured'); @@ -4456,17 +4481,17 @@ function createWindow(){ }); } - view.webContents.on('before-input-event',()=>markActivity()); + view.webContents.on('before-input-event',()=>markActivity(true)); view.webContents.on('did-start-loading',()=>{ if(!programmaticNavigation){ - markActivity(); + markActivity(true); } }); view.webContents.on('did-navigate',()=>{ if(programmaticNavigation){ programmaticNavigation=false; }else{ - markActivity(); + markActivity(true); } }); @@ -4503,6 +4528,14 @@ function createWindow(){ setTimeout(()=>{ attachView(startIndex); startMasterTimer(); + + // v0.9.9: Show lockout on boot if enabled + if(lockoutEnabled){ + console.log('[LOCKOUT] 🔒 Password protection enabled - locking on boot'); + setTimeout(()=>{ + showLockout(); + },500); + } },1000); } @@ -4510,7 +4543,7 @@ function createWindow(){ ipcMain.on('swipe-right',()=>{prevTab();}); ipcMain.on('show-power-menu',showPowerMenu); ipcMain.on('toggle-hidden',toggleHidden); - ipcMain.on('user-activity',markActivity); + ipcMain.on('user-activity',()=>{markActivity(true);}); // Actual user interaction with content ipcMain.on('show-keyboard',()=>{showHTMLKeyboard();}); ipcMain.on('close-keyboard',()=>{closeHTMLKeyboard();}); ipcMain.on('keyboard-activity',()=>{markKeyboardActivity();}); @@ -4672,6 +4705,31 @@ app.on('window-all-closed',()=>{ app.on('activate',()=>{ if(BrowserWindow.getAllWindows().length===0)createWindow(); }); + +// v0.9.9: Lock on suspend/resume/screen lock if password enabled +app.whenReady().then(()=>{ + powerMonitor.on('suspend',()=>{ + if(lockoutEnabled){ + console.log('[LOCKOUT] 💤 System suspending - will lock on resume'); + } + }); + + powerMonitor.on('resume',()=>{ + if(lockoutEnabled){ + console.log('[LOCKOUT] 🔒 System resumed - locking session'); + setTimeout(()=>{ + showLockout(); + },500); + } + }); + + powerMonitor.on('lock-screen',()=>{ + if(lockoutEnabled){ + console.log('[LOCKOUT] 🔒 Screen locked - locking kiosk'); + showLockout(); + } + }); +}); MAINJS ############################################################################# ###############################end of main.js################################ @@ -8277,7 +8335,7 @@ KBFUNC view.webContents.sendInputEvent({type:'keyDown',keyCode:key});\ view.webContents.sendInputEvent({type:'char',keyCode:key});\ view.webContents.sendInputEvent({type:'keyUp',keyCode:key});\ - markActivity();\ + markActivity(true);\ }\ });\ ipcMain.on('close-keyboard',()=>{closeHTMLKeyboard();});\ @@ -8570,10 +8628,15 @@ if [[ "$CHECK_UPDATES" =~ ^[Yy]$ ]]; then if [[ "$DO_UPDATE" =~ ^[Yy]$ ]]; then echo "Updating to v${LATEST_ELECTRON}..." echo "This takes 1-2 minutes..." - - cd "$KIOSK_DIR" || exit 1 + + if ! cd "$KIOSK_DIR"; then + log_error "Cannot access kiosk directory: $KIOSK_DIR" + pause + return 1 + fi + sudo -u "$KIOSK_USER" npm install "electron@${LATEST_ELECTRON}" --save-exact - + if [ $? -eq 0 ]; then echo "✓ Updated successfully!" echo ""