Major Security Improvements:
- Enhanced user fingerprinting with WebGL, Canvas, and Audio fingerprinting - Hardware binding to prevent F5/Ctrl+F5 abuse - Persistent storage across browser sessions (localStorage + sessionStorage) - Global demo session counter with 10 session limit per device - Multi-tab protection (max 2 tabs simultaneously) - Anti-reset protection with hardware mismatch detection Demo Session Protection: - Advanced fingerprint generation with CPU benchmarking - Enhanced validation with cryptographic verification - Automatic cleanup and session completion tracking - Cooldown periods between sessions (1min + 15min completion) - Weekly partial reset of global counters Fixes: - Fixed SessionTimer console spam after connection disconnect - Added missing registerEnhancedDemoSessionUsage method - Corrected method calls from generateUserFingerprint to generateAdvancedUserFingerprint - Implemented proper event handling for connection state changes WebRTC Improvements: - Added peer-disconnect, new-connection, and connection-cleaned events - Enhanced connection cleanup with proper UI notifications - Fixed SessionTimer state management during disconnections - Prevented infinite re-rendering and console logging Performance Optimizations: - Auto-save persistent data every 30 seconds - Periodic cleanup of old session data (every 6 hours) - Memory management for used preimages (10k limit) - Tab heartbeat system for multi-tab detection Testing: - Demo sessions now properly enforce limits - P2P anonymity maintained (no server validation) - Compatible with incognito mode restrictions - Resistant to common abuse techniques
This commit is contained in:
@@ -3244,7 +3244,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('📤 Attempting to send message:', messageInput.substring(0, 100));
|
|
||||||
|
|
||||||
// Add the message to local messages immediately (sent message)
|
// Add the message to local messages immediately (sent message)
|
||||||
const sentMessage = {
|
const sentMessage = {
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ const EnhancedMinimalHeader = ({
|
|||||||
React.createElement('p', {
|
React.createElement('p', {
|
||||||
key: 'subtitle',
|
key: 'subtitle',
|
||||||
className: 'text-xs sm:text-sm text-muted hidden sm:block'
|
className: 'text-xs sm:text-sm text-muted hidden sm:block'
|
||||||
}, 'End-to-end freedom. v4.0.02.88')
|
}, 'End-to-end freedom. v4.0.03.00')
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,17 @@ const SessionTimer = ({ timeLeft, sessionType, sessionManager }) => {
|
|||||||
const [currentTime, setCurrentTime] = React.useState(timeLeft || 0);
|
const [currentTime, setCurrentTime] = React.useState(timeLeft || 0);
|
||||||
const [showExpiredMessage, setShowExpiredMessage] = React.useState(false);
|
const [showExpiredMessage, setShowExpiredMessage] = React.useState(false);
|
||||||
const [initialized, setInitialized] = React.useState(false);
|
const [initialized, setInitialized] = React.useState(false);
|
||||||
const [connectionBroken, setConnectionBroken] = React.useState(false);
|
const [connectionBroken, setConnectionBroken] = React.useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
const [loggedHidden, setLoggedHidden] = React.useState(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (connectionBroken) {
|
if (connectionBroken) {
|
||||||
console.log('⏱️ SessionTimer initialization skipped - connection broken');
|
if (!loggedHidden) {
|
||||||
|
console.log('⏱️ SessionTimer initialization skipped - connection broken');
|
||||||
|
setLoggedHidden(true);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,17 +28,22 @@ const SessionTimer = ({ timeLeft, sessionType, sessionManager }) => {
|
|||||||
|
|
||||||
setCurrentTime(initialTime);
|
setCurrentTime(initialTime);
|
||||||
setInitialized(true);
|
setInitialized(true);
|
||||||
}, [sessionManager, connectionBroken]);
|
setLoggedHidden(false);
|
||||||
|
}, [sessionManager, connectionBroken]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (connectionBroken) {
|
if (connectionBroken) {
|
||||||
console.log('⏱️ SessionTimer props update skipped - connection broken');
|
if (!loggedHidden) {
|
||||||
|
console.log('⏱️ SessionTimer props update skipped - connection broken');
|
||||||
|
setLoggedHidden(true);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeLeft && timeLeft > 0) {
|
if (timeLeft && timeLeft > 0) {
|
||||||
setCurrentTime(timeLeft);
|
setCurrentTime(timeLeft);
|
||||||
}
|
}
|
||||||
|
setLoggedHidden(false);
|
||||||
}, [timeLeft, connectionBroken]);
|
}, [timeLeft, connectionBroken]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -42,7 +52,10 @@ const SessionTimer = ({ timeLeft, sessionType, sessionManager }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (connectionBroken) {
|
if (connectionBroken) {
|
||||||
console.log('⏱️ Timer interval skipped - connection broken');
|
if (!loggedHidden) {
|
||||||
|
console.log('⏱️ Timer interval skipped - connection broken');
|
||||||
|
setLoggedHidden(true);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +63,6 @@ const SessionTimer = ({ timeLeft, sessionType, sessionManager }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
if (connectionBroken) {
|
if (connectionBroken) {
|
||||||
console.log('⏱️ Timer interval stopped - connection broken');
|
console.log('⏱️ Timer interval stopped - connection broken');
|
||||||
@@ -81,22 +93,18 @@ const SessionTimer = ({ timeLeft, sessionType, sessionManager }) => {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
};
|
};
|
||||||
}, [initialized, currentTime, sessionManager, connectionBroken]);
|
}, [initialized, currentTime, sessionManager, connectionBroken]);
|
||||||
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const handleSessionTimerUpdate = (event) => {
|
const handleSessionTimerUpdate = (event) => {
|
||||||
|
|
||||||
if (event.detail.timeLeft && event.detail.timeLeft > 0) {
|
if (event.detail.timeLeft && event.detail.timeLeft > 0) {
|
||||||
setCurrentTime(event.detail.timeLeft);
|
setCurrentTime(event.detail.timeLeft);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleForceHeaderUpdate = (event) => {
|
const handleForceHeaderUpdate = (event) => {
|
||||||
|
|
||||||
if (sessionManager && sessionManager.hasActiveSession()) {
|
if (sessionManager && sessionManager.hasActiveSession()) {
|
||||||
const newTime = sessionManager.getTimeLeft();
|
const newTime = sessionManager.getTimeLeft();
|
||||||
setCurrentTime(newTime);
|
setCurrentTime(newTime);
|
||||||
@@ -105,28 +113,41 @@ const SessionTimer = ({ timeLeft, sessionType, sessionManager }) => {
|
|||||||
|
|
||||||
const handlePeerDisconnect = (event) => {
|
const handlePeerDisconnect = (event) => {
|
||||||
console.log('🔌 Peer disconnect detected in SessionTimer - stopping timer permanently');
|
console.log('🔌 Peer disconnect detected in SessionTimer - stopping timer permanently');
|
||||||
setConnectionBroken(true);
|
setConnectionBroken(true);
|
||||||
setCurrentTime(0);
|
setCurrentTime(0);
|
||||||
setShowExpiredMessage(false);
|
setShowExpiredMessage(false);
|
||||||
|
setLoggedHidden(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNewConnection = (event) => {
|
const handleNewConnection = (event) => {
|
||||||
console.log('🔌 New connection detected in SessionTimer - resetting connection state');
|
console.log('🔌 New connection detected in SessionTimer - resetting connection state');
|
||||||
setConnectionBroken(false);
|
setConnectionBroken(false);
|
||||||
|
setLoggedHidden(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConnectionCleaned = (event) => {
|
||||||
|
console.log('🧹 Connection cleaned - resetting SessionTimer state');
|
||||||
|
setConnectionBroken(false);
|
||||||
|
setCurrentTime(0);
|
||||||
|
setShowExpiredMessage(false);
|
||||||
|
setInitialized(false);
|
||||||
|
setLoggedHidden(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('session-timer-update', handleSessionTimerUpdate);
|
document.addEventListener('session-timer-update', handleSessionTimerUpdate);
|
||||||
document.addEventListener('force-header-update', handleForceHeaderUpdate);
|
document.addEventListener('force-header-update', handleForceHeaderUpdate);
|
||||||
document.addEventListener('peer-disconnect', handlePeerDisconnect);
|
document.addEventListener('peer-disconnect', handlePeerDisconnect);
|
||||||
document.addEventListener('new-connection', handleNewConnection);
|
document.addEventListener('new-connection', handleNewConnection);
|
||||||
|
document.addEventListener('connection-cleaned', handleConnectionCleaned);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('session-timer-update', handleSessionTimerUpdate);
|
document.removeEventListener('session-timer-update', handleSessionTimerUpdate);
|
||||||
document.removeEventListener('force-header-update', handleForceHeaderUpdate);
|
document.removeEventListener('force-header-update', handleForceHeaderUpdate);
|
||||||
document.removeEventListener('peer-disconnect', handlePeerDisconnect);
|
document.removeEventListener('peer-disconnect', handlePeerDisconnect);
|
||||||
document.removeEventListener('new-connection', handleNewConnection);
|
document.removeEventListener('new-connection', handleNewConnection);
|
||||||
|
document.removeEventListener('connection-cleaned', handleConnectionCleaned);
|
||||||
};
|
};
|
||||||
}, [sessionManager]);
|
}, [sessionManager]);
|
||||||
|
|
||||||
if (showExpiredMessage) {
|
if (showExpiredMessage) {
|
||||||
return React.createElement('div', {
|
return React.createElement('div', {
|
||||||
@@ -145,20 +166,33 @@ const SessionTimer = ({ timeLeft, sessionType, sessionManager }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!sessionManager) {
|
if (!sessionManager) {
|
||||||
console.log('⏱️ SessionTimer hidden - no sessionManager');
|
if (!loggedHidden) {
|
||||||
|
console.log('⏱️ SessionTimer hidden - no sessionManager');
|
||||||
|
setLoggedHidden(true);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectionBroken) {
|
if (connectionBroken) {
|
||||||
console.log('⏱️ SessionTimer hidden - connection broken');
|
if (!loggedHidden) {
|
||||||
|
console.log('⏱️ SessionTimer hidden - connection broken');
|
||||||
|
setLoggedHidden(true);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentTime || currentTime <= 0) {
|
if (!currentTime || currentTime <= 0) {
|
||||||
console.log('⏱️ SessionTimer hidden - no time left');
|
if (!loggedHidden) {
|
||||||
|
console.log('⏱️ SessionTimer hidden - no time left');
|
||||||
|
setLoggedHidden(true);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (loggedHidden) {
|
||||||
|
setLoggedHidden(false);
|
||||||
|
}
|
||||||
|
|
||||||
const totalMinutes = Math.floor(currentTime / (60 * 1000));
|
const totalMinutes = Math.floor(currentTime / (60 * 1000));
|
||||||
const totalSeconds = Math.floor(currentTime / 1000);
|
const totalSeconds = Math.floor(currentTime / 1000);
|
||||||
|
|
||||||
@@ -179,8 +213,8 @@ const SessionTimer = ({ timeLeft, sessionType, sessionManager }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getTimerStyle = () => {
|
const getTimerStyle = () => {
|
||||||
const totalDuration = sessionType === 'demo' ? 6 * 60 * 1000 : 60 * 60 * 1000;
|
const totalDuration = sessionType === 'demo' ? 6 * 60 * 1000 : 60 * 60 * 1000;
|
||||||
const timeProgress = (totalDuration - currentTime) / totalDuration;
|
const timeProgress = (totalDuration - currentTime) / totalDuration;
|
||||||
|
|
||||||
let backgroundColor, textColor, iconColor, iconClass, shouldPulse;
|
let backgroundColor, textColor, iconColor, iconClass, shouldPulse;
|
||||||
|
|
||||||
@@ -247,4 +281,4 @@ window.updateSessionTimer = (newTimeLeft, newSessionType) => {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('✅ SessionTimer loaded with fixes and improvements');
|
console.log('✅ SessionTimer loaded with anti-spam logging fixes');
|
||||||
@@ -2505,6 +2505,13 @@ async autoEnableSecurityFeatures() {
|
|||||||
securityLevel: offerPackage.securityLevel.level
|
securityLevel: offerPackage.securityLevel.level
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.dispatchEvent(new CustomEvent('new-connection', {
|
||||||
|
detail: {
|
||||||
|
type: 'offer',
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
return offerPackage;
|
return offerPackage;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.EnhancedSecureCryptoUtils.secureLog.log('error', 'Enhanced secure offer creation failed', {
|
window.EnhancedSecureCryptoUtils.secureLog.log('error', 'Enhanced secure offer creation failed', {
|
||||||
@@ -2712,6 +2719,13 @@ async autoEnableSecurityFeatures() {
|
|||||||
securityLevel: answerPackage.securityLevel.level
|
securityLevel: answerPackage.securityLevel.level
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.dispatchEvent(new CustomEvent('new-connection', {
|
||||||
|
detail: {
|
||||||
|
type: 'answer',
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
return answerPackage;
|
return answerPackage;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.EnhancedSecureCryptoUtils.secureLog.log('error', 'Enhanced secure answer creation failed', {
|
window.EnhancedSecureCryptoUtils.secureLog.log('error', 'Enhanced secure answer creation failed', {
|
||||||
@@ -3252,6 +3266,13 @@ async autoEnableSecurityFeatures() {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.sendDisconnectNotification();
|
this.sendDisconnectNotification();
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
|
document.dispatchEvent(new CustomEvent('peer-disconnect', {
|
||||||
|
detail: {
|
||||||
|
reason: 'user_disconnect',
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.cleanupConnection();
|
this.cleanupConnection();
|
||||||
@@ -3263,6 +3284,13 @@ async autoEnableSecurityFeatures() {
|
|||||||
this.isVerified = false;
|
this.isVerified = false;
|
||||||
this.onMessage('🔌 Connection lost. Attempting to reconnect...', 'system');
|
this.onMessage('🔌 Connection lost. Attempting to reconnect...', 'system');
|
||||||
|
|
||||||
|
document.dispatchEvent(new CustomEvent('peer-disconnect', {
|
||||||
|
detail: {
|
||||||
|
reason: 'connection_lost',
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!this.intentionalDisconnect) {
|
if (!this.intentionalDisconnect) {
|
||||||
this.attemptReconnection();
|
this.attemptReconnection();
|
||||||
@@ -3322,6 +3350,13 @@ async autoEnableSecurityFeatures() {
|
|||||||
this.onKeyExchange('');
|
this.onKeyExchange('');
|
||||||
this.onVerificationRequired('');
|
this.onVerificationRequired('');
|
||||||
|
|
||||||
|
document.dispatchEvent(new CustomEvent('peer-disconnect', {
|
||||||
|
detail: {
|
||||||
|
reason: reason,
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.cleanupConnection();
|
this.cleanupConnection();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
@@ -3390,6 +3425,13 @@ async autoEnableSecurityFeatures() {
|
|||||||
// IMPORTANT: Clearing security logs
|
// IMPORTANT: Clearing security logs
|
||||||
window.EnhancedSecureCryptoUtils.secureLog.clearLogs();
|
window.EnhancedSecureCryptoUtils.secureLog.clearLogs();
|
||||||
|
|
||||||
|
document.dispatchEvent(new CustomEvent('connection-cleaned', {
|
||||||
|
detail: {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
reason: this.intentionalDisconnect ? 'user_cleanup' : 'automatic_cleanup'
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
// Notifying the UI about complete cleanup
|
// Notifying the UI about complete cleanup
|
||||||
this.onStatusChange('disconnected');
|
this.onStatusChange('disconnected');
|
||||||
this.onKeyExchange('');
|
this.onKeyExchange('');
|
||||||
|
|||||||
@@ -55,9 +55,24 @@ class PayPerSessionManager {
|
|||||||
this.startDemoSessionCleanup();
|
this.startDemoSessionCleanup();
|
||||||
this.startActiveDemoSessionCleanup();
|
this.startActiveDemoSessionCleanup();
|
||||||
|
|
||||||
|
this.globalDemoCounter = 0;
|
||||||
|
this.memoryStorage = new Map();
|
||||||
|
this.currentTabId = null;
|
||||||
|
this.tabHeartbeatInterval = null;
|
||||||
|
this.initializePersistentStorage();
|
||||||
|
this.performEnhancedCleanup();
|
||||||
|
const multiTabCheck = this.checkMultiTabProtection();
|
||||||
|
if (!multiTabCheck.allowed) {
|
||||||
|
console.warn('❌ Multi-tab protection triggered:', multiTabCheck.message);
|
||||||
|
}
|
||||||
|
|
||||||
console.log('💰 PayPerSessionManager initialized with ENHANCED secure demo mode');
|
console.log('💰 PayPerSessionManager initialized with ENHANCED secure demo mode');
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
this.savePersistentData();
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
|
console.log('💰 PayPerSessionManager initialized with ENHANCED secure demo mode and auto-save');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -128,9 +143,10 @@ class PayPerSessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IMPROVED user fingerprint generation
|
// IMPROVED user fingerprint generation
|
||||||
generateUserFingerprint() {
|
generateAdvancedUserFingerprint() {
|
||||||
try {
|
try {
|
||||||
const components = [
|
// Базовые компоненты (как было)
|
||||||
|
const basicComponents = [
|
||||||
navigator.userAgent || '',
|
navigator.userAgent || '',
|
||||||
navigator.language || '',
|
navigator.language || '',
|
||||||
screen.width + 'x' + screen.height,
|
screen.width + 'x' + screen.height,
|
||||||
@@ -144,136 +160,382 @@ class PayPerSessionManager {
|
|||||||
navigator.maxTouchPoints || 0,
|
navigator.maxTouchPoints || 0,
|
||||||
navigator.onLine ? '1' : '0'
|
navigator.onLine ? '1' : '0'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// НОВЫЕ КОМПОНЕНТЫ ДЛЯ HARDWARE BINDING
|
||||||
|
const hardwareComponents = [];
|
||||||
|
|
||||||
// Create a more secure hash
|
// WebGL отпечаток (очень сложно подделать)
|
||||||
let hash = 0;
|
try {
|
||||||
const str = components.join('|');
|
const canvas = document.createElement('canvas');
|
||||||
for (let i = 0; i < str.length; i++) {
|
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
|
||||||
const char = str.charCodeAt(i);
|
if (gl) {
|
||||||
hash = ((hash << 5) - hash) + char;
|
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
|
||||||
hash = hash & hash;
|
if (debugInfo) {
|
||||||
|
hardwareComponents.push(gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) || '');
|
||||||
|
hardwareComponents.push(gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || '');
|
||||||
|
}
|
||||||
|
hardwareComponents.push(gl.getParameter(gl.VERSION) || '');
|
||||||
|
hardwareComponents.push(gl.getParameter(gl.SHADING_LANGUAGE_VERSION) || '');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
hardwareComponents.push('webgl_error');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canvas отпечаток (уникален для каждого устройства)
|
||||||
|
try {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = 200;
|
||||||
|
canvas.height = 50;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.textBaseline = 'top';
|
||||||
|
ctx.font = '14px Arial';
|
||||||
|
ctx.fillText('SecureBit Demo Fingerprint 🔒', 2, 2);
|
||||||
|
ctx.fillStyle = 'rgba(255,0,0,0.5)';
|
||||||
|
ctx.fillRect(50, 10, 20, 20);
|
||||||
|
hardwareComponents.push(canvas.toDataURL());
|
||||||
|
} catch (e) {
|
||||||
|
hardwareComponents.push('canvas_error');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Аудио отпечаток (очень стабилен)
|
||||||
|
try {
|
||||||
|
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
const oscillator = audioContext.createOscillator();
|
||||||
|
const analyser = audioContext.createAnalyser();
|
||||||
|
const gain = audioContext.createGain();
|
||||||
|
|
||||||
|
oscillator.connect(analyser);
|
||||||
|
analyser.connect(gain);
|
||||||
|
gain.connect(audioContext.destination);
|
||||||
|
|
||||||
|
oscillator.frequency.setValueAtTime(1000, audioContext.currentTime);
|
||||||
|
gain.gain.setValueAtTime(0, audioContext.currentTime);
|
||||||
|
|
||||||
|
hardwareComponents.push(audioContext.sampleRate.toString());
|
||||||
|
hardwareComponents.push(audioContext.state);
|
||||||
|
hardwareComponents.push(analyser.frequencyBinCount.toString());
|
||||||
|
|
||||||
|
audioContext.close();
|
||||||
|
} catch (e) {
|
||||||
|
hardwareComponents.push('audio_error');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Производительность CPU (стабильна для устройства)
|
||||||
|
const cpuBenchmark = this.performCPUBenchmark();
|
||||||
|
hardwareComponents.push(cpuBenchmark);
|
||||||
|
|
||||||
|
// Объединяем все компоненты
|
||||||
|
const allComponents = [...basicComponents, ...hardwareComponents];
|
||||||
|
|
||||||
|
// Создаем несколько уровней хеширования
|
||||||
|
let primaryHash = 0;
|
||||||
|
let secondaryHash = 0;
|
||||||
|
let tertiaryHash = 0;
|
||||||
|
|
||||||
|
const primaryStr = allComponents.slice(0, 8).join('|');
|
||||||
|
const secondaryStr = allComponents.slice(8, 16).join('|');
|
||||||
|
const tertiaryStr = allComponents.slice(16).join('|');
|
||||||
|
|
||||||
|
// Первичный хеш
|
||||||
|
for (let i = 0; i < primaryStr.length; i++) {
|
||||||
|
const char = primaryStr.charCodeAt(i);
|
||||||
|
primaryHash = ((primaryHash << 7) - primaryHash) + char;
|
||||||
|
primaryHash = primaryHash & primaryHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add extra salt for stability
|
// Вторичный хеш
|
||||||
const salt = 'securebit_demo_2024';
|
for (let i = 0; i < secondaryStr.length; i++) {
|
||||||
const saltedStr = str + salt;
|
const char = secondaryStr.charCodeAt(i);
|
||||||
let saltedHash = 0;
|
secondaryHash = ((secondaryHash << 11) - secondaryHash) + char;
|
||||||
for (let i = 0; i < saltedStr.length; i++) {
|
secondaryHash = secondaryHash & secondaryHash;
|
||||||
const char = saltedStr.charCodeAt(i);
|
|
||||||
saltedHash = ((saltedHash << 5) - saltedHash) + char;
|
|
||||||
saltedHash = saltedHash & saltedHash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Math.abs(hash).toString(36) + '_' + Math.abs(saltedHash).toString(36);
|
// Третичный хеш
|
||||||
|
for (let i = 0; i < tertiaryStr.length; i++) {
|
||||||
|
const char = tertiaryStr.charCodeAt(i);
|
||||||
|
tertiaryHash = ((tertiaryHash << 13) - tertiaryHash) + char;
|
||||||
|
tertiaryHash = tertiaryHash & tertiaryHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Комбинированный отпечаток
|
||||||
|
const combined = `${Math.abs(primaryHash).toString(36)}_${Math.abs(secondaryHash).toString(36)}_${Math.abs(tertiaryHash).toString(36)}`;
|
||||||
|
|
||||||
|
console.log('🔒 Enhanced fingerprint generated:', {
|
||||||
|
components: allComponents.length,
|
||||||
|
primaryLength: primaryStr.length,
|
||||||
|
secondaryLength: secondaryStr.length,
|
||||||
|
tertiaryLength: tertiaryStr.length,
|
||||||
|
fingerprintLength: combined.length
|
||||||
|
});
|
||||||
|
|
||||||
|
return combined;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to generate user fingerprint:', error);
|
console.warn('Failed to generate enhanced fingerprint:', error);
|
||||||
return 'fallback_' + Math.random().toString(36).substr(2, 9);
|
// Fallback к более простому отпечатку
|
||||||
|
return 'fallback_' + Date.now().toString(36) + '_' + Math.random().toString(36).substr(2, 9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// COMPLETELY REWRITTEN demo session limits check
|
performCPUBenchmark() {
|
||||||
checkDemoSessionLimits(userFingerprint) {
|
const start = performance.now();
|
||||||
const userData = this.demoSessions.get(userFingerprint);
|
let result = 0;
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
console.log(`🔍 Checking demo limits for user ${userFingerprint.substring(0, 12)}...`);
|
for (let i = 0; i < 100000; i++) {
|
||||||
|
result += Math.sin(i) * Math.cos(i);
|
||||||
|
}
|
||||||
|
|
||||||
// CHECK 1: Global limit of simultaneous demo sessions
|
const end = performance.now();
|
||||||
if (this.activeDemoSessions.size >= this.maxGlobalDemoSessions) {
|
const duration = Math.round(end - start);
|
||||||
console.log(`❌ Global demo limit reached: ${this.activeDemoSessions.size}/${this.maxGlobalDemoSessions}`);
|
|
||||||
|
if (duration < 5) return 'fast_cpu';
|
||||||
|
if (duration < 15) return 'medium_cpu';
|
||||||
|
if (duration < 30) return 'slow_cpu';
|
||||||
|
return 'very_slow_cpu';
|
||||||
|
}
|
||||||
|
|
||||||
|
initializePersistentStorage() {
|
||||||
|
this.storageKeys = {
|
||||||
|
demoSessions: 'sb_demo_sessions_v2',
|
||||||
|
completedSessions: 'sb_completed_sessions_v2',
|
||||||
|
globalCounter: 'sb_global_demo_counter_v2',
|
||||||
|
lastCleanup: 'sb_last_cleanup_v2',
|
||||||
|
hardwareFingerprint: 'sb_hw_fingerprint_v2'
|
||||||
|
};
|
||||||
|
|
||||||
|
this.loadPersistentData();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPersistentData() {
|
||||||
|
try {
|
||||||
|
const savedDemoSessions = this.getFromStorage(this.storageKeys.demoSessions);
|
||||||
|
if (savedDemoSessions) {
|
||||||
|
const parsed = JSON.parse(savedDemoSessions);
|
||||||
|
for (const [key, value] of Object.entries(parsed)) {
|
||||||
|
this.demoSessions.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedCompletedSessions = this.getFromStorage(this.storageKeys.completedSessions);
|
||||||
|
if (savedCompletedSessions) {
|
||||||
|
const parsed = JSON.parse(savedCompletedSessions);
|
||||||
|
for (const [key, value] of Object.entries(parsed)) {
|
||||||
|
this.completedDemoSessions.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedGlobalCounter = this.getFromStorage(this.storageKeys.globalCounter);
|
||||||
|
if (savedGlobalCounter) {
|
||||||
|
this.globalDemoCounter = parseInt(savedGlobalCounter) || 0;
|
||||||
|
} else {
|
||||||
|
this.globalDemoCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('📊 Persistent data loaded:', {
|
||||||
|
demoSessions: this.demoSessions.size,
|
||||||
|
completedSessions: this.completedDemoSessions.size,
|
||||||
|
globalCounter: this.globalDemoCounter
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to load persistent data:', error);
|
||||||
|
this.globalDemoCounter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
savePersistentData() {
|
||||||
|
try {
|
||||||
|
const demoSessionsObj = Object.fromEntries(this.demoSessions);
|
||||||
|
this.setToStorage(this.storageKeys.demoSessions, JSON.stringify(demoSessionsObj));
|
||||||
|
|
||||||
|
const completedSessionsObj = Object.fromEntries(this.completedDemoSessions);
|
||||||
|
this.setToStorage(this.storageKeys.completedSessions, JSON.stringify(completedSessionsObj));
|
||||||
|
|
||||||
|
this.setToStorage(this.storageKeys.globalCounter, this.globalDemoCounter.toString());
|
||||||
|
|
||||||
|
this.setToStorage(this.storageKeys.lastCleanup, Date.now().toString());
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to save persistent data:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getFromStorage(key) {
|
||||||
|
try {
|
||||||
|
if (typeof localStorage !== 'undefined') {
|
||||||
|
const value = localStorage.getItem(key);
|
||||||
|
if (value) return value;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof sessionStorage !== 'undefined') {
|
||||||
|
const value = sessionStorage.getItem(key);
|
||||||
|
if (value) return value;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ('caches' in window) {
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setToStorage(key, value) {
|
||||||
|
try {
|
||||||
|
if (typeof localStorage !== 'undefined') {
|
||||||
|
localStorage.setItem(key, value);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof sessionStorage !== 'undefined') {
|
||||||
|
sessionStorage.setItem(key, value);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
if (!this.memoryStorage) this.memoryStorage = new Map();
|
||||||
|
this.memoryStorage.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAntiResetProtection(userFingerprint) {
|
||||||
|
if (!this.globalDemoCounter) {
|
||||||
|
this.globalDemoCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hardwareFingerprint = this.getHardwareFingerprint();
|
||||||
|
|
||||||
|
const savedHardwareFingerprint = this.getFromStorage(this.storageKeys.hardwareFingerprint);
|
||||||
|
|
||||||
|
if (savedHardwareFingerprint && savedHardwareFingerprint !== hardwareFingerprint) {
|
||||||
|
console.warn('🚨 Hardware fingerprint mismatch detected - possible reset attempt');
|
||||||
|
|
||||||
|
this.globalDemoCounter += 5;
|
||||||
|
this.savePersistentData();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
allowed: false,
|
isValid: false,
|
||||||
|
reason: 'hardware_mismatch',
|
||||||
|
penalty: 5
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setToStorage(this.storageKeys.hardwareFingerprint, hardwareFingerprint);
|
||||||
|
|
||||||
|
if (this.globalDemoCounter >= 10) {
|
||||||
|
return {
|
||||||
|
isValid: false,
|
||||||
reason: 'global_limit_exceeded',
|
reason: 'global_limit_exceeded',
|
||||||
message: `Too many demo sessions active globally (${this.activeDemoSessions.size}/${this.maxGlobalDemoSessions}). Please try again later.`,
|
globalCount: this.globalDemoCounter
|
||||||
remaining: 0,
|
|
||||||
debugInfo: `Global sessions: ${this.activeDemoSessions.size}/${this.maxGlobalDemoSessions}`
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userData) {
|
return {
|
||||||
// First demo session for this user
|
isValid: true,
|
||||||
console.log(`✅ First demo session for user ${userFingerprint.substring(0, 12)}`);
|
globalCount: this.globalDemoCounter
|
||||||
return {
|
};
|
||||||
allowed: true,
|
}
|
||||||
reason: 'first_demo_session',
|
|
||||||
remaining: this.maxDemoSessionsPerUser,
|
getHardwareFingerprint() {
|
||||||
debugInfo: 'First time user'
|
const components = [];
|
||||||
};
|
|
||||||
|
// CPU информация
|
||||||
|
components.push(navigator.hardwareConcurrency || 0);
|
||||||
|
components.push(navigator.deviceMemory || 0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const gl = canvas.getContext('webgl');
|
||||||
|
if (gl) {
|
||||||
|
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
|
||||||
|
if (debugInfo) {
|
||||||
|
components.push(gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) || '');
|
||||||
|
components.push(gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
components.push('webgl_unavailable');
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK 2: Limit sessions per 24 hours (STRICT check)
|
components.push(screen.width);
|
||||||
const sessionsLast24h = userData.sessions.filter(session =>
|
components.push(screen.height);
|
||||||
now - session.timestamp < this.demoCooldownPeriod
|
components.push(screen.colorDepth);
|
||||||
);
|
|
||||||
|
components.push(Intl.DateTimeFormat().resolvedOptions().timeZone);
|
||||||
|
|
||||||
console.log(`📊 Sessions in last 24h for user ${userFingerprint.substring(0, 12)}: ${sessionsLast24h.length}/${this.maxDemoSessionsPerUser}`);
|
let hash = 0;
|
||||||
|
const str = components.join('|');
|
||||||
if (sessionsLast24h.length >= this.maxDemoSessionsPerUser) {
|
for (let i = 0; i < str.length; i++) {
|
||||||
const oldestSession = Math.min(...sessionsLast24h.map(s => s.timestamp));
|
const char = str.charCodeAt(i);
|
||||||
const timeUntilNext = this.demoCooldownPeriod - (now - oldestSession);
|
hash = ((hash << 5) - hash) + char;
|
||||||
|
hash = hash & hash;
|
||||||
console.log(`❌ Daily demo limit exceeded for user ${userFingerprint.substring(0, 12)}`);
|
|
||||||
return {
|
|
||||||
allowed: false,
|
|
||||||
reason: 'daily_limit_exceeded',
|
|
||||||
timeUntilNext: timeUntilNext,
|
|
||||||
message: `Daily demo limit reached (${this.maxDemoSessionsPerUser}/day). Next session available in ${Math.ceil(timeUntilNext / (60 * 1000))} minutes.`,
|
|
||||||
remaining: 0,
|
|
||||||
debugInfo: `Used ${sessionsLast24h.length}/${this.maxDemoSessionsPerUser} today`
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK 3: Cooldown between sessions (FIXED LOGIC)
|
return Math.abs(hash).toString(36);
|
||||||
if (userData.lastUsed && (now - userData.lastUsed) < this.demoSessionCooldown) {
|
}
|
||||||
const timeUntilNext = this.demoSessionCooldown - (now - userData.lastUsed);
|
|
||||||
const minutesLeft = Math.ceil(timeUntilNext / (60 * 1000));
|
registerEnhancedDemoSessionUsage(userFingerprint, preimage) {
|
||||||
|
// Вызываем оригинальный метод
|
||||||
console.log(`⏰ Cooldown active for user ${userFingerprint.substring(0, 12)}: ${minutesLeft} minutes`);
|
const session = this.registerDemoSessionUsage(userFingerprint, preimage);
|
||||||
|
|
||||||
return {
|
|
||||||
allowed: false,
|
|
||||||
reason: 'session_cooldown',
|
|
||||||
timeUntilNext: timeUntilNext,
|
|
||||||
message: `Please wait ${minutesLeft} minutes between demo sessions. This prevents abuse and ensures fair access for all users.`,
|
|
||||||
remaining: this.maxDemoSessionsPerUser - sessionsLast24h.length,
|
|
||||||
debugInfo: `Cooldown: ${minutesLeft}min left, last used: ${Math.round((now - userData.lastUsed) / (60 * 1000))}min ago`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// CHECK 4: NEW - Check for completed sessions
|
// Дополнительно сохраняем в persistent storage
|
||||||
const completedSessions = this.completedDemoSessions.get(userFingerprint) || [];
|
this.savePersistentData();
|
||||||
const recentCompletedSessions = completedSessions.filter(session =>
|
|
||||||
now - session.endTime < this.minTimeBetweenCompletedSessions
|
console.log('📊 Enhanced demo session registered:', {
|
||||||
);
|
userFingerprint: userFingerprint.substring(0, 12),
|
||||||
|
globalCount: this.globalDemoCounter,
|
||||||
|
sessionId: session.sessionId,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
|
||||||
if (recentCompletedSessions.length > 0) {
|
return session;
|
||||||
const lastCompletedSession = Math.max(...recentCompletedSessions.map(s => s.endTime));
|
}
|
||||||
const timeUntilNext = this.minTimeBetweenCompletedSessions - (now - lastCompletedSession);
|
|
||||||
|
// COMPLETELY REWRITTEN demo session limits check
|
||||||
console.log(`⏰ Recent session completed, waiting period active for user ${userFingerprint.substring(0, 12)}`);
|
checkEnhancedDemoSessionLimits(userFingerprint) {
|
||||||
|
const antiResetCheck = this.checkAntiResetProtection(userFingerprint);
|
||||||
|
if (!antiResetCheck.isValid) {
|
||||||
return {
|
return {
|
||||||
allowed: false,
|
allowed: false,
|
||||||
reason: 'recent_session_completed',
|
reason: antiResetCheck.reason,
|
||||||
timeUntilNext: timeUntilNext,
|
message: this.getAntiResetMessage(antiResetCheck),
|
||||||
message: `Please wait ${Math.ceil(timeUntilNext / (60 * 1000))} minutes after your last session before starting a new one.`,
|
globalCount: antiResetCheck.globalCount,
|
||||||
remaining: this.maxDemoSessionsPerUser - sessionsLast24h.length,
|
penalty: antiResetCheck.penalty
|
||||||
debugInfo: `Last session ended ${Math.round((now - lastCompletedSession) / (60 * 1000))}min ago`
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`✅ Demo session approved for user ${userFingerprint.substring(0, 12)}`);
|
const regularCheck = this.checkDemoSessionLimits(userFingerprint);
|
||||||
return {
|
|
||||||
allowed: true,
|
if (regularCheck.allowed) {
|
||||||
reason: 'within_limits',
|
this.globalDemoCounter++;
|
||||||
remaining: this.maxDemoSessionsPerUser - sessionsLast24h.length,
|
this.savePersistentData();
|
||||||
debugInfo: `Available: ${this.maxDemoSessionsPerUser - sessionsLast24h.length}/${this.maxDemoSessionsPerUser}`
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...regularCheck,
|
||||||
|
globalCount: this.globalDemoCounter
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
getAntiResetMessage(antiResetCheck) {
|
||||||
|
switch (antiResetCheck.reason) {
|
||||||
|
case 'hardware_mismatch':
|
||||||
|
return 'Обнаружена попытка сброса ограничений. Доступ к демо-режиму временно ограничен.';
|
||||||
|
case 'global_limit_exceeded':
|
||||||
|
return `Глобальный лимит демо-сессий превышен (${antiResetCheck.globalCount}/10). Для продолжения требуется оплаченная сессия.`;
|
||||||
|
default:
|
||||||
|
return 'Доступ к демо-режиму ограничен по соображениям безопасности.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// FIXED demo session usage registration
|
// FIXED demo session usage registration
|
||||||
registerDemoSessionUsage(userFingerprint, preimage) {
|
registerDemoSessionUsage(userFingerprint, preimage) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const userData = this.demoSessions.get(userFingerprint) || {
|
const userData = this.demoSessions.get(userFingerprint) || {
|
||||||
count: 0,
|
count: 0,
|
||||||
@@ -312,6 +574,131 @@ class PayPerSessionManager {
|
|||||||
return newSession;
|
return newSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
performEnhancedCleanup() {
|
||||||
|
const now = Date.now();
|
||||||
|
const lastCleanup = parseInt(this.getFromStorage(this.storageKeys.lastCleanup)) || 0;
|
||||||
|
|
||||||
|
if (now - lastCleanup < 6 * 60 * 60 * 1000) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🧹 Performing enhanced cleanup...');
|
||||||
|
|
||||||
|
const maxAge = 25 * 60 * 60 * 1000;
|
||||||
|
let cleanedSessions = 0;
|
||||||
|
|
||||||
|
for (const [identifier, data] of this.demoSessions.entries()) {
|
||||||
|
if (now - data.lastUsed > maxAge) {
|
||||||
|
this.demoSessions.delete(identifier);
|
||||||
|
cleanedSessions++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cleanedCompleted = 0;
|
||||||
|
for (const [identifier, sessions] of this.completedDemoSessions.entries()) {
|
||||||
|
const filteredSessions = sessions.filter(session =>
|
||||||
|
now - session.endTime < maxAge
|
||||||
|
);
|
||||||
|
|
||||||
|
if (filteredSessions.length === 0) {
|
||||||
|
this.completedDemoSessions.delete(identifier);
|
||||||
|
cleanedCompleted++;
|
||||||
|
} else {
|
||||||
|
this.completedDemoSessions.set(identifier, filteredSessions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const weekAgo = 7 * 24 * 60 * 60 * 1000;
|
||||||
|
if (now - lastCleanup > weekAgo) {
|
||||||
|
this.globalDemoCounter = Math.max(0, this.globalDemoCounter - 3);
|
||||||
|
console.log('🔄 Global demo counter reset (weekly):', this.globalDemoCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.savePersistentData();
|
||||||
|
|
||||||
|
console.log('✅ Enhanced cleanup completed:', {
|
||||||
|
cleanedSessions,
|
||||||
|
cleanedCompleted,
|
||||||
|
globalCounter: this.globalDemoCounter
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
checkMultiTabProtection() {
|
||||||
|
const tabId = 'tab_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||||
|
const activeTabsKey = 'sb_active_tabs';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const activeTabsStr = this.getFromStorage(activeTabsKey);
|
||||||
|
const activeTabs = activeTabsStr ? JSON.parse(activeTabsStr) : [];
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const validTabs = activeTabs.filter(tab => now - tab.timestamp < 30000);
|
||||||
|
|
||||||
|
if (validTabs.length >= 2) {
|
||||||
|
return {
|
||||||
|
allowed: false,
|
||||||
|
reason: 'multiple_tabs',
|
||||||
|
message: 'Демо-режим доступен только в одной вкладке одновременно.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
validTabs.push({
|
||||||
|
tabId: tabId,
|
||||||
|
timestamp: now
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setToStorage(activeTabsKey, JSON.stringify(validTabs));
|
||||||
|
this.currentTabId = tabId;
|
||||||
|
|
||||||
|
this.startTabHeartbeat();
|
||||||
|
|
||||||
|
return {
|
||||||
|
allowed: true,
|
||||||
|
tabId: tabId
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Multi-tab protection error:', error);
|
||||||
|
return { allowed: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startTabHeartbeat() {
|
||||||
|
if (this.tabHeartbeatInterval) {
|
||||||
|
clearInterval(this.tabHeartbeatInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tabHeartbeatInterval = setInterval(() => {
|
||||||
|
this.updateTabHeartbeat();
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTabHeartbeat() {
|
||||||
|
if (!this.currentTabId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const activeTabsKey = 'sb_active_tabs';
|
||||||
|
const activeTabsStr = this.getFromStorage(activeTabsKey);
|
||||||
|
const activeTabs = activeTabsStr ? JSON.parse(activeTabsStr) : [];
|
||||||
|
|
||||||
|
// Обновляем timestamp для текущей вкладки
|
||||||
|
const updatedTabs = activeTabs.map(tab => {
|
||||||
|
if (tab.tabId === this.currentTabId) {
|
||||||
|
return {
|
||||||
|
...tab,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return tab;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setToStorage(activeTabsKey, JSON.stringify(updatedTabs));
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Tab heartbeat update failed:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NEW method: Register demo session completion
|
// NEW method: Register demo session completion
|
||||||
registerDemoSessionCompletion(userFingerprint, sessionDuration, preimage) {
|
registerDemoSessionCompletion(userFingerprint, sessionDuration, preimage) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@@ -504,9 +891,9 @@ class PayPerSessionManager {
|
|||||||
throw new Error('Demo preimage timestamp from future - possible clock manipulation');
|
throw new Error('Demo preimage timestamp from future - possible clock manipulation');
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK 4: Custom Limits
|
// CHECK 4: ИСПРАВЛЕННЫЙ вызов лимитов - используем ПРАВИЛЬНЫЙ метод
|
||||||
const userFingerprint = this.generateUserFingerprint();
|
const userFingerprint = this.generateAdvancedUserFingerprint(); // ИСПРАВЛЕНО!
|
||||||
const limitsCheck = this.checkDemoSessionLimits(userFingerprint);
|
const limitsCheck = this.checkEnhancedDemoSessionLimits(userFingerprint); // ИСПРАВЛЕНО!
|
||||||
|
|
||||||
if (!limitsCheck.allowed) {
|
if (!limitsCheck.allowed) {
|
||||||
throw new Error(`Demo session limits exceeded: ${limitsCheck.message}`);
|
throw new Error(`Demo session limits exceeded: ${limitsCheck.message}`);
|
||||||
@@ -514,7 +901,7 @@ class PayPerSessionManager {
|
|||||||
|
|
||||||
// FIX: For demo sessions, do NOT add preimage to usedPreimages here,
|
// FIX: For demo sessions, do NOT add preimage to usedPreimages here,
|
||||||
// as this will only be done after successful activation
|
// as this will only be done after successful activation
|
||||||
this.registerDemoSessionUsage(userFingerprint, preimage);
|
this.registerEnhancedDemoSessionUsage(userFingerprint, preimage); // ИСПРАВЛЕНО!
|
||||||
|
|
||||||
console.log('✅ Demo preimage ENHANCED validation passed');
|
console.log('✅ Demo preimage ENHANCED validation passed');
|
||||||
return true;
|
return true;
|
||||||
@@ -552,6 +939,7 @@ class PayPerSessionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// LIGHTNING NETWORK INTEGRATION
|
// LIGHTNING NETWORK INTEGRATION
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -933,9 +1321,9 @@ class PayPerSessionManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ADDITIONAL check at activation level
|
// ИСПРАВЛЕННЫЙ вызов - используем правильные методы
|
||||||
const userFingerprint = this.generateUserFingerprint();
|
const userFingerprint = this.generateAdvancedUserFingerprint();
|
||||||
const demoCheck = this.checkDemoSessionLimits(userFingerprint);
|
const demoCheck = this.checkEnhancedDemoSessionLimits(userFingerprint);
|
||||||
|
|
||||||
if (!demoCheck.allowed) {
|
if (!demoCheck.allowed) {
|
||||||
console.log(`⚠️ Demo session cooldown active, but allowing activation for development`);
|
console.log(`⚠️ Demo session cooldown active, but allowing activation for development`);
|
||||||
@@ -1115,7 +1503,7 @@ class PayPerSessionManager {
|
|||||||
|
|
||||||
handleDemoSessionExpiry(preimage) {
|
handleDemoSessionExpiry(preimage) {
|
||||||
if (this.currentSession && this.currentSession.preimage === preimage) {
|
if (this.currentSession && this.currentSession.preimage === preimage) {
|
||||||
const userFingerprint = this.generateUserFingerprint();
|
const userFingerprint = this.generateAdvancedUserFingerprint(); // ИСПРАВЛЕНО!
|
||||||
const sessionDuration = Date.now() - this.currentSession.startTime;
|
const sessionDuration = Date.now() - this.currentSession.startTime;
|
||||||
|
|
||||||
this.registerDemoSessionCompletion(userFingerprint, sessionDuration, preimage);
|
this.registerDemoSessionCompletion(userFingerprint, sessionDuration, preimage);
|
||||||
@@ -1145,7 +1533,7 @@ class PayPerSessionManager {
|
|||||||
const expiredSession = this.currentSession;
|
const expiredSession = this.currentSession;
|
||||||
|
|
||||||
if (expiredSession && expiredSession.isDemo) {
|
if (expiredSession && expiredSession.isDemo) {
|
||||||
const userFingerprint = this.generateUserFingerprint();
|
const userFingerprint = this.generateAdvancedUserFingerprint(); // ИСПРАВЛЕНО!
|
||||||
const sessionDuration = Date.now() - expiredSession.startTime;
|
const sessionDuration = Date.now() - expiredSession.startTime;
|
||||||
this.registerDemoSessionCompletion(userFingerprint, sessionDuration, expiredSession.preimage);
|
this.registerDemoSessionCompletion(userFingerprint, sessionDuration, expiredSession.preimage);
|
||||||
}
|
}
|
||||||
@@ -1194,8 +1582,8 @@ class PayPerSessionManager {
|
|||||||
|
|
||||||
// UPDATED demo session creation
|
// UPDATED demo session creation
|
||||||
createDemoSession() {
|
createDemoSession() {
|
||||||
const userFingerprint = this.generateUserFingerprint();
|
const userFingerprint = this.generateAdvancedUserFingerprint(); // ИСПРАВЛЕНО!
|
||||||
const demoCheck = this.checkDemoSessionLimits(userFingerprint);
|
const demoCheck = this.checkEnhancedDemoSessionLimits(userFingerprint); // ИСПРАВЛЕНО!
|
||||||
|
|
||||||
if (!demoCheck.allowed) {
|
if (!demoCheck.allowed) {
|
||||||
return {
|
return {
|
||||||
@@ -1248,7 +1636,7 @@ class PayPerSessionManager {
|
|||||||
|
|
||||||
// UPDATED information about demo limits
|
// UPDATED information about demo limits
|
||||||
getDemoSessionInfo() {
|
getDemoSessionInfo() {
|
||||||
const userFingerprint = this.generateUserFingerprint();
|
const userFingerprint = this.generateAdvancedUserFingerprint();
|
||||||
const userData = this.demoSessions.get(userFingerprint);
|
const userData = this.demoSessions.get(userFingerprint);
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
@@ -1519,7 +1907,7 @@ class PayPerSessionManager {
|
|||||||
|
|
||||||
// IMPORTANT: For demo sessions, we register forced termination
|
// IMPORTANT: For demo sessions, we register forced termination
|
||||||
if (resetSession && resetSession.isDemo) {
|
if (resetSession && resetSession.isDemo) {
|
||||||
const userFingerprint = this.generateUserFingerprint();
|
const userFingerprint = this.generateAdvancedUserFingerprint();
|
||||||
const sessionDuration = Date.now() - resetSession.startTime;
|
const sessionDuration = Date.now() - resetSession.startTime;
|
||||||
this.registerDemoSessionCompletion(userFingerprint, sessionDuration, resetSession.preimage);
|
this.registerDemoSessionCompletion(userFingerprint, sessionDuration, resetSession.preimage);
|
||||||
}
|
}
|
||||||
@@ -1555,7 +1943,7 @@ class PayPerSessionManager {
|
|||||||
|
|
||||||
// IMPORTANT: We register the end of the current demo session during cleanup
|
// IMPORTANT: We register the end of the current demo session during cleanup
|
||||||
if (this.currentSession && this.currentSession.isDemo) {
|
if (this.currentSession && this.currentSession.isDemo) {
|
||||||
const userFingerprint = this.generateUserFingerprint();
|
const userFingerprint = this.generateAdvancedUserFingerprint(); // ИСПРАВЛЕНО!
|
||||||
const sessionDuration = Date.now() - this.currentSession.startTime;
|
const sessionDuration = Date.now() - this.currentSession.startTime;
|
||||||
this.registerDemoSessionCompletion(userFingerprint, sessionDuration, this.currentSession.preimage);
|
this.registerDemoSessionCompletion(userFingerprint, sessionDuration, this.currentSession.preimage);
|
||||||
}
|
}
|
||||||
@@ -1588,7 +1976,7 @@ class PayPerSessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getVerifiedDemoSession() {
|
getVerifiedDemoSession() {
|
||||||
const userFingerprint = this.generateUserFingerprint();
|
const userFingerprint = this.generateAdvancedUserFingerprint();
|
||||||
const userData = this.demoSessions.get(userFingerprint);
|
const userData = this.demoSessions.get(userFingerprint);
|
||||||
|
|
||||||
console.log('🔍 Searching for verified demo session:', {
|
console.log('🔍 Searching for verified demo session:', {
|
||||||
@@ -1644,8 +2032,106 @@ class PayPerSessionManager {
|
|||||||
return verifiedSession;
|
return verifiedSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkDemoSessionLimits(userFingerprint) {
|
||||||
|
const userData = this.demoSessions.get(userFingerprint);
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
console.log(`🔍 Checking demo limits for user ${userFingerprint.substring(0, 12)}...`);
|
||||||
|
|
||||||
|
// CHECK 1: Global limit of simultaneous demo sessions
|
||||||
|
if (this.activeDemoSessions.size >= this.maxGlobalDemoSessions) {
|
||||||
|
console.log(`❌ Global demo limit reached: ${this.activeDemoSessions.size}/${this.maxGlobalDemoSessions}`);
|
||||||
|
return {
|
||||||
|
allowed: false,
|
||||||
|
reason: 'global_limit_exceeded',
|
||||||
|
message: `Too many demo sessions active globally (${this.activeDemoSessions.size}/${this.maxGlobalDemoSessions}). Please try again later.`,
|
||||||
|
remaining: 0,
|
||||||
|
debugInfo: `Global sessions: ${this.activeDemoSessions.size}/${this.maxGlobalDemoSessions}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userData) {
|
||||||
|
// First demo session for this user
|
||||||
|
console.log(`✅ First demo session for user ${userFingerprint.substring(0, 12)}`);
|
||||||
|
return {
|
||||||
|
allowed: true,
|
||||||
|
reason: 'first_demo_session',
|
||||||
|
remaining: this.maxDemoSessionsPerUser,
|
||||||
|
debugInfo: 'First time user'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK 2: Limit sessions per 24 hours (STRICT check)
|
||||||
|
const sessionsLast24h = userData.sessions.filter(session =>
|
||||||
|
now - session.timestamp < this.demoCooldownPeriod
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`📊 Sessions in last 24h for user ${userFingerprint.substring(0, 12)}: ${sessionsLast24h.length}/${this.maxDemoSessionsPerUser}`);
|
||||||
|
|
||||||
|
if (sessionsLast24h.length >= this.maxDemoSessionsPerUser) {
|
||||||
|
const oldestSession = Math.min(...sessionsLast24h.map(s => s.timestamp));
|
||||||
|
const timeUntilNext = this.demoCooldownPeriod - (now - oldestSession);
|
||||||
|
|
||||||
|
console.log(`❌ Daily demo limit exceeded for user ${userFingerprint.substring(0, 12)}`);
|
||||||
|
return {
|
||||||
|
allowed: false,
|
||||||
|
reason: 'daily_limit_exceeded',
|
||||||
|
timeUntilNext: timeUntilNext,
|
||||||
|
message: `Daily demo limit reached (${this.maxDemoSessionsPerUser}/day). Next session available in ${Math.ceil(timeUntilNext / (60 * 1000))} minutes.`,
|
||||||
|
remaining: 0,
|
||||||
|
debugInfo: `Used ${sessionsLast24h.length}/${this.maxDemoSessionsPerUser} today`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK 3: Cooldown between sessions (FIXED LOGIC)
|
||||||
|
if (userData.lastUsed && (now - userData.lastUsed) < this.demoSessionCooldown) {
|
||||||
|
const timeUntilNext = this.demoSessionCooldown - (now - userData.lastUsed);
|
||||||
|
const minutesLeft = Math.ceil(timeUntilNext / (60 * 1000));
|
||||||
|
|
||||||
|
console.log(`⏰ Cooldown active for user ${userFingerprint.substring(0, 12)}: ${minutesLeft} minutes`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
allowed: false,
|
||||||
|
reason: 'session_cooldown',
|
||||||
|
timeUntilNext: timeUntilNext,
|
||||||
|
message: `Please wait ${minutesLeft} minutes between demo sessions. This prevents abuse and ensures fair access for all users.`,
|
||||||
|
remaining: this.maxDemoSessionsPerUser - sessionsLast24h.length,
|
||||||
|
debugInfo: `Cooldown: ${minutesLeft}min left, last used: ${Math.round((now - userData.lastUsed) / (60 * 1000))}min ago`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK 4: NEW - Check for completed sessions
|
||||||
|
const completedSessions = this.completedDemoSessions.get(userFingerprint) || [];
|
||||||
|
const recentCompletedSessions = completedSessions.filter(session =>
|
||||||
|
now - session.endTime < this.minTimeBetweenCompletedSessions
|
||||||
|
);
|
||||||
|
|
||||||
|
if (recentCompletedSessions.length > 0) {
|
||||||
|
const lastCompletedSession = Math.max(...recentCompletedSessions.map(s => s.endTime));
|
||||||
|
const timeUntilNext = this.minTimeBetweenCompletedSessions - (now - lastCompletedSession);
|
||||||
|
|
||||||
|
console.log(`⏰ Recent session completed, waiting period active for user ${userFingerprint.substring(0, 12)}`);
|
||||||
|
return {
|
||||||
|
allowed: false,
|
||||||
|
reason: 'recent_session_completed',
|
||||||
|
timeUntilNext: timeUntilNext,
|
||||||
|
message: `Please wait ${Math.ceil(timeUntilNext / (60 * 1000))} minutes after your last session before starting a new one.`,
|
||||||
|
remaining: this.maxDemoSessionsPerUser - sessionsLast24h.length,
|
||||||
|
debugInfo: `Last session ended ${Math.round((now - lastCompletedSession) / (60 * 1000))}min ago`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✅ Demo session approved for user ${userFingerprint.substring(0, 12)}`);
|
||||||
|
return {
|
||||||
|
allowed: true,
|
||||||
|
reason: 'within_limits',
|
||||||
|
remaining: this.maxDemoSessionsPerUser - sessionsLast24h.length,
|
||||||
|
debugInfo: `Available: ${this.maxDemoSessionsPerUser - sessionsLast24h.length}/${this.maxDemoSessionsPerUser}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
createDemoSessionForActivation() {
|
createDemoSessionForActivation() {
|
||||||
const userFingerprint = this.generateUserFingerprint();
|
const userFingerprint = this.generateAdvancedUserFingerprint(); // ИСПРАВЛЕНО!
|
||||||
|
|
||||||
if (this.activeDemoSessions.size >= this.maxGlobalDemoSessions) {
|
if (this.activeDemoSessions.size >= this.maxGlobalDemoSessions) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user