diff --git a/session_manager.go b/session_manager.go index 024f3ab1..fe16e8ec 100644 --- a/session_manager.go +++ b/session_manager.go @@ -1226,10 +1226,8 @@ func (sm *SessionManager) transferPrimaryRole(fromSessionID, toSessionID, transf // Promote target session toSession.Mode = SessionModePrimary toSession.hidRPCAvailable = false - // Reset LastActive for all emergency promotions to prevent immediate re-timeout - if strings.HasPrefix(transferType, "emergency_") { - toSession.LastActive = time.Now() - } + // For manual transfers, preserve the session's actual LastActive timestamp + // Emergency promotions inherit the observer's activity state - no free time sm.primarySessionID = toSessionID // ALWAYS set lastPrimaryID to the new primary to support WebRTC reconnections @@ -1476,16 +1474,19 @@ func extractBrowserFromUserAgent(userAgent string) *string { ua := strings.ToLower(userAgent) // Check for common browsers (order matters - Chrome contains Safari, etc.) + // Optimize Safari check by caching Chrome detection + hasChrome := strings.Contains(ua, "chrome") + if strings.Contains(ua, "edg/") || strings.Contains(ua, "edge") { return &BrowserEdge } if strings.Contains(ua, "firefox") { return &BrowserFirefox } - if strings.Contains(ua, "chrome") { + if hasChrome { return &BrowserChrome } - if strings.Contains(ua, "safari") && !strings.Contains(ua, "chrome") { + if strings.Contains(ua, "safari") { return &BrowserSafari } if strings.Contains(ua, "opera") || strings.Contains(ua, "opr/") { diff --git a/ui/src/components/AccessDeniedOverlay.tsx b/ui/src/components/AccessDeniedOverlay.tsx index fd7f6f24..a04f23cc 100644 --- a/ui/src/components/AccessDeniedOverlay.tsx +++ b/ui/src/components/AccessDeniedOverlay.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useCallback } from "react"; +import { useEffect, useState, useCallback, useRef } from "react"; import { useNavigate } from "react-router"; import { XCircleIcon } from "@heroicons/react/24/outline"; @@ -30,6 +30,7 @@ export default function AccessDeniedOverlay({ const { maxRejectionAttempts } = useSettingsStore(); const [countdown, setCountdown] = useState(10); const [isRetrying, setIsRetrying] = useState(false); + const hasCountedRef = useRef(false); const handleLogout = useCallback(async () => { try { @@ -50,7 +51,15 @@ export default function AccessDeniedOverlay({ }, [navigate, setUser, clearSession, clearNickname]); useEffect(() => { - if (!show) return; + if (!show) { + hasCountedRef.current = false; + setCountdown(10); + return; + } + + // Only count rejection once per showing + if (hasCountedRef.current) return; + hasCountedRef.current = true; const newCount = incrementRejectionCount();