fix: resolve activity tracking and UI race conditions

- Remove LastActive reset on emergency promotion to preserve actual activity state
- Fix UI rejection count race by tracking whether rejection was already counted
- Optimize browser detection to avoid redundant string searches
This commit is contained in:
Alex P 2025-10-23 16:10:37 +03:00
parent 587f8b5e4c
commit 79b5ec3e29
2 changed files with 18 additions and 8 deletions

View File

@ -1226,10 +1226,8 @@ func (sm *SessionManager) transferPrimaryRole(fromSessionID, toSessionID, transf
// Promote target session // Promote target session
toSession.Mode = SessionModePrimary toSession.Mode = SessionModePrimary
toSession.hidRPCAvailable = false toSession.hidRPCAvailable = false
// Reset LastActive for all emergency promotions to prevent immediate re-timeout // For manual transfers, preserve the session's actual LastActive timestamp
if strings.HasPrefix(transferType, "emergency_") { // Emergency promotions inherit the observer's activity state - no free time
toSession.LastActive = time.Now()
}
sm.primarySessionID = toSessionID sm.primarySessionID = toSessionID
// ALWAYS set lastPrimaryID to the new primary to support WebRTC reconnections // ALWAYS set lastPrimaryID to the new primary to support WebRTC reconnections
@ -1476,16 +1474,19 @@ func extractBrowserFromUserAgent(userAgent string) *string {
ua := strings.ToLower(userAgent) ua := strings.ToLower(userAgent)
// Check for common browsers (order matters - Chrome contains Safari, etc.) // 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") { if strings.Contains(ua, "edg/") || strings.Contains(ua, "edge") {
return &BrowserEdge return &BrowserEdge
} }
if strings.Contains(ua, "firefox") { if strings.Contains(ua, "firefox") {
return &BrowserFirefox return &BrowserFirefox
} }
if strings.Contains(ua, "chrome") { if hasChrome {
return &BrowserChrome return &BrowserChrome
} }
if strings.Contains(ua, "safari") && !strings.Contains(ua, "chrome") { if strings.Contains(ua, "safari") {
return &BrowserSafari return &BrowserSafari
} }
if strings.Contains(ua, "opera") || strings.Contains(ua, "opr/") { if strings.Contains(ua, "opera") || strings.Contains(ua, "opr/") {

View File

@ -1,4 +1,4 @@
import { useEffect, useState, useCallback } from "react"; import { useEffect, useState, useCallback, useRef } from "react";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import { XCircleIcon } from "@heroicons/react/24/outline"; import { XCircleIcon } from "@heroicons/react/24/outline";
@ -30,6 +30,7 @@ export default function AccessDeniedOverlay({
const { maxRejectionAttempts } = useSettingsStore(); const { maxRejectionAttempts } = useSettingsStore();
const [countdown, setCountdown] = useState(10); const [countdown, setCountdown] = useState(10);
const [isRetrying, setIsRetrying] = useState(false); const [isRetrying, setIsRetrying] = useState(false);
const hasCountedRef = useRef(false);
const handleLogout = useCallback(async () => { const handleLogout = useCallback(async () => {
try { try {
@ -50,7 +51,15 @@ export default function AccessDeniedOverlay({
}, [navigate, setUser, clearSession, clearNickname]); }, [navigate, setUser, clearSession, clearNickname]);
useEffect(() => { 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(); const newCount = incrementRejectionCount();