// Enhanced Copy Button with better UX const EnhancedCopyButton = ({ text, className = "", children }) => { const [copied, setCopied] = React.useState(false); const handleCopy = async () => { try { await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (error) { console.error('Copy failed:', error); // Fallback for older browsers const textArea = document.createElement('textarea'); textArea.value = text; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); setCopied(true); setTimeout(() => setCopied(false), 2000); } }; return React.createElement('button', { onClick: handleCopy, className: `${className} transition-all duration-200` }, [ React.createElement('i', { key: 'icon', className: `${copied ? 'fas fa-check accent-green' : 'fas fa-copy text-secondary'} mr-2` }), copied ? 'Copied!' : children ]); }; // Verification Component const VerificationStep = ({ verificationCode, onConfirm, onReject, localConfirmed, remoteConfirmed, bothConfirmed }) => { return React.createElement('div', { className: "card-minimal rounded-xl p-6 border-purple-500/20" }, [ React.createElement('div', { key: 'header', className: "flex items-center mb-4" }, [ React.createElement('div', { key: 'icon', className: "w-10 h-10 bg-purple-500/10 border border-purple-500/20 rounded-lg flex items-center justify-center mr-3" }, [ React.createElement('i', { className: 'fas fa-shield-alt accent-purple' }) ]), React.createElement('h3', { key: 'title', className: "text-lg font-medium text-primary" }, "Security verification") ]), React.createElement('div', { key: 'content', className: "space-y-4" }, [ React.createElement('p', { key: 'description', className: "text-secondary text-sm" }, "Verify the security code with your contact via another communication channel (voice, SMS, etc.):"), React.createElement('div', { key: 'code-display', className: "text-center" }, [ React.createElement('div', { key: 'code', className: "verification-code text-2xl py-4" }, verificationCode) ]), // Verification status indicators React.createElement('div', { key: 'verification-status', className: "space-y-2" }, [ React.createElement('div', { key: 'local-status', className: `flex items-center justify-between p-2 rounded-lg ${localConfirmed ? 'bg-green-500/10 border border-green-500/20' : 'bg-gray-500/10 border border-gray-500/20'}` }, [ React.createElement('span', { key: 'local-label', className: "text-sm text-secondary" }, "Your confirmation:"), React.createElement('div', { key: 'local-indicator', className: "flex items-center" }, [ React.createElement('i', { key: 'local-icon', className: `fas ${localConfirmed ? 'fa-check-circle text-green-400' : 'fa-clock text-gray-400'} mr-2` }), React.createElement('span', { key: 'local-text', className: `text-sm ${localConfirmed ? 'text-green-400' : 'text-gray-400'}` }, localConfirmed ? 'Confirmed' : 'Pending') ]) ]), React.createElement('div', { key: 'remote-status', className: `flex items-center justify-between p-2 rounded-lg ${remoteConfirmed ? 'bg-green-500/10 border border-green-500/20' : 'bg-gray-500/10 border border-gray-500/20'}` }, [ React.createElement('span', { key: 'remote-label', className: "text-sm text-secondary" }, "Peer confirmation:"), React.createElement('div', { key: 'remote-indicator', className: "flex items-center" }, [ React.createElement('i', { key: 'remote-icon', className: `fas ${remoteConfirmed ? 'fa-check-circle text-green-400' : 'fa-clock text-gray-400'} mr-2` }), React.createElement('span', { key: 'remote-text', className: `text-sm ${remoteConfirmed ? 'text-green-400' : 'text-gray-400'}` }, remoteConfirmed ? 'Confirmed' : 'Pending') ]) ]) ]), React.createElement('div', { key: 'warning', className: "p-3 bg-yellow-500/10 border border-yellow-500/20 rounded-lg" }, [ React.createElement('p', { className: "text-yellow-400 text-sm flex items-center" }, [ React.createElement('i', { className: 'fas fa-exclamation-triangle mr-2' }), 'Make sure the codes match exactly.!' ]) ]), React.createElement('div', { key: 'buttons', className: "flex space-x-3" }, [ React.createElement('button', { key: 'confirm', onClick: onConfirm, disabled: localConfirmed, className: `flex-1 py-3 px-4 rounded-lg font-medium transition-all duration-200 ${localConfirmed ? 'bg-gray-500/20 text-gray-400 cursor-not-allowed' : 'btn-verify text-white'}` }, [ React.createElement('i', { className: `fas ${localConfirmed ? 'fa-check-circle' : 'fa-check'} mr-2` }), localConfirmed ? 'Confirmed' : 'The codes match' ]), React.createElement('button', { key: 'reject', onClick: onReject, className: "flex-1 bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/20 py-3 px-4 rounded-lg font-medium transition-all duration-200" }, [ React.createElement('i', { className: 'fas fa-times mr-2' }), 'The codes do not match' ]) ]) ]) ]); }; // Enhanced Chat Message with better security indicators const EnhancedChatMessage = ({ message, type, timestamp }) => { const formatTime = (ts) => { return new Date(ts).toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); }; const getMessageStyle = () => { switch (type) { case 'sent': return { container: "ml-auto bg-orange-500/15 border-orange-500/20 text-primary", icon: "fas fa-lock accent-orange", label: "Encrypted" }; case 'received': return { container: "mr-auto card-minimal text-primary", icon: "fas fa-unlock-alt accent-green", label: "Decrypted" }; case 'system': return { container: "mx-auto bg-yellow-500/10 border border-yellow-500/20 text-yellow-400", icon: "fas fa-info-circle accent-yellow", label: "System" }; default: return { container: "mx-auto card-minimal text-secondary", icon: "fas fa-circle text-muted", label: "Unknown" }; } }; const style = getMessageStyle(); return React.createElement('div', { className: `message-slide mb-3 p-3 rounded-lg max-w-md break-words ${style.container} border` }, [ React.createElement('div', { key: 'content', className: "flex items-start space-x-2" }, [ React.createElement('i', { key: 'icon', className: `${style.icon} text-sm mt-0.5 opacity-70` }), React.createElement('div', { key: 'text', className: "flex-1" }, [ React.createElement('div', { key: 'message', className: "text-sm" }, message), timestamp && React.createElement('div', { key: 'meta', className: "flex items-center justify-between mt-1 text-xs opacity-50" }, [ React.createElement('span', { key: 'time' }, formatTime(timestamp)), React.createElement('span', { key: 'status', className: "text-xs" }, style.label) ]) ]) ]) ]); }; // Enhanced Connection Setup with verification const EnhancedConnectionSetup = ({ messages, onCreateOffer, onCreateAnswer, onConnect, onClearData, onVerifyConnection, connectionStatus, offerData, answerData, offerInput, setOfferInput, answerInput, setAnswerInput, showOfferStep, showAnswerStep, verificationCode, showVerification, showQRCode, qrCodeUrl, showQRScanner, setShowQRCode, setShowQRScanner, setShowQRScannerModal, offerPassword, answerPassword, localVerificationConfirmed, remoteVerificationConfirmed, bothVerificationsConfirmed, // QR control props qrFramesTotal, qrFrameIndex, qrManualMode, toggleQrManualMode, nextQrFrame, prevQrFrame, markAnswerCreated }) => { const [mode, setMode] = React.useState('select'); const resetToSelect = () => { setMode('select'); onClearData(); }; const handleVerificationConfirm = () => { onVerifyConnection(true); }; const handleVerificationReject = () => { onVerifyConnection(false); }; if (showVerification) { return React.createElement('div', { className: "min-h-[calc(100vh-104px)] flex items-center justify-center p-4" }, [ React.createElement('div', { key: 'verification', className: "w-full max-w-md" }, [ React.createElement(VerificationStep, { verificationCode: verificationCode, onConfirm: handleVerificationConfirm, onReject: handleVerificationReject, localConfirmed: localVerificationConfirmed, remoteConfirmed: remoteVerificationConfirmed, bothConfirmed: bothVerificationsConfirmed }) ]) ]); } if (mode === 'select') { return React.createElement('div', { className: "min-h-[calc(100vh-104px)] flex items-center justify-center p-4" }, [ React.createElement('div', { key: 'selector', className: "w-full max-w-4xl" }, [ React.createElement('div', { key: 'header', className: "text-center mb-8" }, [ React.createElement('h2', { key: 'title', className: "text-2xl font-semibold text-primary mb-3" }, 'Start secure communication'), React.createElement('p', { key: 'subtitle', className: "text-secondary max-w-2xl mx-auto" }, "Choose a connection method for a secure channel with ECDH encryption and Perfect Forward Secrecy.") ]), React.createElement('div', { key: 'options', className: "flex flex-col md:flex-row items-center justify-center gap-6 max-w-3xl mx-auto" }, [ // Create Connection React.createElement('div', { key: 'create', onClick: () => setMode('create'), className: "card-minimal rounded-xl p-6 cursor-pointer group flex-1 create" }, [ React.createElement('div', { key: 'icon', className: "w-12 h-12 bg-blue-500/10 border border-blue-500/20 rounded-lg flex items-center justify-center mx-auto mb-4" }, [ React.createElement('i', { className: 'fas fa-plus text-xl text-blue-400' }) ]), React.createElement('h3', { key: 'title', className: "text-lg font-semibold text-primary text-center mb-3" }, "Create channel"), React.createElement('p', { key: 'description', className: "text-secondary text-center text-sm mb-4" }, "Initiate a new secure connection"), React.createElement('div', { key: 'features', className: "space-y-2" }, [ React.createElement('div', { key: 'f1', className: "flex items-center text-sm text-muted" }, [ React.createElement('i', { className: 'fas fa-key accent-orange mr-2 text-xs' }), 'Generating ECDH keys' ]), React.createElement('div', { key: 'f2', className: "flex items-center text-sm text-muted" }, [ React.createElement('i', { className: 'fas fa-shield-alt accent-orange mr-2 text-xs' }), 'Verification code' ]), React.createElement('div', { key: 'f3', className: "flex items-center text-sm text-muted" }, [ React.createElement('i', { className: 'fas fa-sync-alt accent-purple mr-2 text-xs' }), 'PFS key rotation' ]) ]) ]), React.createElement('div', { key: 'divider', className: "flex flex-row md:flex-col items-center gap-4 px-4 w-full md:w-auto" }, [ React.createElement('div', { key: 'line-a', className: "h-px flex-1 bg-gradient-to-r from-transparent via-zinc-700 to-transparent md:h-32 md:w-px md:flex-none md:bg-gradient-to-b" }), React.createElement('div', { key: 'or-text', className: "text-zinc-600 text-sm font-medium px-3" }, "OR"), React.createElement('div', { key: 'line-b', className: "h-px flex-1 bg-gradient-to-r from-transparent via-zinc-700 to-transparent md:h-32 md:w-px md:flex-none md:bg-gradient-to-b" }) ]), // Join Connection React.createElement('div', { key: 'join', onClick: () => setMode('join'), className: "card-minimal rounded-xl p-6 cursor-pointer group flex-1 join" }, [ React.createElement('div', { key: 'icon', className: "w-12 h-12 bg-green-500/10 border border-green-500/20 rounded-lg flex items-center justify-center mx-auto mb-4" }, [ React.createElement('i', { className: 'fas fa-link text-xl accent-green' }) ]), React.createElement('h3', { key: 'title', className: "text-lg font-semibold text-primary text-center mb-3" }, "Join"), React.createElement('p', { key: 'description', className: "text-secondary text-center text-sm mb-4" }, "Connect to an existing secure channel"), React.createElement('div', { key: 'features', className: "space-y-2" }, [ React.createElement('div', { key: 'f1', className: "flex items-center text-sm text-muted" }, [ React.createElement('i', { className: 'fas fa-paste accent-green mr-2 text-xs' }), 'Paste Offer invitation' ]), React.createElement('div', { key: 'f2', className: "flex items-center text-sm text-muted" }, [ React.createElement('i', { className: 'fas fa-check-circle accent-green mr-2 text-xs' }), 'Automatic verification' ]), React.createElement('div', { key: 'f3', className: "flex items-center text-sm text-muted" }, [ React.createElement('i', { className: 'fas fa-sync-alt accent-purple mr-2 text-xs' }), 'PFS protection' ]) ]) ]) ]), React.createElement(SecurityFeatures, { key: 'security-features' }), React.createElement(Testimonials, { key: 'testimonials' }), React.createElement(UniqueFeatureSlider, { key: 'unique-features-slider' }), React.createElement(DownloadApps, { key: 'download-apps' }), React.createElement(ComparisonTable, { key: 'comparison-table' }), React.createElement(Roadmap, { key: 'roadmap' }), ]) ]); } if (mode === 'create') { return React.createElement('div', { className: "min-h-[calc(100vh-104px)] flex items-center justify-center p-4" }, [ React.createElement('div', { key: 'create-flow', className: "w-full max-w-3xl space-y-6" }, [ React.createElement('div', { key: 'header', className: "text-center" }, [ React.createElement('button', { key: 'back', onClick: resetToSelect, className: "mb-4 text-secondary hover:text-primary transition-colors flex items-center mx-auto text-sm" }, [ React.createElement('i', { className: 'fas fa-arrow-left mr-2' }), 'Back to selection' ]), React.createElement('h2', { key: 'title', className: "text-xl font-semibold text-primary mb-2" }, 'Creating a secure channel') ]), // Step 1 !showAnswerStep && React.createElement('div', { key: 'step1', className: "card-minimal rounded-xl p-6" }, [ React.createElement('div', { key: 'step-header', className: "flex items-center mb-4" }, [ React.createElement('div', { key: 'number', className: "step-number mr-3" }, '1'), React.createElement('h3', { key: 'title', className: "text-lg font-medium text-primary" }, "Generating ECDH keys and verification code") ]), React.createElement('p', { key: 'description', className: "text-secondary text-sm mb-4" }, "Creating cryptographically strong keys and codes to protect against attacks"), !showOfferStep && React.createElement('button', { key: 'create-btn', onClick: onCreateOffer, disabled: connectionStatus === 'connecting', className: `w-full btn-primary text-white py-3 px-4 rounded-lg font-medium transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed` }, [ React.createElement('i', { className: 'fas fa-shield-alt mr-2' }), 'Create secure keys' ]), showOfferStep && React.createElement('div', { key: 'offer-result', className: "mt-6 space-y-4" }, [ React.createElement('div', { key: 'success', className: "p-3 bg-green-500/10 border border-green-500/20 rounded-lg" }, [ React.createElement('p', { className: "text-green-400 text-sm font-medium flex items-center" }, [ React.createElement('i', { className: 'fas fa-check-circle mr-2' }), 'Secure invitation created! Send the code to your contact' ]) ]), React.createElement('div', { key: 'offer-data', className: "space-y-3" }, [ // Raw JSON hidden intentionally; users copy compressed string or use QR React.createElement('div', { key: 'buttons', className: "flex gap-2" }, [ React.createElement(EnhancedCopyButton, { key: 'copy', text: (() => { try { const min = typeof offerData === 'object' ? JSON.stringify(offerData) : (offerData || ''); if (typeof window.encodeBinaryToPrefixed === 'function') { return window.encodeBinaryToPrefixed(min); } if (typeof window.compressToPrefixedGzip === 'function') { return window.compressToPrefixedGzip(min); } return min; } catch { return typeof offerData === 'object' ? JSON.stringify(offerData) : (offerData || ''); } })(), className: "flex-1 px-3 py-2 bg-orange-500/10 hover:bg-orange-500/20 text-orange-400 border border-orange-500/20 rounded text-sm font-medium" }, 'Copy invitation code') ]), showQRCode && qrCodeUrl && React.createElement('div', { key: 'qr-container', className: "mt-4 p-4 border border-gray-600/30 rounded-lg text-center" }, [ React.createElement('h4', { key: 'qr-title', className: "text-sm font-medium text-primary mb-3" }, 'Scan QR code to connect'), React.createElement('div', { key: 'qr-wrapper', className: "flex justify-center" }, [ React.createElement('img', { key: 'qr-image', src: qrCodeUrl, alt: "QR Code for secure connection", className: "max-w-none h-auto border border-gray-600/30 rounded w-[20rem] sm:w-[24rem] md:w-[28rem] lg:w-[32rem]" }) ]), ((qrFramesTotal || 0) >= 1) && React.createElement('div', { key: 'qr-controls-below', className: "mt-4 flex flex-col items-center gap-2" }, [ React.createElement('div', { key: 'frame-indicator', className: "text-xs text-gray-300" }, `Frame ${Math.max(1, (qrFrameIndex || 1))}/${qrFramesTotal || 1}`), React.createElement('div', { key: 'control-buttons', className: "flex gap-1" }, [ (qrFramesTotal || 0) > 1 && React.createElement('button', { key: 'prev-frame', onClick: prevQrFrame, className: "w-6 h-6 bg-gray-600 hover:bg-gray-500 text-white rounded text-xs flex items-center justify-center" }, '◀'), React.createElement('button', { key: 'toggle-manual', onClick: toggleQrManualMode, className: `px-2 py-1 rounded text-xs font-medium ${ (qrManualMode || false) ? 'bg-blue-500 text-white' : 'bg-gray-600 text-gray-300 hover:bg-gray-500' }` }, (qrManualMode || false) ? 'Manual' : 'Auto'), (qrFramesTotal || 0) > 1 && React.createElement('button', { key: 'next-frame', onClick: nextQrFrame, className: "w-6 h-6 bg-gray-600 hover:bg-gray-500 text-white rounded text-xs flex items-center justify-center" }, '▶') ]) ]), React.createElement('p', { key: 'qr-description', className: "text-xs text-gray-400 mt-2" }, 'Your contact can scan this QR code to quickly join the secure session') ]) ]) ]) ]), // Step 2 - Session Type Selection // showOfferStep && React.createElement('div', { // key: 'step2', // className: "card-minimal rounded-xl p-6" // }, [ // React.createElement('div', { // key: 'step-header', // className: "flex items-center mb-4" // }, [ // React.createElement('div', { // key: 'number', // className: "w-8 h-8 bg-green-500 text-white rounded-lg flex items-center justify-center font-semibold text-sm mr-3" // }, '2'), // React.createElement('h3', { // key: 'title', // className: "text-lg font-medium text-primary" // }, "Select session type") // ]), // React.createElement('p', { // key: 'description', // className: "text-secondary text-sm mb-4" // }, "Choose a session plan or use limited demo mode for testing."), // React.createElement(SessionTypeSelector, { // key: 'session-selector', // onSelectType: (sessionType) => { // // Save the selected session type // setSelectedSessionType(sessionType); // console.log('🎯 Session type selected:', sessionType); // // FIX: For demo sessions, we immediately call automatic activation // if (sessionType === 'demo') { // console.log('🎮 Demo session selected, scheduling automatic activation...'); // // Delay activation for 2 seconds to stabilize // setTimeout(() => { // if (sessionManager) { // console.log('🚀 Triggering demo session activation from selection...'); // handleDemoVerification(); // } // }, 2000); // } // // Open a modal payment window // if (typeof window.showPaymentModal === 'function') { // window.showPaymentModal(sessionType); // } else { // // Fallback - show session information // console.log('Selected session type:', sessionType); // } // }, // onCancel: resetToSelect, // sessionManager: window.sessionManager // }) // ]), // Step 3 - Waiting for response showOfferStep && React.createElement('div', { key: 'step2', className: "card-minimal rounded-xl p-6" }, [ React.createElement('div', { key: 'step-header', className: "flex items-center mb-4" }, [ React.createElement('div', { key: 'number', className: "w-8 h-8 bg-blue-500 text-white rounded-lg flex items-center justify-center font-semibold text-sm mr-3" }, '2'), React.createElement('h3', { key: 'title', className: "text-lg font-medium text-primary" }, "Waiting for the peer's response") ]), React.createElement('p', { key: 'description', className: "text-secondary text-sm mb-4" }, "Paste the encrypted invitation code from your contact."), React.createElement('div', { key: 'buttons', className: "flex gap-2 mb-4" }, [ React.createElement('button', { key: 'scan-btn', onClick: () => setShowQRScannerModal(true), className: "px-4 py-2 bg-purple-500/10 hover:bg-purple-500/20 text-purple-400 border border-purple-500/20 rounded text-sm font-medium transition-all duration-200" }, [ React.createElement('i', { key: 'icon', className: 'fas fa-qrcode mr-2' }), 'Scan QR Code' ]) ]), React.createElement('textarea', { key: 'input', value: answerInput, onChange: (e) => { setAnswerInput(e.target.value); // Mark answer as created when user manually enters data if (e.target.value.trim().length > 0) { if (typeof markAnswerCreated === 'function') { markAnswerCreated(); } } }, rows: 6, placeholder: "Paste the encrypted response code from your contact or scan QR code...", className: "w-full p-3 bg-custom-bg border border-gray-500/20 rounded-lg resize-none mb-4 text-secondary placeholder-gray-500 focus:border-orange-500/40 focus:outline-none transition-all custom-scrollbar text-sm" }), React.createElement('button', { key: 'connect-btn', onClick: onConnect, disabled: !answerInput.trim(), className: "w-full btn-secondary text-white py-3 px-4 rounded-lg font-medium transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed" }, [ React.createElement('i', { className: 'fas fa-rocket mr-2' }), 'Establish connection' ]) ]) ]) ]); } if (mode === 'join') { return React.createElement('div', { className: "min-h-[calc(100vh-104px)] flex items-center justify-center p-4" }, [ React.createElement('div', { key: 'join-flow', className: "w-full max-w-3xl space-y-6" }, [ React.createElement('div', { key: 'header', className: "text-center" }, [ React.createElement('button', { key: 'back', onClick: resetToSelect, className: "mb-4 text-secondary hover:text-primary transition-colors flex items-center mx-auto text-sm" }, [ React.createElement('i', { className: 'fas fa-arrow-left mr-2' }), 'Back to selection' ]), React.createElement('h2', { key: 'title', className: "text-xl font-semibold text-primary mb-2" }, 'Joining the secure channel') ]), (showAnswerStep ? null : React.createElement('div', { key: 'step1', className: "card-minimal rounded-xl p-6" }, [ React.createElement('div', { key: 'step-header', className: "flex items-center mb-4" }, [ React.createElement('div', { key: 'number', className: "w-8 h-8 bg-green-500 text-white rounded-lg flex items-center justify-center font-semibold text-sm mr-3" }, '1'), React.createElement('h3', { key: 'title', className: "text-lg font-medium text-primary" }, "Paste secure invitation") ]), React.createElement('p', { key: 'description', className: "text-secondary text-sm mb-4" }, "Copy and paste the encrypted invitation code from the initiator."), React.createElement('textarea', { key: 'input', value: offerInput, onChange: (e) => { setOfferInput(e.target.value); if (e.target.value.trim().length > 0) { if (typeof markAnswerCreated === 'function') { markAnswerCreated(); } } }, rows: 8, placeholder: "Paste the encrypted invitation code or scan QR code...", className: "w-full p-3 bg-custom-bg border border-gray-500/20 rounded-lg resize-none mb-4 text-secondary placeholder-gray-500 focus:border-green-500/40 focus:outline-none transition-all custom-scrollbar text-sm" }), React.createElement('div', { key: 'buttons', className: "flex gap-2 mb-4" }, [ React.createElement('button', { key: 'scan-btn', onClick: () => setShowQRScannerModal(true), className: "px-4 py-2 bg-purple-500/10 hover:bg-purple-500/20 text-purple-400 border border-purple-500/20 rounded text-sm font-medium transition-all duration-200" }, [ React.createElement('i', { key: 'icon', className: 'fas fa-qrcode mr-2' }), 'Scan QR Code' ]), React.createElement('button', { key: 'process-btn', onClick: onCreateAnswer, disabled: !offerInput.trim() || connectionStatus === 'connecting', className: "flex-1 btn-secondary text-white py-2 px-4 rounded-lg font-medium transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed" }, [ React.createElement('i', { className: 'fas fa-cogs mr-2' }), 'Process invitation' ]) ]), showQRScanner && React.createElement('div', { key: 'qr-scanner', className: "p-4 bg-gray-800/50 border border-gray-600/30 rounded-lg text-center" }, [ React.createElement('h4', { key: 'scanner-title', className: "text-sm font-medium text-primary mb-3" }, 'QR Code Scanner'), React.createElement('p', { key: 'scanner-description', className: "text-xs text-gray-400 mb-3" }, 'Use your device camera to scan the QR code from the invitation'), React.createElement('button', { key: 'open-scanner', onClick: () => { if (typeof setShowQRScannerModal === 'function') { setShowQRScannerModal(true); } else { console.error('setShowQRScannerModal is not a function:', setShowQRScannerModal); } }, className: "w-full px-4 py-3 bg-purple-600 hover:bg-purple-500 text-white rounded-lg font-medium transition-all duration-200 mb-3" }, [ React.createElement('i', { key: 'camera-icon', className: 'fas fa-camera mr-2' }), 'Open Camera Scanner' ]), React.createElement('button', { key: 'test-qr', onClick: async () => { console.log('Creating test QR code...'); if (window.generateQRCode) { const testData = '{"type":"test","message":"Hello QR Scanner!"}'; const qrUrl = await window.generateQRCode(testData); console.log('Test QR code generated:', qrUrl); const newWindow = window.open(); newWindow.document.write(``); } }, className: "px-3 py-1 bg-green-600/20 hover:bg-green-600/30 text-green-300 border border-green-500/20 rounded text-xs font-medium transition-all duration-200 mr-2" }, 'Test QR'), React.createElement('button', { key: 'close-scanner', onClick: () => setShowQRScanner(false), className: "px-3 py-1 bg-gray-600/20 hover:bg-gray-600/30 text-gray-300 border border-gray-500/20 rounded text-xs font-medium transition-all duration-200" }, 'Close Scanner') ]) ])), // Step 2 showAnswerStep && React.createElement('div', { key: 'step2', className: "card-minimal rounded-xl p-6" }, [ React.createElement('div', { key: 'step-header', className: "flex items-center mb-4" }, [ React.createElement('div', { key: 'number', className: "step-number mr-3" }, '2'), React.createElement('h3', { key: 'title', className: "text-lg font-medium text-primary" }, "Sending a secure response") ]), React.createElement('div', { key: 'success', className: "p-3 bg-green-500/10 border border-green-500/20 rounded-lg mb-4" }, [ React.createElement('p', { className: "text-green-400 text-sm font-medium flex items-center" }, [ React.createElement('i', { className: 'fas fa-check-circle mr-2' }), 'Secure response created! Send this code to the initiator:' ]) ]), React.createElement('div', { key: 'answer-data', className: "space-y-3 mb-4" }, [ // Raw JSON hidden intentionally; users copy compressed string or use QR React.createElement(EnhancedCopyButton, { key: 'copy', text: (() => { try { const min = typeof answerData === 'object' ? JSON.stringify(answerData) : (answerData || ''); if (typeof window.encodeBinaryToPrefixed === 'function') { return window.encodeBinaryToPrefixed(min); } if (typeof window.compressToPrefixedGzip === 'function') { return window.compressToPrefixedGzip(min); } return min; } catch { return typeof answerData === 'object' ? JSON.stringify(answerData) : (answerData || ''); } })(), className: "w-full px-3 py-2 bg-green-500/10 hover:bg-green-500/20 text-green-400 border border-green-500/20 rounded text-sm font-medium" }, 'Copy response code') ]), // QR Code section for answer qrCodeUrl && React.createElement('div', { key: 'qr-container', className: "mt-4 p-4 border border-gray-600/30 rounded-lg text-center" }, [ React.createElement('h4', { key: 'qr-title', className: "text-sm font-medium text-primary mb-3" }, 'Scan QR code to complete connection'), React.createElement('div', { key: 'qr-wrapper', className: "flex justify-center" }, [ React.createElement('img', { key: 'qr-image', src: qrCodeUrl, alt: "QR Code for secure response", className: "max-w-none h-auto border border-gray-600/30 rounded w-[20rem] sm:w-[24rem] md:w-[28rem] lg:w-[32rem]" }) ]), ((qrFramesTotal || 0) >= 1) && React.createElement('div', { key: 'qr-controls-below', className: "mt-4 flex flex-col items-center gap-2" }, [ React.createElement('div', { key: 'frame-indicator', className: "text-xs text-gray-300" }, `Frame ${Math.max(1, (qrFrameIndex || 1))}/${qrFramesTotal || 1}`), React.createElement('div', { key: 'control-buttons', className: "flex gap-1" }, [ (qrFramesTotal || 0) > 1 && React.createElement('button', { key: 'prev-frame', onClick: prevQrFrame, className: "w-6 h-6 bg-gray-600 hover:bg-gray-500 text-white rounded text-xs flex items-center justify-center" }, '◀'), React.createElement('button', { key: 'toggle-manual', onClick: toggleQrManualMode, className: `px-2 py-1 rounded text-xs font-medium ${ qrManualMode ? 'bg-blue-500 text-white' : 'bg-gray-600 text-gray-300 hover:bg-gray-500' }` }, qrManualMode ? 'Manual' : 'Auto'), (qrFramesTotal || 0) > 1 && React.createElement('button', { key: 'next-frame', onClick: nextQrFrame, className: "w-6 h-6 bg-gray-600 hover:bg-gray-500 text-white rounded text-xs flex items-center justify-center" }, '▶') ]) ]), React.createElement('p', { key: 'qr-description', className: "text-xs text-gray-400 mt-2" }, 'The initiator can scan this QR code to complete the secure connection') ]), React.createElement('div', { key: 'info', className: "p-3 bg-purple-500/10 border border-purple-500/20 rounded-lg" }, [ React.createElement('p', { className: "text-purple-400 text-sm flex items-center justify-center" }, [ React.createElement('i', { className: 'fas fa-shield-alt mr-2' }), 'The connection will be established with verification' ]) ]) ]) ]) ]); } }; // Global scroll function - defined outside components to ensure availability const createScrollToBottomFunction = (chatMessagesRef) => { return () => { if (chatMessagesRef && chatMessagesRef.current) { const scrollAttempt = () => { if (chatMessagesRef.current) { chatMessagesRef.current.scrollTo({ top: chatMessagesRef.current.scrollHeight, behavior: 'smooth' }); } }; scrollAttempt(); setTimeout(scrollAttempt, 50); setTimeout(scrollAttempt, 150); setTimeout(scrollAttempt, 300); requestAnimationFrame(() => { setTimeout(scrollAttempt, 100); }); } }; }; const EnhancedChatInterface = ({ messages, messageInput, setMessageInput, onSendMessage, onDisconnect, keyFingerprint, isVerified, chatMessagesRef, scrollToBottom, webrtcManager }) => { const [showScrollButton, setShowScrollButton] = React.useState(false); const [showFileTransfer, setShowFileTransfer] = React.useState(false); React.useEffect(() => { if (chatMessagesRef.current && messages.length > 0) { const { scrollTop, scrollHeight, clientHeight } = chatMessagesRef.current; const isNearBottom = scrollHeight - scrollTop - clientHeight < 100; if (isNearBottom) { const smoothScroll = () => { if (chatMessagesRef.current) { chatMessagesRef.current.scrollTo({ top: chatMessagesRef.current.scrollHeight, behavior: 'smooth' }); } }; smoothScroll(); setTimeout(smoothScroll, 50); setTimeout(smoothScroll, 150); } } }, [messages, chatMessagesRef]); // Обработчик скролла const handleScroll = () => { if (chatMessagesRef.current) { const { scrollTop, scrollHeight, clientHeight } = chatMessagesRef.current; const isNearBottom = scrollHeight - scrollTop - clientHeight < 100; setShowScrollButton(!isNearBottom); } }; // Прокрутка вниз по кнопке const handleScrollToBottom = () => { console.log('🔍 handleScrollToBottom called, scrollToBottom type:', typeof scrollToBottom); if (typeof scrollToBottom === 'function') { scrollToBottom(); setShowScrollButton(false); } else { console.error('scrollToBottom is not a function:', scrollToBottom); // Fallback: direct scroll if (chatMessagesRef.current) { chatMessagesRef.current.scrollTo({ top: chatMessagesRef.current.scrollHeight, behavior: 'smooth' }); } setShowScrollButton(false); } }; const handleKeyPress = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); onSendMessage(); } }; const isFileTransferReady = () => { if (!webrtcManager) return false; const connected = webrtcManager.isConnected ? webrtcManager.isConnected() : false; const verified = webrtcManager.isVerified || false; const hasDataChannel = webrtcManager.dataChannel && webrtcManager.dataChannel.readyState === 'open'; return connected && verified && hasDataChannel; }; // Возврат JSX через React.createElement return React.createElement( 'div', { className: "chat-container flex flex-col", style: { backgroundColor: '#272827', height: 'calc(100vh - 64px)' } }, [ // Область сообщений React.createElement( 'div', { className: "flex-1 flex flex-col overflow-hidden" }, React.createElement( 'div', { className: "flex-1 max-w-4xl mx-auto w-full p-4 overflow-hidden" }, React.createElement( 'div', { ref: chatMessagesRef, onScroll: handleScroll, className: "h-full overflow-y-auto space-y-3 hide-scrollbar pr-2 scroll-smooth" }, messages.length === 0 ? React.createElement( 'div', { className: "flex items-center justify-center h-full" }, React.createElement( 'div', { className: "text-center max-w-md" }, [ React.createElement( 'div', { className: "w-16 h-16 bg-green-500/10 border border-green-500/20 rounded-xl flex items-center justify-center mx-auto mb-4" }, React.createElement( 'svg', { className: "w-8 h-8 text-green-500", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" }, React.createElement('path', { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" }) ) ), React.createElement('h3', { className: "text-lg font-medium text-gray-300 mb-2" }, "Secure channel is ready!"), React.createElement('p', { className: "text-gray-400 text-sm mb-4" }, "All messages are protected by modern cryptographic algorithms"), React.createElement( 'div', { className: "text-left space-y-2" }, [ ['End-to-end encryption', 'M5 13l4 4L19 7'], ['Protection against replay attacks', 'M5 13l4 4L19 7'], ['Integrity verification', 'M5 13l4 4L19 7'], ['Perfect Forward Secrecy', 'M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15'] ].map(([text, d], i) => React.createElement( 'div', { key: `f${i}`, className: "flex items-center text-sm text-gray-400" }, [ React.createElement( 'svg', { className: `w-4 h-4 mr-3 ${i === 3 ? 'text-purple-500' : 'text-green-500'}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" }, React.createElement('path', { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: d }) ), text ] ) ) ) ] ) ) : messages.map((msg) => React.createElement(EnhancedChatMessage, { key: msg.id, message: msg.message, type: msg.type, timestamp: msg.timestamp }) ) ) ) ), // Кнопка прокрутки вниз showScrollButton && React.createElement( 'button', { onClick: handleScrollToBottom, className: "fixed right-6 w-12 h-12 bg-green-500/20 hover:bg-green-500/30 border border-green-500/30 text-green-400 rounded-full flex items-center justify-center transition-all duration-200 shadow-lg z-50", style: { bottom: '160px' } }, React.createElement( 'svg', { className: "w-6 h-6", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" }, React.createElement('path', { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 14l-7 7m0 0l-7-7m7 7V3" }) ) ), React.createElement( 'div', { className: "flex-shrink-0 border-t border-gray-500/10", style: { backgroundColor: '#272827' } }, React.createElement( 'div', { className: "max-w-4xl mx-auto px-4" }, [ React.createElement( 'button', { onClick: () => setShowFileTransfer(!showFileTransfer), className: `flex items-center text-sm text-gray-400 hover:text-gray-300 transition-colors py-4 ${showFileTransfer ? 'mb-4' : ''}` }, [ React.createElement( 'svg', { className: `w-4 h-4 mr-2 transform transition-transform ${showFileTransfer ? 'rotate-180' : ''}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" }, showFileTransfer ? React.createElement('path', { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 15l7-7 7 7" }) : React.createElement('path', { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" }) ), showFileTransfer ? 'Hide file transfer' : 'Send files' ] ), showFileTransfer && React.createElement(window.FileTransferComponent || (() => React.createElement('div', { className: "p-4 text-center text-red-400" }, 'FileTransferComponent not loaded') ), { webrtcManager: webrtcManager, isConnected: isFileTransferReady() }) ] ) ), React.createElement( 'div', { className: "border-t border-gray-500/10" }, React.createElement( 'div', { className: "max-w-4xl mx-auto p-4" }, React.createElement( 'div', { className: "flex items-stretch space-x-3" }, [ React.createElement( 'div', { className: "flex-1 relative" }, [ React.createElement('textarea', { value: messageInput, onChange: (e) => setMessageInput(e.target.value), onKeyDown: handleKeyPress, placeholder: "Enter message to encrypt...", rows: 2, maxLength: 2000, style: { backgroundColor: '#272827' }, className: "w-full p-3 border border-gray-600 rounded-lg resize-none text-gray-300 placeholder-gray-500 focus:border-green-500/40 focus:outline-none transition-all custom-scrollbar text-sm" }), React.createElement( 'div', { className: "absolute bottom-2 right-3 flex items-center space-x-2 text-xs text-gray-400" }, [ React.createElement('span', null, `${messageInput.length}/2000`), React.createElement('span', null, "• Enter to send") ] ) ] ), React.createElement( 'button', { onClick: onSendMessage, disabled: !messageInput.trim(), className: "bg-green-400/20 text-green-400 p-3 rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center min-h-[72px]" }, React.createElement( 'svg', { className: "w-6 h-6", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" }, React.createElement('path', { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 19l9 2-9-18-9 18 9-2zm0 0v-8" }) ) ) ] ) ) ) ] ); }; // Main Enhanced Application Component const EnhancedSecureP2PChat = () => { const [messages, setMessages] = React.useState([]); const [connectionStatus, setConnectionStatus] = React.useState('disconnected'); // Moved scrollToBottom logic to be available globally const [messageInput, setMessageInput] = React.useState(''); const [offerData, setOfferData] = React.useState(''); const [answerData, setAnswerData] = React.useState(''); const [offerInput, setOfferInput] = React.useState(''); const [answerInput, setAnswerInput] = React.useState(''); const [keyFingerprint, setKeyFingerprint] = React.useState(''); const [verificationCode, setVerificationCode] = React.useState(''); const [showOfferStep, setShowOfferStep] = React.useState(false); const [showAnswerStep, setShowAnswerStep] = React.useState(false); const [showVerification, setShowVerification] = React.useState(false); const [showQRCode, setShowQRCode] = React.useState(false); const [qrCodeUrl, setQrCodeUrl] = React.useState(''); const [showQRScanner, setShowQRScanner] = React.useState(false); const [showQRScannerModal, setShowQRScannerModal] = React.useState(false); const [isVerified, setIsVerified] = React.useState(false); const [securityLevel, setSecurityLevel] = React.useState(null); // Mutual verification states const [localVerificationConfirmed, setLocalVerificationConfirmed] = React.useState(false); const [remoteVerificationConfirmed, setRemoteVerificationConfirmed] = React.useState(false); const [bothVerificationsConfirmed, setBothVerificationsConfirmed] = React.useState(false); // PAKE password states removed - using SAS verification instead // Session state - all security features enabled by default const [sessionTimeLeft, setSessionTimeLeft] = React.useState(0); const [pendingSession, setPendingSession] = React.useState(null); // All security features are enabled by default - no payment required // ============================================ // CENTRALIZED CONNECTION STATE MANAGEMENT // ============================================ const [connectionState, setConnectionState] = React.useState({ status: 'disconnected', hasActiveAnswer: false, answerCreatedAt: null, isUserInitiatedDisconnect: false }); // Centralized connection state handler const updateConnectionState = (newState, options = {}) => { const { preserveAnswer = false, isUserAction = false } = options; setConnectionState(prev => ({ ...prev, ...newState, isUserInitiatedDisconnect: isUserAction, hasActiveAnswer: preserveAnswer ? prev.hasActiveAnswer : false, answerCreatedAt: preserveAnswer ? prev.answerCreatedAt : null })); }; // Check if we should preserve answer data const shouldPreserveAnswerData = () => { const now = Date.now(); const answerAge = now - (connectionState.answerCreatedAt || 0); const maxPreserveTime = 300000; const hasAnswerData = (answerData && answerData.trim().length > 0) || (answerInput && answerInput.trim().length > 0); const hasAnswerQR = qrCodeUrl && qrCodeUrl.trim().length > 0; const shouldPreserve = (connectionState.hasActiveAnswer && answerAge < maxPreserveTime && !connectionState.isUserInitiatedDisconnect) || (hasAnswerData && answerAge < maxPreserveTime && !connectionState.isUserInitiatedDisconnect) || (hasAnswerQR && answerAge < maxPreserveTime && !connectionState.isUserInitiatedDisconnect); return shouldPreserve; }; // Mark answer as created const markAnswerCreated = () => { updateConnectionState({ hasActiveAnswer: true, answerCreatedAt: Date.now() }); }; // Global functions for cleanup React.useEffect(() => { window.forceCleanup = () => { handleClearData(); if (webrtcManagerRef.current) { webrtcManagerRef.current.disconnect(); } }; window.clearLogs = () => { if (typeof console.clear === 'function') { console.clear(); } }; return () => { delete window.forceCleanup; delete window.clearLogs; }; }, []); const webrtcManagerRef = React.useRef(null); // Expose for modules/UI that run outside this closure (e.g., inline handlers) // Safe because it's a ref object and we maintain it centrally here window.webrtcManagerRef = webrtcManagerRef; const addMessageWithAutoScroll = React.useCallback((message, type) => { const newMessage = { message, type, id: Date.now() + Math.random(), timestamp: Date.now() }; setMessages(prev => { const updated = [...prev, newMessage]; setTimeout(() => { if (chatMessagesRef?.current) { const container = chatMessagesRef.current; try { const { scrollTop, scrollHeight, clientHeight } = container; const isNearBottom = scrollHeight - scrollTop - clientHeight < 100; if (isNearBottom || prev.length === 0) { requestAnimationFrame(() => { if (container && container.scrollTo) { container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' }); } }); } } catch (error) { console.warn('Scroll error:', error); container.scrollTop = container.scrollHeight; } } }, 50); return updated; }); }, []); // Update security level based on real verification const updateSecurityLevel = React.useCallback(async () => { if (window.isUpdatingSecurity) { return; } window.isUpdatingSecurity = true; try { if (webrtcManagerRef.current) { // All security features are enabled by default - always show MAXIMUM level setSecurityLevel({ level: 'MAXIMUM', score: 100, color: 'green', details: 'All security features enabled by default', passedChecks: 10, totalChecks: 10, isRealData: true }); if (window.DEBUG_MODE) { const currentLevel = webrtcManagerRef.current.ecdhKeyPair && webrtcManagerRef.current.ecdsaKeyPair ? await webrtcManagerRef.current.calculateSecurityLevel() : { level: 'MAXIMUM', score: 100, sessionType: 'premium', passedChecks: 10, totalChecks: 10 }; } } } catch (error) { console.error('Failed to update security level:', error); setSecurityLevel({ level: 'ERROR', score: 0, color: 'red', details: 'Verification failed' }); } finally { setTimeout(() => { window.isUpdatingSecurity = false; }, 2000); } }, []); // Session time ticker - unlimited sessions React.useEffect(() => { const timer = setInterval(() => { // Sessions are unlimited - no time restrictions setSessionTimeLeft(0); }, 1000); return () => clearInterval(timer); }, []); // Sessions are unlimited - no expiration handler needed // All security features are enabled by default - no demo sessions needed const chatMessagesRef = React.useRef(null); // Create scroll function using global helper const scrollToBottom = createScrollToBottomFunction(chatMessagesRef); // Auto-scroll when messages change React.useEffect(() => { if (messages.length > 0 && chatMessagesRef.current) { scrollToBottom(); setTimeout(scrollToBottom, 50); setTimeout(scrollToBottom, 150); } }, [messages]); // PAKE password functions removed - using SAS verification instead React.useEffect(() => { // Prevent multiple initializations if (webrtcManagerRef.current) { console.log('⚠️ WebRTC Manager already initialized, skipping...'); return; } const handleMessage = (message, type) => { if (typeof message === 'string' && message.trim().startsWith('{')) { try { const parsedMessage = JSON.parse(message); const blockedTypes = [ 'file_transfer_start', 'file_transfer_response', 'file_chunk', 'chunk_confirmation', 'file_transfer_complete', 'file_transfer_error', 'heartbeat', 'verification', 'verification_response', 'verification_confirmed', 'verification_both_confirmed', 'peer_disconnect', 'key_rotation_signal', 'key_rotation_ready', 'security_upgrade' ]; if (parsedMessage.type && blockedTypes.includes(parsedMessage.type)) { console.log(`Blocked system/file message from chat: ${parsedMessage.type}`); return; } } catch (parseError) { } } addMessageWithAutoScroll(message, type); }; const handleStatusChange = (status) => { setConnectionStatus(status); if (status === 'connected') { document.dispatchEvent(new CustomEvent('new-connection')); // Не скрываем верификацию при 'connected' - только при 'verified' // setIsVerified(true); // setShowVerification(false); if (!window.isUpdatingSecurity) { updateSecurityLevel().catch(console.error); } } else if (status === 'verifying') { setShowVerification(true); if (!window.isUpdatingSecurity) { updateSecurityLevel().catch(console.error); } } else if (status === 'verified') { setIsVerified(true); setShowVerification(false); setBothVerificationsConfirmed(true); setConnectionStatus('connected'); // Force immediate update of isVerified state setTimeout(() => { setIsVerified(true); }, 0); if (!window.isUpdatingSecurity) { updateSecurityLevel().catch(console.error); } } else if (status === 'connecting') { if (!window.isUpdatingSecurity) { updateSecurityLevel().catch(console.error); } } else if (status === 'disconnected') { updateConnectionState({ status: 'disconnected' }); setConnectionStatus('disconnected'); if (shouldPreserveAnswerData()) { setIsVerified(false); setShowVerification(false); return; } setIsVerified(false); setShowVerification(false); // Dispatch disconnected event for SessionTimer document.dispatchEvent(new CustomEvent('disconnected')); // Clear verification states setLocalVerificationConfirmed(false); setRemoteVerificationConfirmed(false); setBothVerificationsConfirmed(false); // Clear connection data setOfferData(null); setAnswerData(null); setOfferInput(''); setAnswerInput(''); setShowOfferStep(false); setShowAnswerStep(false); setKeyFingerprint(''); setVerificationCode(''); setSecurityLevel(null); // Reset session and timer setSessionTimeLeft(0); // Return to main page after a short delay setTimeout(() => { setConnectionStatus('disconnected'); setShowVerification(false); setOfferData(null); setAnswerData(null); setOfferInput(''); setAnswerInput(''); setShowOfferStep(false); setShowAnswerStep(false); setMessages([]); }, 1000); } else if (status === 'peer_disconnected') { setSessionTimeLeft(0); document.dispatchEvent(new CustomEvent('peer-disconnect')); // A short delay before clearing to display the status setTimeout(() => { setKeyFingerprint(''); setVerificationCode(''); setSecurityLevel(null); setIsVerified(false); setShowVerification(false); setConnectionStatus('disconnected'); // Clear verification states setLocalVerificationConfirmed(false); setRemoteVerificationConfirmed(false); setBothVerificationsConfirmed(false); // Clear connection data setOfferData(null); setAnswerData(null); setOfferInput(''); setAnswerInput(''); setShowOfferStep(false); setShowAnswerStep(false); setMessages([]); if (typeof console.clear === 'function') { console.clear(); } }, 2000); } }; const handleKeyExchange = (fingerprint) => { if (fingerprint === '') { setKeyFingerprint(''); } else { setKeyFingerprint(fingerprint); } }; const handleVerificationRequired = (code) => { if (code === '') { setVerificationCode(''); setShowVerification(false); } else { setVerificationCode(code); setShowVerification(true); } }; const handleVerificationStateChange = (state) => { setLocalVerificationConfirmed(state.localConfirmed); setRemoteVerificationConfirmed(state.remoteConfirmed); setBothVerificationsConfirmed(state.bothConfirmed); }; // Callback for handling response errors const handleAnswerError = (errorType, errorMessage) => { if (errorType === 'replay_attack') { // Reset the session upon replay attack setSessionTimeLeft(0); setPendingSession(null); addMessageWithAutoScroll('💡 Data is outdated. Please create a new invitation or use a current response code.', 'system'); if (typeof console.clear === 'function') { console.clear(); } } else if (errorType === 'security_violation') { // Reset the session upon security breach setSessionTimeLeft(0); setPendingSession(null); addMessageWithAutoScroll(` Security breach: ${errorMessage}`, 'system'); if (typeof console.clear === 'function') { console.clear(); } } }; if (typeof console.clear === 'function') { console.clear(); } webrtcManagerRef.current = new EnhancedSecureWebRTCManager( handleMessage, handleStatusChange, handleKeyExchange, handleVerificationRequired, handleAnswerError, handleVerificationStateChange ); handleMessage(' SecureBit.chat Enhanced Security Edition v4.2.12 - ECDH + DTLS + SAS initialized. Ready to establish a secure connection with ECDH key exchange, DTLS fingerprint verification, and SAS authentication to prevent MITM attacks.', 'system'); const handleBeforeUnload = (event) => { if (event.type === 'beforeunload' && !isTabSwitching) { if (webrtcManagerRef.current && webrtcManagerRef.current.isConnected()) { try { webrtcManagerRef.current.sendSystemMessage({ type: 'peer_disconnect', reason: 'user_disconnect', timestamp: Date.now() }); } catch (error) { } setTimeout(() => { if (webrtcManagerRef.current) { webrtcManagerRef.current.disconnect(); } }, 100); } else if (webrtcManagerRef.current) { webrtcManagerRef.current.disconnect(); } } else if (isTabSwitching) { event.preventDefault(); event.returnValue = ''; } }; window.addEventListener('beforeunload', handleBeforeUnload); let isTabSwitching = false; let tabSwitchTimeout = null; const handleVisibilityChange = () => { if (document.visibilityState === 'hidden') { isTabSwitching = true; if (tabSwitchTimeout) { clearTimeout(tabSwitchTimeout); } tabSwitchTimeout = setTimeout(() => { isTabSwitching = false; }, 5000); } else if (document.visibilityState === 'visible') { isTabSwitching = false; if (tabSwitchTimeout) { clearTimeout(tabSwitchTimeout); tabSwitchTimeout = null; } } }; document.addEventListener('visibilitychange', handleVisibilityChange); // Setup file transfer callbacks if (webrtcManagerRef.current) { webrtcManagerRef.current.setFileTransferCallbacks( // Progress callback (progress) => { console.log('File progress:', progress); }, // File received callback (fileData) => { const sizeMb = Math.max(1, Math.round((fileData.fileSize || 0) / (1024 * 1024))); const downloadMessage = React.createElement('div', { className: 'flex items-center space-x-2' }, [ React.createElement('span', { key: 'label' }, ` File received: ${fileData.fileName} (${sizeMb} MB)`), React.createElement('button', { key: 'btn', className: 'px-3 py-1 rounded bg-blue-600 hover:bg-blue-700 text-white text-xs', onClick: async () => { try { const url = await fileData.getObjectURL(); const a = document.createElement('a'); a.href = url; a.download = fileData.fileName; a.click(); setTimeout(() => fileData.revokeObjectURL(url), 15000); } catch (e) { console.error('Download failed:', e); addMessageWithAutoScroll(` File upload error: ${String(e?.message || e)}`, 'system'); } } }, 'Download') ]); addMessageWithAutoScroll(downloadMessage, 'system'); }, // Error callback (error) => { console.error('File transfer error:', error); if (error.includes('Connection not ready')) { addMessageWithAutoScroll(` File transfer error: connection not ready. Try again later.`, 'system'); } else if (error.includes('File too large')) { addMessageWithAutoScroll(` File is too big. Maximum size: 100 MB`, 'system'); } else { addMessageWithAutoScroll(` File transfer error: ${error}`, 'system'); } } ); } return () => { window.removeEventListener('beforeunload', handleBeforeUnload); document.removeEventListener('visibilitychange', handleVisibilityChange); if (tabSwitchTimeout) { clearTimeout(tabSwitchTimeout); tabSwitchTimeout = null; } if (webrtcManagerRef.current) { webrtcManagerRef.current.disconnect(); webrtcManagerRef.current = null; } }; }, []); // Empty dependency array to run only once // All security features are enabled by default - no session purchase needed const compressOfferData = (offerData) => { try { // Parse the offer data if it's a string const offer = typeof offerData === 'string' ? JSON.parse(offerData) : offerData; // Create a minimal version with only the most essential data const minimalOffer = { type: offer.type, version: offer.version, timestamp: offer.timestamp, sessionId: offer.sessionId, connectionId: offer.connectionId, verificationCode: offer.verificationCode, salt: offer.salt, // Use only key fingerprints instead of full keys keyFingerprints: offer.keyFingerprints, // Add a reference to get full data fullDataAvailable: true, compressionLevel: 'minimal' }; return JSON.stringify(minimalOffer); } catch (error) { console.error('Error compressing offer data:', error); return offerData; // Return original if compression fails } }; const createQRReference = (offerData) => { try { // Create a unique reference ID for this offer const referenceId = `offer_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // Store the full offer data in localStorage with the reference ID localStorage.setItem(`qr_offer_${referenceId}`, JSON.stringify(offerData)); // Create a minimal QR code with just the reference const qrReference = { type: 'secure_offer_reference', referenceId: referenceId, timestamp: Date.now(), message: 'Scan this QR code and use the reference ID to get full offer data' }; return JSON.stringify(qrReference); } catch (error) { console.error('Error creating QR reference:', error); return null; } }; const createTemplateOffer = (offer) => { // Minimal template to keep QR within single image capacity const templateOffer = { type: 'enhanced_secure_offer_template', version: '4.0', sessionId: offer.sessionId, connectionId: offer.connectionId, verificationCode: offer.verificationCode, timestamp: offer.timestamp, // Avoid bulky fields (SDP, raw keys); keep only fingerprints and essentials keyFingerprints: offer.keyFingerprints, // Keep concise auth hints (omit large nonces) authChallenge: offer?.authChallenge?.challenge, // Optionally include a compact capability hint if small capabilities: Array.isArray(offer.capabilities) && offer.capabilities.length <= 5 ? offer.capabilities : undefined }; return templateOffer; }; // Conservative QR payload limits (characters). Adjust per error correction level. const MAX_QR_LEN = 800; // for JSON/plain/gzip const BIN_MAX_QR_LEN = 400; // stricter for SB1:bin to improve scan reliability const [qrFramesTotal, setQrFramesTotal] = React.useState(0); const [qrFrameIndex, setQrFrameIndex] = React.useState(0); const [qrManualMode, setQrManualMode] = React.useState(false); // Animated QR state (for multi-chunk COSE) const qrAnimationRef = React.useRef({ timer: null, chunks: [], idx: 0, active: false }); const stopQrAnimation = () => { try { if (qrAnimationRef.current.timer) { clearInterval(qrAnimationRef.current.timer); } } catch {} qrAnimationRef.current = { timer: null, chunks: [], idx: 0, active: false }; setQrFrameIndex(0); setQrFramesTotal(0); setQrManualMode(false); }; // Render frame at current index (no index mutation) const renderCurrent = async () => { const { chunks, idx } = qrAnimationRef.current || {}; if (!chunks || !chunks.length) return; const current = chunks[idx % chunks.length]; try { const isDesktop = (typeof window !== 'undefined') && ((window.innerWidth || 0) >= 1024); const QR_SIZE = isDesktop ? 720 : 512; const url = await (window.generateQRCode ? window.generateQRCode(current, { errorCorrectionLevel: 'M', margin: 2, size: QR_SIZE }) : Promise.resolve('')); if (url) setQrCodeUrl(url); } catch (e) { console.warn('Animated QR render error (current):', e); } setQrFrameIndex(((qrAnimationRef.current?.idx || 0) % (qrAnimationRef.current?.chunks?.length || 1)) + 1); }; // Render current frame, then advance index by 1 const renderAndAdvance = async () => { await renderCurrent(); const len = qrAnimationRef.current?.chunks?.length || 0; if (len > 0) { const nextIdx = ((qrAnimationRef.current?.idx || 0) + 1) % len; qrAnimationRef.current.idx = nextIdx; setQrFrameIndex(nextIdx + 1); } }; const toggleQrManualMode = () => { const newManualMode = !qrManualMode; setQrManualMode(newManualMode); if (newManualMode) { if (qrAnimationRef.current.timer) { clearInterval(qrAnimationRef.current.timer); qrAnimationRef.current.timer = null; } console.log('QR Manual mode enabled - auto-scroll stopped'); } else { if (qrAnimationRef.current.chunks.length > 1) { const intervalMs = 3000; qrAnimationRef.current.active = true; clearInterval(qrAnimationRef.current.timer); qrAnimationRef.current.timer = setInterval(renderAndAdvance, intervalMs); } console.log('QR Manual mode disabled - auto-scroll resumed'); } }; const nextQrFrame = async () => { console.log('🎮 nextQrFrame called, qrFramesTotal:', qrFramesTotal, 'qrAnimationRef.current:', qrAnimationRef.current); if (qrAnimationRef.current.chunks.length > 1) { const nextIdx = (qrAnimationRef.current.idx + 1) % qrAnimationRef.current.chunks.length; qrAnimationRef.current.idx = nextIdx; setQrFrameIndex(nextIdx + 1); console.log('🎮 Next frame index:', nextIdx + 1); // Ensure auto-advance timer runs in manual mode too try { clearInterval(qrAnimationRef.current.timer); } catch {} qrAnimationRef.current.timer = null; await renderCurrent(); // If not in manual mode, restart auto timer if (!qrManualMode && qrAnimationRef.current.chunks.length > 1) { const intervalMs = 3000; qrAnimationRef.current.active = true; qrAnimationRef.current.timer = setInterval(renderAndAdvance, intervalMs); } else { qrAnimationRef.current.active = false; } } else { console.log('🎮 No multiple frames to navigate'); } }; const prevQrFrame = async () => { console.log('🎮 prevQrFrame called, qrFramesTotal:', qrFramesTotal, 'qrAnimationRef.current:', qrAnimationRef.current); if (qrAnimationRef.current.chunks.length > 1) { const prevIdx = (qrAnimationRef.current.idx - 1 + qrAnimationRef.current.chunks.length) % qrAnimationRef.current.chunks.length; qrAnimationRef.current.idx = prevIdx; setQrFrameIndex(prevIdx + 1); console.log('🎮 Previous frame index:', prevIdx + 1); try { clearInterval(qrAnimationRef.current.timer); } catch {} qrAnimationRef.current.timer = null; await renderCurrent(); if (!qrManualMode && qrAnimationRef.current.chunks.length > 1) { const intervalMs = 3000; qrAnimationRef.current.active = true; qrAnimationRef.current.timer = setInterval(renderAndAdvance, intervalMs); } else { qrAnimationRef.current.active = false; } } else { console.log('🎮 No multiple frames to navigate'); } }; // Buffer for assembling scanned COSE chunks const qrChunksBufferRef = React.useRef({ id: null, total: 0, seen: new Set(), items: [] }); const generateQRCode = async (data) => { try { const originalSize = typeof data === 'string' ? data.length : JSON.stringify(data).length; const isDesktop = (typeof window !== 'undefined') && ((window.innerWidth || 0) >= 1024); const QR_SIZE = isDesktop ? 720 : 512; // Try binary format first (CBOR + deflate + base64url) if (typeof window.generateBinaryQRCodeFromObject === 'function') { try { const obj = typeof data === 'string' ? JSON.parse(data) : data; const qrDataUrl = await window.generateBinaryQRCodeFromObject(obj, { errorCorrectionLevel: 'M', size: QR_SIZE, margin: 2 }); if (qrDataUrl) { try { if (qrAnimationRef.current && qrAnimationRef.current.timer) { clearInterval(qrAnimationRef.current.timer); } } catch {} qrAnimationRef.current = { timer: null, chunks: [], idx: 0, active: false }; setQrFrameIndex(0); setQrFramesTotal(0); setQrManualMode(false); setQrCodeUrl(qrDataUrl); setQrFramesTotal(1); setQrFrameIndex(1); return; } } catch (e) { console.warn('Binary QR generation failed, falling back to compressed:', e?.message || e); } } // Fallback to compressed JSON if (typeof window.generateCompressedQRCode === 'function') { try { const payload = typeof data === 'string' ? data : JSON.stringify(data); const qrDataUrl = await window.generateCompressedQRCode(payload, { errorCorrectionLevel: 'M', size: QR_SIZE, margin: 2 }); if (qrDataUrl) { try { if (qrAnimationRef.current && qrAnimationRef.current.timer) { clearInterval(qrAnimationRef.current.timer); } } catch {} qrAnimationRef.current = { timer: null, chunks: [], idx: 0, active: false }; setQrFrameIndex(0); setQrFramesTotal(0); setQrManualMode(false); setQrCodeUrl(qrDataUrl); setQrFramesTotal(1); setQrFrameIndex(1); return; } } catch (e) { console.warn('Compressed QR generation failed, falling back to plain:', e?.message || e); } } // Final fallback to plain JSON const payload = typeof data === 'string' ? data : JSON.stringify(data); if (payload.length <= MAX_QR_LEN) { if (!window.generateQRCode) throw new Error('QR code generator unavailable'); try { if (qrAnimationRef.current && qrAnimationRef.current.timer) { clearInterval(qrAnimationRef.current.timer); } } catch {} qrAnimationRef.current = { timer: null, chunks: [], idx: 0, active: false }; setQrFrameIndex(0); setQrFramesTotal(0); setQrManualMode(false); const qrDataUrl = await window.generateQRCode(payload, { errorCorrectionLevel: 'M', size: QR_SIZE, margin: 2 }); setQrCodeUrl(qrDataUrl); setQrFramesTotal(1); setQrFrameIndex(1); return; } // Large payload: разбиваем на фреймы (plain JSON) try { if (qrAnimationRef.current && qrAnimationRef.current.timer) { clearInterval(qrAnimationRef.current.timer); } } catch {} qrAnimationRef.current = { timer: null, chunks: [], idx: 0, active: false }; setQrFrameIndex(0); setQrFramesTotal(0); setQrManualMode(false); const id = `raw_${Date.now()}_${Math.random().toString(36).slice(2)}`; const TARGET_CHUNKS = 10; const FRAME_MAX = Math.max(200, Math.floor(payload.length / TARGET_CHUNKS)); const total = Math.ceil(payload.length / FRAME_MAX); const rawChunks = []; for (let i = 0; i < total; i++) { const seq = i + 1; const part = payload.slice(i * FRAME_MAX, (i + 1) * FRAME_MAX); rawChunks.push(JSON.stringify({ hdr: { v: 1, id, seq, total, rt: 'raw' }, body: part })); } if (!window.generateQRCode) throw new Error('QR code generator unavailable'); if (rawChunks.length === 1) { const url = await window.generateQRCode(rawChunks[0], { errorCorrectionLevel: 'M', margin: 2, size: QR_SIZE }); setQrCodeUrl(url); setQrFramesTotal(1); setQrFrameIndex(1); return; } qrAnimationRef.current.chunks = rawChunks; qrAnimationRef.current.idx = 0; qrAnimationRef.current.active = true; setQrFramesTotal(rawChunks.length); setQrFrameIndex(1); const EC_OPTS = { errorCorrectionLevel: 'M', margin: 2, size: QR_SIZE }; await renderNext(); if (!qrManualMode) { const intervalMs = 4000; // 4 seconds per frame for better readability qrAnimationRef.current.active = true; qrAnimationRef.current.timer = setInterval(renderAndAdvance, intervalMs); } return; } catch (error) { console.error('QR code generation failed:', error); setMessages(prev => [...prev, { message: ` QR code generation failed: ${error.message}`, type: 'error' }]); } }; const reconstructFromTemplate = (templateData) => { // Reconstruct full offer from template const fullOffer = { type: "enhanced_secure_offer", version: templateData.version, timestamp: templateData.timestamp, sessionId: templateData.sessionId, connectionId: templateData.connectionId, verificationCode: templateData.verificationCode, salt: templateData.salt, sdp: templateData.sdp, keyFingerprints: templateData.keyFingerprints, capabilities: templateData.capabilities, // Reconstruct ECDH key object ecdhPublicKey: { keyType: "ECDH", keyData: templateData.ecdhKeyData, timestamp: templateData.timestamp - 1000, // Approximate version: templateData.version, signature: templateData.ecdhSignature }, // Reconstruct ECDSA key object ecdsaPublicKey: { keyType: "ECDSA", keyData: templateData.ecdsaKeyData, timestamp: templateData.timestamp - 999, // Approximate version: templateData.version, signature: templateData.ecdsaSignature }, // Reconstruct auth challenge authChallenge: { challenge: templateData.authChallenge, timestamp: templateData.timestamp, nonce: templateData.authNonce, version: templateData.version }, // Generate security level (can be recalculated) securityLevel: { level: "CRITICAL", score: 20, color: "red", verificationResults: { encryption: { passed: false, details: "Encryption not working", points: 0 }, keyExchange: { passed: true, details: "Simple key exchange verified", points: 15 }, messageIntegrity: { passed: false, details: "Message integrity failed", points: 0 }, rateLimiting: { passed: true, details: "Rate limiting active", points: 5 }, ecdsa: { passed: false, details: "Enhanced session required - feature not available", points: 0 }, metadataProtection: { passed: false, details: "Enhanced session required - feature not available", points: 0 }, pfs: { passed: false, details: "Enhanced session required - feature not available", points: 0 }, nestedEncryption: { passed: false, details: "Enhanced session required - feature not available", points: 0 }, packetPadding: { passed: false, details: "Enhanced session required - feature not available", points: 0 }, advancedFeatures: { passed: false, details: "Premium session required - feature not available", points: 0 } }, timestamp: templateData.timestamp, details: "Real verification: 20/100 security checks passed (2/4 available)", isRealData: true, passedChecks: 2, totalChecks: 4, sessionType: "demo", maxPossibleScore: 50 } }; return fullOffer; }; const handleQRScan = async (scannedData) => { try { console.log('QR Code scanned:', scannedData.substring(0, 100) + '...'); console.log('Current buffer state:', qrChunksBufferRef.current); // Check if this is a binary chunk (starts with SB1:bin: or is a raw binary chunk) if (scannedData.startsWith('SB1:bin:') || (qrChunksBufferRef.current && qrChunksBufferRef.current.id)) { console.log('Binary chunk detected:', scannedData.substring(0, 50) + '...'); // This is a binary chunk - add to buffer if (!qrChunksBufferRef.current.id) { console.log('Initializing buffer for binary chunks'); // Initialize buffer for binary chunks qrChunksBufferRef.current = { id: `bin_${Date.now()}`, total: 4, // We expect 4 chunks seen: new Set(), items: [], lastUpdateMs: Date.now() }; } // Add chunk to buffer (use data hash as identifier) const chunkHash = scannedData.substring(0, 50); // Use first 50 chars as hash // Check if this chunk was already scanned if (qrChunksBufferRef.current.seen.has(chunkHash)) { console.log(`Chunk already scanned, ignoring...`); return Promise.resolve(false); } qrChunksBufferRef.current.seen.add(chunkHash); qrChunksBufferRef.current.items.push(scannedData); qrChunksBufferRef.current.lastUpdateMs = Date.now(); // Emit progress and force re-render try { const uniqueCount = qrChunksBufferRef.current.seen.size; document.dispatchEvent(new CustomEvent('qr-scan-progress', { detail: { id: qrChunksBufferRef.current.id, seq: uniqueCount, total: qrChunksBufferRef.current.total } })); // Force re-render to update progress indicator setQrFramesTotal(qrChunksBufferRef.current.total); setQrFrameIndex(uniqueCount); } catch {} // Check if we have all chunks const isComplete = qrChunksBufferRef.current.seen.size >= qrChunksBufferRef.current.total; console.log(`Chunks collected: ${qrChunksBufferRef.current.seen.size}/${qrChunksBufferRef.current.total}, complete: ${isComplete}`); if (!isComplete) { // Keep scanner open for more chunks console.log(`Scanned chunk ${qrChunksBufferRef.current.seen.size}/${qrChunksBufferRef.current.total}, waiting for more...`); return Promise.resolve(false); } // All chunks collected - reconstruct binary data try { const fullBinaryData = qrChunksBufferRef.current.items.join(''); // Store the original binary data, not decoded JSON if (showOfferStep) { setAnswerInput(fullBinaryData); } else { setOfferInput(fullBinaryData); } setMessages(prev => [...prev, { message: 'All binary chunks captured. Payload reconstructed.', type: 'success' }]); // Clear buffer and close scanner qrChunksBufferRef.current = { id: null, total: 0, seen: new Set(), items: [] }; setShowQRScannerModal(false); return Promise.resolve(true); } catch (e) { console.warn('Binary chunks reconstruction failed:', e); return Promise.resolve(false); } } // Check if this might be a binary chunk (long string without JSON structure) if (scannedData.length > 100 && !scannedData.startsWith('{') && !scannedData.startsWith('[')) { console.log('Detected potential binary chunk (long non-JSON string):', scannedData.substring(0, 50) + '...'); // Initialize buffer if not exists if (!qrChunksBufferRef.current.id) { console.log('Initializing buffer for potential binary chunks'); qrChunksBufferRef.current = { id: `bin_${Date.now()}`, total: 4, // We expect 4 chunks seen: new Set(), items: [], lastUpdateMs: Date.now() }; } // Add chunk to buffer (use data hash as identifier) const chunkHash = scannedData.substring(0, 50); // Use first 50 chars as hash // Check if this chunk was already scanned if (qrChunksBufferRef.current.seen.has(chunkHash)) { console.log(`Chunk already scanned, ignoring...`); return Promise.resolve(false); } qrChunksBufferRef.current.seen.add(chunkHash); qrChunksBufferRef.current.items.push(scannedData); qrChunksBufferRef.current.lastUpdateMs = Date.now(); // Force re-render to update progress indicator try { const uniqueCount = qrChunksBufferRef.current.seen.size; document.dispatchEvent(new CustomEvent('qr-scan-progress', { detail: { id: qrChunksBufferRef.current.id, seq: uniqueCount, total: qrChunksBufferRef.current.total } })); // Force re-render to update progress indicator setQrFramesTotal(qrChunksBufferRef.current.total); setQrFrameIndex(uniqueCount); } catch {} // Check if we have all chunks const isComplete = qrChunksBufferRef.current.seen.size >= qrChunksBufferRef.current.total; console.log(`Chunks collected: ${qrChunksBufferRef.current.seen.size}/${qrChunksBufferRef.current.total}, complete: ${isComplete}`); if (!isComplete) { // Keep scanner open for more chunks console.log(`Scanned chunk ${qrChunksBufferRef.current.seen.size}/${qrChunksBufferRef.current.total}, waiting for more...`); return Promise.resolve(false); } // All chunks collected - reconstruct binary data try { const fullBinaryData = qrChunksBufferRef.current.items.join(''); // Store the original binary data, not decoded JSON if (showOfferStep) { setAnswerInput(fullBinaryData); } else { setOfferInput(fullBinaryData); } setMessages(prev => [...prev, { message: 'All binary chunks captured. Payload reconstructed.', type: 'success' }]); // Clear buffer and close scanner qrChunksBufferRef.current = { id: null, total: 0, seen: new Set(), items: [] }; setShowQRScannerModal(false); return Promise.resolve(true); } catch (e) { console.warn('Binary chunks reconstruction failed:', e); return Promise.resolve(false); } } // Single QR code - try to decode directly console.log('Processing single QR code:', scannedData.substring(0, 50) + '...'); let parsedData; if (typeof window.decodeAnyPayload === 'function') { const any = window.decodeAnyPayload(scannedData); if (typeof any === 'string') { parsedData = JSON.parse(any); } else { parsedData = any; // object from binary } } else { const maybeDecompressed = (typeof window.decompressIfNeeded === 'function') ? window.decompressIfNeeded(scannedData) : scannedData; parsedData = JSON.parse(maybeDecompressed); } console.log('Decoded data:', parsedData); // QR with hdr/body: COSE or RAW/BIN animated frames if (parsedData.hdr && parsedData.body) { const { hdr } = parsedData; // Initialize/rotate buffer by id if (!qrChunksBufferRef.current.id || qrChunksBufferRef.current.id !== hdr.id) { qrChunksBufferRef.current = { id: hdr.id, total: hdr.total || 1, seen: new Set(), items: [], lastUpdateMs: Date.now() }; try { document.dispatchEvent(new CustomEvent('qr-scan-progress', { detail: { id: hdr.id, seq: 0, total: hdr.total || 1 } })); } catch {} } // Deduplicate & record if (!qrChunksBufferRef.current.seen.has(hdr.seq)) { qrChunksBufferRef.current.seen.add(hdr.seq); qrChunksBufferRef.current.items.push(scannedData); qrChunksBufferRef.current.lastUpdateMs = Date.now(); } // Emit progress based on unique frames captured try { const uniqueCount = qrChunksBufferRef.current.seen.size; document.dispatchEvent(new CustomEvent('qr-scan-progress', { detail: { id: hdr.id, seq: uniqueCount, total: qrChunksBufferRef.current.total || hdr.total || 0 } })); } catch {} const isComplete = qrChunksBufferRef.current.seen.size >= (qrChunksBufferRef.current.total || 1); if (!isComplete) { // Explicitly keep scanner open return Promise.resolve(false); } // Completed: decide RAW vs BIN vs COSE if (hdr.rt === 'raw') { try { // Sort by seq and concatenate bodies const parts = qrChunksBufferRef.current.items .map(s => JSON.parse(s)) .sort((a, b) => (a.hdr.seq || 0) - (b.hdr.seq || 0)) .map(p => p.body || ''); const fullText = parts.join(''); const payloadObj = JSON.parse(fullText); if (showOfferStep) { setAnswerInput(JSON.stringify(payloadObj, null, 2)); } else { setOfferInput(JSON.stringify(payloadObj, null, 2)); } setMessages(prev => [...prev, { message: 'All frames captured. RAW payload reconstructed.', type: 'success' }]); try { document.dispatchEvent(new CustomEvent('qr-scan-complete', { detail: { id: hdr.id } })); } catch {} // Close scanner from caller by returning true qrChunksBufferRef.current = { id: null, total: 0, seen: new Set(), items: [] }; setShowQRScannerModal(false); return Promise.resolve(true); } catch (e) { console.warn('RAW multi-frame reconstruction failed:', e); return Promise.resolve(false); } } else if (hdr.rt === 'bin') { try { const parts = qrChunksBufferRef.current.items .map(s => JSON.parse(s)) .sort((a, b) => (a.hdr.seq || 0) - (b.hdr.seq || 0)) .map(p => p.body || ''); const fullText = parts.join(''); // SB1:bin:... let payloadObj; if (typeof window.decodeAnyPayload === 'function') { const any = window.decodeAnyPayload(fullText); payloadObj = (typeof any === 'string') ? JSON.parse(any) : any; } else { payloadObj = JSON.parse(fullText); } if (showOfferStep) { setAnswerInput(JSON.stringify(payloadObj, null, 2)); } else { setOfferInput(JSON.stringify(payloadObj, null, 2)); } setMessages(prev => [...prev, { message: 'All frames captured. BIN payload reconstructed.', type: 'success' }]); try { document.dispatchEvent(new CustomEvent('qr-scan-complete', { detail: { id: hdr.id } })); } catch {} qrChunksBufferRef.current = { id: null, total: 0, seen: new Set(), items: [] }; setShowQRScannerModal(false); return Promise.resolve(true); } catch (e) { console.warn('BIN multi-frame reconstruction failed:', e); return Promise.resolve(false); } } else if (window.receiveAndProcess) { try { const results = await window.receiveAndProcess(qrChunksBufferRef.current.items); if (results.length > 0) { const { payloadObj } = results[0]; if (showOfferStep) { setAnswerInput(JSON.stringify(payloadObj, null, 2)); } else { setOfferInput(JSON.stringify(payloadObj, null, 2)); } setMessages(prev => [...prev, { message: 'All frames captured. COSE payload reconstructed.', type: 'success' }]); try { document.dispatchEvent(new CustomEvent('qr-scan-complete', { detail: { id: hdr.id } })); } catch {} qrChunksBufferRef.current = { id: null, total: 0, seen: new Set(), items: [] }; setShowQRScannerModal(false); return Promise.resolve(true); } } catch (e) { console.warn('COSE multi-chunk processing failed:', e); } return Promise.resolve(false); } else { return Promise.resolve(false); } } // Check if this is a template-based QR code if (parsedData.type === 'enhanced_secure_offer_template') { console.log('QR scan: Template-based offer detected, reconstructing...'); const fullOffer = reconstructFromTemplate(parsedData); // Determine which input to populate based on current mode if (showOfferStep) { // In "Waiting for peer's response" mode - populate answerInput setAnswerInput(JSON.stringify(fullOffer, null, 2)); console.log('📱 Template data populated to answerInput (waiting for response mode)'); } else { // In "Paste secure invitation" mode - populate offerInput setOfferInput(JSON.stringify(fullOffer, null, 2)); console.log('📱 Template data populated to offerInput (paste invitation mode)'); } setMessages(prev => [...prev, { message: '📱 QR code scanned successfully! Full offer reconstructed from template.', type: 'success' }]); setShowQRScannerModal(false); // Close QR scanner modal return true; } // Check if this is a reference-based QR code else if (parsedData.type === 'secure_offer_reference' && parsedData.referenceId) { // Try to get the full offer data from localStorage const fullOfferData = localStorage.getItem(`qr_offer_${parsedData.referenceId}`); if (fullOfferData) { const fullOffer = JSON.parse(fullOfferData); // Determine which input to populate based on current mode if (showOfferStep) { // In "Waiting for peer's response" mode - populate answerInput setAnswerInput(JSON.stringify(fullOffer, null, 2)); } else { // In "Paste secure invitation" mode - populate offerInput setOfferInput(JSON.stringify(fullOffer, null, 2)); } setMessages(prev => [...prev, { message: '📱 QR code scanned successfully! Full offer data retrieved.', type: 'success' }]); setShowQRScannerModal(false); // Close QR scanner modal return true; } else { setMessages(prev => [...prev, { message: 'QR code reference found but full data not available. Please use copy/paste.', type: 'error' }]); return false; } } else { // If payload was compressed, it's already decompressed above; keep legacy warning only when clearly incomplete if (!parsedData.sdp && parsedData.type === 'enhanced_secure_offer') { setMessages(prev => [...prev, { message: 'Compressed QR may omit SDP for brevity. Use copy/paste if connection fails.', type: 'warning' }]); } // Determine which input to populate based on current mode if (showOfferStep) { // In "Waiting for peer's response" mode - populate answerInput console.log('QR scan: Populating answerInput with:', parsedData); setAnswerInput(JSON.stringify(parsedData, null, 2)); } else { // In "Paste secure invitation" mode - populate offerInput console.log('QR scan: Populating offerInput with:', parsedData); setOfferInput(JSON.stringify(parsedData, null, 2)); } setMessages(prev => [...prev, { message: '📱 QR code scanned successfully!', type: 'success' }]); setShowQRScannerModal(false); return true; } } catch (error) { // If not JSON, use as plain text if (showOfferStep) { // In "Waiting for peer's response" mode - populate answerInput setAnswerInput(scannedData); } else { // In "Paste secure invitation" mode - populate offerInput setOfferInput(scannedData); } setMessages(prev => [...prev, { message: '📱 QR code scanned successfully!', type: 'success' }]); setShowQRScannerModal(false); return true; } }; const handleCreateOffer = async () => { try { // All security features are enabled by default setOfferData(''); setShowOfferStep(false); setShowQRCode(false); setQrCodeUrl(''); const offer = await webrtcManagerRef.current.createSecureOffer(); // Store offer data directly (no encryption needed with SAS) setOfferData(offer); setShowOfferStep(true); // Generate QR code with binary format and chunking const offerString = typeof offer === 'object' ? JSON.stringify(offer) : offer; try { if (typeof window.encodeBinaryToPrefixed === 'function') { const bin = window.encodeBinaryToPrefixed(offerString); // Force chunking into 4 parts - split binary data directly const TARGET_CHUNKS = 4; let FRAME_MAX = Math.max(200, Math.floor(bin.length / TARGET_CHUNKS)); if (FRAME_MAX <= 0) FRAME_MAX = 200; let total = Math.ceil(bin.length / FRAME_MAX); if (total < 2) { total = 2; FRAME_MAX = Math.ceil(bin.length / 2) || 1; } const id = `bin_${Date.now()}_${Math.random().toString(36).slice(2)}`; const chunks = []; for (let i = 0; i < total; i++) { const seq = i + 1; const part = bin.slice(i * FRAME_MAX, (i + 1) * FRAME_MAX); // Store binary chunks directly without JSON wrapper chunks.push(part); } // Seed first frame and start auto-advance immediately const isDesktop = (typeof window !== 'undefined') && ((window.innerWidth || 0) >= 1024); const QR_SIZE = isDesktop ? 720 : 512; if (window.generateQRCode && chunks.length > 0) { const firstUrl = await window.generateQRCode(chunks[0], { errorCorrectionLevel: 'M', size: QR_SIZE, margin: 2 }); if (firstUrl) setQrCodeUrl(firstUrl); } // Store precomputed chunks to ref, ready for animation try { if (qrAnimationRef.current && qrAnimationRef.current.timer) { clearInterval(qrAnimationRef.current.timer); } } catch {} qrAnimationRef.current = { timer: null, chunks, idx: 0, active: true }; setQrFramesTotal(chunks.length); setQrFrameIndex(1); setQrManualMode(false); // Start auto-advance loop for Offer immediately const intervalMs = 3000; qrAnimationRef.current.timer = setInterval(renderAndAdvance, intervalMs); // Show QR immediately for Offer flow try { setShowQRCode(true); } catch {} } else { // Fallback to single QR await generateQRCode(offer); try { setShowQRCode(true); } catch {} } } catch (e) { console.warn('Offer QR generation failed:', e); } const existingMessages = messages.filter(m => m.type === 'system' && (m.message.includes('Secure invitation created') || m.message.includes('Send the encrypted code')) ); if (existingMessages.length === 0) { setMessages(prev => [...prev, { message: 'Secure invitation created and encrypted!', type: 'system', id: Date.now(), timestamp: Date.now() }]); setMessages(prev => [...prev, { message: '📤 Send the invitation code to your interlocutor via a secure channel (voice call, SMS, etc.)..', type: 'system', id: Date.now(), timestamp: Date.now() }]); } if (!window.isUpdatingSecurity) { updateSecurityLevel().catch(console.error); } } catch (error) { setMessages(prev => [...prev, { message: `Error creating invitation: ${error.message}`, type: 'system', id: Date.now(), timestamp: Date.now() }]); } }; const handleCreateAnswer = async () => { try { if (!offerInput.trim()) { setMessages(prev => [...prev, { message: 'You need to insert the invitation code from your interlocutor.', type: 'system', id: Date.now(), timestamp: Date.now() }]); return; } try { setMessages(prev => [...prev, { message: 'Processing the secure invitation...', type: 'system', id: Date.now(), timestamp: Date.now() }]); let offer; try { // Prefer binary decode first, then gzip JSON if (typeof window.decodeAnyPayload === 'function') { const any = window.decodeAnyPayload(offerInput.trim()); offer = (typeof any === 'string') ? JSON.parse(any) : any; } else { const rawText = (typeof window.decompressIfNeeded === 'function') ? window.decompressIfNeeded(offerInput.trim()) : offerInput.trim(); offer = JSON.parse(rawText); } } catch (parseError) { throw new Error(`Invalid invitation format: ${parseError.message}`); } if (!offer || typeof offer !== 'object') { throw new Error('The invitation must be an object'); } // Support both compact and legacy offer formats const isValidOfferType = (offer.t === 'offer') || (offer.type === 'enhanced_secure_offer'); if (!isValidOfferType) { throw new Error('Invalid invitation type. Expected offer or enhanced_secure_offer'); } const answer = await webrtcManagerRef.current.createSecureAnswer(offer); // Store answer data directly (no encryption needed with SAS) setAnswerData(answer); setShowAnswerStep(true); // Generate QR code with binary format and chunking const answerString = typeof answer === 'object' ? JSON.stringify(answer) : answer; try { if (typeof window.encodeBinaryToPrefixed === 'function') { const bin = window.encodeBinaryToPrefixed(answerString); // Force chunking into 4 parts - split binary data directly const TARGET_CHUNKS = 4; let FRAME_MAX = Math.max(200, Math.floor(bin.length / TARGET_CHUNKS)); if (FRAME_MAX <= 0) FRAME_MAX = 200; let total = Math.ceil(bin.length / FRAME_MAX); if (total < 2) { total = 2; FRAME_MAX = Math.ceil(bin.length / 2) || 1; } const id = `ans_${Date.now()}_${Math.random().toString(36).slice(2)}`; const chunks = []; for (let i = 0; i < total; i++) { const seq = i + 1; const part = bin.slice(i * FRAME_MAX, (i + 1) * FRAME_MAX); // Store binary chunks directly without JSON wrapper chunks.push(part); } const isDesktop = (typeof window !== 'undefined') && ((window.innerWidth || 0) >= 1024); const QR_SIZE = isDesktop ? 720 : 512; if (window.generateQRCode && chunks.length > 0) { const firstUrl = await window.generateQRCode(chunks[0], { errorCorrectionLevel: 'M', size: QR_SIZE, margin: 2 }); if (firstUrl) setQrCodeUrl(firstUrl); } try { if (qrAnimationRef.current && qrAnimationRef.current.timer) { clearInterval(qrAnimationRef.current.timer); } } catch {} qrAnimationRef.current = { timer: null, chunks, idx: 0, active: true }; setQrFramesTotal(chunks.length); setQrFrameIndex(1); setQrManualMode(false); const intervalMs = 3000; qrAnimationRef.current.timer = setInterval(renderAndAdvance, intervalMs); // Show QR immediately for Answer flow try { setShowQRCode(true); } catch {} } else { // Fallback to single QR await generateQRCode(answer); try { setShowQRCode(true); } catch {} } } catch (e) { console.warn('Answer QR generation failed:', e); } // Mark answer as created for state management if (e.target.value.trim().length > 0) { if (typeof markAnswerCreated === 'function') { markAnswerCreated(); } } const existingResponseMessages = messages.filter(m => m.type === 'system' && (m.message.includes('Secure response created') || m.message.includes('Send the response')) ); if (existingResponseMessages.length === 0) { setMessages(prev => [...prev, { message: 'Secure response created!', type: 'system', id: Date.now(), timestamp: Date.now() }]); setMessages(prev => [...prev, { message: 'Send the response code to the initiator via a secure channel or let them scan the QR code below.', type: 'system', id: Date.now(), timestamp: Date.now() }]); } // Update security level after creating answer if (!window.isUpdatingSecurity) { updateSecurityLevel().catch(console.error); } } catch (error) { console.error('Error in handleCreateAnswer:', error); setMessages(prev => [...prev, { message: `Error processing the invitation: ${error.message}`, type: 'system', id: Date.now(), timestamp: Date.now() }]); } } catch (error) { console.error('Error in handleCreateAnswer:', error); setMessages(prev => [...prev, { message: `Invitation processing error: ${error.message}`, type: 'system', id: Date.now(), timestamp: Date.now() }]); } }; const handleConnect = async () => { try { if (!answerInput.trim()) { setMessages(prev => [...prev, { message: 'You need to insert the response code from your interlocutor.', type: 'system', id: Date.now(), timestamp: Date.now() }]); return; } try { setMessages(prev => [...prev, { message: 'Processing the secure response...', type: 'system', id: Date.now(), timestamp: Date.now() }]); let answer; try { // Prefer binary decode first, then gzip JSON if (typeof window.decodeAnyPayload === 'function') { const anyAnswer = window.decodeAnyPayload(answerInput.trim()); answer = (typeof anyAnswer === 'string') ? JSON.parse(anyAnswer) : anyAnswer; } else { const rawText = (typeof window.decompressIfNeeded === 'function') ? window.decompressIfNeeded(answerInput.trim()) : answerInput.trim(); answer = JSON.parse(rawText); } } catch (parseError) { throw new Error(`Invalid response format: ${parseError.message}`); } if (!answer || typeof answer !== 'object') { throw new Error('The response must be an object'); } // Support both compact and legacy formats const answerType = answer.t || answer.type; if (!answerType || (answerType !== 'answer' && answerType !== 'enhanced_secure_answer')) { throw new Error('Invalid response type. Expected answer or enhanced_secure_answer'); } await webrtcManagerRef.current.handleSecureAnswer(answer); // All security features are enabled by default - no session activation needed if (pendingSession) { setPendingSession(null); setMessages(prev => [...prev, { message: `All security features enabled by default`, type: 'system', id: Date.now(), timestamp: Date.now() }]); } setMessages(prev => [...prev, { message: 'Finalizing the secure connection...', type: 'system', id: Date.now(), timestamp: Date.now() }]); // Update security level after handling answer if (!window.isUpdatingSecurity) { updateSecurityLevel().catch(console.error); } } catch (error) { console.error('Error in handleConnect inner try:', error); // Более детальная обработка ошибок let errorMessage = 'Connection setup error'; if (error.message.includes('CRITICAL SECURITY FAILURE')) { if (error.message.includes('ECDH public key structure')) { errorMessage = 'Invalid response code - missing or corrupted cryptographic key. Please check the code and try again.'; } else if (error.message.includes('ECDSA public key structure')) { errorMessage = 'Invalid response code - missing signature verification key. Please check the code and try again.'; } else { errorMessage = 'Security validation failed - possible attack detected'; } } else if (error.message.includes('too old') || error.message.includes('replay')) { errorMessage = 'Response data is outdated - please use a fresh invitation'; } else if (error.message.includes('MITM') || error.message.includes('signature')) { errorMessage = 'Security breach detected - connection rejected'; } else if (error.message.includes('Invalid') || error.message.includes('format')) { errorMessage = 'Invalid response format - please check the code'; } else { errorMessage = ` ${error.message}`; } setMessages(prev => [...prev, { message: errorMessage, type: 'system', id: Date.now(), timestamp: Date.now(), showRetryButton: true }]); if (!error.message.includes('too old') && !error.message.includes('replay')) { setPendingSession(null); setSessionTimeLeft(0); } setConnectionStatus('failed'); } } catch (error) { console.error('Error in handleConnect outer try:', error); let errorMessage = 'Connection setup error'; if (error.message.includes('CRITICAL SECURITY FAILURE')) { if (error.message.includes('ECDH public key structure')) { errorMessage = 'Invalid response code - missing or corrupted cryptographic key. Please check the code and try again.'; } else if (error.message.includes('ECDSA public key structure')) { errorMessage = 'Invalid response code - missing signature verification key. Please check the code and try again.'; } else { errorMessage = 'Security validation failed - possible attack detected'; } } else if (error.message.includes('too old') || error.message.includes('replay')) { errorMessage = 'Response data is outdated - please use a fresh invitation'; } else if (error.message.includes('MITM') || error.message.includes('signature')) { errorMessage = 'Security breach detected - connection rejected'; } else if (error.message.includes('Invalid') || error.message.includes('format')) { errorMessage = 'Invalid response format - please check the code'; } else { errorMessage = `${error.message}`; } setMessages(prev => [...prev, { message: errorMessage, type: 'system', id: Date.now(), timestamp: Date.now(), showRetryButton: true }]); if (!error.message.includes('too old') && !error.message.includes('replay')) { setPendingSession(null); setSessionTimeLeft(0); } setConnectionStatus('failed'); } }; const handleVerifyConnection = (isValid) => { if (isValid) { webrtcManagerRef.current.confirmVerification(); // Mark local verification as confirmed setLocalVerificationConfirmed(true); } else { setMessages(prev => [...prev, { message: ' Verification rejected. The connection is unsafe! Session reset..', type: 'system', id: Date.now(), timestamp: Date.now() }]); // Clear verification states setLocalVerificationConfirmed(false); setRemoteVerificationConfirmed(false); setBothVerificationsConfirmed(false); setShowVerification(false); setVerificationCode(''); // Reset UI to initial state setConnectionStatus('disconnected'); setOfferData(null); setAnswerData(null); setOfferInput(''); setAnswerInput(''); setShowOfferStep(false); setShowAnswerStep(false); setKeyFingerprint(''); setSecurityLevel(null); setIsVerified(false); setMessages([]); setSessionTimeLeft(0); setPendingSession(null); // Dispatch disconnected event for SessionTimer document.dispatchEvent(new CustomEvent('disconnected')); handleDisconnect(); } }; const handleSendMessage = async () => { if (!messageInput.trim()) { return; } if (!webrtcManagerRef.current) { return; } if (!webrtcManagerRef.current.isConnected()) { return; } try { // Add the message to local messages immediately (sent message) addMessageWithAutoScroll(messageInput.trim(), 'sent'); // Use sendMessage for simple text messages instead of sendSecureMessage await webrtcManagerRef.current.sendMessage(messageInput); setMessageInput(''); } catch (error) { const msg = String(error?.message || error); if (!/queued for sending|Data channel not ready/i.test(msg)) { addMessageWithAutoScroll(`Sending error: ${msg}`,'system'); } } }; const handleClearData = () => { setOfferData(''); setAnswerData(''); setOfferInput(''); setAnswerInput(''); setShowOfferStep(false); if (!shouldPreserveAnswerData()) { setShowAnswerStep(false); } setShowVerification(false); setShowQRCode(false); setShowQRScanner(false); setShowQRScannerModal(false); // Clear QR scanner buffer qrChunksBufferRef.current = { id: null, total: 0, seen: new Set(), items: [] }; if (!shouldPreserveAnswerData()) { setQrCodeUrl(''); } setVerificationCode(''); setIsVerified(false); setKeyFingerprint(''); setSecurityLevel(null); setConnectionStatus('disconnected'); setMessages([]); setMessageInput(''); // Clear verification states setLocalVerificationConfirmed(false); setRemoteVerificationConfirmed(false); setBothVerificationsConfirmed(false); // PAKE passwords removed - using SAS verification instead if (typeof console.clear === 'function') { console.clear(); } // Cleanup session state setSessionTimeLeft(0); setPendingSession(null); document.dispatchEvent(new CustomEvent('peer-disconnect')); // Session manager removed - all features enabled by default }; const handleDisconnect = () => { setSessionTimeLeft(0); // Mark as user-initiated disconnect updateConnectionState({ status: 'disconnected', isUserInitiatedDisconnect: true }); // Cleanup session state if (webrtcManagerRef.current) { webrtcManagerRef.current.disconnect(); } setKeyFingerprint(''); setVerificationCode(''); setSecurityLevel(null); setIsVerified(false); setShowVerification(false); setConnectionStatus('disconnected'); // Clear verification states setLocalVerificationConfirmed(false); setRemoteVerificationConfirmed(false); setBothVerificationsConfirmed(false); // Reset UI to initial state (user-initiated disconnect always clears data) setConnectionStatus('disconnected'); setShowVerification(false); setOfferData(null); setAnswerData(null); setOfferInput(''); setAnswerInput(''); setShowOfferStep(false); setShowAnswerStep(false); setKeyFingerprint(''); setVerificationCode(''); setSecurityLevel(null); setIsVerified(false); setMessages([]); if (typeof console.clear === 'function') { console.clear(); } document.dispatchEvent(new CustomEvent('peer-disconnect')); document.dispatchEvent(new CustomEvent('disconnected')); document.dispatchEvent(new CustomEvent('session-cleanup', { detail: { timestamp: Date.now(), reason: 'manual_disconnect' } })); setTimeout(() => { setSessionTimeLeft(0); }, 500); handleClearData(); setTimeout(() => { // Session manager removed - all features enabled by default }, 1000); }; const handleSessionActivated = (session) => { let message; if (session.type === 'demo') { message = ` Demo session activated for 6 minutes. You can create invitations!`; } else { message = ` All security features enabled by default. You can create invitations!`; } addMessageWithAutoScroll(message, 'system'); }; React.useEffect(() => { if (connectionStatus === 'connected' && isVerified) { addMessageWithAutoScroll(' Secure connection successfully established and verified! You can now communicate safely with full protection against MITM attacks and Perfect Forward Secrecy..', 'system'); } }, [connectionStatus, isVerified]); const isConnectedAndVerified = (connectionStatus === 'connected' || connectionStatus === 'verified') && isVerified; React.useEffect(() => { // All security features are enabled by default - no session activation needed if (isConnectedAndVerified && pendingSession && connectionStatus !== 'failed') { setPendingSession(null); setSessionTimeLeft(0); addMessageWithAutoScroll(' All security features enabled by default', 'system'); } }, [isConnectedAndVerified, pendingSession, connectionStatus]); // QR Scanner initialization React.useEffect(() => { if (showQRScannerModal && window.Html5Qrcode) { const html5Qrcode = new window.Html5Qrcode("qr-reader"); const config = { fps: 10 // Убираем qrbox чтобы использовать всю область }; let isScanning = true; html5Qrcode.start( { facingMode: "environment" }, // Use back camera config, (decodedText, decodedResult) => { if (!isScanning) { console.log('Scanner stopped, ignoring scan'); return; } console.log('QR Code scanned:', decodedText); console.log('Current buffer state:', qrChunksBufferRef.current); handleQRScan(decodedText).then((success) => { console.log('QR scan result:', success); if (success) { // Successfully processed - stop scanner and close modal console.log('Closing scanner and modal'); isScanning = false; // Stop scanner first, then clear try { console.log('Stopping scanner...'); html5Qrcode.stop().then(() => { console.log('Scanner stopped, clearing...'); html5Qrcode.clear(); setShowQRScannerModal(false); }).catch((err) => { console.log('Error stopping scanner:', err); // Try to clear anyway try { html5Qrcode.clear(); } catch (clearErr) { console.log('Error clearing scanner:', clearErr); } setShowQRScannerModal(false); }); } catch (err) { console.log('Error in scanner cleanup:', err); setShowQRScannerModal(false); } } else { console.log('Continuing to scan for more chunks...'); } }).catch((error) => { console.error('QR scan processing error:', error); // Continue scanning on error }); }, (error) => { // Ignore scanning errors - continue scanning if (isScanning) { console.log('QR scan error (ignored):', error); } } ).catch((err) => { console.error('QR Scanner start error:', err); // Close modal on start error setShowQRScannerModal(false); }); return () => { isScanning = false; try { // Try to stop scanner, but don't worry if it's already stopped html5Qrcode.stop().then(() => { html5Qrcode.clear(); }).catch((err) => { // Scanner might already be stopped, just clear it console.log('Scanner already stopped or error stopping:', err); try { html5Qrcode.clear(); } catch (clearErr) { console.log('Error clearing scanner in cleanup:', clearErr); } }); } catch (err) { console.log('Error in cleanup:', err); // Just try to clear, don't worry about stopping try { html5Qrcode.clear(); } catch (clearErr) { console.log('Error clearing scanner in cleanup:', clearErr); } } }; } }, [showQRScannerModal]); return React.createElement('div', { className: "minimal-bg min-h-screen" }, [ React.createElement(EnhancedMinimalHeader, { key: 'header', status: connectionStatus, fingerprint: keyFingerprint, verificationCode: verificationCode, onDisconnect: handleDisconnect, isConnected: isConnectedAndVerified, securityLevel: securityLevel, // sessionManager removed - all features enabled by default sessionTimeLeft: sessionTimeLeft, webrtcManager: webrtcManagerRef.current }), React.createElement('main', { key: 'main' }, (() => { return isConnectedAndVerified; })() ? (() => { return React.createElement(EnhancedChatInterface, { messages: messages, messageInput: messageInput, setMessageInput: setMessageInput, onSendMessage: handleSendMessage, onDisconnect: handleDisconnect, keyFingerprint: keyFingerprint, isVerified: isVerified, chatMessagesRef: chatMessagesRef, scrollToBottom: scrollToBottom, webrtcManager: webrtcManagerRef.current }); })() : React.createElement(EnhancedConnectionSetup, { onCreateOffer: handleCreateOffer, onCreateAnswer: handleCreateAnswer, onConnect: handleConnect, onClearData: handleClearData, onVerifyConnection: handleVerifyConnection, connectionStatus: connectionStatus, offerData: offerData, answerData: answerData, offerInput: offerInput, setOfferInput: setOfferInput, answerInput: answerInput, setAnswerInput: setAnswerInput, showOfferStep: showOfferStep, showAnswerStep: showAnswerStep, verificationCode: verificationCode, showVerification: showVerification, showQRCode: showQRCode, qrCodeUrl: qrCodeUrl, showQRScanner: showQRScanner, setShowQRCode: setShowQRCode, setShowQRScanner: setShowQRScanner, setShowQRScannerModal: setShowQRScannerModal, messages: messages, localVerificationConfirmed: localVerificationConfirmed, remoteVerificationConfirmed: remoteVerificationConfirmed, bothVerificationsConfirmed: bothVerificationsConfirmed, // QR control props qrFramesTotal: qrFramesTotal, qrFrameIndex: qrFrameIndex, qrManualMode: qrManualMode, toggleQrManualMode: toggleQrManualMode, nextQrFrame: nextQrFrame, prevQrFrame: prevQrFrame, // PAKE passwords removed - using SAS verification instead markAnswerCreated: markAnswerCreated }) ), // QR Scanner Modal showQRScannerModal && React.createElement('div', { key: 'qr-scanner-modal', className: "fixed inset-0 bg-black/80 flex items-center justify-center z-50" }, [ React.createElement('div', { key: 'scanner-container', className: "bg-gray-900 rounded-lg p-4 max-w-2xl w-full mx-4" }, [ React.createElement('div', { key: 'scanner-header', className: "flex items-center justify-between mb-4" }, [ React.createElement('h3', { key: 'scanner-title', className: "text-lg font-medium text-white" }, 'QR Code Scanner'), React.createElement('button', { key: 'close-btn', onClick: () => { setShowQRScannerModal(false); // Clear QR scanner buffer qrChunksBufferRef.current = { id: null, total: 0, seen: new Set(), items: [] }; }, className: "text-gray-400 hover:text-white" }, [ React.createElement('i', { key: 'close-icon', className: 'fas fa-times' }) ]) ]), React.createElement('div', { key: 'scanner-content', className: "text-center" }, [ React.createElement('p', { key: 'scanner-description', className: "text-gray-400 mb-4" }, 'Point your camera at the QR code to scan'), qrChunksBufferRef.current && qrChunksBufferRef.current.id && React.createElement('div', { key: 'progress-indicator', className: "mb-4 p-3 bg-blue-500/10 border border-blue-500/20 rounded-lg" }, [ React.createElement('p', { key: 'progress-text', className: "text-blue-400 text-sm" }, `Scanned: ${qrChunksBufferRef.current.seen.size}/${qrChunksBufferRef.current.total} parts`), React.createElement('div', { key: 'progress-bar', className: "w-full bg-gray-700 rounded-full h-2 mt-2" }, [ React.createElement('div', { key: 'progress-fill', className: "bg-blue-500 h-2 rounded-full transition-all duration-300", style: { width: `${(qrChunksBufferRef.current.seen.size / qrChunksBufferRef.current.total) * 100}%` } }) ]) ]), React.createElement('div', { key: 'scanner-placeholder', id: "qr-reader", className: "w-full h-96 bg-gray-800 rounded-lg flex items-center justify-center", style: { minHeight: '400px' } }, [ React.createElement('p', { key: 'scanner-placeholder-text', className: "text-gray-500" }, 'Camera will start here...') ]) ]) ]) ]) ]); }; function initializeApp() { if (window.EnhancedSecureCryptoUtils && window.EnhancedSecureWebRTCManager) { ReactDOM.render(React.createElement(EnhancedSecureP2PChat), document.getElementById('root')); } else { console.error('Модули не загружены:', { hasCrypto: !!window.EnhancedSecureCryptoUtils, hasWebRTC: !!window.EnhancedSecureWebRTCManager }); } } if (typeof window !== 'undefined') { window.addEventListener('unhandledrejection', (event) => { console.error('Unhandled promise rejection:', event.reason); event.preventDefault(); }); window.addEventListener('error', (event) => { console.error('Global error:', event.error); event.preventDefault(); }); if (!window.initializeApp) { window.initializeApp = initializeApp; } } // Render Enhanced Application ReactDOM.render(React.createElement(EnhancedSecureP2PChat), document.getElementById('root'));