feat: Enhanced demo mode security and vulnerability fixes

- **Fixed demo mode timing attack vulnerability** - Added strict rate limiting and user fingerprinting
- **Eliminated replay attack vectors** - Implemented preimage tracking and expiration validation
- **Enhanced key reuse protection** - Added cryptographic validation and session isolation
- **Strengthened free tier abuse prevention** - Multi-layer cooldown system with global limits

- **Secure user fingerprinting** - Browser-based identification without privacy invasion
- **Global session limits** - Maximum 10 concurrent demo sessions across all users
- **Per-user daily limits** - 3 demo sessions per 24 hours with smart cooldown
- **Session completion tracking** - Prevents rapid reconnection abuse
- **Enhanced preimage generation** - Timestamped, versioned, and entropy-validated

- **Configurable security layers** - Individual toggle for encryption, obfuscation, and traffic features
- **Debug mode controls** - `window.DEBUG_MODE` for detailed logging and diagnostics
- **Emergency security disable** - Graceful fallback when advanced features cause issues
- **Vulnerability testing support** - Controlled security layer bypass for penetration testing

- **Cross-session compatibility** - Works seamlessly with both paid and free sessions
- **Real-time UI updates** - Synchronized timer display across all components
- **Session state management** - Automatic cleanup and notification system
- **Payment integration** - Smooth transition between demo and paid sessions

- **Layered security architecture** - 7+ configurable security features with independent controls
- **Traffic analysis protection** - Advanced obfuscation with fake traffic and packet padding
- **Connection state monitoring** - Enhanced logging for security audit and debugging
- **Fallback mechanisms** - Robust error handling with security-first degradation

- **Structured security logs** - Detailed audit trail for security events
- **Performance monitoring** - Connection state and encryption layer metrics
- **Attack detection logging** - Comprehensive tracking of security violations
- **Development diagnostics** - Enhanced debugging for faster development cycles

- Refactored `PayPerSessionManager` with enhanced security controls
- Added `generateUserFingerprint()` with privacy-preserving identification
- Implemented `checkDemoSessionLimits()` with multi-tier validation
- Enhanced `EnhancedSecureWebRTCManager` with configurable security layers
- Added emergency security disable functionality for testing environments
- Improved session timer with cross-component synchronization

**Breaking Changes:** None - All changes are backward compatible
**Security Impact:** High - Eliminates critical vulnerabilities in free tier
**Testing Impact:** Significantly improved - New debug modes and security layer controls
This commit is contained in:
lockbitchat
2025-08-14 23:34:54 -04:00
parent 19e3047282
commit 5437bef9c5
7 changed files with 2211 additions and 901 deletions

View File

