2025-10-08 00:08:13 -04:00
|
|
|
// SessionTimer Component - v4.3.120 - ECDH + DTLS + SAS
|
2025-09-04 17:25:01 -04:00
|
|
|
const SessionTimer = ({ timeLeft, sessionType, sessionManager, onDisconnect }) => {
|
2025-08-14 23:34:54 -04:00
|
|
|
const [currentTime, setCurrentTime] = React.useState(timeLeft || 0);
|
|
|
|
|
const [showExpiredMessage, setShowExpiredMessage] = React.useState(false);
|
|
|
|
|
const [initialized, setInitialized] = React.useState(false);
|
2025-08-16 20:58:42 -04:00
|
|
|
const [connectionBroken, setConnectionBroken] = React.useState(false);
|
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
|
2025-08-16 20:58:42 -04:00
|
|
|
const [loggedHidden, setLoggedHidden] = React.useState(false);
|
2025-08-14 23:34:54 -04:00
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
if (connectionBroken) {
|
2025-08-16 20:58:42 -04:00
|
|
|
if (!loggedHidden) {
|
|
|
|
|
console.log('⏱️ SessionTimer initialization skipped - connection broken');
|
|
|
|
|
setLoggedHidden(true);
|
|
|
|
|
}
|
2025-08-14 23:34:54 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let initialTime = 0;
|
|
|
|
|
|
|
|
|
|
if (sessionManager?.hasActiveSession()) {
|
|
|
|
|
initialTime = sessionManager.getTimeLeft();
|
|
|
|
|
} else if (timeLeft && timeLeft > 0) {
|
|
|
|
|
initialTime = timeLeft;
|
|
|
|
|
}
|
2025-08-17 20:38:47 -04:00
|
|
|
|
|
|
|
|
if (initialTime <= 0) {
|
|
|
|
|
setCurrentTime(0);
|
|
|
|
|
setInitialized(false);
|
|
|
|
|
setLoggedHidden(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (connectionBroken) {
|
|
|
|
|
setCurrentTime(0);
|
|
|
|
|
setInitialized(false);
|
|
|
|
|
setLoggedHidden(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-08-14 23:34:54 -04:00
|
|
|
setCurrentTime(initialTime);
|
|
|
|
|
setInitialized(true);
|
2025-08-16 20:58:42 -04:00
|
|
|
setLoggedHidden(false);
|
|
|
|
|
}, [sessionManager, connectionBroken]);
|
2025-08-14 23:34:54 -04:00
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
if (connectionBroken) {
|
2025-08-16 20:58:42 -04:00
|
|
|
if (!loggedHidden) {
|
|
|
|
|
setLoggedHidden(true);
|
|
|
|
|
}
|
2025-08-14 23:34:54 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (timeLeft && timeLeft > 0) {
|
|
|
|
|
setCurrentTime(timeLeft);
|
|
|
|
|
}
|
2025-08-16 20:58:42 -04:00
|
|
|
setLoggedHidden(false);
|
2025-08-14 23:34:54 -04:00
|
|
|
}, [timeLeft, connectionBroken]);
|
|
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
if (!initialized) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (connectionBroken) {
|
2025-08-16 20:58:42 -04:00
|
|
|
if (!loggedHidden) {
|
|
|
|
|
setLoggedHidden(true);
|
|
|
|
|
}
|
2025-08-14 23:34:54 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!currentTime || currentTime <= 0 || !sessionManager) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const interval = setInterval(() => {
|
|
|
|
|
if (connectionBroken) {
|
|
|
|
|
setCurrentTime(0);
|
|
|
|
|
clearInterval(interval);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sessionManager?.hasActiveSession()) {
|
|
|
|
|
const newTime = sessionManager.getTimeLeft();
|
|
|
|
|
setCurrentTime(newTime);
|
|
|
|
|
|
|
|
|
|
if (window.DEBUG_MODE && Math.floor(Date.now() / 30000) !== Math.floor((Date.now() - 1000) / 30000)) {
|
|
|
|
|
console.log('⏱️ Timer tick:', Math.floor(newTime / 1000) + 's');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (newTime <= 0) {
|
|
|
|
|
setShowExpiredMessage(true);
|
|
|
|
|
setTimeout(() => setShowExpiredMessage(false), 5000);
|
|
|
|
|
clearInterval(interval);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
setCurrentTime(0);
|
|
|
|
|
clearInterval(interval);
|
|
|
|
|
}
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
clearInterval(interval);
|
|
|
|
|
};
|
2025-08-16 20:58:42 -04:00
|
|
|
}, [initialized, currentTime, sessionManager, connectionBroken]);
|
2025-08-14 23:34:54 -04:00
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
const handleSessionTimerUpdate = (event) => {
|
2025-08-17 20:38:47 -04:00
|
|
|
if (connectionBroken) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
if (event.detail.timeLeft && event.detail.timeLeft > 0) {
|
|
|
|
|
setCurrentTime(event.detail.timeLeft);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleForceHeaderUpdate = (event) => {
|
2025-08-17 20:38:47 -04:00
|
|
|
if (connectionBroken) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
if (sessionManager && sessionManager.hasActiveSession()) {
|
|
|
|
|
const newTime = sessionManager.getTimeLeft();
|
|
|
|
|
setCurrentTime(newTime);
|
2025-08-17 20:38:47 -04:00
|
|
|
} else {
|
|
|
|
|
setCurrentTime(event.detail.timeLeft);
|
2025-08-14 23:34:54 -04:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handlePeerDisconnect = (event) => {
|
2025-08-16 20:58:42 -04:00
|
|
|
setConnectionBroken(true);
|
2025-08-14 23:34:54 -04:00
|
|
|
setCurrentTime(0);
|
|
|
|
|
setShowExpiredMessage(false);
|
2025-08-16 20:58:42 -04:00
|
|
|
setLoggedHidden(false);
|
2025-08-14 23:34:54 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleNewConnection = (event) => {
|
2025-08-16 20:58:42 -04:00
|
|
|
setConnectionBroken(false);
|
|
|
|
|
setLoggedHidden(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleConnectionCleaned = (event) => {
|
2025-09-04 17:25:01 -04:00
|
|
|
setConnectionBroken(true);
|
2025-08-16 20:58:42 -04:00
|
|
|
setCurrentTime(0);
|
|
|
|
|
setShowExpiredMessage(false);
|
|
|
|
|
setInitialized(false);
|
|
|
|
|
setLoggedHidden(false);
|
2025-08-14 23:34:54 -04:00
|
|
|
};
|
|
|
|
|
|
2025-08-17 20:38:47 -04:00
|
|
|
const handleSessionReset = (event) => {
|
|
|
|
|
setConnectionBroken(true);
|
|
|
|
|
setCurrentTime(0);
|
|
|
|
|
setShowExpiredMessage(false);
|
|
|
|
|
setInitialized(false);
|
|
|
|
|
setLoggedHidden(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSessionCleanup = (event) => {
|
|
|
|
|
setConnectionBroken(true);
|
|
|
|
|
setCurrentTime(0);
|
|
|
|
|
setShowExpiredMessage(false);
|
|
|
|
|
setInitialized(false);
|
|
|
|
|
setLoggedHidden(false);
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-04 17:25:01 -04:00
|
|
|
const handleDisconnected = (event) => {
|
|
|
|
|
setConnectionBroken(true);
|
|
|
|
|
setCurrentTime(0);
|
|
|
|
|
setShowExpiredMessage(false);
|
|
|
|
|
setInitialized(false);
|
|
|
|
|
setLoggedHidden(false);
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
document.addEventListener('session-timer-update', handleSessionTimerUpdate);
|
|
|
|
|
document.addEventListener('force-header-update', handleForceHeaderUpdate);
|
|
|
|
|
document.addEventListener('peer-disconnect', handlePeerDisconnect);
|
|
|
|
|
document.addEventListener('new-connection', handleNewConnection);
|
2025-08-16 20:58:42 -04:00
|
|
|
document.addEventListener('connection-cleaned', handleConnectionCleaned);
|
2025-08-17 20:38:47 -04:00
|
|
|
document.addEventListener('session-reset', handleSessionReset);
|
|
|
|
|
document.addEventListener('session-cleanup', handleSessionCleanup);
|
2025-09-04 17:25:01 -04:00
|
|
|
document.addEventListener('disconnected', handleDisconnected);
|
2025-08-14 23:34:54 -04:00
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
document.removeEventListener('session-timer-update', handleSessionTimerUpdate);
|
|
|
|
|
document.removeEventListener('force-header-update', handleForceHeaderUpdate);
|
|
|
|
|
document.removeEventListener('peer-disconnect', handlePeerDisconnect);
|
|
|
|
|
document.removeEventListener('new-connection', handleNewConnection);
|
2025-08-16 20:58:42 -04:00
|
|
|
document.removeEventListener('connection-cleaned', handleConnectionCleaned);
|
2025-08-17 20:38:47 -04:00
|
|
|
document.removeEventListener('session-reset', handleSessionReset);
|
|
|
|
|
document.removeEventListener('session-cleanup', handleSessionCleanup);
|
2025-09-04 17:25:01 -04:00
|
|
|
document.removeEventListener('disconnected', handleDisconnected);
|
2025-08-14 23:34:54 -04:00
|
|
|
};
|
2025-08-16 20:58:42 -04:00
|
|
|
}, [sessionManager]);
|
2025-08-14 23:34:54 -04:00
|
|
|
|
|
|
|
|
if (showExpiredMessage) {
|
|
|
|
|
return React.createElement('div', {
|
|
|
|
|
className: 'session-timer expired flex items-center space-x-2 px-3 py-1.5 rounded-lg animate-pulse',
|
|
|
|
|
style: { background: 'linear-gradient(135deg, rgba(239, 68, 68, 0.2) 0%, rgba(220, 38, 38, 0.2) 100%)' }
|
|
|
|
|
}, [
|
|
|
|
|
React.createElement('i', {
|
|
|
|
|
key: 'icon',
|
|
|
|
|
className: 'fas fa-exclamation-triangle text-red-400'
|
|
|
|
|
}),
|
|
|
|
|
React.createElement('span', {
|
|
|
|
|
key: 'message',
|
|
|
|
|
className: 'text-red-400 text-sm font-medium'
|
|
|
|
|
}, 'Session Expired!')
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!sessionManager) {
|
2025-08-16 20:58:42 -04:00
|
|
|
if (!loggedHidden) {
|
|
|
|
|
console.log('⏱️ SessionTimer hidden - no sessionManager');
|
|
|
|
|
setLoggedHidden(true);
|
|
|
|
|
}
|
2025-08-14 23:34:54 -04:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (connectionBroken) {
|
2025-08-16 20:58:42 -04:00
|
|
|
if (!loggedHidden) {
|
|
|
|
|
console.log('⏱️ SessionTimer hidden - connection broken');
|
|
|
|
|
setLoggedHidden(true);
|
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
if (!currentTime || currentTime <= 0) {
|
2025-08-16 20:58:42 -04:00
|
|
|
if (!loggedHidden) {
|
2025-08-17 20:38:47 -04:00
|
|
|
console.log('⏱️ SessionTimer hidden - no time left, currentTime:', currentTime);
|
2025-08-16 20:58:42 -04:00
|
|
|
setLoggedHidden(true);
|
|
|
|
|
}
|
2025-08-14 23:34:54 -04:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-16 20:58:42 -04:00
|
|
|
if (loggedHidden) {
|
|
|
|
|
setLoggedHidden(false);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
const totalMinutes = Math.floor(currentTime / (60 * 1000));
|
|
|
|
|
const totalSeconds = Math.floor(currentTime / 1000);
|
|
|
|
|
|
|
|
|
|
const isDemo = sessionType === 'demo';
|
|
|
|
|
const isWarning = isDemo ? totalMinutes <= 2 : totalMinutes <= 10;
|
|
|
|
|
const isCritical = isDemo ? totalSeconds <= 60 : totalMinutes <= 5;
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
|
|
|
const formatTime = (ms) => {
|
|
|
|
|
const hours = Math.floor(ms / (60 * 60 * 1000));
|
|
|
|
|
const minutes = Math.floor((ms % (60 * 60 * 1000)) / (60 * 1000));
|
|
|
|
|
const seconds = Math.floor((ms % (60 * 1000)) / 1000);
|
|
|
|
|
|
|
|
|
|
if (hours > 0) {
|
|
|
|
|
return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
|
|
|
|
} else {
|
|
|
|
|
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
const getTimerStyle = () => {
|
2025-08-16 20:58:42 -04:00
|
|
|
const totalDuration = sessionType === 'demo' ? 6 * 60 * 1000 : 60 * 60 * 1000;
|
|
|
|
|
const timeProgress = (totalDuration - currentTime) / totalDuration;
|
2025-08-14 23:34:54 -04:00
|
|
|
|
|
|
|
|
let backgroundColor, textColor, iconColor, iconClass, shouldPulse;
|
|
|
|
|
|
|
|
|
|
if (timeProgress <= 0.33) {
|
|
|
|
|
backgroundColor = 'linear-gradient(135deg, rgba(34, 197, 94, 0.15) 0%, rgba(22, 163, 74, 0.15) 100%)';
|
|
|
|
|
textColor = 'text-green-400';
|
|
|
|
|
iconColor = 'text-green-400';
|
|
|
|
|
iconClass = 'fas fa-clock';
|
|
|
|
|
shouldPulse = false;
|
|
|
|
|
} else if (timeProgress <= 0.66) {
|
|
|
|
|
backgroundColor = 'linear-gradient(135deg, rgba(234, 179, 8, 0.15) 0%, rgba(202, 138, 4, 0.15) 100%)';
|
|
|
|
|
textColor = 'text-yellow-400';
|
|
|
|
|
iconColor = 'text-yellow-400';
|
|
|
|
|
iconClass = 'fas fa-clock';
|
|
|
|
|
shouldPulse = false;
|
|
|
|
|
} else {
|
|
|
|
|
backgroundColor = 'linear-gradient(135deg, rgba(239, 68, 68, 0.15) 0%, rgba(220, 38, 38, 0.15) 100%)';
|
|
|
|
|
textColor = 'text-red-400';
|
|
|
|
|
iconColor = 'text-red-400';
|
|
|
|
|
iconClass = 'fas fa-exclamation-triangle';
|
|
|
|
|
shouldPulse = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { backgroundColor, textColor, iconColor, iconClass, shouldPulse };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const timerStyle = getTimerStyle();
|
|
|
|
|
|
2025-09-04 17:25:01 -04:00
|
|
|
const handleTimerClick = () => {
|
|
|
|
|
if (onDisconnect && typeof onDisconnect === 'function') {
|
|
|
|
|
onDisconnect();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
return React.createElement('div', {
|
2025-09-04 17:25:01 -04:00
|
|
|
className: `session-timer flex items-center space-x-2 px-3 py-1.5 rounded-lg transition-all duration-500 cursor-pointer hover:opacity-80 ${
|
2025-08-14 23:34:54 -04:00
|
|
|
isDemo ? 'demo-session' : ''
|
|
|
|
|
} ${timerStyle.shouldPulse ? 'animate-pulse' : ''}`,
|
2025-09-04 17:25:01 -04:00
|
|
|
style: { background: timerStyle.backgroundColor },
|
|
|
|
|
onClick: handleTimerClick,
|
|
|
|
|
title: 'Click to disconnect and clear session'
|
2025-08-11 20:52:14 -04:00
|
|
|
}, [
|
|
|
|
|
React.createElement('i', {
|
|
|
|
|
key: 'icon',
|
2025-08-14 23:34:54 -04:00
|
|
|
className: `${timerStyle.iconClass} ${timerStyle.iconColor}`
|
2025-08-11 20:52:14 -04:00
|
|
|
}),
|
|
|
|
|
React.createElement('span', {
|
2025-08-14 23:34:54 -04:00
|
|
|
key: 'time',
|
|
|
|
|
className: `text-sm font-mono font-semibold ${timerStyle.textColor}`
|
|
|
|
|
}, formatTime(currentTime)),
|
|
|
|
|
React.createElement('div', {
|
|
|
|
|
key: 'progress',
|
|
|
|
|
className: 'ml-2 w-16 h-1 bg-gray-700 rounded-full overflow-hidden'
|
|
|
|
|
}, [
|
|
|
|
|
React.createElement('div', {
|
|
|
|
|
key: 'progress-bar',
|
|
|
|
|
className: `${timerStyle.textColor.replace('text-', 'bg-')} h-full rounded-full transition-all duration-500`,
|
|
|
|
|
style: {
|
|
|
|
|
width: `${Math.max(0, Math.min(100, (currentTime / (sessionType === 'demo' ? 6 * 60 * 1000 : 60 * 60 * 1000)) * 100))}%`
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
])
|
2025-08-11 20:52:14 -04:00
|
|
|
]);
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
window.SessionTimer = SessionTimer;
|
|
|
|
|
|
|
|
|
|
window.updateSessionTimer = (newTimeLeft, newSessionType) => {
|
|
|
|
|
document.dispatchEvent(new CustomEvent('session-timer-update', {
|
|
|
|
|
detail: { timeLeft: newTimeLeft, sessionType: newSessionType }
|
|
|
|
|
}));
|
|
|
|
|
};
|
|
|
|
|
|