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
This commit is contained in:
Claude
2025-11-17 02:21:52 +00:00
parent fc72c84b3c
commit b169653ab6
+92 -29
View File
@@ -9,15 +9,29 @@
# - Moved Session Lockout configuration from Sites menu to Core Settings menu # - Moved Session Lockout configuration from Sites menu to Core Settings menu
# * Now accessible as option 7 in Core Settings # * Now accessible as option 7 in Core Settings
# * Makes password protection easier to find and configure # * 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 # - Fixed inactivity popup not appearing on rotation sites
# * Popup now appears on ALL sites where user has interacted (not just manual sites) # * Popup now appears on ALL sites where user has interacted (not just manual sites)
# * Inactivity check now happens BEFORE rotation check # * Inactivity check now happens BEFORE rotation check
# * Auto-rotation preserves user interaction flag (critical fix!)
# * Prevents rotation from interrupting the inactivity prompt # * Prevents rotation from interrupting the inactivity prompt
# * Rotation pauses while inactivity prompt is displayed # * Rotation pauses while inactivity prompt is displayed
# - Fixed session lockout being interrupted by rotation # - Fixed session lockout being interrupted by rotation
# * Rotation now pauses completely when session is locked # * Rotation now pauses completely when session is locked
# * Inactivity prompts won't appear while session is locked # * Inactivity prompts won't appear while session is locked
# * Password lockout screen stays visible until correct password entered # * 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): # Previous features (v0.9.8):
# - Added password-protected session lockout # - Added password-protected session lockout
@@ -3426,7 +3440,7 @@ full_reinstall() {
fi fi
echo "" echo ""
log_success "Nuclear reinstall complete! System is FRESH." log_success "Reinstall complete! System is fresh."
echo "" echo ""
pause pause
} }
@@ -3528,7 +3542,7 @@ first_time_install() {
################################################################### ###################################################################
echo "[13/27] Installing Electron..." echo "[13/27] Installing Electron..."
sudo -u "$KIOSK_USER" tee "$KIOSK_DIR/main.js" > /dev/null <<'MAINJS' 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 {exec}=require('child_process');
const fs=require('fs'); const fs=require('fs');
const path=require('path'); const path=require('path');
@@ -3612,7 +3626,7 @@ function loadConfig(){
} }
} }
function markActivity(){ function markActivity(resetLockoutTimer){
const now=Date.now(); const now=Date.now();
const timeSinceLastActivity=now-lastUserInteraction; const timeSinceLastActivity=now-lastUserInteraction;
@@ -3622,7 +3636,13 @@ function markActivity(){
lastUserInteraction=now; lastUserInteraction=now;
userRecentlyActive=true; 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 // v0.9.8: Mark that user has interacted with this site
// This triggers inactivity prompt logic for ANY site (not just manual/home) // 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) // 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){ if(lockoutEnabled&&!sessionLocked){
const idleTime=now-lastUserInteraction; const idleTime=now-lastLockoutCheck;
// Log every 30 seconds when getting close to lockout // Log every 30 seconds when getting close to lockout
if(idleTime>lockoutTimeout*0.75){ if(idleTime>lockoutTimeout*0.75){
@@ -3962,11 +3984,11 @@ function rotateToNextSite(){
if(found&&nextIdx!==currentIndex){ if(found&&nextIdx!==currentIndex){
currentIndex=nextIdx; currentIndex=nextIdx;
attachView(currentIndex); attachView(currentIndex,true); // Pass true to indicate auto-rotation
} }
} }
function attachView(i){ function attachView(i,isAutoRotation){
closeHTMLKeyboard(); closeHTMLKeyboard();
if(!mainWindow||!views[i]||showingHidden)return; if(!mainWindow||!views[i]||showingHidden)return;
@@ -3990,10 +4012,13 @@ function attachView(i){
views[i].webContents.focus(); views[i].webContents.focus();
siteStartTime=Date.now(); siteStartTime=Date.now();
// v0.9.8: Clear interaction flag when site changes // v0.9.9 CRITICAL FIX: Only reset interaction flag on MANUAL navigation
// Will be set back to true if user manually swiped here (markActivity called after) // Don't reset during auto-rotation - this allows inactivity prompt to work on rotation sites!
// Will stay false if auto-rotated (no user interaction yet) // Auto-rotation: user taps site A → rotates to site B after 30s → inactivity prompt can still appear
userInteractedWithCurrentSite=false; // Manual swipe: user deliberately navigated, reset the flag (will be set again by markActivity())
if(!isAutoRotation){
userInteractedWithCurrentSite=false;
}
} }
function nextTab(){ function nextTab(){
@@ -4010,7 +4035,7 @@ function nextTab(){
manualNavigationMode=true; manualNavigationMode=true;
currentIndex=(currentIndex+1)%views.length; currentIndex=(currentIndex+1)%views.length;
attachView(currentIndex); attachView(currentIndex);
markActivity(); markActivity(true); // Actual user interaction - reset lockout timer
} }
function prevTab(){ function prevTab(){
@@ -4027,7 +4052,7 @@ function prevTab(){
manualNavigationMode=true; manualNavigationMode=true;
currentIndex=(currentIndex-1+views.length)%views.length; currentIndex=(currentIndex-1+views.length)%views.length;
attachView(currentIndex); attachView(currentIndex);
markActivity(); markActivity(true); // Actual user interaction - reset lockout timer
} }
function getHomeViewIndex(){ function getHomeViewIndex(){
@@ -4066,11 +4091,9 @@ function returnToHome(){
attachView(currentIndex); attachView(currentIndex);
// CRITICAL FIX: Don't clear extensions unless explicitly requested // v0.9.9: Don't reset lockout timer when returning to rotation
// Extensions are only cleared when user chooses "Return to Rotation" button // This is a prompt response, not actual user interaction with content
// or when the prompt times out (no response) markActivity(); // Resets inactivity timer but NOT lockout timer
// Don't auto-clear here - let the extension logic handle expiration
markActivity();
} }
function showInactivityPrompt(){ function showInactivityPrompt(){
@@ -4116,11 +4139,13 @@ function showInactivityPrompt(){
inactivityExtensionUntil=0; inactivityExtensionUntil=0;
returnToHome(); returnToHome();
}else if(minutes===0){ }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; inactivityExtensionUntil=0;
markActivity(); markActivity(); // Resets inactivity timer but NOT lockout timer
}else{ }else{
// User chose a time extension - grant it! // User chose a time extension - grant it!
// Don't reset lockout timer - they're just buying more time
const now=Date.now(); const now=Date.now();
inactivityExtensionUntil=now+(minutes*60*1000); inactivityExtensionUntil=now+(minutes*60*1000);
lastUserInteraction=now; lastUserInteraction=now;
@@ -4308,7 +4333,7 @@ function returnToTabs(){
showingHidden=false; showingHidden=false;
currentHiddenIndex=0; currentHiddenIndex=0;
markActivity(); markActivity(true); // User toggled hidden - actual interaction
} }
function showPinEntry(){ function showPinEntry(){
@@ -4408,8 +4433,8 @@ function createWindow(){
mainWindow.setMenu(null); mainWindow.setMenu(null);
mainWindow.show(); mainWindow.show();
mainWindow.on('focus',()=>markActivity()); mainWindow.on('focus',()=>markActivity(true));
mainWindow.webContents.on('before-input-event',()=>markActivity()); mainWindow.webContents.on('before-input-event',()=>markActivity(true));
if(!tabs.length){ if(!tabs.length){
mainWindow.loadURL('data:text/html,<body>No Sites Configured</body>'); mainWindow.loadURL('data:text/html,<body>No Sites Configured</body>');
@@ -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',()=>{ view.webContents.on('did-start-loading',()=>{
if(!programmaticNavigation){ if(!programmaticNavigation){
markActivity(); markActivity(true);
} }
}); });
view.webContents.on('did-navigate',()=>{ view.webContents.on('did-navigate',()=>{
if(programmaticNavigation){ if(programmaticNavigation){
programmaticNavigation=false; programmaticNavigation=false;
}else{ }else{
markActivity(); markActivity(true);
} }
}); });
@@ -4503,6 +4528,14 @@ function createWindow(){
setTimeout(()=>{ setTimeout(()=>{
attachView(startIndex); attachView(startIndex);
startMasterTimer(); 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); },1000);
} }
@@ -4510,7 +4543,7 @@ function createWindow(){
ipcMain.on('swipe-right',()=>{prevTab();}); ipcMain.on('swipe-right',()=>{prevTab();});
ipcMain.on('show-power-menu',showPowerMenu); ipcMain.on('show-power-menu',showPowerMenu);
ipcMain.on('toggle-hidden',toggleHidden); 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('show-keyboard',()=>{showHTMLKeyboard();});
ipcMain.on('close-keyboard',()=>{closeHTMLKeyboard();}); ipcMain.on('close-keyboard',()=>{closeHTMLKeyboard();});
ipcMain.on('keyboard-activity',()=>{markKeyboardActivity();}); ipcMain.on('keyboard-activity',()=>{markKeyboardActivity();});
@@ -4672,6 +4705,31 @@ app.on('window-all-closed',()=>{
app.on('activate',()=>{ app.on('activate',()=>{
if(BrowserWindow.getAllWindows().length===0)createWindow(); 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 MAINJS
############################################################################# #############################################################################
###############################end of main.js################################ ###############################end of main.js################################
@@ -8277,7 +8335,7 @@ KBFUNC
view.webContents.sendInputEvent({type:'keyDown',keyCode:key});\ view.webContents.sendInputEvent({type:'keyDown',keyCode:key});\
view.webContents.sendInputEvent({type:'char',keyCode:key});\ view.webContents.sendInputEvent({type:'char',keyCode:key});\
view.webContents.sendInputEvent({type:'keyUp',keyCode:key});\ view.webContents.sendInputEvent({type:'keyUp',keyCode:key});\
markActivity();\ markActivity(true);\
}\ }\
});\ });\
ipcMain.on('close-keyboard',()=>{closeHTMLKeyboard();});\ ipcMain.on('close-keyboard',()=>{closeHTMLKeyboard();});\
@@ -8571,7 +8629,12 @@ if [[ "$CHECK_UPDATES" =~ ^[Yy]$ ]]; then
echo "Updating to v${LATEST_ELECTRON}..." echo "Updating to v${LATEST_ELECTRON}..."
echo "This takes 1-2 minutes..." 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 sudo -u "$KIOSK_USER" npm install "electron@${LATEST_ELECTRON}" --save-exact
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then