@@ -1,5 +1,3 @@
const React = window.React;
const EnhancedMinimalHeader = ({
status,
fingerprint,
@@ -10,14 +8,80 @@ const EnhancedMinimalHeader = ({
sessionManager,
sessionTimeLeft
}) => {
const [currentTimeLeft, setCurrentTimeLeft] = React.useState(sessionTimeLeft || 0);
const [hasActiveSession, setHasActiveSession] = React.useState(false);
const [sessionType, setSessionType] = React.useState('unknown');
React.useEffect(() => {
const updateSessionInfo = () => {
if (sessionManager) {
const isActive = sessionManager.hasActiveSession();
const timeLeft = sessionManager.getTimeLeft();
const currentSession = sessionManager.currentSession;
setHasActiveSession(isActive);
setCurrentTimeLeft(timeLeft);
setSessionType(currentSession?.type || 'unknown');
}
};
updateSessionInfo();
const interval = setInterval(updateSessionInfo, 1000);
return () => clearInterval(interval);
}, [sessionManager]);
React.useEffect(() => {
if (sessionManager?.hasActiveSession()) {
setCurrentTimeLeft(sessionManager.getTimeLeft());
setHasActiveSession(true);
} else {
setHasActiveSession(false);
}
}, [sessionManager, sessionTimeLeft]);
const handleSecurityClick = () => {
if (securityLevel?.verificationResults) {
alert('Security check details:\n\n' +
Object.entries(securityLevel.verificationResults)
.map(([key, result]) => `${key}: ${result.passed ? '✅' : '❌'} ${result.details}`)
.join('\n')
);
} else if (securityLevel) {
alert(`Security Level: ${securityLevel.level}\nScore: ${securityLevel.score}%\nDetails: ${securityLevel.details || 'No additional details available'}`);
}
};
const shouldShowTimer = hasActiveSession && currentTimeLeft > 0 && window.SessionTimer;
React.useEffect(() => {
const handleForceUpdate = (event) => {
if (sessionManager) {
const isActive = sessionManager.hasActiveSession();
const timeLeft = sessionManager.getTimeLeft();
const currentSession = sessionManager.currentSession;
setHasActiveSession(isActive);
setCurrentTimeLeft(timeLeft);
setSessionType(currentSession?.type || 'unknown');
}
};
document.addEventListener('force-header-update', handleForceUpdate);
return () => document.removeEventListener('force-header-update', handleForceUpdate);
}, [sessionManager]);
const getStatusConfig = () => {
switch (status) {
case 'connected':
return {
text: 'Connected',
className: 'status-connected',
badgeClass: 'bg-green-500/10 text-green-400 border-green-500/20'
};
text: 'Connected',
className: 'status-connected',
badgeClass: 'bg-green-500/10 text-green-400 border-green-500/20'
};
case 'verifying':
return {
text: 'Verifying...',
@@ -60,22 +124,11 @@ const EnhancedMinimalHeader = ({
className: 'status-disconnected',
badgeClass: 'bg-gray-500/10 text-gray-400 border-gray-500/20'
};
}
};
const config = getStatusConfig();
const handleSecurityClick = () => {
if (securityLevel?.verificationResults) {
alert('Security check details:\n\n' +
Object.entries(securityLevel.verificationResults)
.map(([key, result]) => `${key}: ${result.passed ? '✅' : '❌'} ${result.details}`)
.join('\n')
);
}
};
return React.createElement('header', {
className: 'header-minimal sticky top-0 z-50'
}, [
@@ -87,6 +140,7 @@ const EnhancedMinimalHeader = ({
key: 'content',
className: 'flex items-center justify-between h-16'
}, [
// Logo and Title
React.createElement('div', {
key: 'logo-section',
className: 'flex items-center space-x-2 sm:space-x-3'
@@ -109,32 +163,29 @@ const EnhancedMinimalHeader = ({
React.createElement('p', {
key: 'subtitle',
className: 'text-xs sm:text-sm text-muted hidden sm:block'
}, 'End-to-end freedom. v4.0.02.12')
}, 'End-to-end freedom. v4.0.02.88')
])
]),
// Status and Controls - Mobile Responsive
// Status and Controls - Responsive
React.createElement('div', {
key: 'status-section',
className: 'flex items-center space-x-2 sm:space-x-3'
}, [
(() => {
const hasActive = sessionManager?.hasActiveSession();
const hasTimer = !!window.SessionTimer;
return hasActive && hasTimer && React.createElement(window.SessionTimer, {
key: 'session-timer',
timeLeft: sessionTimeLeft,
sessionType: sessionManager.currentSession?.type || 'unknown'
});
})(),
// Session Timer
shouldShowTimer && React.createElement(window.SessionTimer, {
key: 'session-timer',
timeLeft: currentTimeLeft,
sessionType: sessionType,
sessionManager: sessionManager
}),
// Security Level Indicator - Hidden on mobile, shown on tablet+ (Clickable)
// Security Level Indicator
securityLevel && React.createElement('div', {
key: 'security-level',
className: 'hidden md:flex items-center space-x-2 cursor-pointer hover:opacity-80 transition-opacity duration-200',
onClick: handleSecurityClick,
title: 'Click to view security details'
title: `${securityLevel.level} (${securityLevel.score}%) - Click for details`
}, [
React.createElement('div', {
key: 'security-icon',
@@ -178,7 +229,7 @@ const EnhancedMinimalHeader = ({
])
]),
// Mobile Security Indicator - Only icon on mobile (Clickable)
// Mobile Security Indicator
securityLevel && React.createElement('div', {
key: 'mobile-security',
className: 'md:hidden flex items-center'
@@ -195,13 +246,13 @@ const EnhancedMinimalHeader = ({
React.createElement('i', {
className: `fas fa-shield-alt text-sm ${
securityLevel.color === 'green' ? 'text-green-400' :
securityLevel.color === 'yellow' ? 'text-yellow-400' : 'text-red-400'
securityLevel.color === 'yellow' ? 'text-yellow-400' : 'bg-red-400'
}`
})
])
]),
// Status Badge - Compact on mobile
// Status Badge
React.createElement('div', {
key: 'status-badge',
className: `px-2 sm:px-3 py-1.5 rounded-lg border ${config.badgeClass} flex items-center space-x-1 sm:space-x-2`
@@ -216,18 +267,16 @@ const EnhancedMinimalHeader = ({
}, config.text)
]),
// Disconnect Button - Icon only on mobile
// Disconnect Button
isConnected && React.createElement('button', {
key: 'disconnect-btn',
onClick: onDisconnect,
className: 'p-1.5 sm:px-3 sm:py-1.5 bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/20 rounded-lg transition-all duration-200 text-sm'
}, [
React.createElement('i', {
key: 'disconnect-icon',
className: 'fas fa-power-off sm:mr-2'
}),
React.createElement('span', {
key: 'disconnect-text',
className: 'hidden sm:inline'
}, 'Disconnect')
])
@@ -237,4 +286,6 @@ const EnhancedMinimalHeader = ({
]);
};
window.EnhancedMinimalHeader = EnhancedMinimalHeader;
window.EnhancedMinimalHeader = EnhancedMinimalHeader;
console.log('✅ EnhancedMinimalHeader loaded with timer fixes');

View File

@@ -1,13 +1,170 @@
const React = window.React;
const SessionTimer = ({ timeLeft, sessionType, sessionManager }) => {
const [currentTime, setCurrentTime] = React.useState(timeLeft || 0);
const [showExpiredMessage, setShowExpiredMessage] = React.useState(false);
const [initialized, setInitialized] = React.useState(false);
const [connectionBroken, setConnectionBroken] = React.useState(false);
const SessionTimer = ({ timeLeft, sessionType }) => {
if (!timeLeft || timeLeft <= 0) {
React.useEffect(() => {
if (connectionBroken) {
console.log('⏱️ SessionTimer initialization skipped - connection broken');
return;
}
let initialTime = 0;
if (sessionManager?.hasActiveSession()) {
initialTime = sessionManager.getTimeLeft();
console.log('⏱️ SessionTimer initialized from sessionManager:', Math.floor(initialTime / 1000) + 's');
} else if (timeLeft && timeLeft > 0) {
initialTime = timeLeft;
console.log('⏱️ SessionTimer initialized from props:', Math.floor(initialTime / 1000) + 's');
}
setCurrentTime(initialTime);
setInitialized(true);
}, [sessionManager, connectionBroken]);
React.useEffect(() => {
if (connectionBroken) {
console.log('⏱️ SessionTimer props update skipped - connection broken');
return;
}
if (timeLeft && timeLeft > 0) {
setCurrentTime(timeLeft);
}
}, [timeLeft, connectionBroken]);
React.useEffect(() => {
if (!initialized) {
return;
}
if (connectionBroken) {
console.log('⏱️ Timer interval skipped - connection broken');
return;
}
if (!currentTime || currentTime <= 0 || !sessionManager) {
return;
}
const interval = setInterval(() => {
if (connectionBroken) {
console.log('⏱️ Timer interval stopped - connection broken');
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) {
console.log('⏱️ Session expired!');
setShowExpiredMessage(true);
setTimeout(() => setShowExpiredMessage(false), 5000);
clearInterval(interval);
}
} else {
console.log('⏱️ Session inactive, stopping timer');
setCurrentTime(0);
clearInterval(interval);
}
}, 1000);
return () => {
clearInterval(interval);
};
}, [initialized, currentTime, sessionManager, connectionBroken]);
React.useEffect(() => {
const handleSessionTimerUpdate = (event) => {
if (event.detail.timeLeft && event.detail.timeLeft > 0) {
setCurrentTime(event.detail.timeLeft);
}
};
const handleForceHeaderUpdate = (event) => {
if (sessionManager && sessionManager.hasActiveSession()) {
const newTime = sessionManager.getTimeLeft();
setCurrentTime(newTime);
}
};
const handlePeerDisconnect = (event) => {
console.log('🔌 Peer disconnect detected in SessionTimer - stopping timer permanently');
setConnectionBroken(true);
setCurrentTime(0);
setShowExpiredMessage(false);
};
const handleNewConnection = (event) => {
console.log('🔌 New connection detected in SessionTimer - resetting connection state');
setConnectionBroken(false);
};
document.addEventListener('session-timer-update', handleSessionTimerUpdate);
document.addEventListener('force-header-update', handleForceHeaderUpdate);
document.addEventListener('peer-disconnect', handlePeerDisconnect);
document.addEventListener('new-connection', handleNewConnection);
return () => {
document.removeEventListener('session-timer-update', handleSessionTimerUpdate);
document.removeEventListener('force-header-update', handleForceHeaderUpdate);
document.removeEventListener('peer-disconnect', handlePeerDisconnect);
document.removeEventListener('new-connection', handleNewConnection);
};
}, [sessionManager]);
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) {
console.log('⏱️ SessionTimer hidden - no sessionManager');
return null;
}
const totalMinutes = Math.floor(timeLeft / (60 * 1000));
const isWarning = totalMinutes <= 10;
const isCritical = totalMinutes <= 5;
if (connectionBroken) {
console.log('⏱️ SessionTimer hidden - connection broken');
return null;
}
if (!currentTime || currentTime <= 0) {
console.log('⏱️ SessionTimer hidden - no time left');
return null;
}
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;
const formatTime = (ms) => {
const hours = Math.floor(ms / (60 * 60 * 1000));
@@ -21,21 +178,73 @@ const SessionTimer = ({ timeLeft, sessionType }) => {
}
};
const getTimerStyle = () => {
const totalDuration = sessionType === 'demo' ? 6 * 60 * 1000 : 60 * 60 * 1000;
const timeProgress = (totalDuration - currentTime) / totalDuration;
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();
return React.createElement('div', {
className: `session-timer ${isCritical ? 'critical' : isWarning ? 'warning' : ''}`
className: `session-timer flex items-center space-x-2 px-3 py-1.5 rounded-lg transition-all duration-500 ${
isDemo ? 'demo-session' : ''
} ${timerStyle.shouldPulse ? 'animate-pulse' : ''}`,
style: { background: timerStyle.backgroundColor }
}, [
React.createElement('i', {
key: 'icon',
className: 'fas fa-clock'
className: `${timerStyle.iconClass} ${timerStyle.iconColor}`
}),
React.createElement('span', {
key: 'time'
}, formatTime(timeLeft)),
React.createElement('span', {
key: 'type',
className: 'text-xs opacity-80'
}, sessionType?.toUpperCase() || '')
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))}%`
}
})
])
]);
};
window.SessionTimer = SessionTimer;
window.SessionTimer = SessionTimer;
window.updateSessionTimer = (newTimeLeft, newSessionType) => {
console.log('⏱️ Global timer update:', { newTimeLeft, newSessionType });
document.dispatchEvent(new CustomEvent('session-timer-update', {
detail: { timeLeft: newTimeLeft, sessionType: newSessionType }
}));
};
console.log('✅ SessionTimer loaded with fixes and improvements');

View File

@@ -1,17 +1,46 @@
const React = window.React;
const SessionTypeSelector = ({ onSelectType, onCancel, sessionManager }) => {
const [selectedType, setSelectedType] = React.useState(null);
const [demoInfo, setDemoInfo] = React.useState(null);
const [refreshTimer, setRefreshTimer] = React.useState(null);
const [lastRefresh, setLastRefresh] = React.useState(Date.now());
// Получаем информацию о demo лимитах при загрузке
React.useEffect(() => {
// We receive up-to-date information about demo limits
const updateDemoInfo = React.useCallback(() => {
if (sessionManager && sessionManager.getDemoSessionInfo) {
const info = sessionManager.getDemoSessionInfo();
setDemoInfo(info);
try {
const info = sessionManager.getDemoSessionInfo();
if (window.DEBUG_MODE) {
console.log('🔄 Demo info updated:', info);
}
setDemoInfo(info);
setLastRefresh(Date.now());
} catch (error) {
console.error('Failed to get demo info:', error);
}
}
}, [sessionManager]);
// Update information on load and every 10 seconds
React.useEffect(() => {
updateDemoInfo();
const interval = setInterval(updateDemoInfo, 10000);
setRefreshTimer(interval);
return () => {
if (interval) clearInterval(interval);
};
}, [updateDemoInfo]);
// Clear timer on unmount
React.useEffect(() => {
return () => {
if (refreshTimer) {
clearInterval(refreshTimer);
}
};
}, [refreshTimer]);
const sessionTypes = [
{
id: 'demo',
@@ -19,16 +48,17 @@ const SessionTypeSelector = ({ onSelectType, onCancel, sessionManager }) => {
duration: '6 minutes',
price: '0 sat',
usd: '$0.00',
popular: true,
popular: false,
description: 'Limited testing session',
warning: demoInfo ? `Available: ${demoInfo.available}/${demoInfo.total}` : 'Loading...'
features: ['End-to-end encryption', 'Basic features', 'No payment required']
},
{
id: 'basic',
name: 'Basic',
duration: '1 hour',
price: '500 sat',
usd: '$0.20'
usd: '$0.20',
features: ['End-to-end encryption', 'Full features', '1 hour duration']
},
{
id: 'premium',
@@ -36,81 +66,149 @@ const SessionTypeSelector = ({ onSelectType, onCancel, sessionManager }) => {
duration: '4 hours',
price: '1000 sat',
usd: '$0.40',
popular: true
popular: true,
features: ['End-to-end encryption', 'Full features', '4 hours duration', 'Priority support']
},
{
id: 'extended',
name: 'Extended',
duration: '24 hours',
price: '2000 sat',
usd: '$0.80'
usd: '$0.80',
features: ['End-to-end encryption', 'Full features', '24 hours duration', 'Priority support']
}
];
const handleTypeSelect = (typeId) => {
console.log(`🎯 Selecting session type: ${typeId}`);
if (typeId === 'demo') {
// Проверяем доступность demo сессии
if (demoInfo && !demoInfo.canUseNow) {
alert(`Demo session not available now. ${demoInfo.nextAvailable}`);
let message = `Demo session not available.\n\n`;
if (demoInfo.blockingReason === 'global_limit') {
message += `Reason: Too many global demo sessions active (${demoInfo.globalActive}/${demoInfo.globalLimit})\n`;
message += `Please try again in a few minutes.`;
} else if (demoInfo.blockingReason === 'daily_limit') {
message += `Reason: Daily limit reached (${demoInfo.used}/${demoInfo.total})\n`;
message += `Next available: ${demoInfo.nextAvailable}`;
} else if (demoInfo.blockingReason === 'session_cooldown') {
message += `Reason: Cooldown between sessions\n`;
message += `Next available: ${demoInfo.nextAvailable}`;
} else if (demoInfo.blockingReason === 'completion_cooldown') {
message += `Reason: Wait period after last session\n`;
message += `Next available: ${demoInfo.nextAvailable}`;
} else {
message += `Next available: ${demoInfo.nextAvailable}`;
}
alert(message);
return;
}
}
setSelectedType(typeId);
};
const formatCooldownTime = (minutes) => {
if (minutes >= 60) {
const hours = Math.floor(minutes / 60);
const remainingMinutes = minutes % 60;
return `${hours}h ${remainingMinutes}m`;
}
return `${minutes}m`;
};
return React.createElement('div', { className: 'space-y-6' }, [
React.createElement('div', { key: 'header', className: 'text-center' }, [
React.createElement('h3', {
key: 'title',
className: 'text-xl font-semibold text-white mb-2'
}, 'Choose a plan'),
}, 'Choose Your Session'),
React.createElement('p', {
key: 'subtitle',
className: 'text-gray-300 text-sm'
}, 'Pay via Lightning Network or use limited demo session')
}, 'Pay via Lightning Network or try our demo session')
]),
React.createElement('div', { key: 'types', className: 'space-y-3' },
sessionTypes.map(type =>
React.createElement('div', {
sessionTypes.map(type => {
const isDemo = type.id === 'demo';
const isDisabled = isDemo && demoInfo && !demoInfo.canUseNow;
return React.createElement('div', {
key: type.id,
onClick: () => handleTypeSelect(type.id),
className: `card-minimal rounded-lg p-4 cursor-pointer border-2 transition-all ${
onClick: () => !isDisabled && handleTypeSelect(type.id),
className: `card-minimal rounded-lg p-4 border-2 transition-all ${
selectedType === type.id ? 'border-orange-500 bg-orange-500/10' : 'border-gray-600 hover:border-orange-400'
} ${type.popular ? 'relative' : ''} ${
type.id === 'demo' && demoInfo && !demoInfo.canUseNow ? 'opacity-50 cursor-not-allowed' : ''
isDisabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'
}`
}, [
type.popular && React.createElement('div', {
key: 'badge',
className: 'absolute -top-2 right-3 bg-orange-500 text-white text-xs px-2 py-1 rounded-full'
}, type.id === 'demo' ? 'Demo' : 'Popular'),
}, 'Popular'),
React.createElement('div', { key: 'content', className: 'flex items-center justify-between' }, [
React.createElement('div', { key: 'info' }, [
React.createElement('h4', {
key: 'name',
className: 'text-lg font-semibold text-white'
}, type.name),
React.createElement('div', { key: 'content', className: 'flex items-start justify-between' }, [
React.createElement('div', { key: 'info', className: 'flex-1' }, [
React.createElement('div', { key: 'header', className: 'flex items-center gap-2 mb-2' }, [
React.createElement('h4', {
key: 'name',
className: 'text-lg font-semibold text-white'
}, type.name),
isDemo && React.createElement('span', {
key: 'demo-badge',
className: 'text-xs bg-blue-500/20 text-blue-300 px-2 py-1 rounded-full'
}, 'FREE')
]),
React.createElement('p', {
key: 'duration',
className: 'text-gray-300 text-sm'
}, type.duration),
className: 'text-gray-300 text-sm mb-1'
}, `Duration: ${type.duration}`),
type.description && React.createElement('p', {
key: 'description',
className: 'text-xs text-gray-400 mt-1'
className: 'text-xs text-gray-400 mb-2'
}, type.description),
type.id === 'demo' && React.createElement('p', {
key: 'warning',
className: `text-xs mt-1 ${
demoInfo && demoInfo.canUseNow ? 'text-green-400' : 'text-yellow-400'
}`
}, type.warning)
isDemo && demoInfo && React.createElement('div', {
key: 'demo-status',
className: 'text-xs mb-2'
}, [
React.createElement('div', {
key: 'availability',
className: demoInfo.canUseNow ? 'text-green-400' : 'text-yellow-400'
}, demoInfo.canUseNow ?
`✅ Available (${demoInfo.available}/${demoInfo.total} today)` :
`⏰ Next: ${demoInfo.nextAvailable}`
),
demoInfo.globalActive > 0 && React.createElement('div', {
key: 'global-status',
className: 'text-blue-300 mt-1'
}, `🌐 Global: ${demoInfo.globalActive}/${demoInfo.globalLimit} active`)
]),
type.features && React.createElement('div', {
key: 'features',
className: 'text-xs text-gray-400 space-y-1'
}, type.features.map((feature, index) =>
React.createElement('div', {
key: index,
className: 'flex items-center gap-1'
}, [
React.createElement('i', {
key: 'check',
className: 'fas fa-check text-green-400 w-3'
}),
React.createElement('span', {
key: 'text'
}, feature)
])
))
]),
React.createElement('div', { key: 'pricing', className: 'text-right' }, [
React.createElement('div', {
key: 'sats',
className: 'text-lg font-bold text-orange-400'
className: `text-lg font-bold ${isDemo ? 'text-green-400' : 'text-orange-400'}`
}, type.price),
React.createElement('div', {
key: 'usd',
@@ -119,49 +217,81 @@ const SessionTypeSelector = ({ onSelectType, onCancel, sessionManager }) => {
])
])
])
)
})
),
// Информация о demo лимитах
demoInfo && React.createElement('div', {
key: 'demo-info',
className: 'bg-blue-900/20 border border-blue-700 rounded-lg p-3'
className: 'bg-gradient-to-r from-blue-900/20 to-purple-900/20 border border-blue-700/50 rounded-lg p-4'
}, [
React.createElement('div', {
key: 'demo-header',
className: 'text-blue-300 text-sm font-medium mb-2'
}, '📱 Demo Session Limits'),
className: 'flex items-center gap-2 text-blue-300 text-sm font-medium mb-3'
}, [
React.createElement('i', {
key: 'icon',
className: 'fas fa-info-circle'
}),
React.createElement('span', {
key: 'title'
}, 'Demo Session Information')
]),
React.createElement('div', {
key: 'demo-details',
className: 'text-blue-200 text-xs space-y-1'
className: 'grid grid-cols-1 md:grid-cols-2 gap-3 text-blue-200 text-xs'
}, [
React.createElement('div', { key: 'limit' },
`• Maximum ${demoInfo.total} demo sessions per day`),
React.createElement('div', { key: 'cooldown' },
`• 5 minutes between sessions, 1 hour between series`),
React.createElement('div', { key: 'duration' },
`• Each session limited to ${demoInfo.durationMinutes} minutes`),
React.createElement('div', { key: 'status' },
`• Status: ${demoInfo.canUseNow ? 'Available now' : `Next available: ${demoInfo.nextAvailable}`}`)
])
React.createElement('div', { key: 'limits', className: 'space-y-1' }, [
React.createElement('div', { key: 'daily' }, `📅 Daily limit: ${demoInfo.total} sessions`),
React.createElement('div', { key: 'duration' }, `⏱️ Duration: ${demoInfo.durationMinutes} minutes each`),
React.createElement('div', { key: 'cooldown' }, `⏰ Cooldown: ${demoInfo.sessionCooldownMinutes} min between sessions`)
]),
React.createElement('div', { key: 'status', className: 'space-y-1' }, [
React.createElement('div', { key: 'used' }, `📊 Used today: ${demoInfo.used}/${demoInfo.total}`),
React.createElement('div', { key: 'global' }, `🌐 Global active: ${demoInfo.globalActive}/${demoInfo.globalLimit}`),
React.createElement('div', {
key: 'next',
className: demoInfo.canUseNow ? 'text-green-300' : 'text-yellow-300'
}, `🎯 Status: ${demoInfo.canUseNow ? 'Available now' : demoInfo.nextAvailable}`)
])
]),
React.createElement('div', {
key: 'last-updated',
className: 'text-xs text-gray-400 mt-3 text-center'
}, `Last updated: ${new Date(lastRefresh).toLocaleTimeString()}`)
]),
React.createElement('div', { key: 'buttons', className: 'flex space-x-3' }, [
React.createElement('button', {
key: 'continue',
onClick: () => selectedType && onSelectType(selectedType),
onClick: () => {
if (selectedType) {
console.log(`🚀 Proceeding with session type: ${selectedType}`);
onSelectType(selectedType);
}
},
disabled: !selectedType || (selectedType === 'demo' && demoInfo && !demoInfo.canUseNow),
className: 'flex-1 lightning-button text-white py-3 px-4 rounded-lg font-medium disabled:opacity-50'
className: 'flex-1 lightning-button text-white py-3 px-4 rounded-lg font-medium disabled:opacity-50 disabled:cursor-not-allowed transition-all'
}, [
React.createElement('i', { className: 'fas fa-bolt mr-2' }),
selectedType === 'demo' ? 'Start Demo Session' : 'Continue to payment'
React.createElement('i', {
key: 'icon',
className: selectedType === 'demo' ? 'fas fa-play mr-2' : 'fas fa-bolt mr-2'
}),
selectedType === 'demo' ? 'Start Demo Session' : 'Continue to Payment'
]),
React.createElement('button', {
key: 'cancel',
onClick: onCancel,
className: 'px-6 py-3 bg-gray-600 hover:bg-gray-500 text-white rounded-lg'
}, 'Cancel')
])
className: 'px-6 py-3 bg-gray-600 hover:bg-gray-500 text-white rounded-lg transition-all'
}, 'Cancel'),
React.createElement('button', {
key: 'refresh',
onClick: updateDemoInfo,
className: 'px-3 py-3 bg-blue-600 hover:bg-blue-500 text-white rounded-lg transition-all',
title: 'Refresh demo status'
}, React.createElement('i', { className: 'fas fa-sync-alt' }))
]),
]);
};