2025-10-01 20:20:15 -04:00
2025-09-08 16:04:58 -04:00
// 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 ,
2025-09-23 20:01:02 -04:00
showQRCode ,
qrCodeUrl ,
showQRScanner ,
setShowQRCode ,
setShowQRScanner ,
setShowQRScannerModal ,
2025-09-08 16:04:58 -04:00
offerPassword ,
answerPassword ,
localVerificationConfirmed ,
remoteVerificationConfirmed ,
2025-09-27 19:07:17 -04:00
bothVerificationsConfirmed ,
// QR control props
qrFramesTotal ,
qrFrameIndex ,
qrManualMode ,
toggleQrManualMode ,
nextQrFrame ,
2025-10-02 01:43:32 -04:00
prevQrFrame ,
markAnswerCreated
2025-09-08 16:04:58 -04:00
} ) => {
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' ,
2025-10-01 20:20:15 -04:00
className : "flex flex-col md:flex-row items-center justify-center gap-6 max-w-3xl mx-auto"
2025-09-08 16:04:58 -04:00
} , [
// Create Connection
React . createElement ( 'div' , {
key : 'create' ,
onClick : ( ) => setMode ( 'create' ) ,
2025-10-01 20:20:15 -04:00
className : "card-minimal rounded-xl p-6 cursor-pointer group flex-1 create"
2025-09-08 16:04:58 -04:00
} , [
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"
2025-10-01 20:20:15 -04:00
} , "Initiate a new secure connection" ) ,
2025-09-08 16:04:58 -04:00
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'
] )
] )
] ) ,
2025-10-01 20:20:15 -04:00
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"
} )
] ) ,
2025-09-08 16:04:58 -04:00
// Join Connection
React . createElement ( 'div' , {
key : 'join' ,
onClick : ( ) => setMode ( 'join' ) ,
2025-10-01 20:20:15 -04:00
className : "card-minimal rounded-xl p-6 cursor-pointer group flex-1 join"
2025-09-08 16:04:58 -04:00
} , [
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 ( 'div' , {
key : 'security-features' ,
className : "grid grid-cols-2 md:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4 max-w-6xl mx-auto mt-8"
} , [
React . createElement ( 'div' , { key : 'feature1' , className : "text-center p-3 sm:p-4" } , [
2025-10-01 20:20:15 -04:00
React . createElement ( 'div' , { key : 'icon' , className : "w-10 h-10 sm:w-12 sm:h-12 rounded-lg flex items-center justify-center mx-auto mb-2 sm:mb-3" } , [
2025-09-08 16:04:58 -04:00
React . createElement ( 'i' , { className : 'fas fa-key accent-green' } )
] ) ,
React . createElement ( 'h4' , { key : 'title' , className : "text-xs sm:text-sm font-medium text-primary mb-1" } , "ECDH P-384 Key Exchange" ) ,
React . createElement ( 'p' , { key : 'desc' , className : "text-xs text-muted leading-tight" } , "Military-grade elliptic curve key exchange" )
] ) ,
React . createElement ( 'div' , { key : 'feature2' , className : "text-center p-3 sm:p-4" } , [
2025-10-01 20:20:15 -04:00
React . createElement ( 'div' , { key : 'icon' , className : "w-10 h-10 sm:w-12 sm:h-12 rounded-lg flex items-center justify-center mx-auto mb-2 sm:mb-3" } , [
2025-09-08 16:04:58 -04:00
React . createElement ( 'i' , { className : 'fas fa-user-shield accent-purple' } )
] ) ,
React . createElement ( 'h4' , { key : 'title' , className : "text-xs sm:text-sm font-medium text-primary mb-1" } , "MITM Protection" ) ,
React . createElement ( 'p' , { key : 'desc' , className : "text-xs text-muted leading-tight" } , "Out-of-band verification against attacks" )
] ) ,
React . createElement ( 'div' , { key : 'feature3' , className : "text-center p-3 sm:p-4" } , [
2025-10-01 20:20:15 -04:00
React . createElement ( 'div' , { key : 'icon' , className : "w-10 h-10 sm:w-12 sm:h-12 rounded-lg flex items-center justify-center mx-auto mb-2 sm:mb-3" } , [
2025-09-08 16:04:58 -04:00
React . createElement ( 'i' , { className : 'fas fa-lock accent-orange' } )
] ) ,
React . createElement ( 'h4' , { key : 'title' , className : "text-xs sm:text-sm font-medium text-primary mb-1" } , "AES-GCM 256 Encryption" ) ,
React . createElement ( 'p' , { key : 'desc' , className : "text-xs text-muted leading-tight" } , "Authenticated encryption standard" )
] ) ,
React . createElement ( 'div' , { key : 'feature4' , className : "text-center p-3 sm:p-4" } , [
2025-10-01 20:20:15 -04:00
React . createElement ( 'div' , { key : 'icon' , className : "w-10 h-10 sm:w-12 sm:h-12 rounded-lg flex items-center justify-center mx-auto mb-2 sm:mb-3" } , [
2025-09-08 16:04:58 -04:00
React . createElement ( 'i' , { className : 'fas fa-sync-alt accent-cyan' } )
] ) ,
React . createElement ( 'h4' , { key : 'title' , className : "text-xs sm:text-sm font-medium text-primary mb-1" } , "Perfect Forward Secrecy" ) ,
React . createElement ( 'p' , { key : 'desc' , className : "text-xs text-muted leading-tight" } , "Automatic key rotation every 5 minutes" )
] ) ,
React . createElement ( 'div' , { key : 'feature5' , className : "text-center p-3 sm:p-4" } , [
2025-10-01 20:20:15 -04:00
React . createElement ( 'div' , { key : 'icon' , className : "w-10 h-10 sm:w-12 sm:h-12 rounded-lg flex items-center justify-center mx-auto mb-2 sm:mb-3" } , [
2025-09-08 16:04:58 -04:00
React . createElement ( 'i' , { className : 'fas fa-signature accent-blue' } )
] ) ,
React . createElement ( 'h4' , { key : 'title' , className : "text-xs sm:text-sm font-medium text-primary mb-1" } , "ECDSA P-384 Signatures" ) ,
React . createElement ( 'p' , { key : 'desc' , className : "text-xs text-muted leading-tight" } , "Digital signatures for message integrity" )
] ) ,
2025-10-01 20:20:15 -04:00
React . createElement ( 'div' , { key : 'feature6' , className : "text-center p-3 sm:p-4" } , [
React . createElement ( 'div' , { key : 'icon' , className : "w-10 h-10 sm:w-12 sm:h-12 rounded-lg flex items-center justify-center mx-auto mb-2 sm:mb-3" } , [
React . createElement ( 'i' , { className : 'fas fa-shield-alt accent-red' } )
] ) ,
React . createElement ( 'h4' , { key : 'title' , className : "text-xs sm:text-sm font-medium text-primary mb-1" } , "SAS Security" ) ,
React . createElement ( 'p' , { key : 'desc' , className : "text-xs text-muted leading-tight" } , "Revolutionary key exchange & MITM protection" )
] ) ,
2025-09-08 16:04:58 -04:00
] ) ,
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
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" ) ,
React . createElement ( 'button' , {
key : 'create-btn' ,
onClick : onCreateOffer ,
disabled : connectionStatus === 'connecting' || showOfferStep ,
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'
} ) ,
showOfferStep ? 'Keys created ✓' : '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'
} ) ,
2025-10-01 23:26:07 -04:00
'Secure invitation created! Send the code to your contact'
2025-09-08 16:04:58 -04:00
] )
] ) ,
React . createElement ( 'div' , {
key : 'offer-data' ,
className : "space-y-3"
} , [
React . createElement ( 'textarea' , {
key : 'textarea' ,
value : typeof offerData === 'object' ? JSON . stringify ( offerData , null , 2 ) : offerData ,
readOnly : true ,
rows : 8 ,
className : "w-full p-3 bg-custom-bg border border-gray-500/20 rounded-lg font-mono text-xs text-secondary resize-none custom-scrollbar"
} ) ,
2025-09-23 20:01:02 -04:00
React . createElement ( 'div' , {
key : 'buttons' ,
className : "flex gap-2"
} , [
2025-09-08 16:04:58 -04:00
React . createElement ( EnhancedCopyButton , {
key : 'copy' ,
text : typeof offerData === 'object' ? JSON . stringify ( offerData , null , 2 ) : offerData ,
2025-09-23 20:01:02 -04:00
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' ) ,
React . createElement ( 'button' , {
key : 'qr-toggle' ,
onClick : async ( ) => {
const next = ! showQRCode ;
setShowQRCode ( next ) ;
if ( next ) {
try {
const payload = typeof offerData === 'object' ? JSON . stringify ( offerData ) : offerData ;
if ( payload && payload . length ) {
await generateQRCode ( payload ) ;
}
} catch ( e ) {
console . warn ( 'QR regenerate on toggle failed:' , e ) ;
}
}
} ,
className : "px-3 py-2 bg-blue-500/10 hover:bg-blue-500/20 text-blue-400 border border-blue-500/20 rounded text-sm font-medium transition-all duration-200"
} , [
React . createElement ( 'i' , {
key : 'icon' ,
className : showQRCode ? 'fas fa-eye-slash mr-1' : 'fas fa-qrcode mr-1'
} ) ,
showQRCode ? 'Hide QR' : 'Show QR'
] )
] ) ,
showQRCode && qrCodeUrl && React . createElement ( 'div' , {
key : 'qr-container' ,
className : "mt-4 p-4 bg-gray-800/50 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]"
2025-09-27 19:07:17 -04:00
} )
] ) ,
2025-10-02 01:43:32 -04:00
2025-09-27 19:07:17 -04:00
( ( 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"
} , '▶' )
] )
2025-09-23 20:01:02 -04:00
] ) ,
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' )
] )
2025-09-08 16:04:58 -04:00
] )
] )
] ) ,
// 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." ) ,
2025-09-23 20:01:02 -04:00
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'
] )
] ) ,
2025-09-08 16:04:58 -04:00
React . createElement ( 'textarea' , {
key : 'input' ,
value : answerInput ,
2025-09-23 20:01:02 -04:00
onChange : ( e ) => {
setAnswerInput ( e . target . value ) ;
// Mark answer as created when user manually enters data
if ( e . target . value . trim ( ) . length > 0 ) {
2025-10-02 01:43:32 -04:00
if ( typeof markAnswerCreated === 'function' ) {
markAnswerCreated ( ) ;
}
2025-09-23 20:01:02 -04:00
}
2025-10-02 01:43:32 -04:00
2025-09-23 20:01:02 -04:00
} ,
2025-09-08 16:04:58 -04:00
rows : 6 ,
2025-09-23 20:01:02 -04:00
placeholder : "Paste the encrypted response code from your contact or scan QR code..." ,
2025-09-08 16:04:58 -04:00
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' )
] ) ,
// Step 1
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 ,
2025-09-23 20:01:02 -04:00
onChange : ( e ) => {
setOfferInput ( e . target . value ) ;
// Mark answer as created when user manually enters data
if ( e . target . value . trim ( ) . length > 0 ) {
2025-10-02 01:43:32 -04:00
if ( typeof markAnswerCreated === 'function' ) {
2025-09-23 20:01:02 -04:00
markAnswerCreated ( ) ;
}
2025-10-02 01:43:32 -04:00
}
2025-09-23 20:01:02 -04:00
} ,
2025-09-08 16:04:58 -04:00
rows : 8 ,
2025-09-23 20:01:02 -04:00
placeholder : "Paste the encrypted invitation code or scan QR code..." ,
2025-09-08 16:04:58 -04:00
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"
} ) ,
2025-09-23 20:01:02 -04:00
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'
] ) ,
2025-09-08 16:04:58 -04:00
React . createElement ( 'button' , {
key : 'process-btn' ,
onClick : onCreateAnswer ,
disabled : ! offerInput . trim ( ) || connectionStatus === 'connecting' ,
2025-09-23 20:01:02 -04:00
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"
2025-09-08 16:04:58 -04:00
} , [
React . createElement ( 'i' , {
className : 'fas fa-cogs mr-2'
} ) ,
2025-09-23 20:01:02 -04:00
'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 : ( ) => {
2025-10-02 01:43:32 -04:00
2025-09-23 20:01:02 -04:00
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 ) ;
// Open QR code in new tab for testing
const newWindow = window . open ( ) ;
newWindow . document . write ( ` <img src=" ${ qrUrl } " style="width: 300px; height: 300px;"> ` ) ;
}
} ,
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' )
2025-09-08 16:04:58 -04:00
] )
] ) ,
// 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"
} , [
React . createElement ( 'textarea' , {
key : 'textarea' ,
value : typeof answerData === 'object' ? JSON . stringify ( answerData , null , 2 ) : answerData ,
readOnly : true ,
rows : 6 ,
className : "w-full p-3 bg-custom-bg border border-green-500/20 rounded-lg font-mono text-xs text-secondary resize-none custom-scrollbar"
} ) ,
React . createElement ( EnhancedCopyButton , {
key : 'copy' ,
text : typeof answerData === 'object' ? JSON . stringify ( answerData , null , 2 ) : 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' )
] ) ,
2025-09-27 19:07:17 -04:00
// QR Code section for answer
qrCodeUrl && React . createElement ( 'div' , {
key : 'qr-container' ,
className : "mt-4 p-4 bg-gray-800/50 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]"
} )
] ) ,
2025-10-02 01:43:32 -04:00
2025-09-27 19:07:17 -04:00
( ( 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' )
] ) ,
2025-09-08 16:04:58 -04:00
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'
] )
] )
] )
] )
] ) ;
}
} ;
2025-09-23 20:01:02 -04:00
// 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 ) ;
} ) ;
}
} ;
} ;
2025-09-08 16:04:58 -04:00
const EnhancedChatInterface = ( {
messages ,
messageInput ,
setMessageInput ,
onSendMessage ,
onDisconnect ,
keyFingerprint ,
isVerified ,
chatMessagesRef ,
scrollToBottom ,
webrtcManager
} ) => {
const [ showScrollButton , setShowScrollButton ] = React . useState ( false ) ;
const [ showFileTransfer , setShowFileTransfer ] = React . useState ( false ) ;
2025-10-02 01:43:32 -04:00
2025-09-08 16:04:58 -04:00
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 = ( ) => {
2025-09-23 20:01:02 -04:00
console . log ( '🔍 handleScrollToBottom called, scrollToBottom type:' , typeof scrollToBottom ) ;
if ( typeof scrollToBottom === 'function' ) {
scrollToBottom ( ) ;
setShowScrollButton ( false ) ;
} else {
2025-10-02 01:43:32 -04:00
console . error ( 'scrollToBottom is not a function:' , scrollToBottom ) ;
2025-09-23 20:01:02 -04:00
// Fallback: direct scroll
if ( chatMessagesRef . current ) {
chatMessagesRef . current . scrollTo ( {
top : chatMessagesRef . current . scrollHeight ,
behavior : 'smooth'
} ) ;
}
setShowScrollButton ( false ) ;
}
2025-09-08 16:04:58 -04:00
} ;
2025-10-02 01:43:32 -04:00
2025-09-08 16:04:58 -04:00
const handleKeyPress = ( e ) => {
if ( e . key === 'Enter' && ! e . shiftKey ) {
e . preventDefault ( ) ;
onSendMessage ( ) ;
}
} ;
2025-10-02 01:43:32 -04:00
2025-09-08 16:04:58 -04:00
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"
} )
)
) ,
2025-10-02 01:43:32 -04:00
2025-09-08 16:04:58 -04:00
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 ( )
} )
]
)
) ,
2025-10-02 01:43:32 -04:00
2025-09-08 16:04:58 -04:00
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 = ( ) => {
2025-10-02 01:43:32 -04:00
2025-09-08 16:04:58 -04:00
const [ messages , setMessages ] = React . useState ( [ ] ) ;
const [ connectionStatus , setConnectionStatus ] = React . useState ( 'disconnected' ) ;
2025-09-23 20:01:02 -04:00
// Moved scrollToBottom logic to be available globally
2025-09-08 16:04:58 -04:00
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 ) ;
2025-09-23 20:01:02 -04:00
const [ showQRCode , setShowQRCode ] = React . useState ( false ) ;
const [ qrCodeUrl , setQrCodeUrl ] = React . useState ( '' ) ;
const [ showQRScanner , setShowQRScanner ] = React . useState ( false ) ;
const [ showQRScannerModal , setShowQRScannerModal ] = React . useState ( false ) ;
2025-09-08 16:04:58 -04:00
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
2025-09-23 20:01:02 -04:00
// Session state - all security features enabled by default
2025-09-08 16:04:58 -04:00
const [ sessionTimeLeft , setSessionTimeLeft ] = React . useState ( 0 ) ;
2025-09-23 20:01:02 -04:00
const [ pendingSession , setPendingSession ] = React . useState ( null ) ;
2025-09-08 16:04:58 -04:00
2025-09-23 20:01:02 -04:00
// All security features are enabled by default - no payment required
2025-09-08 16:04:58 -04:00
2025-09-23 20:01:02 -04:00
// ============================================
// 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 ;
2025-09-08 16:04:58 -04:00
2025-09-23 20:01:02 -04:00
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 ) ;
2025-10-02 01:43:32 -04:00
const maxPreserveTime = 300000 ;
2025-09-23 20:01:02 -04:00
2025-10-02 01:43:32 -04:00
2025-09-23 20:01:02 -04:00
const hasAnswerData = ( answerData && answerData . trim ( ) . length > 0 ) ||
( answerInput && answerInput . trim ( ) . length > 0 ) ;
2025-10-02 01:43:32 -04:00
2025-09-27 19:07:17 -04:00
const hasAnswerQR = qrCodeUrl && qrCodeUrl . trim ( ) . length > 0 ;
2025-09-23 20:01:02 -04:00
const shouldPreserve = ( connectionState . hasActiveAnswer &&
answerAge < maxPreserveTime &&
! connectionState . isUserInitiatedDisconnect ) ||
( hasAnswerData && answerAge < maxPreserveTime &&
2025-09-27 19:07:17 -04:00
! connectionState . isUserInitiatedDisconnect ) ||
( hasAnswerQR && answerAge < maxPreserveTime &&
2025-09-23 20:01:02 -04:00
! connectionState . isUserInitiatedDisconnect ) ;
return shouldPreserve ;
} ;
// Mark answer as created
const markAnswerCreated = ( ) => {
updateConnectionState ( {
hasActiveAnswer : true ,
answerCreatedAt : Date . now ( )
} ) ;
} ;
// Global functions for cleanup
React . useEffect ( ( ) => {
2025-09-08 16:04:58 -04:00
window . forceCleanup = ( ) => {
handleClearData ( ) ;
if ( webrtcManagerRef . current ) {
webrtcManagerRef . current . disconnect ( ) ;
}
} ;
2025-09-23 20:01:02 -04:00
2025-09-08 16:04:58 -04:00
window . clearLogs = ( ) => {
if ( typeof console . clear === 'function' ) {
console . clear ( ) ;
}
} ;
return ( ) => {
delete window . forceCleanup ;
delete window . clearLogs ;
} ;
2025-09-23 20:01:02 -04:00
} , [ ] ) ;
2025-09-08 16:04:58 -04:00
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 ) {
2025-09-23 20:01:02 -04:00
// 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
} ) ;
2025-09-08 16:04:58 -04:00
if ( window . DEBUG _MODE ) {
2025-09-23 20:01:02 -04:00
const currentLevel = webrtcManagerRef . current . ecdhKeyPair && webrtcManagerRef . current . ecdsaKeyPair
? await webrtcManagerRef . current . calculateSecurityLevel ( )
: {
level : 'MAXIMUM' ,
score : 100 ,
sessionType : 'premium' ,
passedChecks : 10 ,
totalChecks : 10
} ;
2025-09-08 16:04:58 -04:00
}
}
} 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 ) ;
}
} , [ ] ) ;
2025-09-23 20:01:02 -04:00
// Session time ticker - unlimited sessions
2025-09-08 16:04:58 -04:00
React . useEffect ( ( ) => {
const timer = setInterval ( ( ) => {
2025-09-23 20:01:02 -04:00
// Sessions are unlimited - no time restrictions
2025-09-08 16:04:58 -04:00
setSessionTimeLeft ( 0 ) ;
} , 1000 ) ;
return ( ) => clearInterval ( timer ) ;
2025-09-23 20:01:02 -04:00
} , [ ] ) ;
2025-09-08 16:04:58 -04:00
2025-09-23 20:01:02 -04:00
// Sessions are unlimited - no expiration handler needed
2025-09-08 16:04:58 -04:00
2025-09-23 20:01:02 -04:00
// All security features are enabled by default - no demo sessions needed
2025-09-08 16:04:58 -04:00
const chatMessagesRef = React . useRef ( null ) ;
2025-09-23 20:01:02 -04:00
// 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 ) ;
2025-09-08 16:04:58 -04:00
}
2025-09-23 20:01:02 -04:00
} , [ messages ] ) ;
2025-09-08 16:04:58 -04:00
// 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 ) ) {
2025-10-02 01:43:32 -04:00
console . log ( ` Blocked system/file message from chat: ${ parsedMessage . type } ` ) ;
return ;
2025-09-08 16:04:58 -04:00
}
} catch ( parseError ) {
2025-10-02 01:43:32 -04:00
2025-09-08 16:04:58 -04:00
}
}
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' ) ;
2025-09-23 20:01:02 -04:00
// Force immediate update of isVerified state
setTimeout ( ( ) => {
setIsVerified ( true ) ;
} , 0 ) ;
2025-09-08 16:04:58 -04:00
if ( ! window . isUpdatingSecurity ) {
updateSecurityLevel ( ) . catch ( console . error ) ;
}
} else if ( status === 'connecting' ) {
if ( ! window . isUpdatingSecurity ) {
updateSecurityLevel ( ) . catch ( console . error ) ;
}
} else if ( status === 'disconnected' ) {
2025-09-23 20:01:02 -04:00
updateConnectionState ( { status : 'disconnected' } ) ;
2025-09-08 16:04:58 -04:00
setConnectionStatus ( 'disconnected' ) ;
2025-10-02 01:43:32 -04:00
2025-09-23 20:01:02 -04:00
if ( shouldPreserveAnswerData ( ) ) {
setIsVerified ( false ) ;
setShowVerification ( false ) ;
return ;
}
2025-10-02 01:43:32 -04:00
2025-09-08 16:04:58 -04:00
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 ) ;
2025-09-23 20:01:02 -04:00
2025-09-08 16:04:58 -04:00
setOfferData ( null ) ;
setAnswerData ( null ) ;
setOfferInput ( '' ) ;
setAnswerInput ( '' ) ;
setShowOfferStep ( false ) ;
setShowAnswerStep ( false ) ;
setMessages ( [ ] ) ;
} , 1000 ) ;
2025-10-02 01:43:32 -04:00
2025-09-08 16:04:58 -04:00
} 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 ( [ ] ) ;
2025-10-02 01:43:32 -04:00
if ( typeof console . clear === 'function' ) {
console . clear ( ) ;
}
2025-09-08 16:04:58 -04:00
} , 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 ) ;
2025-10-02 01:43:32 -04:00
addMessageWithAutoScroll ( ` Security breach: ${ errorMessage } ` , 'system' ) ;
2025-09-08 16:04:58 -04:00
if ( typeof console . clear === 'function' ) {
console . clear ( ) ;
}
}
} ;
if ( typeof console . clear === 'function' ) {
console . clear ( ) ;
}
webrtcManagerRef . current = new EnhancedSecureWebRTCManager (
handleMessage ,
handleStatusChange ,
handleKeyExchange ,
handleVerificationRequired ,
handleAnswerError ,
handleVerificationStateChange
) ;
2025-10-02 01:43:32 -04:00
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' ) ;
2025-09-08 16:04:58 -04:00
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'
} , [
2025-10-02 01:43:32 -04:00
React . createElement ( 'span' , { key : 'label' } , ` File received: ${ fileData . fileName } ( ${ sizeMb } MB) ` ) ,
2025-09-08 16:04:58 -04:00
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 ) ;
2025-10-02 01:43:32 -04:00
addMessageWithAutoScroll ( ` File upload error: ${ String ( e ? . message || e ) } ` , 'system' ) ;
2025-09-08 16:04:58 -04:00
}
}
} , 'Download' )
] ) ;
addMessageWithAutoScroll ( downloadMessage , 'system' ) ;
} ,
// Error callback
( error ) => {
console . error ( 'File transfer error:' , error ) ;
if ( error . includes ( 'Connection not ready' ) ) {
2025-10-02 01:43:32 -04:00
addMessageWithAutoScroll ( ` File transfer error: connection not ready. Try again later. ` , 'system' ) ;
2025-09-08 16:04:58 -04:00
} else if ( error . includes ( 'File too large' ) ) {
2025-10-02 01:43:32 -04:00
addMessageWithAutoScroll ( ` File is too big. Maximum size: 100 MB ` , 'system' ) ;
2025-09-08 16:04:58 -04:00
} else {
2025-10-02 01:43:32 -04:00
addMessageWithAutoScroll ( ` File transfer error: ${ error } ` , 'system' ) ;
2025-09-08 16:04:58 -04:00
}
}
) ;
}
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
2025-09-23 20:01:02 -04:00
// 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 limit (characters). Adjust per error correction level.
const MAX _QR _LEN = 800 ;
const [ qrFramesTotal , setQrFramesTotal ] = React . useState ( 0 ) ;
const [ qrFrameIndex , setQrFrameIndex ] = React . useState ( 0 ) ;
2025-09-27 19:07:17 -04:00
const [ qrManualMode , setQrManualMode ] = React . useState ( false ) ;
2025-09-23 20:01:02 -04:00
// 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 ) ;
2025-09-27 19:07:17 -04:00
setQrManualMode ( false ) ;
} ;
const toggleQrManualMode = ( ) => {
const newManualMode = ! qrManualMode ;
setQrManualMode ( newManualMode ) ;
if ( newManualMode ) {
2025-10-02 01:43:32 -04:00
2025-09-27 19:07:17 -04:00
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 && qrAnimationRef . current . active ) {
const intervalMs = 4000 ;
qrAnimationRef . current . timer = setInterval ( renderNext , intervalMs ) ;
}
console . log ( 'QR Manual mode disabled - auto-scroll resumed' ) ;
}
} ;
const nextQrFrame = ( ) => {
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 ) ;
renderNext ( ) ;
} else {
console . log ( '🎮 No multiple frames to navigate' ) ;
}
} ;
const prevQrFrame = ( ) => {
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 ) ;
renderNext ( ) ;
} else {
console . log ( '🎮 No multiple frames to navigate' ) ;
}
2025-09-23 20:01:02 -04:00
} ;
// 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 ;
// Small payload: прямой JSON в один QR (без сжатия, без обёрток)
const payload = typeof data === 'string' ? data : JSON . stringify ( data ) ;
const isDesktop = ( typeof window !== 'undefined' ) && ( ( window . innerWidth || 0 ) >= 1024 ) ;
const QR _SIZE = isDesktop ? 720 : 512 ;
if ( payload . length <= MAX _QR _LEN ) {
if ( ! window . generateQRCode ) throw new Error ( 'QR code generator unavailable' ) ;
stopQrAnimation ( ) ;
const qrDataUrl = await window . generateQRCode ( payload , { errorCorrectionLevel : 'M' , size : QR _SIZE , margin : 2 } ) ;
setQrCodeUrl ( qrDataUrl ) ;
setQrFramesTotal ( 1 ) ;
setQrFrameIndex ( 1 ) ;
return ;
}
stopQrAnimation ( ) ;
const id = ` raw_ ${ Date . now ( ) } _ ${ Math . random ( ) . toString ( 36 ) . slice ( 2 ) } ` ;
2025-10-02 01:43:32 -04:00
2025-09-27 19:07:17 -04:00
const TARGET _CHUNKS = 10 ;
const FRAME _MAX = Math . max ( 200 , Math . floor ( payload . length / TARGET _CHUNKS ) ) ;
2025-09-23 20:01:02 -04:00
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 } ;
const renderNext = async ( ) => {
const { chunks , idx , active } = qrAnimationRef . current ;
if ( ! active || ! chunks . length ) return ;
const current = chunks [ idx % chunks . length ] ;
try {
const url = await window . generateQRCode ( current , EC _OPTS ) ;
setQrCodeUrl ( url ) ;
} catch ( e ) {
console . warn ( 'Animated QR render error (raw):' , e ) ;
}
const nextIdx = ( idx + 1 ) % chunks . length ;
qrAnimationRef . current . idx = nextIdx ;
setQrFrameIndex ( nextIdx + 1 ) ;
} ;
await renderNext ( ) ;
2025-10-02 01:43:32 -04:00
2025-09-27 19:07:17 -04:00
if ( ! qrManualMode ) {
const ua = ( typeof navigator !== 'undefined' && navigator . userAgent ) ? navigator . userAgent : '' ;
const isIOS = /iPhone|iPad|iPod/i . test ( ua ) ;
const intervalMs = 4000 ; // 4 seconds per frame for better readability
qrAnimationRef . current . timer = setInterval ( renderNext , intervalMs ) ;
}
2025-09-23 20:01:02 -04:00
return ;
} catch ( error ) {
console . error ( 'QR code generation failed:' , error ) ;
setMessages ( prev => [ ... prev , {
2025-10-02 01:43:32 -04:00
message : ` QR code generation failed: ${ error . message } ` ,
2025-09-23 20:01:02 -04:00
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 {
// Try to parse as JSON first
const parsedData = JSON . parse ( scannedData ) ;
// QR with hdr/body: COSE or RAW 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 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 ) ) ;
}
2025-10-02 01:43:32 -04:00
setMessages ( prev => [ ... prev , { message : 'All frames captured. RAW payload reconstructed.' , type : 'success' } ] ) ;
2025-09-23 20:01:02 -04:00
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 ( 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 ) ) ;
}
2025-10-02 01:43:32 -04:00
setMessages ( prev => [ ... prev , { message : 'All frames captured. COSE payload reconstructed.' , type : 'success' } ] ) ;
2025-09-23 20:01:02 -04:00
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 , {
2025-10-02 01:43:32 -04:00
message : 'QR code reference found but full data not available. Please use copy/paste.' ,
2025-09-23 20:01:02 -04:00
type : 'error'
} ] ) ;
return false ;
}
} else {
// Check if this is compressed data (missing SDP)
if ( ! parsedData . sdp ) {
setMessages ( prev => [ ... prev , {
2025-10-02 01:43:32 -04:00
message : 'QR code contains compressed data (SDP removed). Please use copy/paste for full data.' ,
2025-09-23 20:01:02 -04:00
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 ;
}
2025-09-08 16:04:58 -04:00
} ;
const handleCreateOffer = async ( ) => {
try {
2025-09-23 20:01:02 -04:00
// All security features are enabled by default
2025-09-08 16:04:58 -04:00
setOfferData ( '' ) ;
setShowOfferStep ( false ) ;
2025-09-23 20:01:02 -04:00
setShowQRCode ( false ) ;
setQrCodeUrl ( '' ) ;
2025-09-08 16:04:58 -04:00
const offer = await webrtcManagerRef . current . createSecureOffer ( ) ;
// Store offer data directly (no encryption needed with SAS)
setOfferData ( offer ) ;
setShowOfferStep ( true ) ;
2025-09-23 20:01:02 -04:00
// Generate QR code for the offer data
// Use compact JSON (no pretty-printing) to reduce size
const offerString = typeof offer === 'object' ? JSON . stringify ( offer ) : offer ;
await generateQRCode ( offerString ) ;
2025-09-08 16:04:58 -04:00
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 , {
2025-10-02 01:43:32 -04:00
message : 'Secure invitation created and encrypted!' ,
2025-09-08 16:04:58 -04:00
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 , {
2025-10-02 01:43:32 -04:00
message : ` Error creating invitation: ${ error . message } ` ,
2025-09-08 16:04:58 -04:00
type : 'system' ,
id : Date . now ( ) ,
timestamp : Date . now ( )
} ] ) ;
}
} ;
const handleCreateAnswer = async ( ) => {
try {
2025-09-23 20:01:02 -04:00
2025-09-08 16:04:58 -04:00
if ( ! offerInput . trim ( ) ) {
setMessages ( prev => [ ... prev , {
2025-10-02 01:43:32 -04:00
message : 'You need to insert the invitation code from your interlocutor.' ,
2025-09-08 16:04:58 -04:00
type : 'system' ,
id : Date . now ( ) ,
timestamp : Date . now ( )
} ] ) ;
return ;
}
try {
setMessages ( prev => [ ... prev , {
2025-10-02 01:43:32 -04:00
message : 'Processing the secure invitation...' ,
2025-09-08 16:04:58 -04:00
type : 'system' ,
id : Date . now ( ) ,
timestamp : Date . now ( )
} ] ) ;
let offer ;
try {
// Parse the offer data directly (no decryption needed with SAS)
offer = JSON . parse ( offerInput . trim ( ) ) ;
} catch ( parseError ) {
throw new Error ( ` Invalid invitation format: ${ parseError . message } ` ) ;
}
if ( ! offer || typeof offer !== 'object' ) {
throw new Error ( 'The invitation must be an object' ) ;
}
2025-09-23 20:01:02 -04:00
// 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' ) ;
2025-09-08 16:04:58 -04:00
}
2025-10-02 01:43:32 -04:00
2025-09-08 16:04:58 -04:00
const answer = await webrtcManagerRef . current . createSecureAnswer ( offer ) ;
// Store answer data directly (no encryption needed with SAS)
setAnswerData ( answer ) ;
setShowAnswerStep ( true ) ;
2025-09-23 20:01:02 -04:00
2025-09-27 19:07:17 -04:00
// Generate QR code for the answer data
const answerString = typeof answer === 'object' ? JSON . stringify ( answer ) : answer ;
2025-10-02 01:43:32 -04:00
2025-09-27 19:07:17 -04:00
await generateQRCode ( answerString ) ;
2025-09-23 20:01:02 -04:00
// Mark answer as created for state management
2025-10-02 01:43:32 -04:00
if ( e . target . value . trim ( ) . length > 0 ) {
if ( typeof markAnswerCreated === 'function' ) {
markAnswerCreated ( ) ;
}
}
2025-09-08 16:04:58 -04:00
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 , {
2025-10-02 01:43:32 -04:00
message : 'Secure response created!' ,
2025-09-08 16:04:58 -04:00
type : 'system' ,
id : Date . now ( ) ,
timestamp : Date . now ( )
} ] ) ;
setMessages ( prev => [ ... prev , {
2025-10-02 01:43:32 -04:00
message : 'Send the response code to the initiator via a secure channel or let them scan the QR code below.' ,
2025-09-08 16:04:58 -04:00
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 , {
2025-10-02 01:43:32 -04:00
message : ` Error processing the invitation: ${ error . message } ` ,
2025-09-08 16:04:58 -04:00
type : 'system' ,
id : Date . now ( ) ,
timestamp : Date . now ( )
} ] ) ;
}
} catch ( error ) {
console . error ( 'Error in handleCreateAnswer:' , error ) ;
setMessages ( prev => [ ... prev , {
2025-10-02 01:43:32 -04:00
message : ` Invitation processing error: ${ error . message } ` ,
2025-09-08 16:04:58 -04:00
type : 'system' ,
id : Date . now ( ) ,
timestamp : Date . now ( )
} ] ) ;
}
} ;
const handleConnect = async ( ) => {
try {
if ( ! answerInput . trim ( ) ) {
setMessages ( prev => [ ... prev , {
2025-10-02 01:43:32 -04:00
message : 'You need to insert the response code from your interlocutor.' ,
2025-09-08 16:04:58 -04:00
type : 'system' ,
id : Date . now ( ) ,
timestamp : Date . now ( )
} ] ) ;
return ;
}
try {
setMessages ( prev => [ ... prev , {
2025-10-02 01:43:32 -04:00
message : 'Processing the secure response...' ,
2025-09-08 16:04:58 -04:00
type : 'system' ,
id : Date . now ( ) ,
timestamp : Date . now ( )
} ] ) ;
let answer ;
try {
// Parse the answer data directly (no decryption needed with SAS)
answer = JSON . parse ( answerInput . trim ( ) ) ;
} catch ( parseError ) {
throw new Error ( ` Invalid response format: ${ parseError . message } ` ) ;
}
if ( ! answer || typeof answer !== 'object' ) {
throw new Error ( 'The response must be an object' ) ;
}
2025-09-23 20:01:02 -04:00
// 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' ) ;
2025-09-08 16:04:58 -04:00
}
await webrtcManagerRef . current . handleSecureAnswer ( answer ) ;
2025-09-23 20:01:02 -04:00
// All security features are enabled by default - no session activation needed
if ( pendingSession ) {
2025-09-08 16:04:58 -04:00
setPendingSession ( null ) ;
setMessages ( prev => [ ... prev , {
2025-10-02 01:43:32 -04:00
message : ` All security features enabled by default ` ,
2025-09-08 16:04:58 -04:00
type : 'system' ,
id : Date . now ( ) ,
timestamp : Date . now ( )
} ] ) ;
}
setMessages ( prev => [ ... prev , {
2025-10-02 01:43:32 -04:00
message : 'Finalizing the secure connection...' ,
2025-09-08 16:04:58 -04:00
type : 'system' ,
id : Date . now ( ) ,
timestamp : Date . now ( )
} ] ) ;
// Update security level after handling answer
if ( ! window . isUpdatingSecurity ) {
updateSecurityLevel ( ) . catch ( console . error ) ;
}
} catch ( error ) {
2025-09-23 20:01:02 -04:00
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' ) ) {
2025-10-02 01:43:32 -04:00
errorMessage = 'Invalid response code - missing or corrupted cryptographic key. Please check the code and try again.' ;
2025-09-23 20:01:02 -04:00
} else if ( error . message . includes ( 'ECDSA public key structure' ) ) {
2025-10-02 01:43:32 -04:00
errorMessage = 'Invalid response code - missing signature verification key. Please check the code and try again.' ;
2025-09-23 20:01:02 -04:00
} else {
2025-10-02 01:43:32 -04:00
errorMessage = 'Security validation failed - possible attack detected' ;
2025-09-23 20:01:02 -04:00
}
} else if ( error . message . includes ( 'too old' ) || error . message . includes ( 'replay' ) ) {
2025-10-02 01:43:32 -04:00
errorMessage = 'Response data is outdated - please use a fresh invitation' ;
2025-09-23 20:01:02 -04:00
} else if ( error . message . includes ( 'MITM' ) || error . message . includes ( 'signature' ) ) {
2025-10-02 01:43:32 -04:00
errorMessage = 'Security breach detected - connection rejected' ;
2025-09-23 20:01:02 -04:00
} else if ( error . message . includes ( 'Invalid' ) || error . message . includes ( 'format' ) ) {
2025-10-02 01:43:32 -04:00
errorMessage = 'Invalid response format - please check the code' ;
2025-09-23 20:01:02 -04:00
} else {
2025-10-02 01:43:32 -04:00
errorMessage = ` ${ error . message } ` ;
2025-09-23 20:01:02 -04:00
}
2025-09-08 16:04:58 -04:00
setMessages ( prev => [ ... prev , {
2025-09-23 20:01:02 -04:00
message : errorMessage ,
2025-09-08 16:04:58 -04:00
type : 'system' ,
id : Date . now ( ) ,
2025-09-23 20:01:02 -04:00
timestamp : Date . now ( ) ,
showRetryButton : true
2025-09-08 16:04:58 -04:00
} ] ) ;
2025-10-02 01:43:32 -04:00
2025-09-23 20:01:02 -04:00
if ( ! error . message . includes ( 'too old' ) && ! error . message . includes ( 'replay' ) ) {
2025-09-08 16:04:58 -04:00
setPendingSession ( null ) ;
2025-09-23 20:01:02 -04:00
setSessionTimeLeft ( 0 ) ;
2025-09-08 16:04:58 -04:00
}
2025-10-02 01:43:32 -04:00
2025-09-23 20:01:02 -04:00
setConnectionStatus ( 'failed' ) ;
2025-09-08 16:04:58 -04:00
}
} catch ( error ) {
2025-09-23 20:01:02 -04:00
console . error ( 'Error in handleConnect outer try:' , error ) ;
2025-10-02 01:43:32 -04:00
2025-09-23 20:01:02 -04:00
let errorMessage = 'Connection setup error' ;
if ( error . message . includes ( 'CRITICAL SECURITY FAILURE' ) ) {
if ( error . message . includes ( 'ECDH public key structure' ) ) {
2025-10-02 01:43:32 -04:00
errorMessage = 'Invalid response code - missing or corrupted cryptographic key. Please check the code and try again.' ;
2025-09-23 20:01:02 -04:00
} else if ( error . message . includes ( 'ECDSA public key structure' ) ) {
2025-10-02 01:43:32 -04:00
errorMessage = 'Invalid response code - missing signature verification key. Please check the code and try again.' ;
2025-09-23 20:01:02 -04:00
} else {
2025-10-02 01:43:32 -04:00
errorMessage = 'Security validation failed - possible attack detected' ;
2025-09-23 20:01:02 -04:00
}
} else if ( error . message . includes ( 'too old' ) || error . message . includes ( 'replay' ) ) {
2025-10-02 01:43:32 -04:00
errorMessage = 'Response data is outdated - please use a fresh invitation' ;
2025-09-23 20:01:02 -04:00
} else if ( error . message . includes ( 'MITM' ) || error . message . includes ( 'signature' ) ) {
2025-10-02 01:43:32 -04:00
errorMessage = 'Security breach detected - connection rejected' ;
2025-09-23 20:01:02 -04:00
} else if ( error . message . includes ( 'Invalid' ) || error . message . includes ( 'format' ) ) {
2025-10-02 01:43:32 -04:00
errorMessage = 'Invalid response format - please check the code' ;
2025-09-23 20:01:02 -04:00
} else {
2025-10-02 01:43:32 -04:00
errorMessage = ` ${ error . message } ` ;
2025-09-23 20:01:02 -04:00
}
2025-09-08 16:04:58 -04:00
setMessages ( prev => [ ... prev , {
2025-09-23 20:01:02 -04:00
message : errorMessage ,
2025-09-08 16:04:58 -04:00
type : 'system' ,
id : Date . now ( ) ,
2025-09-23 20:01:02 -04:00
timestamp : Date . now ( ) ,
showRetryButton : true
2025-09-08 16:04:58 -04:00
} ] ) ;
2025-10-02 01:43:32 -04:00
2025-09-23 20:01:02 -04:00
if ( ! error . message . includes ( 'too old' ) && ! error . message . includes ( 'replay' ) ) {
2025-09-08 16:04:58 -04:00
setPendingSession ( null ) ;
2025-09-23 20:01:02 -04:00
setSessionTimeLeft ( 0 ) ;
2025-09-08 16:04:58 -04:00
}
2025-10-02 01:43:32 -04:00
2025-09-23 20:01:02 -04:00
setConnectionStatus ( 'failed' ) ;
2025-09-08 16:04:58 -04:00
}
} ;
const handleVerifyConnection = ( isValid ) => {
if ( isValid ) {
webrtcManagerRef . current . confirmVerification ( ) ;
// Mark local verification as confirmed
setLocalVerificationConfirmed ( true ) ;
} else {
setMessages ( prev => [ ... prev , {
2025-10-02 01:43:32 -04:00
message : ' Verification rejected. The connection is unsafe! Session reset..' ,
2025-09-08 16:04:58 -04:00
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 ) ) {
2025-10-02 01:43:32 -04:00
addMessageWithAutoScroll ( ` Sending error: ${ msg } ` , 'system' ) ;
2025-09-08 16:04:58 -04:00
}
}
} ;
const handleClearData = ( ) => {
setOfferData ( '' ) ;
setAnswerData ( '' ) ;
setOfferInput ( '' ) ;
setAnswerInput ( '' ) ;
setShowOfferStep ( false ) ;
2025-10-02 01:43:32 -04:00
2025-09-27 19:07:17 -04:00
if ( ! shouldPreserveAnswerData ( ) ) {
setShowAnswerStep ( false ) ;
}
2025-09-08 16:04:58 -04:00
setShowVerification ( false ) ;
2025-09-23 20:01:02 -04:00
setShowQRCode ( false ) ;
setShowQRScanner ( false ) ;
setShowQRScannerModal ( false ) ;
2025-10-02 01:43:32 -04:00
2025-09-27 19:07:17 -04:00
if ( ! shouldPreserveAnswerData ( ) ) {
setQrCodeUrl ( '' ) ;
}
2025-09-08 16:04:58 -04:00
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
2025-10-02 01:43:32 -04:00
if ( typeof console . clear === 'function' ) {
console . clear ( ) ;
}
2025-09-08 16:04:58 -04:00
2025-09-23 20:01:02 -04:00
// Cleanup session state
2025-09-08 16:04:58 -04:00
setSessionTimeLeft ( 0 ) ;
setPendingSession ( null ) ;
document . dispatchEvent ( new CustomEvent ( 'peer-disconnect' ) ) ;
2025-09-23 20:01:02 -04:00
// Session manager removed - all features enabled by default
2025-09-08 16:04:58 -04:00
} ;
const handleDisconnect = ( ) => {
setSessionTimeLeft ( 0 ) ;
2025-09-23 20:01:02 -04:00
// Mark as user-initiated disconnect
updateConnectionState ( {
status : 'disconnected' ,
isUserInitiatedDisconnect : true
} ) ;
// Cleanup session state
2025-09-08 16:04:58 -04:00
if ( webrtcManagerRef . current ) {
2025-09-23 20:01:02 -04:00
webrtcManagerRef . current . disconnect ( ) ;
2025-09-08 16:04:58 -04:00
}
setKeyFingerprint ( '' ) ;
setVerificationCode ( '' ) ;
setSecurityLevel ( null ) ;
setIsVerified ( false ) ;
setShowVerification ( false ) ;
setConnectionStatus ( 'disconnected' ) ;
// Clear verification states
setLocalVerificationConfirmed ( false ) ;
setRemoteVerificationConfirmed ( false ) ;
setBothVerificationsConfirmed ( false ) ;
2025-09-23 20:01:02 -04:00
// Reset UI to initial state (user-initiated disconnect always clears data)
2025-09-08 16:04:58 -04:00
setConnectionStatus ( 'disconnected' ) ;
setShowVerification ( false ) ;
setOfferData ( null ) ;
setAnswerData ( null ) ;
setOfferInput ( '' ) ;
setAnswerInput ( '' ) ;
setShowOfferStep ( false ) ;
setShowAnswerStep ( false ) ;
setKeyFingerprint ( '' ) ;
setVerificationCode ( '' ) ;
setSecurityLevel ( null ) ;
setIsVerified ( false ) ;
setMessages ( [ ] ) ;
2025-10-02 01:43:32 -04:00
if ( typeof console . clear === 'function' ) {
console . clear ( ) ;
}
2025-09-08 16:04:58 -04:00
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 ( ( ) => {
2025-09-23 20:01:02 -04:00
// Session manager removed - all features enabled by default
2025-09-08 16:04:58 -04:00
} , 1000 ) ;
} ;
const handleSessionActivated = ( session ) => {
let message ;
if ( session . type === 'demo' ) {
2025-10-02 01:43:32 -04:00
message = ` Demo session activated for 6 minutes. You can create invitations! ` ;
2025-09-08 16:04:58 -04:00
} else {
2025-10-02 01:43:32 -04:00
message = ` All security features enabled by default. You can create invitations! ` ;
2025-09-08 16:04:58 -04:00
}
addMessageWithAutoScroll ( message , 'system' ) ;
} ;
React . useEffect ( ( ) => {
if ( connectionStatus === 'connected' && isVerified ) {
2025-10-02 01:43:32 -04:00
addMessageWithAutoScroll ( ' Secure connection successfully established and verified! You can now communicate safely with full protection against MITM attacks and Perfect Forward Secrecy..' , 'system' ) ;
2025-09-08 16:04:58 -04:00
}
} , [ connectionStatus , isVerified ] ) ;
2025-09-23 20:01:02 -04:00
const isConnectedAndVerified = ( connectionStatus === 'connected' || connectionStatus === 'verified' ) && isVerified ;
2025-09-08 16:04:58 -04:00
React . useEffect ( ( ) => {
2025-09-23 20:01:02 -04:00
// All security features are enabled by default - no session activation needed
if ( isConnectedAndVerified && pendingSession && connectionStatus !== 'failed' ) {
2025-09-08 16:04:58 -04:00
setPendingSession ( null ) ;
2025-09-23 20:01:02 -04:00
setSessionTimeLeft ( 0 ) ;
2025-10-02 01:43:32 -04:00
addMessageWithAutoScroll ( ' All security features enabled by default' , 'system' ) ;
2025-09-08 16:04:58 -04:00
}
2025-09-23 20:01:02 -04:00
} , [ isConnectedAndVerified , pendingSession , connectionStatus ] ) ;
2025-09-08 16:04:58 -04:00
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 ,
2025-09-23 20:01:02 -04:00
// sessionManager removed - all features enabled by default
sessionTimeLeft : sessionTimeLeft ,
webrtcManager : webrtcManagerRef . current
2025-09-08 16:04:58 -04:00
} ) ,
React . createElement ( 'main' , {
key : 'main'
} ,
2025-09-23 20:01:02 -04:00
( ( ) => {
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
} ) ;
} ) ( )
2025-09-08 16:04:58 -04:00
: 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 ,
2025-09-23 20:01:02 -04:00
showQRCode : showQRCode ,
qrCodeUrl : qrCodeUrl ,
showQRScanner : showQRScanner ,
setShowQRCode : setShowQRCode ,
setShowQRScanner : setShowQRScanner ,
setShowQRScannerModal : setShowQRScannerModal ,
2025-09-08 16:04:58 -04:00
messages : messages ,
localVerificationConfirmed : localVerificationConfirmed ,
remoteVerificationConfirmed : remoteVerificationConfirmed ,
bothVerificationsConfirmed : bothVerificationsConfirmed ,
2025-09-27 19:07:17 -04:00
// QR control props
qrFramesTotal : qrFramesTotal ,
qrFrameIndex : qrFrameIndex ,
qrManualMode : qrManualMode ,
toggleQrManualMode : toggleQrManualMode ,
nextQrFrame : nextQrFrame ,
prevQrFrame : prevQrFrame ,
2025-09-08 16:04:58 -04:00
// PAKE passwords removed - using SAS verification instead
2025-10-02 01:43:32 -04:00
markAnswerCreated : markAnswerCreated
2025-09-08 16:04:58 -04:00
} )
) ,
] ) ;
} ;
function initializeApp ( ) {
if ( window . EnhancedSecureCryptoUtils && window . EnhancedSecureWebRTCManager ) {
ReactDOM . render ( React . createElement ( EnhancedSecureP2PChat ) , document . getElementById ( 'root' ) ) ;
} else {
2025-10-02 01:43:32 -04:00
console . error ( 'Модули не загружены:' , {
2025-09-08 16:04:58 -04:00
hasCrypto : ! ! window . EnhancedSecureCryptoUtils ,
hasWebRTC : ! ! window . EnhancedSecureWebRTCManager
} ) ;
}
}
2025-10-02 01:43:32 -04:00
2025-09-23 20:01:02 -04:00
if ( typeof window !== 'undefined' ) {
2025-10-02 01:43:32 -04:00
2025-09-23 20:01:02 -04:00
window . addEventListener ( 'unhandledrejection' , ( event ) => {
2025-10-02 01:43:32 -04:00
console . error ( 'Unhandled promise rejection:' , event . reason ) ;
event . preventDefault ( ) ;
2025-09-23 20:01:02 -04:00
} ) ;
2025-10-02 01:43:32 -04:00
2025-09-23 20:01:02 -04:00
window . addEventListener ( 'error' , ( event ) => {
2025-10-02 01:43:32 -04:00
console . error ( 'Global error:' , event . error ) ;
event . preventDefault ( ) ;
2025-09-23 20:01:02 -04:00
} ) ;
if ( ! window . initializeApp ) {
window . initializeApp = initializeApp ;
}
2025-09-08 16:04:58 -04:00
}
// Render Enhanced Application
ReactDOM . render ( React . createElement ( EnhancedSecureP2PChat ) , document . getElementById ( 'root' ) ) ;