2026-05-17 22:58:21 -04:00
import { installDebugWindowHooks } from './utils/debugWindowHooks.js' ;
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 }) => {
2026-05-17 14:48:52 -04:00
const [ sasInput , setSasInput ] = React . useState ( '' );
const [ error , setError ] = React . useState ( '' );
const normalizedExpectedLength = ( verificationCode || '' ). replace ( /[-\s]/g , '' ). length ;
const normalizedInputLength = sasInput . replace ( /[-\s]/g , '' ). length ;
const canConfirm = ! localConfirmed && normalizedExpectedLength > 0 && normalizedInputLength === normalizedExpectedLength ;
React . useEffect (() => {
setSasInput ( '' );
setError ( '' );
}, [ verificationCode ]);
const handleConfirm = async () => {
try {
setError ( '' );
await onConfirm ( sasInput );
} catch ( confirmationError ) {
setSasInput ( '' );
if ( confirmationError ? . message === 'SAS_MAX_ATTEMPTS' ) {
setError ( 'Too many incorrect attempts. Session reset for safety.' );
} else {
setError ( 'Incorrect code. Check it with your peer and try again.' );
}
}
};
2025-09-08 16:04:58 -04:00
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"
2026-05-17 14:48:52 -04:00
}, "Compare this code with your peer out-of-band, then type the same code below to unlock the chat." ),
2025-09-08 16:04:58 -04:00
React . createElement ( 'div' , {
key : 'code-display' ,
className : "text-center"
}, [
React . createElement ( 'div' , {
key : 'code' ,
className : "verification-code text-2xl py-4"
}, verificationCode )
]),
2026-05-17 14:48:52 -04:00
React . createElement ( 'div' , {
key : 'sas-input-wrap' ,
className : "space-y-2"
}, [
React . createElement ( 'label' , {
key : 'sas-label' ,
className : "block text-sm text-secondary"
}, "Enter the verified code" ),
React . createElement ( 'input' , {
key : 'sas-input' ,
type : 'text' ,
value : sasInput ,
onChange : ( event ) => {
setSasInput ( event . target . value . toUpperCase ());
if ( error ) setError ( '' );
},
autoFocus : true ,
autoComplete : 'off' ,
spellCheck : false ,
inputMode : 'text' ,
disabled : localConfirmed ,
placeholder : verificationCode ? 'Type code here' : 'Waiting for code…' ,
className : "w-full rounded-lg border border-purple-500/30 bg-black/20 px-4 py-3 text-center text-xl tracking-[0.3em] text-primary uppercase focus:border-purple-400 focus:outline-none disabled:cursor-not-allowed disabled:opacity-60" ,
style : { fontFamily : 'monospace' , textTransform : 'uppercase' }
}),
error && React . createElement ( 'p' , {
key : 'sas-error' ,
className : "text-sm text-red-400"
}, error )
]),
2025-09-08 16:04:58 -04:00
// 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' ,
2026-05-17 14:48:52 -04:00
onClick : handleConfirm ,
disabled : ! canConfirm ,
className : `flex-1 py-3 px-4 rounded-lg font-medium transition-all duration-200 ${ ! canConfirm ? 'bg-gray-500/20 text-gray-400 cursor-not-allowed' : 'btn-verify text-white' } `
2025-09-08 16:04:58 -04:00
}, [
React . createElement ( 'i' , {
className : `fas ${ localConfirmed ? 'fa-check-circle' : 'fa-check' } mr-2`
}),
2026-05-17 14:48:52 -04:00
localConfirmed ? 'Confirmed' : 'Confirm code'
2025-09-08 16:04:58 -04:00
]),
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' ,
2025-12-28 20:28:22 -04:00
className : "text-sm break-words whitespace-normal"
2025-09-08 16:04:58 -04:00
}, 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 ,
2025-10-15 19:58:28 -04:00
markAnswerCreated ,
2025-10-19 15:23:02 -04:00
notificationIntegrationRef ,
isGeneratingKeys ,
2026-05-18 19:49:57 -04:00
setIsGeneratingKeys ,
2026-05-17 14:48:52 -04:00
handleCreateOffer ,
relayOnlyMode ,
2026-05-18 19:49:57 -04:00
setRelayOnlyMode ,
webrtcManagerRef
2025-09-08 16:04:58 -04:00
}) => {
const [ mode , setMode ] = React . useState ( 'select' );
2025-10-15 19:58:28 -04:00
const [ notificationPermissionRequested , setNotificationPermissionRequested ] = React . useState ( false );
2025-09-08 16:04:58 -04:00
const resetToSelect = () => {
setMode ( 'select' );
2025-10-19 15:23:02 -04:00
setIsGeneratingKeys ( false );
2025-09-08 16:04:58 -04:00
onClearData ();
};
2026-05-17 14:48:52 -04:00
const handleVerificationConfirm = ( userCode ) => {
return onVerifyConnection ( userCode );
2025-09-08 16:04:58 -04:00
};
const handleVerificationReject = () => {
2026-05-17 14:48:52 -04:00
onVerifyConnection ( null , false );
2025-09-08 16:04:58 -04:00
};
2025-10-15 19:58:28 -04:00
// Request notification permission on first user interaction
const requestNotificationPermissionOnInteraction = async () => {
if ( notificationPermissionRequested ) {
return ; // Already requested
}
try {
// Check if Notification API is supported
if ( ! ( 'Notification' in window )) {
return ;
}
// Check if we're in a secure context
if ( ! window . isSecureContext && window . location . protocol !== 'https:' && window . location . hostname !== 'localhost' ) {
return ;
}
// Check current permission status
2025-10-19 20:51:44 -04:00
const currentPermission = ( typeof Notification !== 'undefined' && Notification )
? Notification . permission
: 'denied' ;
2025-10-15 19:58:28 -04:00
// Only request if permission is default (not granted or denied)
2025-10-19 20:51:44 -04:00
if ( currentPermission === 'default' && typeof Notification !== 'undefined' && Notification ) {
2025-10-15 19:58:28 -04:00
const permission = await Notification . requestPermission ();
if ( permission === 'granted' ) {
// Initialize notification integration immediately
try {
if ( window . NotificationIntegration && webrtcManagerRef . current ) {
const integration = new window . NotificationIntegration ( webrtcManagerRef . current );
await integration . init ();
// Store reference for cleanup
notificationIntegrationRef . current = integration ;
}
} catch ( error ) {
// Handle error silently
}
2025-10-19 15:23:02 -04:00
// Send welcome notification
2025-10-15 19:58:28 -04:00
setTimeout (() => {
try {
const welcomeNotification = new Notification ( 'SecureBit Chat' , {
body : 'Notifications enabled! You will receive alerts for new messages.' ,
icon : '/logo/icon-192x192.png' ,
tag : 'welcome-notification'
});
welcomeNotification . onclick = () => {
welcomeNotification . close ();
};
setTimeout (() => {
welcomeNotification . close ();
}, 5000 );
} catch ( error ) {
// Handle error silently
}
}, 1000 );
}
2025-10-19 15:23:02 -04:00
} else if ( currentPermission === 'granted' ) {
2025-10-15 19:58:28 -04:00
// Initialize notification integration immediately
try {
if ( window . NotificationIntegration && webrtcManagerRef . current && ! notificationIntegrationRef . current ) {
const integration = new window . NotificationIntegration ( webrtcManagerRef . current );
await integration . init ();
// Store reference for cleanup
notificationIntegrationRef . current = integration ;
}
} catch ( error ) {
// Handle error silently
}
// Test notification to confirm it works
setTimeout (() => {
try {
const testNotification = new Notification ( 'SecureBit Chat' , {
body : 'Notifications are working! You will receive alerts for new messages.' ,
icon : '/logo/icon-192x192.png' ,
tag : 'test-notification'
});
testNotification . onclick = () => {
testNotification . close ();
};
setTimeout (() => {
testNotification . close ();
}, 5000 );
} catch ( error ) {
// Handle error silently
}
}, 1000 );
}
setNotificationPermissionRequested ( true );
} catch ( error ) {
// Handle error silently
}
};
2025-09-08 16:04:58 -04:00
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." )
]),
2026-05-17 14:48:52 -04:00
React . createElement ( 'label' , {
key : 'privacy-mode' ,
className : "mb-6 mx-auto flex max-w-2xl items-start gap-3 rounded-xl border border-purple-500/20 bg-purple-500/10 p-4 text-left"
}, [
React . createElement ( 'input' , {
key : 'input' ,
type : 'checkbox' ,
checked : relayOnlyMode ,
onChange : ( event ) => setRelayOnlyMode ( event . target . checked ),
className : "mt-1"
}),
React . createElement ( 'span' , { key : 'copy' }, [
React . createElement ( 'span' , {
key : 'title' ,
className : "block text-sm font-medium text-primary"
}, 'Privacy mode: relay-only WebRTC' ),
React . createElement ( 'span' , {
key : 'desc' ,
className : "block text-sm text-secondary"
}, 'Uses TURN relay-only when configured. Without TURN, direct WebRTC may expose IP addresses and relay-only connections cannot start.' )
])
]),
2025-09-08 16:04:58 -04:00
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' ,
2025-10-15 19:58:28 -04:00
onClick : () => {
requestNotificationPermissionOnInteraction ();
setMode ( 'create' );
2025-10-19 15:23:02 -04:00
// Автоматически запускаем генерацию ключей
setTimeout (() => {
if ( webrtcManagerRef . current ) {
handleCreateOffer ();
}
}, 100 );
2025-10-15 19:58:28 -04:00
},
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' ,
2025-10-15 19:58:28 -04:00
onClick : () => {
requestNotificationPermissionOnInteraction ();
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'
])
])
])
]),
2025-10-02 16:52:31 -04:00
React . createElement ( SecurityFeatures , { key : 'security-features' }),
2025-10-02 19:39:40 -04:00
React . createElement ( Testimonials , { key : 'testimonials' }),
2025-09-08 16:04:58 -04:00
React . createElement ( UniqueFeatureSlider , { key : 'unique-features-slider' }),
React . createElement ( DownloadApps , { key : 'download-apps' }),
2025-12-30 04:16:41 -04:00
React . createElement ( BecomePartner , { key : 'become-partner' }),
React . createElement ( ComparisonTable , { key : 'comparison-table' }),
2025-09-08 16:04:58 -04:00
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' )
]),
2025-10-19 15:23:02 -04:00
2025-09-08 16:04:58 -04:00
// Step 1
2025-10-05 06:21:14 -04:00
! showAnswerStep && React . createElement ( 'div' , {
2025-09-08 16:04:58 -04:00
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" ),
2025-10-19 15:23:02 -04:00
! showOfferStep && isGeneratingKeys && React . createElement ( 'div' , {
key : 'loading-state' ,
className : "w-full py-3 px-4 rounded-lg font-medium transition-all duration-200 bg-blue-500/10 border border-blue-500/20 text-blue-400 flex items-center justify-center"
2025-09-08 16:04:58 -04:00
}, [
React . createElement ( 'i' , {
2025-10-19 15:23:02 -04:00
key : 'spinner' ,
className : 'fas fa-spinner fa-spin mr-2'
2025-09-08 16:04:58 -04:00
}),
2025-10-19 15:23:02 -04:00
'Generating secure keys...'
2025-09-08 16:04:58 -04:00
]),
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"
}, [
2025-10-05 06:21:14 -04:00
// Raw JSON hidden intentionally; users copy compressed string or use QR
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' ,
2025-10-05 06:21:14 -04:00
text : (() => {
try {
const min = typeof offerData === 'object' ? JSON . stringify ( offerData ) : ( offerData || '' );
2026-05-18 19:49:57 -04:00
if ( ! min ) return '' ;
2025-10-05 06:21:14 -04:00
if ( typeof window . encodeBinaryToPrefixed === 'function' ) {
return window . encodeBinaryToPrefixed ( min );
2025-09-23 20:01:02 -04:00
}
2025-10-05 06:21:14 -04:00
if ( typeof window . compressToPrefixedGzip === 'function' ) {
return window . compressToPrefixedGzip ( min );
}
return min ;
} catch { return typeof offerData === 'object' ? JSON . stringify ( offerData ) : ( offerData || '' ); }
})(),
className : "flex-1 px-3 py-2 bg-orange-500/10 hover:bg-orange-500/20 text-orange-400 border border-orange-500/20 rounded text-sm font-medium"
2025-10-15 01:46:36 -04:00
}, 'Copy invitation code' ),
2025-09-23 20:01:02 -04:00
]),
showQRCode && qrCodeUrl && React . createElement ( 'div' , {
key : 'qr-container' ,
2025-10-07 23:58:54 -04:00
className : "mt-4 p-4 border border-gray-600/30 rounded-lg text-center"
2025-09-23 20:01:02 -04:00
}, [
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
])
])
2025-10-15 04:13:14 -04:00
]),
2025-09-08 16:04:58 -04:00
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-10-13 11:13:11 -04:00
]),
2025-09-23 20:01:02 -04:00
]),
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' )
]),
2025-10-05 06:21:14 -04:00
( showAnswerStep ? null : React . createElement ( 'div' , {
2025-09-08 16:04:58 -04:00
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 );
if ( e . target . value . trim (). length > 0 ) {
2025-10-07 23:58:54 -04:00
if ( typeof markAnswerCreated === 'function' ) {
markAnswerCreated ();
2025-09-23 20:01:02 -04:00
}
2025-10-07 23:58:54 -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-10-07 23:58:54 -04:00
React . createElement ( 'button' , {
key : 'process-btn' ,
onClick : onCreateAnswer ,
disabled : ! offerInput . trim () || connectionStatus === 'connecting' ,
className : "flex-1 btn-secondary text-white py-2 px-4 rounded-lg font-medium transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
}, [
React . createElement ( 'i' , {
className : 'fas fa-cogs mr-2'
}),
'Process invitation'
])
2025-09-23 20:01:02 -04:00
]),
showQRScanner && React . createElement ( 'div' , {
key : 'qr-scanner' ,
className : "p-4 bg-gray-800/50 border border-gray-600/30 rounded-lg text-center"
}, [
React . createElement ( 'h4' , {
key : 'scanner-title' ,
className : "text-sm font-medium text-primary mb-3"
}, 'QR Code Scanner' ),
React . createElement ( 'p' , {
key : 'scanner-description' ,
className : "text-xs text-gray-400 mb-3"
}, 'Use your device camera to scan the QR code from the invitation' ),
React . createElement ( 'button' , {
key : 'open-scanner' ,
onClick : () => {
if ( typeof setShowQRScannerModal === 'function' ) {
setShowQRScannerModal ( true );
} else {
console . error ( 'setShowQRScannerModal is not a function:' , setShowQRScannerModal );
}
},
className : "w-full px-4 py-3 bg-purple-600 hover:bg-purple-500 text-white rounded-lg font-medium transition-all duration-200 mb-3"
}, [
React . createElement ( 'i' , {
key : 'camera-icon' ,
className : 'fas fa-camera mr-2'
}),
'Open Camera Scanner'
]),
React . createElement ( 'button' , {
key : 'test-qr' ,
onClick : async () => {
console . log ( 'Creating test QR code...' );
if ( window . generateQRCode ) {
const testData = '{"type":"test","message":"Hello QR Scanner!"}' ;
const qrUrl = await window . generateQRCode ( testData );
console . log ( 'Test QR code generated:' , qrUrl );
const newWindow = window . open ();
newWindow . document . write ( `<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
])
2025-10-05 06:21:14 -04:00
])),
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"
}, [
2025-10-05 06:21:14 -04:00
// Raw JSON hidden intentionally; users copy compressed string or use QR
2025-09-08 16:04:58 -04:00
React . createElement ( EnhancedCopyButton , {
key : 'copy' ,
2025-10-05 06:21:14 -04:00
text : (() => {
try {
const min = typeof answerData === 'object' ? JSON . stringify ( answerData ) : ( answerData || '' );
2026-05-18 19:49:57 -04:00
if ( ! min ) return '' ;
2025-10-05 06:21:14 -04:00
if ( typeof window . encodeBinaryToPrefixed === 'function' ) {
return window . encodeBinaryToPrefixed ( min );
}
if ( typeof window . compressToPrefixedGzip === 'function' ) {
return window . compressToPrefixedGzip ( min );
}
return min ;
} catch { return typeof answerData === 'object' ? JSON . stringify ( answerData ) : ( answerData || '' ); }
})(),
2025-09-08 16:04:58 -04:00
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' ,
2025-10-07 23:58:54 -04:00
className : "mt-4 p-4 border border-gray-600/30 rounded-lg text-center"
2025-09-27 19:07:17 -04:00
}, [
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-10-15 19:58:28 -04:00
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' );
2026-05-17 14:48:52 -04:00
const [ relayOnlyMode , setRelayOnlyMode ] = React . useState (() => {
try { return localStorage . getItem ( 'securebit_relay_only_mode' ) === 'true' ; } catch { return false ; }
});
2025-09-08 16:04:58 -04:00
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-10-19 15:23:02 -04:00
const [ isGeneratingKeys , setIsGeneratingKeys ] = React . useState ( false );
2025-10-13 11:13:11 -04:00
2025-09-08 16:04:58 -04:00
const [ isVerified , setIsVerified ] = React . useState ( false );
const [ securityLevel , setSecurityLevel ] = React . useState ( null );
2025-10-14 22:51:48 -04:00
const [ sessionTimeLeft , setSessionTimeLeft ] = React . useState ( 0 );
2025-09-08 16:04:58 -04:00
// 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
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 = () => {
2026-05-18 19:49:57 -04:00
const hasAnswerData = !! answerData ||
( answerInput && typeof answerInput === 'string' && answerInput . trim (). length > 0 );
2025-10-02 01:43:32 -04:00
2026-05-18 19:49:57 -04:00
const hasAnswerQR = qrCodeUrl && typeof qrCodeUrl === 'string' && qrCodeUrl . trim (). length > 0 ;
2025-09-27 19:07:17 -04:00
2025-09-23 20:01:02 -04:00
const shouldPreserve = ( connectionState . hasActiveAnswer &&
! connectionState . isUserInitiatedDisconnect ) ||
2026-05-18 19:49:57 -04:00
( hasAnswerData &&
2025-09-27 19:07:17 -04:00
! connectionState . isUserInitiatedDisconnect ) ||
2026-05-18 19:49:57 -04:00
( hasAnswerQR &&
2025-09-23 20:01:02 -04:00
! connectionState . isUserInitiatedDisconnect );
return shouldPreserve ;
};
// Mark answer as created
const markAnswerCreated = () => {
updateConnectionState ({
hasActiveAnswer : true ,
answerCreatedAt : Date . now ()
});
};
2025-09-08 16:04:58 -04:00
const webrtcManagerRef = React . useRef ( null );
2025-10-15 19:58:28 -04:00
const notificationIntegrationRef = React . useRef ( null );
2026-05-17 22:58:21 -04:00
// Development-only debug helpers. Production never exposes
// manager internals or cleanup controls on `window`.
React . useEffect (() => {
return installDebugWindowHooks ({
targetWindow : window ,
webrtcManagerRef ,
onClearData : handleClearData
});
}, []);
2025-09-08 16:04:58 -04:00
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-10-13 11:13:11 -04:00
// Session time ticker removed - sessions are unlimited
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 );
2026-05-17 14:48:52 -04:00
React . useEffect (() => {
try { localStorage . setItem ( 'securebit_relay_only_mode' , String ( relayOnlyMode )); } catch {}
if ( webrtcManagerRef . current ? . _config ? . webrtc ) {
2026-05-17 23:09:45 -04:00
webrtcManagerRef . current . _setRelayOnlyMode ( relayOnlyMode );
2026-05-17 14:48:52 -04:00
}
}, [ relayOnlyMode ]);
2025-09-23 20:01:02 -04:00
// 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
2025-10-14 22:51:48 -04:00
setOfferData ( '' );
setAnswerData ( '' );
2025-09-08 16:04:58 -04:00
setOfferInput ( '' );
setAnswerInput ( '' );
setShowOfferStep ( false );
setShowAnswerStep ( false );
setKeyFingerprint ( '' );
setVerificationCode ( '' );
setSecurityLevel ( null );
// Return to main page after a short delay
setTimeout (() => {
setConnectionStatus ( 'disconnected' );
setShowVerification ( false );
2025-09-23 20:01:02 -04:00
2025-10-14 22:51:48 -04:00
setOfferData ( '' );
setAnswerData ( '' );
2025-09-08 16:04:58 -04:00
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
2025-10-14 22:51:48 -04:00
setOfferData ( '' );
setAnswerData ( '' );
2025-09-08 16:04:58 -04:00
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 ,
2026-05-17 14:48:52 -04:00
handleVerificationStateChange ,
{
webrtc : {
relayOnly : relayOnlyMode ,
iceServers : Array . isArray ( window . SECUREBIT_ICE_SERVERS ) ? window . SECUREBIT_ICE_SERVERS : undefined
}
}
2025-09-08 16:04:58 -04:00
);
2025-10-15 19:58:28 -04:00
// Initialize notification integration if permission was already granted
2025-10-19 20:51:44 -04:00
if ( typeof Notification !== 'undefined' && Notification && Notification . permission === 'granted' && window . NotificationIntegration && ! notificationIntegrationRef . current ) {
2025-10-15 19:58:28 -04:00
try {
const integration = new window . NotificationIntegration ( webrtcManagerRef . current );
integration . init (). then (() => {
notificationIntegrationRef . current = integration ;
}). catch (( error ) => {
// Handle error silently
});
} catch ( error ) {
// Handle error silently
}
}
2026-05-19 09:49:22 -04:00
handleMessage ( ' SecureBit.chat Enhanced Security Edition v4.8.7 - 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 ;
};
2025-10-05 06:21:14 -04:00
// Conservative QR payload limits (characters). Adjust per error correction level.
const MAX_QR_LEN = 800 ; // for JSON/plain/gzip
const BIN_MAX_QR_LEN = 400 ; // stricter for SB1:bin to improve scan reliability
2025-09-23 20:01:02 -04:00
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 );
};
2025-10-05 06:21:14 -04:00
// Render frame at current index (no index mutation)
const renderCurrent = async () => {
const { chunks , idx } = qrAnimationRef . current || {};
if ( ! chunks || ! chunks . length ) return ;
const current = chunks [ idx % chunks . length ];
try {
const isDesktop = ( typeof window !== 'undefined' ) && (( window . innerWidth || 0 ) >= 1024 );
const QR_SIZE = isDesktop ? 720 : 512 ;
const url = await ( window . generateQRCode ? window . generateQRCode ( current , { errorCorrectionLevel : 'M' , margin : 2 , size : QR_SIZE }) : Promise . resolve ( '' ));
if ( url ) setQrCodeUrl ( url );
} catch ( e ) {
console . warn ( 'Animated QR render error (current):' , e );
}
setQrFrameIndex ((( qrAnimationRef . current ? . idx || 0 ) % ( qrAnimationRef . current ? . chunks ? . length || 1 )) + 1 );
};
// Render current frame, then advance index by 1
const renderAndAdvance = async () => {
await renderCurrent ();
const len = qrAnimationRef . current ? . chunks ? . length || 0 ;
if ( len > 0 ) {
const nextIdx = (( qrAnimationRef . current ? . idx || 0 ) + 1 ) % len ;
qrAnimationRef . current . idx = nextIdx ;
setQrFrameIndex ( nextIdx + 1 );
}
};
2025-09-27 19:07:17 -04:00
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 {
2025-10-05 06:21:14 -04:00
if ( qrAnimationRef . current . chunks . length > 1 ) {
const intervalMs = 3000 ;
qrAnimationRef . current . active = true ;
clearInterval ( qrAnimationRef . current . timer );
qrAnimationRef . current . timer = setInterval ( renderAndAdvance , intervalMs );
2025-09-27 19:07:17 -04:00
}
console . log ( 'QR Manual mode disabled - auto-scroll resumed' );
}
};
2025-10-05 06:21:14 -04:00
const nextQrFrame = async () => {
2025-09-27 19:07:17 -04:00
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 );
2025-10-05 06:21:14 -04:00
// Ensure auto-advance timer runs in manual mode too
try { clearInterval ( qrAnimationRef . current . timer ); } catch {}
qrAnimationRef . current . timer = null ;
await renderCurrent ();
// If not in manual mode, restart auto timer
if ( ! qrManualMode && qrAnimationRef . current . chunks . length > 1 ) {
const intervalMs = 3000 ;
qrAnimationRef . current . active = true ;
qrAnimationRef . current . timer = setInterval ( renderAndAdvance , intervalMs );
} else {
qrAnimationRef . current . active = false ;
}
2025-09-27 19:07:17 -04:00
} else {
console . log ( '🎮 No multiple frames to navigate' );
}
};
2025-10-05 06:21:14 -04:00
const prevQrFrame = async () => {
2025-09-27 19:07:17 -04:00
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 );
2025-10-05 06:21:14 -04:00
try { clearInterval ( qrAnimationRef . current . timer ); } catch {}
qrAnimationRef . current . timer = null ;
await renderCurrent ();
if ( ! qrManualMode && qrAnimationRef . current . chunks . length > 1 ) {
const intervalMs = 3000 ;
qrAnimationRef . current . active = true ;
qrAnimationRef . current . timer = setInterval ( renderAndAdvance , intervalMs );
} else {
qrAnimationRef . current . active = false ;
}
2025-09-27 19:07:17 -04:00
} 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 ;
const isDesktop = ( typeof window !== 'undefined' ) && (( window . innerWidth || 0 ) >= 1024 );
const QR_SIZE = isDesktop ? 720 : 512 ;
2025-10-07 23:58:54 -04:00
// Try binary format first (CBOR + deflate + base64url)
if ( typeof window . generateBinaryQRCodeFromObject === 'function' ) {
try {
const obj = typeof data === 'string' ? JSON . parse ( data ) : data ;
const qrDataUrl = await window . generateBinaryQRCodeFromObject ( obj , { errorCorrectionLevel : 'M' , size : QR_SIZE , margin : 2 });
if ( qrDataUrl ) {
try { if ( qrAnimationRef . current && qrAnimationRef . current . timer ) { clearInterval ( qrAnimationRef . current . timer ); } } catch {}
qrAnimationRef . current = { timer : null , chunks : [], idx : 0 , active : false };
setQrFrameIndex ( 0 );
setQrFramesTotal ( 0 );
setQrManualMode ( false );
setQrCodeUrl ( qrDataUrl );
setQrFramesTotal ( 1 );
setQrFrameIndex ( 1 );
return ;
}
} catch ( e ) {
console . warn ( 'Binary QR generation failed, falling back to compressed:' , e ? . message || e );
}
}
// Fallback to compressed JSON
if ( typeof window . generateCompressedQRCode === 'function' ) {
try {
const payload = typeof data === 'string' ? data : JSON . stringify ( data );
const qrDataUrl = await window . generateCompressedQRCode ( payload , { errorCorrectionLevel : 'M' , size : QR_SIZE , margin : 2 });
if ( qrDataUrl ) {
try { if ( qrAnimationRef . current && qrAnimationRef . current . timer ) { clearInterval ( qrAnimationRef . current . timer ); } } catch {}
qrAnimationRef . current = { timer : null , chunks : [], idx : 0 , active : false };
setQrFrameIndex ( 0 );
setQrFramesTotal ( 0 );
setQrManualMode ( false );
setQrCodeUrl ( qrDataUrl );
setQrFramesTotal ( 1 );
setQrFrameIndex ( 1 );
return ;
}
} catch ( e ) {
console . warn ( 'Compressed QR generation failed, falling back to plain:' , e ? . message || e );
}
}
// Final fallback to plain JSON
const payload = typeof data === 'string' ? data : JSON . stringify ( data );
2025-09-23 20:01:02 -04:00
if ( payload . length <= MAX_QR_LEN ) {
if ( ! window . generateQRCode ) throw new Error ( 'QR code generator unavailable' );
2025-10-05 06:21:14 -04:00
try { if ( qrAnimationRef . current && qrAnimationRef . current . timer ) { clearInterval ( qrAnimationRef . current . timer ); } } catch {}
qrAnimationRef . current = { timer : null , chunks : [], idx : 0 , active : false };
setQrFrameIndex ( 0 );
setQrFramesTotal ( 0 );
setQrManualMode ( false );
2025-09-23 20:01:02 -04:00
const qrDataUrl = await window . generateQRCode ( payload , { errorCorrectionLevel : 'M' , size : QR_SIZE , margin : 2 });
setQrCodeUrl ( qrDataUrl );
setQrFramesTotal ( 1 );
setQrFrameIndex ( 1 );
return ;
}
2025-10-07 23:58:54 -04:00
// Large payload: разбиваем на фреймы (plain JSON)
2025-10-05 06:21:14 -04:00
try { if ( qrAnimationRef . current && qrAnimationRef . current . timer ) { clearInterval ( qrAnimationRef . current . timer ); } } catch {}
qrAnimationRef . current = { timer : null , chunks : [], idx : 0 , active : false };
setQrFrameIndex ( 0 );
setQrFramesTotal ( 0 );
setQrManualMode ( false );
2025-09-23 20:01:02 -04:00
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 };
2025-10-07 23:58:54 -04:00
await renderNext ();
2025-10-02 01:43:32 -04:00
2025-09-27 19:07:17 -04:00
if ( ! qrManualMode ) {
const intervalMs = 4000 ; // 4 seconds per frame for better readability
2025-10-07 23:58:54 -04:00
qrAnimationRef . current . active = true ;
qrAnimationRef . current . timer = setInterval ( renderAndAdvance , intervalMs );
2025-09-27 19:07:17 -04:00
}
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 {
2025-10-07 23:58:54 -04:00
console . log ( 'QR Code scanned:' , scannedData . substring ( 0 , 100 ) + '...' );
console . log ( 'Current buffer state:' , qrChunksBufferRef . current );
// Check if this is a binary chunk (starts with SB1:bin: or is a raw binary chunk)
if ( scannedData . startsWith ( 'SB1:bin:' ) || ( qrChunksBufferRef . current && qrChunksBufferRef . current . id )) {
console . log ( 'Binary chunk detected:' , scannedData . substring ( 0 , 50 ) + '...' );
// This is a binary chunk - add to buffer
if ( ! qrChunksBufferRef . current . id ) {
console . log ( 'Initializing buffer for binary chunks' );
// Initialize buffer for binary chunks
qrChunksBufferRef . current = {
id : `bin_ ${ Date . now () } ` ,
total : 4 , // We expect 4 chunks
seen : new Set (),
items : [],
lastUpdateMs : Date . now ()
};
}
// Add chunk to buffer (use data hash as identifier)
const chunkHash = scannedData . substring ( 0 , 50 ); // Use first 50 chars as hash
// Check if this chunk was already scanned
if ( qrChunksBufferRef . current . seen . has ( chunkHash )) {
console . log ( `Chunk already scanned, ignoring...` );
return Promise . resolve ( false );
}
qrChunksBufferRef . current . seen . add ( chunkHash );
qrChunksBufferRef . current . items . push ( scannedData );
qrChunksBufferRef . current . lastUpdateMs = Date . now ();
// Emit progress and force re-render
try {
const uniqueCount = qrChunksBufferRef . current . seen . size ;
document . dispatchEvent ( new CustomEvent ( 'qr-scan-progress' , {
detail : {
id : qrChunksBufferRef . current . id ,
seq : uniqueCount ,
total : qrChunksBufferRef . current . total
}
}));
// Force re-render to update progress indicator
setQrFramesTotal ( qrChunksBufferRef . current . total );
setQrFrameIndex ( uniqueCount );
} catch {}
// Check if we have all chunks
const isComplete = qrChunksBufferRef . current . seen . size >= qrChunksBufferRef . current . total ;
console . log ( `Chunks collected: ${ qrChunksBufferRef . current . seen . size } / ${ qrChunksBufferRef . current . total } , complete: ${ isComplete } ` );
if ( ! isComplete ) {
// Keep scanner open for more chunks
console . log ( `Scanned chunk ${ qrChunksBufferRef . current . seen . size } / ${ qrChunksBufferRef . current . total } , waiting for more...` );
return Promise . resolve ( false );
}
// All chunks collected - reconstruct binary data
try {
const fullBinaryData = qrChunksBufferRef . current . items . join ( '' );
// Store the original binary data, not decoded JSON
if ( showOfferStep ) {
setAnswerInput ( fullBinaryData );
} else {
setOfferInput ( fullBinaryData );
}
setMessages ( prev => [... prev , {
message : 'All binary chunks captured. Payload reconstructed.' ,
type : 'success'
}]);
// Clear buffer and close scanner
qrChunksBufferRef . current = { id : null , total : 0 , seen : new Set (), items : [] };
setShowQRScannerModal ( false );
return Promise . resolve ( true );
} catch ( e ) {
console . warn ( 'Binary chunks reconstruction failed:' , e );
return Promise . resolve ( false );
}
}
// Check if this might be a binary chunk (long string without JSON structure)
if ( scannedData . length > 100 && ! scannedData . startsWith ( '{' ) && ! scannedData . startsWith ( '[' )) {
console . log ( 'Detected potential binary chunk (long non-JSON string):' , scannedData . substring ( 0 , 50 ) + '...' );
// Initialize buffer if not exists
if ( ! qrChunksBufferRef . current . id ) {
console . log ( 'Initializing buffer for potential binary chunks' );
qrChunksBufferRef . current = {
id : `bin_ ${ Date . now () } ` ,
total : 4 , // We expect 4 chunks
seen : new Set (),
items : [],
lastUpdateMs : Date . now ()
};
}
// Add chunk to buffer (use data hash as identifier)
const chunkHash = scannedData . substring ( 0 , 50 ); // Use first 50 chars as hash
// Check if this chunk was already scanned
if ( qrChunksBufferRef . current . seen . has ( chunkHash )) {
console . log ( `Chunk already scanned, ignoring...` );
return Promise . resolve ( false );
}
qrChunksBufferRef . current . seen . add ( chunkHash );
qrChunksBufferRef . current . items . push ( scannedData );
qrChunksBufferRef . current . lastUpdateMs = Date . now ();
// Force re-render to update progress indicator
try {
const uniqueCount = qrChunksBufferRef . current . seen . size ;
document . dispatchEvent ( new CustomEvent ( 'qr-scan-progress' , {
detail : {
id : qrChunksBufferRef . current . id ,
seq : uniqueCount ,
total : qrChunksBufferRef . current . total
}
}));
// Force re-render to update progress indicator
setQrFramesTotal ( qrChunksBufferRef . current . total );
setQrFrameIndex ( uniqueCount );
} catch {}
// Check if we have all chunks
const isComplete = qrChunksBufferRef . current . seen . size >= qrChunksBufferRef . current . total ;
console . log ( `Chunks collected: ${ qrChunksBufferRef . current . seen . size } / ${ qrChunksBufferRef . current . total } , complete: ${ isComplete } ` );
if ( ! isComplete ) {
// Keep scanner open for more chunks
console . log ( `Scanned chunk ${ qrChunksBufferRef . current . seen . size } / ${ qrChunksBufferRef . current . total } , waiting for more...` );
return Promise . resolve ( false );
}
// All chunks collected - reconstruct binary data
try {
const fullBinaryData = qrChunksBufferRef . current . items . join ( '' );
// Store the original binary data, not decoded JSON
if ( showOfferStep ) {
setAnswerInput ( fullBinaryData );
} else {
setOfferInput ( fullBinaryData );
}
setMessages ( prev => [... prev , {
message : 'All binary chunks captured. Payload reconstructed.' ,
type : 'success'
}]);
// Clear buffer and close scanner
qrChunksBufferRef . current = { id : null , total : 0 , seen : new Set (), items : [] };
setShowQRScannerModal ( false );
return Promise . resolve ( true );
} catch ( e ) {
console . warn ( 'Binary chunks reconstruction failed:' , e );
return Promise . resolve ( false );
}
}
// Single QR code - try to decode directly
2025-10-30 15:16:36 -04:00
// Removed verbose debug log
2025-10-05 06:21:14 -04:00
let parsedData ;
if ( typeof window . decodeAnyPayload === 'function' ) {
const any = window . decodeAnyPayload ( scannedData );
if ( typeof any === 'string' ) {
parsedData = JSON . parse ( any );
} else {
parsedData = any ; // object from binary
}
} else {
const maybeDecompressed = ( typeof window . decompressIfNeeded === 'function' ) ? window . decompressIfNeeded ( scannedData ) : scannedData ;
parsedData = JSON . parse ( maybeDecompressed );
}
2025-10-07 23:58:54 -04:00
console . log ( 'Decoded data:' , parsedData );
2025-09-23 20:01:02 -04:00
2025-10-05 06:21:14 -04:00
// QR with hdr/body: COSE or RAW/BIN animated frames
2025-09-23 20:01:02 -04:00
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 );
}
2025-10-05 06:21:14 -04:00
// Completed: decide RAW vs BIN vs COSE
2025-09-23 20:01:02 -04:00
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 );
}
2025-10-05 06:21:14 -04:00
} else if ( hdr . rt === 'bin' ) {
try {
const parts = qrChunksBufferRef . current . items
. map ( s => JSON . parse ( s ))
. sort (( a , b ) => ( a . hdr . seq || 0 ) - ( b . hdr . seq || 0 ))
. map ( p => p . body || '' );
const fullText = parts . join ( '' ); // SB1:bin:...
let payloadObj ;
if ( typeof window . decodeAnyPayload === 'function' ) {
const any = window . decodeAnyPayload ( fullText );
payloadObj = ( typeof any === 'string' ) ? JSON . parse ( any ) : any ;
} else {
payloadObj = JSON . parse ( fullText );
}
if ( showOfferStep ) {
setAnswerInput ( JSON . stringify ( payloadObj , null , 2 ));
} else {
setOfferInput ( JSON . stringify ( payloadObj , null , 2 ));
}
setMessages ( prev => [... prev , { message : 'All frames captured. BIN payload reconstructed.' , type : 'success' }]);
try { document . dispatchEvent ( new CustomEvent ( 'qr-scan-complete' , { detail : { id : hdr . id } })); } catch {}
qrChunksBufferRef . current = { id : null , total : 0 , seen : new Set (), items : [] };
setShowQRScannerModal ( false );
return Promise . resolve ( true );
} catch ( e ) {
console . warn ( 'BIN multi-frame reconstruction failed:' , e );
return Promise . resolve ( false );
}
2025-09-23 20:01:02 -04:00
} 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 {
2025-10-05 06:21:14 -04:00
// If payload was compressed, it's already decompressed above; keep legacy warning only when clearly incomplete
if ( ! parsedData . sdp && parsedData . type === 'enhanced_secure_offer' ) {
2025-09-23 20:01:02 -04:00
setMessages ( prev => [... prev , {
2025-10-05 06:21:14 -04:00
message : 'Compressed QR may omit SDP for brevity. Use copy/paste if connection fails.' ,
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
};
2025-10-13 11:13:11 -04:00
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-10-19 15:23:02 -04:00
setIsGeneratingKeys ( true );
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
2025-10-07 23:58:54 -04:00
// Generate QR code with binary format and chunking
2025-09-23 20:01:02 -04:00
const offerString = typeof offer === 'object' ? JSON . stringify ( offer ) : offer ;
2025-10-05 06:21:14 -04:00
try {
if ( typeof window . encodeBinaryToPrefixed === 'function' ) {
const bin = window . encodeBinaryToPrefixed ( offerString );
2025-10-07 23:58:54 -04:00
// Force chunking into 4 parts - split binary data directly
const TARGET_CHUNKS = 4 ;
2025-10-08 01:24:04 -04:00
let total = TARGET_CHUNKS ;
let FRAME_MAX = Math . max ( 200 , Math . ceil ( bin . length / TARGET_CHUNKS ));
2025-10-05 06:21:14 -04:00
if ( FRAME_MAX <= 0 ) FRAME_MAX = 200 ;
2025-10-08 01:24:04 -04:00
// Ensure we don't exceed TARGET_CHUNKS
if ( bin . length <= FRAME_MAX ) {
total = 1 ;
FRAME_MAX = bin . length ;
} else {
// Recalculate to ensure exactly TARGET_CHUNKS parts
FRAME_MAX = Math . ceil ( bin . length / TARGET_CHUNKS );
total = TARGET_CHUNKS ;
}
2025-10-07 23:58:54 -04:00
const id = `bin_ ${ Date . now () } _ ${ Math . random (). toString ( 36 ). slice ( 2 ) } ` ;
2025-10-05 06:21:14 -04:00
const chunks = [];
for ( let i = 0 ; i < total ; i ++ ) {
const seq = i + 1 ;
const part = bin . slice ( i * FRAME_MAX , ( i + 1 ) * FRAME_MAX );
2025-10-07 23:58:54 -04:00
// Store binary chunks directly without JSON wrapper
chunks . push ( part );
2025-10-05 06:21:14 -04:00
}
2025-10-07 23:58:54 -04:00
2025-10-05 06:21:14 -04:00
// Seed first frame and start auto-advance immediately
const isDesktop = ( typeof window !== 'undefined' ) && (( window . innerWidth || 0 ) >= 1024 );
const QR_SIZE = isDesktop ? 720 : 512 ;
if ( window . generateQRCode && chunks . length > 0 ) {
const firstUrl = await window . generateQRCode ( chunks [ 0 ], { errorCorrectionLevel : 'M' , size : QR_SIZE , margin : 2 });
if ( firstUrl ) setQrCodeUrl ( firstUrl );
}
2025-10-07 23:58:54 -04:00
2025-10-05 06:21:14 -04:00
// Store precomputed chunks to ref, ready for animation
try { if ( qrAnimationRef . current && qrAnimationRef . current . timer ) { clearInterval ( qrAnimationRef . current . timer ); } } catch {}
qrAnimationRef . current = { timer : null , chunks , idx : 0 , active : true };
setQrFramesTotal ( chunks . length );
setQrFrameIndex ( 1 );
setQrManualMode ( false );
2025-10-07 23:58:54 -04:00
2025-10-05 06:21:14 -04:00
// Start auto-advance loop for Offer immediately
const intervalMs = 3000 ;
qrAnimationRef . current . timer = setInterval ( renderAndAdvance , intervalMs );
2025-10-07 23:58:54 -04:00
2025-10-05 06:21:14 -04:00
// Show QR immediately for Offer flow
try { setShowQRCode ( true ); } catch {}
2025-10-07 23:58:54 -04:00
} else {
// Fallback to single QR
await generateQRCode ( offer );
try { setShowQRCode ( true ); } catch {}
2025-10-05 06:21:14 -04:00
}
} catch ( e ) {
2025-10-07 23:58:54 -04:00
console . warn ( 'Offer QR generation failed:' , e );
2025-10-05 06:21:14 -04:00
}
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 ()
}]);
2025-10-19 15:23:02 -04:00
} finally {
setIsGeneratingKeys ( false );
2025-09-08 16:04:58 -04:00
}
};
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 {
2025-10-05 06:21:14 -04:00
// Prefer binary decode first, then gzip JSON
if ( typeof window . decodeAnyPayload === 'function' ) {
const any = window . decodeAnyPayload ( offerInput . trim ());
offer = ( typeof any === 'string' ) ? JSON . parse ( any ) : any ;
} else {
const rawText = ( typeof window . decompressIfNeeded === 'function' ) ? window . decompressIfNeeded ( offerInput . trim ()) : offerInput . trim ();
offer = JSON . parse ( rawText );
}
2025-09-08 16:04:58 -04:00
} 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-10-07 23:58:54 -04:00
// Generate QR code with binary format and chunking
2025-09-27 19:07:17 -04:00
const answerString = typeof answer === 'object' ? JSON . stringify ( answer ) : answer ;
2025-10-05 06:21:14 -04:00
try {
if ( typeof window . encodeBinaryToPrefixed === 'function' ) {
const bin = window . encodeBinaryToPrefixed ( answerString );
2025-10-07 23:58:54 -04:00
// Force chunking into 4 parts - split binary data directly
const TARGET_CHUNKS = 4 ;
2025-10-08 01:24:04 -04:00
let total = TARGET_CHUNKS ;
let FRAME_MAX = Math . max ( 200 , Math . ceil ( bin . length / TARGET_CHUNKS ));
2025-10-05 06:21:14 -04:00
if ( FRAME_MAX <= 0 ) FRAME_MAX = 200 ;
2025-10-08 01:24:04 -04:00
// Ensure we don't exceed TARGET_CHUNKS
if ( bin . length <= FRAME_MAX ) {
total = 1 ;
FRAME_MAX = bin . length ;
} else {
// Recalculate to ensure exactly TARGET_CHUNKS parts
FRAME_MAX = Math . ceil ( bin . length / TARGET_CHUNKS );
total = TARGET_CHUNKS ;
}
2025-10-07 23:58:54 -04:00
const id = `ans_ ${ Date . now () } _ ${ Math . random (). toString ( 36 ). slice ( 2 ) } ` ;
2025-10-05 06:21:14 -04:00
const chunks = [];
for ( let i = 0 ; i < total ; i ++ ) {
const seq = i + 1 ;
const part = bin . slice ( i * FRAME_MAX , ( i + 1 ) * FRAME_MAX );
2025-10-07 23:58:54 -04:00
// Store binary chunks directly without JSON wrapper
chunks . push ( part );
2025-10-05 06:21:14 -04:00
}
2025-10-07 23:58:54 -04:00
2025-10-05 06:21:14 -04:00
const isDesktop = ( typeof window !== 'undefined' ) && (( window . innerWidth || 0 ) >= 1024 );
const QR_SIZE = isDesktop ? 720 : 512 ;
if ( window . generateQRCode && chunks . length > 0 ) {
const firstUrl = await window . generateQRCode ( chunks [ 0 ], { errorCorrectionLevel : 'M' , size : QR_SIZE , margin : 2 });
if ( firstUrl ) setQrCodeUrl ( firstUrl );
}
2025-10-07 23:58:54 -04:00
2025-10-05 06:21:14 -04:00
try { if ( qrAnimationRef . current && qrAnimationRef . current . timer ) { clearInterval ( qrAnimationRef . current . timer ); } } catch {}
qrAnimationRef . current = { timer : null , chunks , idx : 0 , active : true };
setQrFramesTotal ( chunks . length );
setQrFrameIndex ( 1 );
setQrManualMode ( false );
2025-10-07 23:58:54 -04:00
2025-10-05 06:21:14 -04:00
const intervalMs = 3000 ;
qrAnimationRef . current . timer = setInterval ( renderAndAdvance , intervalMs );
2025-10-07 23:58:54 -04:00
// Show QR immediately for Answer flow
2025-10-05 06:21:14 -04:00
try { setShowQRCode ( true ); } catch {}
} else {
2025-10-07 23:58:54 -04:00
// Fallback to single QR
await generateQRCode ( answer );
2025-10-05 06:21:14 -04:00
try { setShowQRCode ( true ); } catch {}
}
} catch ( e ) {
console . warn ( 'Answer QR generation failed:' , e );
}
2025-09-27 19:07:17 -04:00
2026-05-18 19:49:57 -04:00
// Mark generated answers as active immediately.
// `answerInput` is empty on the joiner path
// because the response was created locally,
// not pasted by the user.
2025-10-15 01:46:36 -04:00
if ( typeof markAnswerCreated === 'function' ) {
markAnswerCreated ();
2025-10-02 01:43:32 -04:00
}
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 {
2025-10-05 06:21:14 -04:00
// Prefer binary decode first, then gzip JSON
if ( typeof window . decodeAnyPayload === 'function' ) {
const anyAnswer = window . decodeAnyPayload ( answerInput . trim ());
answer = ( typeof anyAnswer === 'string' ) ? JSON . parse ( anyAnswer ) : anyAnswer ;
} else {
const rawText = ( typeof window . decompressIfNeeded === 'function' ) ? window . decompressIfNeeded ( answerInput . trim ()) : answerInput . trim ();
answer = JSON . parse ( rawText );
}
2025-09-08 16:04:58 -04:00
} 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
}
};
2026-05-17 14:48:52 -04:00
const handleVerifyConnection = async ( userCode , isValid = true ) => {
2025-09-08 16:04:58 -04:00
if ( isValid ) {
2026-05-17 14:48:52 -04:00
webrtcManagerRef . current . confirmVerification ( userCode );
2025-09-08 16:04:58 -04:00
// Mark local verification as confirmed
setLocalVerificationConfirmed ( true );
2025-10-15 19:58:28 -04:00
// Initialize notification integration if permission was granted
try {
if ( window . NotificationIntegration && webrtcManagerRef . current && ! notificationIntegrationRef . current ) {
const integration = new window . NotificationIntegration ( webrtcManagerRef . current );
await integration . init ();
// Store reference for cleanup
notificationIntegrationRef . current = integration ;
// Check if permission was already granted
const status = integration . getStatus ();
if ( status . permission === 'granted' ) {
setMessages ( prev => [... prev , {
message : '✓ Notifications enabled - you will receive alerts when the tab is inactive' ,
type : 'system' ,
id : Date . now (),
timestamp : Date . now ()
}]);
} else {
setMessages ( prev => [... prev , {
message : 'ℹ Notifications disabled - you can enable them using the button on the main page' ,
type : 'system' ,
id : Date . now (),
timestamp : Date . now ()
}]);
}
} else if ( notificationIntegrationRef . current ) {
} else {
// Handle error silently
}
} catch ( error ) {
console . warn ( 'Failed to initialize notifications:' , error );
// Don't show error to user, notifications are optional
}
2025-09-08 16:04:58 -04:00
} 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' );
2025-10-14 22:51:48 -04:00
setOfferData ( '' );
setAnswerData ( '' );
2025-09-08 16:04:58 -04:00
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-19 15:23:02 -04:00
setIsGeneratingKeys ( 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-07 23:58:54 -04:00
// Clear QR scanner buffer
qrChunksBufferRef . current = { id : null , total : 0 , seen : new Set (), items : [] };
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 = () => {
2025-10-14 22:51:48 -04:00
try {
2025-09-08 16:04:58 -04:00
setSessionTimeLeft ( 0 );
2025-10-15 01:46:36 -04:00
// Mark as user-initiated disconnect
updateConnectionState ({
status : 'disconnected' ,
isUserInitiatedDisconnect : true
});
2025-10-14 22:51:48 -04:00
// Cleanup WebRTC connection
2025-10-15 01:46:36 -04:00
if ( webrtcManagerRef . current ) {
webrtcManagerRef . current . disconnect ();
}
2025-10-15 19:58:28 -04:00
// Cleanup notification integration
if ( notificationIntegrationRef . current ) {
notificationIntegrationRef . current . cleanup ();
notificationIntegrationRef . current = null ;
}
2025-10-15 01:46:36 -04:00
2025-10-14 22:51:48 -04:00
// Clear all connection-related states
2025-10-15 01:46:36 -04:00
setKeyFingerprint ( '' );
setVerificationCode ( '' );
setSecurityLevel ( null );
setIsVerified ( false );
setShowVerification ( false );
setConnectionStatus ( 'disconnected' );
// Clear verification states
setLocalVerificationConfirmed ( false );
setRemoteVerificationConfirmed ( false );
setBothVerificationsConfirmed ( false );
2025-10-19 15:23:02 -04:00
// Reset UI to initial state
setOfferData ( '' );
setAnswerData ( '' );
2025-10-15 01:46:36 -04:00
setOfferInput ( '' );
setAnswerInput ( '' );
setShowOfferStep ( false );
setShowAnswerStep ( false );
2025-10-19 15:23:02 -04:00
setIsGeneratingKeys ( false );
2025-10-14 22:51:48 -04:00
setShowQRCode ( false );
setQrCodeUrl ( '' );
setShowQRScanner ( false );
setShowQRScannerModal ( false );
// Clear messages
2025-10-15 01:46:36 -04:00
setMessages ([]);
2025-10-14 22:51:48 -04:00
// Clear console
2025-10-15 01:46:36 -04:00
if ( typeof console . clear === 'function' ) {
console . clear ();
}
2025-10-14 22:51:48 -04:00
// Dispatch disconnect events
2025-10-15 01:46:36 -04:00
document . dispatchEvent ( new CustomEvent ( 'peer-disconnect' ));
document . dispatchEvent ( new CustomEvent ( 'disconnected' ));
2025-10-14 22:51:48 -04:00
// Dispatch session cleanup event
2025-10-15 01:46:36 -04:00
document . dispatchEvent ( new CustomEvent ( 'session-cleanup' , {
detail : {
timestamp : Date . now (),
reason : 'manual_disconnect'
}
}));
2025-10-14 22:51:48 -04:00
// Clear data and reset session timer
handleClearData ();
2025-10-15 01:46:36 -04:00
setTimeout (() => {
2025-09-08 16:04:58 -04:00
setSessionTimeLeft ( 0 );
2025-10-15 01:46:36 -04:00
}, 500 );
2025-10-14 22:51:48 -04:00
console . log ( 'Disconnect completed successfully' );
} catch ( error ) {
console . error ( 'Error during disconnect:' , error );
}
2025-09-08 16:04:58 -04:00
};
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-10-07 23:58:54 -04:00
// QR Scanner initialization
React . useEffect (() => {
if ( showQRScannerModal && window . Html5Qrcode ) {
const html5Qrcode = new window . Html5Qrcode ( "qr-reader" );
const config = {
fps : 10
// Убираем qrbox чтобы использовать всю область
};
let isScanning = true ;
html5Qrcode . start (
{ facingMode : "environment" }, // Use back camera
config ,
( decodedText , decodedResult ) => {
if ( ! isScanning ) {
console . log ( 'Scanner stopped, ignoring scan' );
return ;
}
console . log ( 'QR Code scanned:' , decodedText );
console . log ( 'Current buffer state:' , qrChunksBufferRef . current );
handleQRScan ( decodedText ). then (( success ) => {
console . log ( 'QR scan result:' , success );
if ( success ) {
// Successfully processed - stop scanner and close modal
console . log ( 'Closing scanner and modal' );
isScanning = false ;
// Stop scanner first, then clear
try {
console . log ( 'Stopping scanner...' );
html5Qrcode . stop (). then (() => {
console . log ( 'Scanner stopped, clearing...' );
html5Qrcode . clear ();
setShowQRScannerModal ( false );
}). catch (( err ) => {
console . log ( 'Error stopping scanner:' , err );
// Try to clear anyway
try {
html5Qrcode . clear ();
} catch ( clearErr ) {
console . log ( 'Error clearing scanner:' , clearErr );
}
setShowQRScannerModal ( false );
});
} catch ( err ) {
console . log ( 'Error in scanner cleanup:' , err );
setShowQRScannerModal ( false );
}
} else {
console . log ( 'Continuing to scan for more chunks...' );
}
}). catch (( error ) => {
console . error ( 'QR scan processing error:' , error );
// Continue scanning on error
});
},
( error ) => {
// Ignore scanning errors - continue scanning
if ( isScanning ) {
console . log ( 'QR scan error (ignored):' , error );
}
}
). catch (( err ) => {
console . error ( 'QR Scanner start error:' , err );
// Close modal on start error
setShowQRScannerModal ( false );
});
return () => {
isScanning = false ;
try {
// Try to stop scanner, but don't worry if it's already stopped
html5Qrcode . stop (). then (() => {
html5Qrcode . clear ();
}). catch (( err ) => {
// Scanner might already be stopped, just clear it
console . log ( 'Scanner already stopped or error stopping:' , err );
try {
html5Qrcode . clear ();
} catch ( clearErr ) {
console . log ( 'Error clearing scanner in cleanup:' , clearErr );
}
});
} catch ( err ) {
console . log ( 'Error in cleanup:' , err );
// Just try to clear, don't worry about stopping
try {
html5Qrcode . clear ();
} catch ( clearErr ) {
console . log ( 'Error clearing scanner in cleanup:' , clearErr );
}
}
};
}
}, [ showQRScannerModal ]);
2025-09-08 16:04:58 -04:00
return React . createElement ( 'div' , {
className : "minimal-bg min-h-screen"
}, [
2025-10-13 11:13:11 -04:00
window . EnhancedMinimalHeader && React . createElement ( window . EnhancedMinimalHeader , {
2025-09-08 16:04:58 -04:00
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
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-15 19:58:28 -04:00
markAnswerCreated : markAnswerCreated ,
2025-10-19 15:23:02 -04:00
notificationIntegrationRef : notificationIntegrationRef ,
isGeneratingKeys : isGeneratingKeys ,
2026-05-18 19:49:57 -04:00
setIsGeneratingKeys : setIsGeneratingKeys ,
2026-05-17 14:48:52 -04:00
handleCreateOffer : handleCreateOffer ,
relayOnlyMode : relayOnlyMode ,
2026-05-18 19:49:57 -04:00
setRelayOnlyMode : setRelayOnlyMode ,
webrtcManagerRef : webrtcManagerRef
2025-09-08 16:04:58 -04:00
})
),
2025-10-07 23:58:54 -04:00
// QR Scanner Modal
showQRScannerModal && React . createElement ( 'div' , {
key : 'qr-scanner-modal' ,
className : "fixed inset-0 bg-black/80 flex items-center justify-center z-50"
}, [
React . createElement ( 'div' , {
key : 'scanner-container' ,
className : "bg-gray-900 rounded-lg p-4 max-w-2xl w-full mx-4"
}, [
React . createElement ( 'div' , {
key : 'scanner-header' ,
className : "flex items-center justify-between mb-4"
}, [
React . createElement ( 'h3' , {
key : 'scanner-title' ,
className : "text-lg font-medium text-white"
}, 'QR Code Scanner' ),
React . createElement ( 'button' , {
key : 'close-btn' ,
onClick : () => {
setShowQRScannerModal ( false );
// Clear QR scanner buffer
qrChunksBufferRef . current = { id : null , total : 0 , seen : new Set (), items : [] };
},
className : "text-gray-400 hover:text-white"
}, [
React . createElement ( 'i' , {
key : 'close-icon' ,
className : 'fas fa-times'
})
])
]),
React . createElement ( 'div' , {
key : 'scanner-content' ,
className : "text-center"
}, [
React . createElement ( 'p' , {
key : 'scanner-description' ,
className : "text-gray-400 mb-4"
}, 'Point your camera at the QR code to scan' ),
qrChunksBufferRef . current && qrChunksBufferRef . current . id && React . createElement ( 'div' , {
key : 'progress-indicator' ,
className : "mb-4 p-3 bg-blue-500/10 border border-blue-500/20 rounded-lg"
}, [
React . createElement ( 'p' , {
key : 'progress-text' ,
className : "text-blue-400 text-sm"
}, `Scanned: ${ qrChunksBufferRef . current . seen . size } / ${ qrChunksBufferRef . current . total } parts` ),
React . createElement ( 'div' , {
key : 'progress-bar' ,
className : "w-full bg-gray-700 rounded-full h-2 mt-2"
}, [
React . createElement ( 'div' , {
key : 'progress-fill' ,
className : "bg-blue-500 h-2 rounded-full transition-all duration-300" ,
style : {
width : ` ${ ( qrChunksBufferRef . current . seen . size / qrChunksBufferRef . current . total ) * 100 } %`
}
})
])
]),
React . createElement ( 'div' , {
key : 'scanner-placeholder' ,
id : "qr-reader" ,
className : "w-full h-96 bg-gray-800 rounded-lg flex items-center justify-center" ,
style : { minHeight : '400px' }
}, [
React . createElement ( 'p' , {
key : 'scanner-placeholder-text' ,
className : "text-gray-500"
}, 'Camera will start here...' )
])
])
])
2025-10-13 11:13:11 -04:00
]),
2025-10-07 23:58:54 -04:00
2025-09-08 16:04:58 -04:00
]);
};
2025-12-29 10:51:07 -04:00
// UpdateChecker компонент для автоматической проверки обновлений
const UpdateCheckerWrapper = ({ children }) => {
// Проверяем доступность UpdateChecker
if ( typeof window !== 'undefined' && window . UpdateChecker ) {
return React . createElement ( window . UpdateChecker , {
debug : false
}, children );
}
// Fallback если UpdateChecker не загружен
return children ;
};
2025-09-08 16:04:58 -04:00
function initializeApp () {
if ( window . EnhancedSecureCryptoUtils && window . EnhancedSecureWebRTCManager ) {
2025-12-29 10:51:07 -04:00
// Оборачиваем приложение в UpdateChecker для автоматической проверки обновлений
const AppWithUpdateChecker = React . createElement ( UpdateCheckerWrapper , null ,
React . createElement ( EnhancedSecureP2PChat )
);
ReactDOM . render ( AppWithUpdateChecker , document . getElementById ( 'root' ));
2025-09-08 16:04:58 -04:00
} 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-10-15 19:58:28 -04:00
};
2025-12-29 10:51:07 -04:00
// Render Enhanced Application with UpdateChecker
if ( window . EnhancedSecureCryptoUtils && window . EnhancedSecureWebRTCManager ) {
const UpdateCheckerWrapper = ({ children }) => {
if ( typeof window !== 'undefined' && window . UpdateChecker ) {
return React . createElement ( window . UpdateChecker , {
debug : false
}, children );
}
return children ;
};
const AppWithUpdateChecker = React . createElement ( UpdateCheckerWrapper , null ,
React . createElement ( EnhancedSecureP2PChat )
);
ReactDOM . render ( AppWithUpdateChecker , document . getElementById ( 'root' ));
} else {
ReactDOM . render ( React . createElement ( EnhancedSecureP2PChat ), document . getElementById ( 'root' ));
2026-05-17 14:48:52 -04:00
}