session bug fix
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1079
-1
File diff suppressed because it is too large
Load Diff
Vendored
+4
-4
File diff suppressed because one or more lines are too long
Vendored
+172
-8
@@ -710,6 +710,17 @@ var EnhancedConnectionSetup = ({
|
|||||||
}),
|
}),
|
||||||
"Scan QR Code"
|
"Scan QR Code"
|
||||||
])
|
])
|
||||||
|
// React.createElement('button', {
|
||||||
|
// key: 'bluetooth-btn',
|
||||||
|
// onClick: () => { try { document.dispatchEvent(new CustomEvent('open-bluetooth-transfer', { detail: { role: 'responder' } })); } catch {} },
|
||||||
|
// className: "px-4 py-2 bg-blue-500/10 hover:bg-blue-500/20 text-blue-400 border border-blue-500/20 rounded text-sm font-medium transition-all duration-200"
|
||||||
|
// }, [
|
||||||
|
// React.createElement('i', {
|
||||||
|
// key: 'icon',
|
||||||
|
// className: 'fas fa-bluetooth mr-2'
|
||||||
|
// }),
|
||||||
|
// 'Bluetooth'
|
||||||
|
// ])
|
||||||
]),
|
]),
|
||||||
React.createElement("textarea", {
|
React.createElement("textarea", {
|
||||||
key: "input",
|
key: "input",
|
||||||
@@ -819,6 +830,17 @@ var EnhancedConnectionSetup = ({
|
|||||||
}),
|
}),
|
||||||
"Scan QR Code"
|
"Scan QR Code"
|
||||||
]),
|
]),
|
||||||
|
// React.createElement('button', {
|
||||||
|
// key: 'bluetooth-btn',
|
||||||
|
// onClick: () => { try { document.dispatchEvent(new CustomEvent('open-bluetooth-transfer', { detail: { role: 'initiator' } })); } catch {} },
|
||||||
|
// className: "px-4 py-2 bg-blue-500/10 hover:bg-blue-500/20 text-blue-400 border border-blue-500/20 rounded text-sm font-medium transition-all duration-200"
|
||||||
|
// }, [
|
||||||
|
// React.createElement('i', {
|
||||||
|
// key: 'icon',
|
||||||
|
// className: 'fas fa-bluetooth mr-2'
|
||||||
|
// }),
|
||||||
|
// 'Bluetooth'
|
||||||
|
// ]),
|
||||||
React.createElement("button", {
|
React.createElement("button", {
|
||||||
key: "process-btn",
|
key: "process-btn",
|
||||||
onClick: onCreateAnswer,
|
onClick: onCreateAnswer,
|
||||||
@@ -1332,6 +1354,21 @@ var EnhancedSecureP2PChat = () => {
|
|||||||
const [qrCodeUrl, setQrCodeUrl] = React.useState("");
|
const [qrCodeUrl, setQrCodeUrl] = React.useState("");
|
||||||
const [showQRScanner, setShowQRScanner] = React.useState(false);
|
const [showQRScanner, setShowQRScanner] = React.useState(false);
|
||||||
const [showQRScannerModal, setShowQRScannerModal] = React.useState(false);
|
const [showQRScannerModal, setShowQRScannerModal] = React.useState(false);
|
||||||
|
const [showBluetoothTransfer, setShowBluetoothTransfer] = React.useState(false);
|
||||||
|
const [bluetoothAutoRole, setBluetoothAutoRole] = React.useState(null);
|
||||||
|
React.useEffect(() => {
|
||||||
|
const openBt = (e2) => {
|
||||||
|
try {
|
||||||
|
const role = e2?.detail?.role || null;
|
||||||
|
setBluetoothAutoRole(role);
|
||||||
|
setShowBluetoothTransfer(true);
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener("open-bluetooth-transfer", openBt);
|
||||||
|
return () => document.removeEventListener("open-bluetooth-transfer", openBt);
|
||||||
|
}, []);
|
||||||
|
const [bluetoothManager, setBluetoothManager] = React.useState(null);
|
||||||
const [isVerified, setIsVerified] = React.useState(false);
|
const [isVerified, setIsVerified] = React.useState(false);
|
||||||
const [securityLevel, setSecurityLevel] = React.useState(null);
|
const [securityLevel, setSecurityLevel] = React.useState(null);
|
||||||
const [localVerificationConfirmed, setLocalVerificationConfirmed] = React.useState(false);
|
const [localVerificationConfirmed, setLocalVerificationConfirmed] = React.useState(false);
|
||||||
@@ -1462,12 +1499,6 @@ var EnhancedSecureP2PChat = () => {
|
|||||||
}, 2e3);
|
}, 2e3);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
React.useEffect(() => {
|
|
||||||
const timer = setInterval(() => {
|
|
||||||
setSessionTimeLeft(0);
|
|
||||||
}, 1e3);
|
|
||||||
return () => clearInterval(timer);
|
|
||||||
}, []);
|
|
||||||
const chatMessagesRef = React.useRef(null);
|
const chatMessagesRef = React.useRef(null);
|
||||||
const scrollToBottom = createScrollToBottomFunction(chatMessagesRef);
|
const scrollToBottom = createScrollToBottomFunction(chatMessagesRef);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -2413,6 +2444,128 @@ var EnhancedSecureP2PChat = () => {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const handleBluetoothKeyReceived = async (keyData, deviceId) => {
|
||||||
|
try {
|
||||||
|
console.log("Bluetooth key received from device:", deviceId);
|
||||||
|
const keyString = JSON.stringify(keyData, null, 2);
|
||||||
|
if (showOfferStep) {
|
||||||
|
setAnswerInput(keyString);
|
||||||
|
} else {
|
||||||
|
setOfferInput(keyString);
|
||||||
|
}
|
||||||
|
setMessages((prev) => [...prev, {
|
||||||
|
message: "\u{1F535} Bluetooth key received successfully!",
|
||||||
|
type: "success"
|
||||||
|
}]);
|
||||||
|
setShowBluetoothTransfer(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to process Bluetooth key:", error);
|
||||||
|
setMessages((prev) => [...prev, {
|
||||||
|
message: "Failed to process Bluetooth key: " + error.message,
|
||||||
|
type: "error"
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleBluetoothStatusChange = (statusType, data) => {
|
||||||
|
console.log("Bluetooth status change:", statusType, data);
|
||||||
|
switch (statusType) {
|
||||||
|
case "bluetooth_ready":
|
||||||
|
setMessages((prev) => [...prev, {
|
||||||
|
message: "\u{1F535} Bluetooth ready for key exchange",
|
||||||
|
type: "info"
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case "connected":
|
||||||
|
setMessages((prev) => [...prev, {
|
||||||
|
message: `\u{1F535} Connected to device: ${data.deviceName}`,
|
||||||
|
type: "success"
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case "key_sent":
|
||||||
|
setMessages((prev) => [...prev, {
|
||||||
|
message: "\u{1F535} Public key sent via Bluetooth",
|
||||||
|
type: "success"
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case "key_received":
|
||||||
|
setMessages((prev) => [...prev, {
|
||||||
|
message: "\u{1F535} Public key received via Bluetooth",
|
||||||
|
type: "success"
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case "auto_connection_starting":
|
||||||
|
setMessages((prev) => [...prev, {
|
||||||
|
message: "\u{1F535} Starting automatic connection...",
|
||||||
|
type: "info"
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case "creating_offer":
|
||||||
|
setMessages((prev) => [...prev, {
|
||||||
|
message: "\u{1F535} Creating secure offer...",
|
||||||
|
type: "info"
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case "offer_sent":
|
||||||
|
setMessages((prev) => [...prev, {
|
||||||
|
message: "\u{1F535} Offer sent, waiting for answer...",
|
||||||
|
type: "info"
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case "waiting_for_answer":
|
||||||
|
setMessages((prev) => [...prev, {
|
||||||
|
message: "\u{1F535} Waiting for answer...",
|
||||||
|
type: "info"
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case "processing_answer":
|
||||||
|
setMessages((prev) => [...prev, {
|
||||||
|
message: "\u{1F535} Processing answer...",
|
||||||
|
type: "info"
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case "waiting_for_verification":
|
||||||
|
setMessages((prev) => [...prev, {
|
||||||
|
message: "\u{1F535} Waiting for verification...",
|
||||||
|
type: "info"
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case "auto_connection_complete":
|
||||||
|
setMessages((prev) => [...prev, {
|
||||||
|
message: "\u{1F535} Automatic connection completed!",
|
||||||
|
type: "success"
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case "auto_connection_failed":
|
||||||
|
setMessages((prev) => [...prev, {
|
||||||
|
message: "\u{1F535} Automatic connection failed: " + (data.error || "Unknown error"),
|
||||||
|
type: "error"
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleBluetoothError = (error) => {
|
||||||
|
console.error("Bluetooth error:", error);
|
||||||
|
setMessages((prev) => [...prev, {
|
||||||
|
message: "Bluetooth error: " + error.message,
|
||||||
|
type: "error"
|
||||||
|
}]);
|
||||||
|
};
|
||||||
|
const handleBluetoothAutoConnection = async (connectionData) => {
|
||||||
|
try {
|
||||||
|
console.log("Bluetooth auto connection completed:", connectionData);
|
||||||
|
setMessages((prev) => [...prev, {
|
||||||
|
message: "\u{1F535} Bluetooth auto connection completed successfully!",
|
||||||
|
type: "success"
|
||||||
|
}]);
|
||||||
|
setShowBluetoothTransfer(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to process auto connection:", error);
|
||||||
|
setMessages((prev) => [...prev, {
|
||||||
|
message: "Failed to process auto connection: " + error.message,
|
||||||
|
type: "error"
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
};
|
||||||
const handleCreateOffer = async () => {
|
const handleCreateOffer = async () => {
|
||||||
try {
|
try {
|
||||||
setOfferData("");
|
setOfferData("");
|
||||||
@@ -3011,7 +3164,7 @@ var EnhancedSecureP2PChat = () => {
|
|||||||
return React.createElement("div", {
|
return React.createElement("div", {
|
||||||
className: "minimal-bg min-h-screen"
|
className: "minimal-bg min-h-screen"
|
||||||
}, [
|
}, [
|
||||||
React.createElement(EnhancedMinimalHeader, {
|
window.EnhancedMinimalHeader && React.createElement(window.EnhancedMinimalHeader, {
|
||||||
key: "header",
|
key: "header",
|
||||||
status: connectionStatus,
|
status: connectionStatus,
|
||||||
fingerprint: keyFingerprint,
|
fingerprint: keyFingerprint,
|
||||||
@@ -3153,7 +3306,18 @@ var EnhancedSecureP2PChat = () => {
|
|||||||
])
|
])
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
])
|
]),
|
||||||
|
// Bluetooth Key Transfer Modal
|
||||||
|
showBluetoothTransfer && window.BluetoothKeyTransfer && React.createElement(window.BluetoothKeyTransfer, {
|
||||||
|
key: "bluetooth-transfer-modal",
|
||||||
|
webrtcManager: webrtcManagerRef.current,
|
||||||
|
onKeyReceived: handleBluetoothKeyReceived,
|
||||||
|
onStatusChange: handleBluetoothStatusChange,
|
||||||
|
onError: handleBluetoothError,
|
||||||
|
onAutoConnection: handleBluetoothAutoConnection,
|
||||||
|
isVisible: showBluetoothTransfer,
|
||||||
|
onClose: () => setShowBluetoothTransfer(false)
|
||||||
|
})
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
function initializeApp() {
|
function initializeApp() {
|
||||||
|
|||||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
@@ -151,8 +151,8 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="dist/app.js?v=1757383304"></script>
|
|
||||||
<script type="module" src="dist/app-boot.js?v=1757383304"></script>
|
<script type="module" src="dist/app-boot.js?v=1757383304"></script>
|
||||||
|
<script type="module" src="dist/app.js?v=1757383304"></script>
|
||||||
|
|
||||||
<script src="src/scripts/pwa-register.js"></script>
|
<script src="src/scripts/pwa-register.js"></script>
|
||||||
<script src="./src/pwa/install-prompt.js" type="module"></script>
|
<script src="./src/pwa/install-prompt.js" type="module"></script>
|
||||||
|
|||||||
+200
-11
@@ -745,7 +745,18 @@
|
|||||||
className: 'fas fa-qrcode mr-2'
|
className: 'fas fa-qrcode mr-2'
|
||||||
}),
|
}),
|
||||||
'Scan QR Code'
|
'Scan QR Code'
|
||||||
])
|
]),
|
||||||
|
// React.createElement('button', {
|
||||||
|
// key: 'bluetooth-btn',
|
||||||
|
// onClick: () => { try { document.dispatchEvent(new CustomEvent('open-bluetooth-transfer', { detail: { role: 'responder' } })); } catch {} },
|
||||||
|
// className: "px-4 py-2 bg-blue-500/10 hover:bg-blue-500/20 text-blue-400 border border-blue-500/20 rounded text-sm font-medium transition-all duration-200"
|
||||||
|
// }, [
|
||||||
|
// React.createElement('i', {
|
||||||
|
// key: 'icon',
|
||||||
|
// className: 'fas fa-bluetooth mr-2'
|
||||||
|
// }),
|
||||||
|
// 'Bluetooth'
|
||||||
|
// ])
|
||||||
]),
|
]),
|
||||||
React.createElement('textarea', {
|
React.createElement('textarea', {
|
||||||
key: 'input',
|
key: 'input',
|
||||||
@@ -859,6 +870,17 @@
|
|||||||
}),
|
}),
|
||||||
'Scan QR Code'
|
'Scan QR Code'
|
||||||
]),
|
]),
|
||||||
|
// React.createElement('button', {
|
||||||
|
// key: 'bluetooth-btn',
|
||||||
|
// onClick: () => { try { document.dispatchEvent(new CustomEvent('open-bluetooth-transfer', { detail: { role: 'initiator' } })); } catch {} },
|
||||||
|
// className: "px-4 py-2 bg-blue-500/10 hover:bg-blue-500/20 text-blue-400 border border-blue-500/20 rounded text-sm font-medium transition-all duration-200"
|
||||||
|
// }, [
|
||||||
|
// React.createElement('i', {
|
||||||
|
// key: 'icon',
|
||||||
|
// className: 'fas fa-bluetooth mr-2'
|
||||||
|
// }),
|
||||||
|
// 'Bluetooth'
|
||||||
|
// ]),
|
||||||
React.createElement('button', {
|
React.createElement('button', {
|
||||||
key: 'process-btn',
|
key: 'process-btn',
|
||||||
onClick: onCreateAnswer,
|
onClick: onCreateAnswer,
|
||||||
@@ -1410,6 +1432,23 @@
|
|||||||
const [qrCodeUrl, setQrCodeUrl] = React.useState('');
|
const [qrCodeUrl, setQrCodeUrl] = React.useState('');
|
||||||
const [showQRScanner, setShowQRScanner] = React.useState(false);
|
const [showQRScanner, setShowQRScanner] = React.useState(false);
|
||||||
const [showQRScannerModal, setShowQRScannerModal] = React.useState(false);
|
const [showQRScannerModal, setShowQRScannerModal] = React.useState(false);
|
||||||
|
|
||||||
|
// Bluetooth key transfer states
|
||||||
|
const [showBluetoothTransfer, setShowBluetoothTransfer] = React.useState(false);
|
||||||
|
const [bluetoothAutoRole, setBluetoothAutoRole] = React.useState(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const openBt = (e) => {
|
||||||
|
try {
|
||||||
|
const role = e?.detail?.role || null;
|
||||||
|
setBluetoothAutoRole(role);
|
||||||
|
setShowBluetoothTransfer(true);
|
||||||
|
} catch {}
|
||||||
|
};
|
||||||
|
document.addEventListener('open-bluetooth-transfer', openBt);
|
||||||
|
return () => document.removeEventListener('open-bluetooth-transfer', openBt);
|
||||||
|
}, []);
|
||||||
|
const [bluetoothManager, setBluetoothManager] = React.useState(null);
|
||||||
const [isVerified, setIsVerified] = React.useState(false);
|
const [isVerified, setIsVerified] = React.useState(false);
|
||||||
const [securityLevel, setSecurityLevel] = React.useState(null);
|
const [securityLevel, setSecurityLevel] = React.useState(null);
|
||||||
|
|
||||||
@@ -1596,14 +1635,7 @@
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Session time ticker - unlimited sessions
|
// Session time ticker removed - sessions are unlimited
|
||||||
React.useEffect(() => {
|
|
||||||
const timer = setInterval(() => {
|
|
||||||
// Sessions are unlimited - no time restrictions
|
|
||||||
setSessionTimeLeft(0);
|
|
||||||
}, 1000);
|
|
||||||
return () => clearInterval(timer);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Sessions are unlimited - no expiration handler needed
|
// Sessions are unlimited - no expiration handler needed
|
||||||
|
|
||||||
@@ -2721,6 +2753,151 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Bluetooth key transfer handlers
|
||||||
|
const handleBluetoothKeyReceived = async (keyData, deviceId) => {
|
||||||
|
try {
|
||||||
|
console.log('Bluetooth key received from device:', deviceId);
|
||||||
|
|
||||||
|
// Convert key data to the format expected by the app
|
||||||
|
const keyString = JSON.stringify(keyData, null, 2);
|
||||||
|
|
||||||
|
// Determine which input to populate based on current mode
|
||||||
|
if (showOfferStep) {
|
||||||
|
// In "Waiting for peer's response" mode - populate answerInput
|
||||||
|
setAnswerInput(keyString);
|
||||||
|
} else {
|
||||||
|
// In "Paste secure invitation" mode - populate offerInput
|
||||||
|
setOfferInput(keyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
message: '🔵 Bluetooth key received successfully!',
|
||||||
|
type: 'success'
|
||||||
|
}]);
|
||||||
|
|
||||||
|
// Close Bluetooth transfer modal
|
||||||
|
setShowBluetoothTransfer(false);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to process Bluetooth key:', error);
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
message: 'Failed to process Bluetooth key: ' + error.message,
|
||||||
|
type: 'error'
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBluetoothStatusChange = (statusType, data) => {
|
||||||
|
console.log('Bluetooth status change:', statusType, data);
|
||||||
|
|
||||||
|
switch (statusType) {
|
||||||
|
case 'bluetooth_ready':
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
message: '🔵 Bluetooth ready for key exchange',
|
||||||
|
type: 'info'
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case 'connected':
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
message: `🔵 Connected to device: ${data.deviceName}`,
|
||||||
|
type: 'success'
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case 'key_sent':
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
message: '🔵 Public key sent via Bluetooth',
|
||||||
|
type: 'success'
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case 'key_received':
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
message: '🔵 Public key received via Bluetooth',
|
||||||
|
type: 'success'
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case 'auto_connection_starting':
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
message: '🔵 Starting automatic connection...',
|
||||||
|
type: 'info'
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case 'creating_offer':
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
message: '🔵 Creating secure offer...',
|
||||||
|
type: 'info'
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case 'offer_sent':
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
message: '🔵 Offer sent, waiting for answer...',
|
||||||
|
type: 'info'
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case 'waiting_for_answer':
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
message: '🔵 Waiting for answer...',
|
||||||
|
type: 'info'
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case 'processing_answer':
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
message: '🔵 Processing answer...',
|
||||||
|
type: 'info'
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case 'waiting_for_verification':
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
message: '🔵 Waiting for verification...',
|
||||||
|
type: 'info'
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case 'auto_connection_complete':
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
message: '🔵 Automatic connection completed!',
|
||||||
|
type: 'success'
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case 'auto_connection_failed':
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
message: '🔵 Automatic connection failed: ' + (data.error || 'Unknown error'),
|
||||||
|
type: 'error'
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBluetoothError = (error) => {
|
||||||
|
console.error('Bluetooth error:', error);
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
message: 'Bluetooth error: ' + error.message,
|
||||||
|
type: 'error'
|
||||||
|
}]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBluetoothAutoConnection = async (connectionData) => {
|
||||||
|
try {
|
||||||
|
console.log('Bluetooth auto connection completed:', connectionData);
|
||||||
|
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
message: '🔵 Bluetooth auto connection completed successfully!',
|
||||||
|
type: 'success'
|
||||||
|
}]);
|
||||||
|
|
||||||
|
// Close Bluetooth transfer modal
|
||||||
|
setShowBluetoothTransfer(false);
|
||||||
|
|
||||||
|
// The connection is now established automatically
|
||||||
|
// No need for manual offer/answer processing
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to process auto connection:', error);
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
message: 'Failed to process auto connection: ' + error.message,
|
||||||
|
type: 'error'
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleCreateOffer = async () => {
|
const handleCreateOffer = async () => {
|
||||||
try {
|
try {
|
||||||
// All security features are enabled by default
|
// All security features are enabled by default
|
||||||
@@ -3459,7 +3636,7 @@
|
|||||||
return React.createElement('div', {
|
return React.createElement('div', {
|
||||||
className: "minimal-bg min-h-screen"
|
className: "minimal-bg min-h-screen"
|
||||||
}, [
|
}, [
|
||||||
React.createElement(EnhancedMinimalHeader, {
|
window.EnhancedMinimalHeader && React.createElement(window.EnhancedMinimalHeader, {
|
||||||
key: 'header',
|
key: 'header',
|
||||||
status: connectionStatus,
|
status: connectionStatus,
|
||||||
fingerprint: keyFingerprint,
|
fingerprint: keyFingerprint,
|
||||||
@@ -3604,7 +3781,19 @@
|
|||||||
])
|
])
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
])
|
]),
|
||||||
|
|
||||||
|
// Bluetooth Key Transfer Modal
|
||||||
|
showBluetoothTransfer && window.BluetoothKeyTransfer && React.createElement(window.BluetoothKeyTransfer, {
|
||||||
|
key: 'bluetooth-transfer-modal',
|
||||||
|
webrtcManager: webrtcManagerRef.current,
|
||||||
|
onKeyReceived: handleBluetoothKeyReceived,
|
||||||
|
onStatusChange: handleBluetoothStatusChange,
|
||||||
|
onError: handleBluetoothError,
|
||||||
|
onAutoConnection: handleBluetoothAutoConnection,
|
||||||
|
isVisible: showBluetoothTransfer,
|
||||||
|
onClose: () => setShowBluetoothTransfer(false)
|
||||||
|
})
|
||||||
|
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,514 @@
|
|||||||
|
/**
|
||||||
|
* Bluetooth Key Transfer UI Component
|
||||||
|
*
|
||||||
|
* Provides user interface for Bluetooth key exchange:
|
||||||
|
* - Device discovery and connection
|
||||||
|
* - Key transmission status
|
||||||
|
* - Error handling and fallbacks
|
||||||
|
* - Integration with existing QR/manual methods
|
||||||
|
*/
|
||||||
|
|
||||||
|
const BluetoothKeyTransfer = ({
|
||||||
|
webrtcManager,
|
||||||
|
onKeyReceived,
|
||||||
|
onStatusChange,
|
||||||
|
onAutoConnection,
|
||||||
|
isVisible = false,
|
||||||
|
onClose
|
||||||
|
}) => {
|
||||||
|
const [bluetoothManager, setBluetoothManager] = React.useState(null);
|
||||||
|
const [isSupported, setIsSupported] = React.useState(false);
|
||||||
|
const [isAvailable, setIsAvailable] = React.useState(false);
|
||||||
|
const [isScanning, setIsScanning] = React.useState(false);
|
||||||
|
const [isAdvertising, setIsAdvertising] = React.useState(false);
|
||||||
|
const [connectedDevices, setConnectedDevices] = React.useState([]);
|
||||||
|
const [status, setStatus] = React.useState('idle');
|
||||||
|
const [error, setError] = React.useState(null);
|
||||||
|
const [logs, setLogs] = React.useState([]);
|
||||||
|
|
||||||
|
// Initialize Bluetooth manager
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isVisible && !bluetoothManager) {
|
||||||
|
initializeBluetooth();
|
||||||
|
}
|
||||||
|
}, [isVisible]);
|
||||||
|
|
||||||
|
// Cleanup on unmount
|
||||||
|
React.useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (bluetoothManager) {
|
||||||
|
bluetoothManager.cleanup();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [bluetoothManager]);
|
||||||
|
|
||||||
|
const initializeBluetooth = async () => {
|
||||||
|
try {
|
||||||
|
const manager = new window.BluetoothKeyTransfer(
|
||||||
|
webrtcManager,
|
||||||
|
handleStatusChange,
|
||||||
|
handleKeyReceived,
|
||||||
|
handleError,
|
||||||
|
handleAutoConnection
|
||||||
|
);
|
||||||
|
|
||||||
|
setBluetoothManager(manager);
|
||||||
|
|
||||||
|
// Check support after initialization
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsSupported(manager.isSupported);
|
||||||
|
setIsAvailable(manager.isAvailable);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize Bluetooth manager:', error);
|
||||||
|
setError('Failed to initialize Bluetooth: ' + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStatusChange = (statusType, data) => {
|
||||||
|
setStatus(statusType);
|
||||||
|
addLog(`Status: ${statusType}`, data);
|
||||||
|
|
||||||
|
// Update UI state based on status
|
||||||
|
switch (statusType) {
|
||||||
|
case 'bluetooth_ready':
|
||||||
|
setIsSupported(data.supported);
|
||||||
|
setIsAvailable(data.available);
|
||||||
|
break;
|
||||||
|
case 'scanning_active':
|
||||||
|
setIsScanning(true);
|
||||||
|
break;
|
||||||
|
case 'scanning_stopped':
|
||||||
|
setIsScanning(false);
|
||||||
|
break;
|
||||||
|
case 'advertising_active':
|
||||||
|
setIsAdvertising(true);
|
||||||
|
break;
|
||||||
|
case 'advertising_stopped':
|
||||||
|
setIsAdvertising(false);
|
||||||
|
break;
|
||||||
|
case 'connected':
|
||||||
|
setConnectedDevices(prev => [...prev, {
|
||||||
|
id: data.deviceId,
|
||||||
|
name: data.deviceName,
|
||||||
|
connected: true
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
onStatusChange?.(statusType, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyReceived = (keyData, deviceId) => {
|
||||||
|
addLog('Key received from device', { deviceId });
|
||||||
|
onKeyReceived?.(keyData, deviceId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleError = (error) => {
|
||||||
|
console.error('Bluetooth error:', error);
|
||||||
|
setError(error.message);
|
||||||
|
addLog('Error', error.message);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAutoConnection = (connectionData) => {
|
||||||
|
console.log('Auto connection completed:', connectionData);
|
||||||
|
addLog('Auto Connection Completed', connectionData);
|
||||||
|
onAutoConnection?.(connectionData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addLog = (message, data = null) => {
|
||||||
|
const timestamp = new Date().toLocaleTimeString();
|
||||||
|
const logEntry = {
|
||||||
|
timestamp,
|
||||||
|
message,
|
||||||
|
data: data ? JSON.stringify(data, null, 2) : null
|
||||||
|
};
|
||||||
|
setLogs(prev => [...prev.slice(-9), logEntry]); // Keep last 10 logs
|
||||||
|
};
|
||||||
|
|
||||||
|
const startScanning = async () => {
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
await bluetoothManager.startScanning();
|
||||||
|
} catch (error) {
|
||||||
|
setError('Failed to start scanning: ' + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopScanning = async () => {
|
||||||
|
try {
|
||||||
|
await bluetoothManager.stopScanning();
|
||||||
|
} catch (error) {
|
||||||
|
setError('Failed to stop scanning: ' + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startAdvertising = async () => {
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
if (!webrtcManager || !webrtcManager.ecdhKeyPair) {
|
||||||
|
throw new Error('No public key available for advertising');
|
||||||
|
}
|
||||||
|
|
||||||
|
await bluetoothManager.startAdvertising(
|
||||||
|
webrtcManager.ecdhKeyPair.publicKey,
|
||||||
|
'SecureBit Device'
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
setError('Failed to start advertising: ' + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopAdvertising = async () => {
|
||||||
|
try {
|
||||||
|
await bluetoothManager.stopAdvertising();
|
||||||
|
} catch (error) {
|
||||||
|
setError('Failed to stop advertising: ' + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendPublicKey = async (deviceId) => {
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
if (!webrtcManager || !webrtcManager.ecdhKeyPair) {
|
||||||
|
throw new Error('No public key available for sending');
|
||||||
|
}
|
||||||
|
|
||||||
|
await bluetoothManager.sendPublicKey(
|
||||||
|
webrtcManager.ecdhKeyPair.publicKey,
|
||||||
|
deviceId
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
setError('Failed to send public key: ' + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearLogs = () => {
|
||||||
|
setLogs([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const startAutoConnection = async (deviceId) => {
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
await bluetoothManager.startAutoConnection(deviceId);
|
||||||
|
} catch (error) {
|
||||||
|
setError('Failed to start auto connection: ' + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startAutoConnectionAsResponder = async (deviceId) => {
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
await bluetoothManager.startAutoConnectionAsResponder(deviceId);
|
||||||
|
} catch (error) {
|
||||||
|
setError('Failed to start auto connection as responder: ' + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isVisible) return null;
|
||||||
|
|
||||||
|
return React.createElement('div', {
|
||||||
|
className: 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4'
|
||||||
|
}, [
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'modal',
|
||||||
|
className: 'bg-gray-900 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-hidden'
|
||||||
|
}, [
|
||||||
|
// Header
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'header',
|
||||||
|
className: 'flex items-center justify-between p-6 border-b border-gray-700'
|
||||||
|
}, [
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'title',
|
||||||
|
className: 'flex items-center space-x-3'
|
||||||
|
}, [
|
||||||
|
React.createElement('i', {
|
||||||
|
key: 'icon',
|
||||||
|
className: 'fas fa-bluetooth text-blue-400 text-xl'
|
||||||
|
}),
|
||||||
|
React.createElement('h2', {
|
||||||
|
key: 'text',
|
||||||
|
className: 'text-xl font-semibold text-white'
|
||||||
|
}, 'Bluetooth Key Transfer')
|
||||||
|
]),
|
||||||
|
React.createElement('button', {
|
||||||
|
key: 'close',
|
||||||
|
onClick: onClose,
|
||||||
|
className: 'text-gray-400 hover:text-white transition-colors'
|
||||||
|
}, [
|
||||||
|
React.createElement('i', {
|
||||||
|
className: 'fas fa-times text-xl'
|
||||||
|
})
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Content
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'content',
|
||||||
|
className: 'p-6 space-y-6 overflow-y-auto max-h-[calc(90vh-200px)]'
|
||||||
|
}, [
|
||||||
|
// Status Section
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'status',
|
||||||
|
className: 'space-y-4'
|
||||||
|
}, [
|
||||||
|
React.createElement('h3', {
|
||||||
|
key: 'title',
|
||||||
|
className: 'text-lg font-medium text-white'
|
||||||
|
}, 'Bluetooth Status'),
|
||||||
|
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'indicators',
|
||||||
|
className: 'grid grid-cols-2 gap-4'
|
||||||
|
}, [
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'support',
|
||||||
|
className: 'flex items-center space-x-2'
|
||||||
|
}, [
|
||||||
|
React.createElement('div', {
|
||||||
|
className: `w-3 h-3 rounded-full ${isSupported ? 'bg-green-500' : 'bg-red-500'}`
|
||||||
|
}),
|
||||||
|
React.createElement('span', {
|
||||||
|
className: 'text-sm text-gray-300'
|
||||||
|
}, 'Bluetooth Supported')
|
||||||
|
]),
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'availability',
|
||||||
|
className: 'flex items-center space-x-2'
|
||||||
|
}, [
|
||||||
|
React.createElement('div', {
|
||||||
|
className: `w-3 h-3 rounded-full ${isAvailable ? 'bg-green-500' : 'bg-red-500'}`
|
||||||
|
}),
|
||||||
|
React.createElement('span', {
|
||||||
|
className: 'text-sm text-gray-300'
|
||||||
|
}, 'Bluetooth Available')
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Controls Section
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'controls',
|
||||||
|
className: 'space-y-4'
|
||||||
|
}, [
|
||||||
|
React.createElement('h3', {
|
||||||
|
key: 'title',
|
||||||
|
className: 'text-lg font-medium text-white'
|
||||||
|
}, 'Key Exchange'),
|
||||||
|
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'buttons',
|
||||||
|
className: 'grid grid-cols-1 sm:grid-cols-2 gap-4'
|
||||||
|
}, [
|
||||||
|
// Scanning Controls
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'scanning',
|
||||||
|
className: 'space-y-2'
|
||||||
|
}, [
|
||||||
|
React.createElement('h4', {
|
||||||
|
key: 'title',
|
||||||
|
className: 'text-sm font-medium text-gray-300'
|
||||||
|
}, 'Discover Devices'),
|
||||||
|
React.createElement('button', {
|
||||||
|
key: 'scan',
|
||||||
|
onClick: isScanning ? stopScanning : startScanning,
|
||||||
|
disabled: !isSupported || !isAvailable,
|
||||||
|
className: `w-full px-4 py-2 rounded-lg font-medium transition-colors ${
|
||||||
|
isScanning
|
||||||
|
? 'bg-red-600 hover:bg-red-700 text-white'
|
||||||
|
: 'bg-blue-600 hover:bg-blue-700 text-white disabled:bg-gray-600 disabled:cursor-not-allowed'
|
||||||
|
}`
|
||||||
|
}, [
|
||||||
|
React.createElement('i', {
|
||||||
|
key: 'icon',
|
||||||
|
className: `fas ${isScanning ? 'fa-stop' : 'fa-search'} mr-2`
|
||||||
|
}),
|
||||||
|
isScanning ? 'Stop Scanning' : 'Start Scanning'
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Advertising Controls
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'advertising',
|
||||||
|
className: 'space-y-2'
|
||||||
|
}, [
|
||||||
|
React.createElement('h4', {
|
||||||
|
key: 'title',
|
||||||
|
className: 'text-sm font-medium text-gray-300'
|
||||||
|
}, 'Share Your Key'),
|
||||||
|
React.createElement('button', {
|
||||||
|
key: 'advertise',
|
||||||
|
onClick: isAdvertising ? stopAdvertising : startAdvertising,
|
||||||
|
disabled: !isSupported || !isAvailable,
|
||||||
|
className: `w-full px-4 py-2 rounded-lg font-medium transition-colors ${
|
||||||
|
isAdvertising
|
||||||
|
? 'bg-red-600 hover:bg-red-700 text-white'
|
||||||
|
: 'bg-green-600 hover:bg-green-700 text-white disabled:bg-gray-600 disabled:cursor-not-allowed'
|
||||||
|
}`
|
||||||
|
}, [
|
||||||
|
React.createElement('i', {
|
||||||
|
key: 'icon',
|
||||||
|
className: `fas ${isAdvertising ? 'fa-stop' : 'fa-broadcast-tower'} mr-2`
|
||||||
|
}),
|
||||||
|
isAdvertising ? 'Stop Sharing' : 'Start Sharing'
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Connected Devices
|
||||||
|
connectedDevices.length > 0 && React.createElement('div', {
|
||||||
|
key: 'devices',
|
||||||
|
className: 'space-y-4'
|
||||||
|
}, [
|
||||||
|
React.createElement('h3', {
|
||||||
|
key: 'title',
|
||||||
|
className: 'text-lg font-medium text-white'
|
||||||
|
}, 'Connected Devices'),
|
||||||
|
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'list',
|
||||||
|
className: 'space-y-2'
|
||||||
|
}, connectedDevices.map(device =>
|
||||||
|
React.createElement('div', {
|
||||||
|
key: device.id,
|
||||||
|
className: 'flex items-center justify-between p-3 bg-gray-800 rounded-lg'
|
||||||
|
}, [
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'info',
|
||||||
|
className: 'flex items-center space-x-3'
|
||||||
|
}, [
|
||||||
|
React.createElement('i', {
|
||||||
|
key: 'icon',
|
||||||
|
className: 'fas fa-mobile-alt text-blue-400'
|
||||||
|
}),
|
||||||
|
React.createElement('span', {
|
||||||
|
key: 'name',
|
||||||
|
className: 'text-white'
|
||||||
|
}, device.name)
|
||||||
|
]),
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'buttons',
|
||||||
|
className: 'flex space-x-2'
|
||||||
|
}, [
|
||||||
|
React.createElement('button', {
|
||||||
|
key: 'auto-connect',
|
||||||
|
onClick: () => startAutoConnection(device.id),
|
||||||
|
className: 'px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-sm rounded transition-colors'
|
||||||
|
}, 'Auto Connect'),
|
||||||
|
React.createElement('button', {
|
||||||
|
key: 'auto-respond',
|
||||||
|
onClick: () => startAutoConnectionAsResponder(device.id),
|
||||||
|
className: 'px-3 py-1 bg-purple-600 hover:bg-purple-700 text-white text-sm rounded transition-colors'
|
||||||
|
}, 'Auto Respond'),
|
||||||
|
React.createElement('button', {
|
||||||
|
key: 'send',
|
||||||
|
onClick: () => sendPublicKey(device.id),
|
||||||
|
className: 'px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded transition-colors'
|
||||||
|
}, 'Send Key')
|
||||||
|
])
|
||||||
|
])
|
||||||
|
))
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Error Display
|
||||||
|
error && React.createElement('div', {
|
||||||
|
key: 'error',
|
||||||
|
className: 'p-4 bg-red-900 border border-red-700 rounded-lg'
|
||||||
|
}, [
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'header',
|
||||||
|
className: 'flex items-center space-x-2 mb-2'
|
||||||
|
}, [
|
||||||
|
React.createElement('i', {
|
||||||
|
key: 'icon',
|
||||||
|
className: 'fas fa-exclamation-triangle text-red-400'
|
||||||
|
}),
|
||||||
|
React.createElement('h4', {
|
||||||
|
key: 'title',
|
||||||
|
className: 'text-red-400 font-medium'
|
||||||
|
}, 'Error')
|
||||||
|
]),
|
||||||
|
React.createElement('p', {
|
||||||
|
key: 'message',
|
||||||
|
className: 'text-red-300 text-sm'
|
||||||
|
}, error)
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Logs Section
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'logs',
|
||||||
|
className: 'space-y-4'
|
||||||
|
}, [
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'header',
|
||||||
|
className: 'flex items-center justify-between'
|
||||||
|
}, [
|
||||||
|
React.createElement('h3', {
|
||||||
|
key: 'title',
|
||||||
|
className: 'text-lg font-medium text-white'
|
||||||
|
}, 'Activity Log'),
|
||||||
|
React.createElement('button', {
|
||||||
|
key: 'clear',
|
||||||
|
onClick: clearLogs,
|
||||||
|
className: 'text-sm text-gray-400 hover:text-white transition-colors'
|
||||||
|
}, 'Clear')
|
||||||
|
]),
|
||||||
|
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'log-list',
|
||||||
|
className: 'bg-gray-800 rounded-lg p-4 max-h-40 overflow-y-auto'
|
||||||
|
}, logs.length === 0 ?
|
||||||
|
React.createElement('p', {
|
||||||
|
key: 'empty',
|
||||||
|
className: 'text-gray-400 text-sm text-center'
|
||||||
|
}, 'No activity yet') :
|
||||||
|
logs.map((log, index) =>
|
||||||
|
React.createElement('div', {
|
||||||
|
key: index,
|
||||||
|
className: 'text-xs text-gray-300 mb-1'
|
||||||
|
}, [
|
||||||
|
React.createElement('span', {
|
||||||
|
key: 'time',
|
||||||
|
className: 'text-gray-500'
|
||||||
|
}, `[${log.timestamp}] `),
|
||||||
|
React.createElement('span', {
|
||||||
|
key: 'message',
|
||||||
|
className: 'text-gray-300'
|
||||||
|
}, log.message),
|
||||||
|
log.data && React.createElement('pre', {
|
||||||
|
key: 'data',
|
||||||
|
className: 'text-gray-400 mt-1 ml-4'
|
||||||
|
}, log.data)
|
||||||
|
])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Footer
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'footer',
|
||||||
|
className: 'flex items-center justify-between p-6 border-t border-gray-700'
|
||||||
|
}, [
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'info',
|
||||||
|
className: 'text-sm text-gray-400'
|
||||||
|
}, 'Bluetooth key exchange provides secure device-to-device communication'),
|
||||||
|
|
||||||
|
React.createElement('button', {
|
||||||
|
key: 'close-footer',
|
||||||
|
onClick: onClose,
|
||||||
|
className: 'px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition-colors'
|
||||||
|
}, 'Close')
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export component
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.BluetoothKeyTransfer = BluetoothKeyTransfer;
|
||||||
|
}
|
||||||
@@ -9,6 +9,10 @@ const EnhancedMinimalHeader = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [realSecurityLevel, setRealSecurityLevel] = React.useState(null);
|
const [realSecurityLevel, setRealSecurityLevel] = React.useState(null);
|
||||||
const [lastSecurityUpdate, setLastSecurityUpdate] = React.useState(0);
|
const [lastSecurityUpdate, setLastSecurityUpdate] = React.useState(0);
|
||||||
|
// Added local session state to remove references errors after session timer removal
|
||||||
|
const [hasActiveSession, setHasActiveSession] = React.useState(false);
|
||||||
|
const [currentTimeLeft, setCurrentTimeLeft] = React.useState(0);
|
||||||
|
const [sessionType, setSessionType] = React.useState('unknown');
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// FIXED SECURITY UPDATE LOGIC
|
// FIXED SECURITY UPDATE LOGIC
|
||||||
@@ -154,7 +158,7 @@ const EnhancedMinimalHeader = ({
|
|||||||
setHasActiveSession(true);
|
setHasActiveSession(true);
|
||||||
setCurrentTimeLeft(0);
|
setCurrentTimeLeft(0);
|
||||||
setSessionType('premium'); // All features enabled
|
setSessionType('premium'); // All features enabled
|
||||||
}, [sessionTimeLeft]);
|
}, []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const handleForceUpdate = (event) => {
|
const handleForceUpdate = (event) => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { EnhancedSecureCryptoUtils } from '../crypto/EnhancedSecureCryptoUtils.js';
|
import { EnhancedSecureCryptoUtils } from '../crypto/EnhancedSecureCryptoUtils.js';
|
||||||
import { EnhancedSecureWebRTCManager } from '../network/EnhancedSecureWebRTCManager.js';
|
import { EnhancedSecureWebRTCManager } from '../network/EnhancedSecureWebRTCManager.js';
|
||||||
import { EnhancedSecureFileTransfer } from '../transfer/EnhancedSecureFileTransfer.js';
|
import { EnhancedSecureFileTransfer } from '../transfer/EnhancedSecureFileTransfer.js';
|
||||||
|
import BluetoothKeyTransfer from '../transfer/BluetoothKeyTransfer.js';
|
||||||
|
|
||||||
// Import UI components (side-effect: they attach themselves to window.*)
|
// Import UI components (side-effect: they attach themselves to window.*)
|
||||||
import '../components/ui/Header.jsx';
|
import '../components/ui/Header.jsx';
|
||||||
@@ -11,11 +12,13 @@ import '../components/ui/Testimonials.jsx';
|
|||||||
import '../components/ui/ComparisonTable.jsx';
|
import '../components/ui/ComparisonTable.jsx';
|
||||||
import '../components/ui/Roadmap.jsx';
|
import '../components/ui/Roadmap.jsx';
|
||||||
import '../components/ui/FileTransfer.jsx';
|
import '../components/ui/FileTransfer.jsx';
|
||||||
|
import '../components/ui/BluetoothKeyTransfer.jsx';
|
||||||
|
|
||||||
// Expose to global for legacy usage inside app code
|
// Expose to global for legacy usage inside app code
|
||||||
window.EnhancedSecureCryptoUtils = EnhancedSecureCryptoUtils;
|
window.EnhancedSecureCryptoUtils = EnhancedSecureCryptoUtils;
|
||||||
window.EnhancedSecureWebRTCManager = EnhancedSecureWebRTCManager;
|
window.EnhancedSecureWebRTCManager = EnhancedSecureWebRTCManager;
|
||||||
window.EnhancedSecureFileTransfer = EnhancedSecureFileTransfer;
|
window.EnhancedSecureFileTransfer = EnhancedSecureFileTransfer;
|
||||||
|
window.BluetoothKeyTransfer = BluetoothKeyTransfer;
|
||||||
|
|
||||||
// Mount application once DOM and modules are ready
|
// Mount application once DOM and modules are ready
|
||||||
const start = () => {
|
const start = () => {
|
||||||
|
|||||||
Vendored
+5
-1
@@ -3,10 +3,11 @@
|
|||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
const [cryptoModule, webrtcModule, fileTransferModule] = await Promise.all([
|
const [cryptoModule, webrtcModule, fileTransferModule, bluetoothModule] = await Promise.all([
|
||||||
import(`../crypto/EnhancedSecureCryptoUtils.js?v=${timestamp}`),
|
import(`../crypto/EnhancedSecureCryptoUtils.js?v=${timestamp}`),
|
||||||
import(`../network/EnhancedSecureWebRTCManager.js?v=${timestamp}`),
|
import(`../network/EnhancedSecureWebRTCManager.js?v=${timestamp}`),
|
||||||
import(`../transfer/EnhancedSecureFileTransfer.js?v=${timestamp}`),
|
import(`../transfer/EnhancedSecureFileTransfer.js?v=${timestamp}`),
|
||||||
|
import(`../transfer/BluetoothKeyTransfer.js?v=${timestamp}`),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { EnhancedSecureCryptoUtils } = cryptoModule;
|
const { EnhancedSecureCryptoUtils } = cryptoModule;
|
||||||
@@ -15,6 +16,8 @@
|
|||||||
window.EnhancedSecureWebRTCManager = EnhancedSecureWebRTCManager;
|
window.EnhancedSecureWebRTCManager = EnhancedSecureWebRTCManager;
|
||||||
const { EnhancedSecureFileTransfer } = fileTransferModule;
|
const { EnhancedSecureFileTransfer } = fileTransferModule;
|
||||||
window.EnhancedSecureFileTransfer = EnhancedSecureFileTransfer;
|
window.EnhancedSecureFileTransfer = EnhancedSecureFileTransfer;
|
||||||
|
const { default: BluetoothKeyTransfer } = bluetoothModule;
|
||||||
|
window.BluetoothKeyTransfer = BluetoothKeyTransfer;
|
||||||
|
|
||||||
async function loadReactComponent(path) {
|
async function loadReactComponent(path) {
|
||||||
const response = await fetch(`${path}?v=${timestamp}`);
|
const response = await fetch(`${path}?v=${timestamp}`);
|
||||||
@@ -33,6 +36,7 @@
|
|||||||
loadReactComponent('../components/ui/Testimonials.jsx'),
|
loadReactComponent('../components/ui/Testimonials.jsx'),
|
||||||
loadReactComponent('../components/ui/Roadmap.jsx'),
|
loadReactComponent('../components/ui/Roadmap.jsx'),
|
||||||
loadReactComponent('../components/ui/FileTransfer.jsx'),
|
loadReactComponent('../components/ui/FileTransfer.jsx'),
|
||||||
|
loadReactComponent('../components/ui/BluetoothKeyTransfer.jsx'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (typeof window.initializeApp === 'function') {
|
if (typeof window.initializeApp === 'function') {
|
||||||
|
|||||||
@@ -0,0 +1,771 @@
|
|||||||
|
/**
|
||||||
|
* Bluetooth Key Transfer Module for SecureBit.chat
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Secure Bluetooth Low Energy (BLE) key exchange
|
||||||
|
* - Automatic device discovery and pairing
|
||||||
|
* - Encrypted key transmission
|
||||||
|
* - Fallback to manual/QR methods
|
||||||
|
* - Cross-platform compatibility
|
||||||
|
*
|
||||||
|
* Security:
|
||||||
|
* - Uses BLE advertising for device discovery
|
||||||
|
* - Encrypts key data before transmission
|
||||||
|
* - Implements secure pairing protocols
|
||||||
|
* - Validates received keys before acceptance
|
||||||
|
*/
|
||||||
|
|
||||||
|
class BluetoothKeyTransfer {
|
||||||
|
constructor(webrtcManager, onStatusChange, onKeyReceived, onError, onAutoConnection) {
|
||||||
|
this.webrtcManager = webrtcManager;
|
||||||
|
this.onStatusChange = onStatusChange;
|
||||||
|
this.onKeyReceived = onKeyReceived;
|
||||||
|
this.onError = onError;
|
||||||
|
this.onAutoConnection = onAutoConnection;
|
||||||
|
|
||||||
|
// Bluetooth state
|
||||||
|
this.isSupported = false;
|
||||||
|
this.isAvailable = false;
|
||||||
|
this.isScanning = false;
|
||||||
|
this.isAdvertising = false;
|
||||||
|
this.connectedDevices = new Map();
|
||||||
|
this.advertisingData = null;
|
||||||
|
|
||||||
|
// Service and characteristic UUIDs
|
||||||
|
this.SERVICE_UUID = '6e400001-b5a3-f393-e0a9-e50e24dcca9e'; // Nordic UART Service
|
||||||
|
this.TX_CHARACTERISTIC_UUID = '6e400002-b5a3-f393-e0a9-e50e24dcca9e'; // TX Characteristic
|
||||||
|
this.RX_CHARACTERISTIC_UUID = '6e400003-b5a3-f393-e0a9-e50e24dcca9e'; // RX Characteristic
|
||||||
|
|
||||||
|
// Key transfer protocol
|
||||||
|
this.PROTOCOL_VERSION = '1.0';
|
||||||
|
this.MAX_CHUNK_SIZE = 20; // BLE characteristic max size
|
||||||
|
this.TRANSFER_TIMEOUT = 30000; // 30 seconds
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
try {
|
||||||
|
// Check Bluetooth support
|
||||||
|
if (!navigator.bluetooth) {
|
||||||
|
this.log('warn', 'Bluetooth API not supported in this browser');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isSupported = true;
|
||||||
|
|
||||||
|
// Check if Bluetooth is available
|
||||||
|
const available = await navigator.bluetooth.getAvailability();
|
||||||
|
this.isAvailable = available;
|
||||||
|
|
||||||
|
if (!available) {
|
||||||
|
this.log('warn', 'Bluetooth is not available on this device');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log('info', 'Bluetooth Key Transfer initialized successfully');
|
||||||
|
this.onStatusChange?.('bluetooth_ready', { supported: true, available: true });
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to initialize Bluetooth Key Transfer', error);
|
||||||
|
this.onError?.(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// PUBLIC METHODS
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start advertising this device for key exchange
|
||||||
|
*/
|
||||||
|
async startAdvertising(publicKey, deviceName = 'SecureBit Device') {
|
||||||
|
if (!this.isSupported || !this.isAvailable) {
|
||||||
|
throw new Error('Bluetooth not supported or available');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.log('info', 'Starting Bluetooth advertising...');
|
||||||
|
this.onStatusChange?.('advertising_starting', { deviceName });
|
||||||
|
|
||||||
|
// Prepare advertising data
|
||||||
|
const keyData = await this.prepareKeyData(publicKey);
|
||||||
|
this.advertisingData = {
|
||||||
|
deviceName,
|
||||||
|
keyData,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
protocolVersion: this.PROTOCOL_VERSION
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start advertising
|
||||||
|
const options = {
|
||||||
|
filters: [{
|
||||||
|
services: [this.SERVICE_UUID]
|
||||||
|
}],
|
||||||
|
optionalServices: [this.SERVICE_UUID]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isAdvertising = true;
|
||||||
|
this.onStatusChange?.('advertising_active', { deviceName });
|
||||||
|
|
||||||
|
this.log('info', 'Bluetooth advertising started successfully');
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to start Bluetooth advertising', error);
|
||||||
|
this.isAdvertising = false;
|
||||||
|
this.onError?.(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop advertising
|
||||||
|
*/
|
||||||
|
async stopAdvertising() {
|
||||||
|
try {
|
||||||
|
this.isAdvertising = false;
|
||||||
|
this.advertisingData = null;
|
||||||
|
this.onStatusChange?.('advertising_stopped');
|
||||||
|
this.log('info', 'Bluetooth advertising stopped');
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to stop advertising', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start scanning for nearby devices
|
||||||
|
*/
|
||||||
|
async startScanning() {
|
||||||
|
if (!this.isSupported || !this.isAvailable) {
|
||||||
|
throw new Error('Bluetooth not supported or available');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.log('info', 'Starting Bluetooth device scan...');
|
||||||
|
this.onStatusChange?.('scanning_starting');
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
filters: [{
|
||||||
|
services: [this.SERVICE_UUID]
|
||||||
|
}],
|
||||||
|
optionalServices: [this.SERVICE_UUID]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isScanning = true;
|
||||||
|
this.onStatusChange?.('scanning_active');
|
||||||
|
|
||||||
|
// Start scanning
|
||||||
|
const device = await navigator.bluetooth.requestDevice(options);
|
||||||
|
|
||||||
|
if (device) {
|
||||||
|
this.log('info', 'Device selected:', device.name);
|
||||||
|
await this.connectToDevice(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to start scanning', error);
|
||||||
|
this.isScanning = false;
|
||||||
|
this.onError?.(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop scanning
|
||||||
|
*/
|
||||||
|
async stopScanning() {
|
||||||
|
try {
|
||||||
|
this.isScanning = false;
|
||||||
|
this.onStatusChange?.('scanning_stopped');
|
||||||
|
this.log('info', 'Bluetooth scanning stopped');
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to stop scanning', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send public key to connected device
|
||||||
|
*/
|
||||||
|
async sendPublicKey(publicKey, deviceId) {
|
||||||
|
try {
|
||||||
|
const device = this.connectedDevices.get(deviceId);
|
||||||
|
if (!device) {
|
||||||
|
throw new Error('Device not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log('info', 'Sending public key to device:', deviceId);
|
||||||
|
this.onStatusChange?.('key_sending', { deviceId });
|
||||||
|
|
||||||
|
const keyData = await this.prepareKeyData(publicKey);
|
||||||
|
await this.sendData(keyData, device);
|
||||||
|
|
||||||
|
this.onStatusChange?.('key_sent', { deviceId });
|
||||||
|
this.log('info', 'Public key sent successfully');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to send public key', error);
|
||||||
|
this.onError?.(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start automatic connection process (offer → answer → verification)
|
||||||
|
*/
|
||||||
|
async startAutoConnection(deviceId) {
|
||||||
|
try {
|
||||||
|
this.log('info', 'Starting automatic connection process');
|
||||||
|
this.onStatusChange?.('auto_connection_starting', { deviceId });
|
||||||
|
|
||||||
|
if (!this.webrtcManager) {
|
||||||
|
throw new Error('WebRTC Manager not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Create and send offer
|
||||||
|
this.onStatusChange?.('creating_offer', { deviceId });
|
||||||
|
const offer = await this.webrtcManager.createSecureOffer();
|
||||||
|
|
||||||
|
// Send offer via Bluetooth
|
||||||
|
await this.sendConnectionData(offer, deviceId, 'offer');
|
||||||
|
this.onStatusChange?.('offer_sent', { deviceId });
|
||||||
|
|
||||||
|
// Step 2: Wait for answer
|
||||||
|
this.onStatusChange?.('waiting_for_answer', { deviceId });
|
||||||
|
const answer = await this.waitForConnectionData(deviceId, 'answer');
|
||||||
|
|
||||||
|
// Step 3: Process answer
|
||||||
|
this.onStatusChange?.('processing_answer', { deviceId });
|
||||||
|
await this.webrtcManager.createSecureAnswer(answer);
|
||||||
|
|
||||||
|
// Step 4: Wait for verification
|
||||||
|
this.onStatusChange?.('waiting_for_verification', { deviceId });
|
||||||
|
const verification = await this.waitForConnectionData(deviceId, 'verification');
|
||||||
|
|
||||||
|
// Step 5: Complete connection
|
||||||
|
this.onStatusChange?.('completing_connection', { deviceId });
|
||||||
|
await this.completeConnection(verification, deviceId);
|
||||||
|
|
||||||
|
this.onStatusChange?.('auto_connection_complete', { deviceId });
|
||||||
|
this.log('info', 'Automatic connection completed successfully');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Automatic connection failed', error);
|
||||||
|
this.onStatusChange?.('auto_connection_failed', { deviceId, error: error.message });
|
||||||
|
this.onError?.(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start automatic connection as responder (wait for offer → create answer → send verification)
|
||||||
|
*/
|
||||||
|
async startAutoConnectionAsResponder(deviceId) {
|
||||||
|
try {
|
||||||
|
this.log('info', 'Starting automatic connection as responder');
|
||||||
|
this.onStatusChange?.('auto_connection_responder_starting', { deviceId });
|
||||||
|
|
||||||
|
if (!this.webrtcManager) {
|
||||||
|
throw new Error('WebRTC Manager not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Wait for offer
|
||||||
|
this.onStatusChange?.('waiting_for_offer', { deviceId });
|
||||||
|
const offer = await this.waitForConnectionData(deviceId, 'offer');
|
||||||
|
|
||||||
|
// Step 2: Create and send answer
|
||||||
|
this.onStatusChange?.('creating_answer', { deviceId });
|
||||||
|
const answer = await this.webrtcManager.createSecureAnswer(offer);
|
||||||
|
|
||||||
|
// Send answer via Bluetooth
|
||||||
|
await this.sendConnectionData(answer, deviceId, 'answer');
|
||||||
|
this.onStatusChange?.('answer_sent', { deviceId });
|
||||||
|
|
||||||
|
// Step 3: Send verification
|
||||||
|
this.onStatusChange?.('sending_verification', { deviceId });
|
||||||
|
const verification = await this.createVerificationData();
|
||||||
|
await this.sendConnectionData(verification, deviceId, 'verification');
|
||||||
|
|
||||||
|
this.onStatusChange?.('auto_connection_responder_complete', { deviceId });
|
||||||
|
this.log('info', 'Automatic connection as responder completed successfully');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Automatic connection as responder failed', error);
|
||||||
|
this.onStatusChange?.('auto_connection_responder_failed', { deviceId, error: error.message });
|
||||||
|
this.onError?.(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// PRIVATE METHODS
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to a discovered device
|
||||||
|
*/
|
||||||
|
async connectToDevice(device) {
|
||||||
|
try {
|
||||||
|
this.log('info', 'Connecting to device:', device.name);
|
||||||
|
this.onStatusChange?.('connecting', { deviceName: device.name });
|
||||||
|
|
||||||
|
const server = await device.gatt.connect();
|
||||||
|
const service = await server.getPrimaryService(this.SERVICE_UUID);
|
||||||
|
|
||||||
|
// Get characteristics
|
||||||
|
const txCharacteristic = await service.getCharacteristic(this.TX_CHARACTERISTIC_UUID);
|
||||||
|
const rxCharacteristic = await service.getCharacteristic(this.RX_CHARACTERISTIC_UUID);
|
||||||
|
|
||||||
|
// Set up data reception
|
||||||
|
rxCharacteristic.addEventListener('characteristicvaluechanged', (event) => {
|
||||||
|
this.handleReceivedData(event, device.id);
|
||||||
|
});
|
||||||
|
await rxCharacteristic.startNotifications();
|
||||||
|
|
||||||
|
// Store device connection
|
||||||
|
this.connectedDevices.set(device.id, {
|
||||||
|
device,
|
||||||
|
server,
|
||||||
|
service,
|
||||||
|
txCharacteristic,
|
||||||
|
rxCharacteristic,
|
||||||
|
connected: true
|
||||||
|
});
|
||||||
|
|
||||||
|
this.onStatusChange?.('connected', { deviceId: device.id, deviceName: device.name });
|
||||||
|
this.log('info', 'Successfully connected to device:', device.name);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to connect to device', error);
|
||||||
|
this.onError?.(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send data to connected device
|
||||||
|
*/
|
||||||
|
async sendData(data, device) {
|
||||||
|
try {
|
||||||
|
const { txCharacteristic } = device;
|
||||||
|
const dataString = JSON.stringify(data);
|
||||||
|
const chunks = this.chunkString(dataString, this.MAX_CHUNK_SIZE);
|
||||||
|
|
||||||
|
// Send chunks sequentially
|
||||||
|
for (let i = 0; i < chunks.length; i++) {
|
||||||
|
const chunk = chunks[i];
|
||||||
|
const chunkData = new TextEncoder().encode(chunk);
|
||||||
|
await txCharacteristic.writeValue(chunkData);
|
||||||
|
|
||||||
|
// Small delay between chunks
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log('info', `Sent ${chunks.length} chunks to device`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to send data', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle received data from device
|
||||||
|
*/
|
||||||
|
async handleReceivedData(event, deviceId) {
|
||||||
|
try {
|
||||||
|
const value = event.target.value;
|
||||||
|
const data = new TextDecoder().decode(value);
|
||||||
|
|
||||||
|
// Try to parse as connection data first
|
||||||
|
try {
|
||||||
|
const connectionData = JSON.parse(data);
|
||||||
|
if (connectionData.type && ['offer', 'answer', 'verification'].includes(connectionData.type)) {
|
||||||
|
this.handleConnectionData(connectionData, deviceId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Not connection data, continue with key processing
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process received data as key data
|
||||||
|
const keyData = await this.processReceivedData(data, deviceId);
|
||||||
|
if (keyData) {
|
||||||
|
this.onKeyReceived?.(keyData, deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to handle received data', error);
|
||||||
|
this.onError?.(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle connection data (offer, answer, verification)
|
||||||
|
*/
|
||||||
|
async handleConnectionData(connectionData, deviceId) {
|
||||||
|
try {
|
||||||
|
this.log('info', `Received ${connectionData.type} from device:`, deviceId);
|
||||||
|
|
||||||
|
// Store connection data for waiting processes
|
||||||
|
if (!this.connectionDataBuffer) {
|
||||||
|
this.connectionDataBuffer = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.connectionDataBuffer.has(deviceId)) {
|
||||||
|
this.connectionDataBuffer.set(deviceId, new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connectionDataBuffer.get(deviceId).set(connectionData.type, connectionData);
|
||||||
|
|
||||||
|
// Notify waiting processes
|
||||||
|
this.onStatusChange?.(`${connectionData.type}_received`, { deviceId, data: connectionData });
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to handle connection data', error);
|
||||||
|
this.onError?.(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare key data for transmission
|
||||||
|
*/
|
||||||
|
async prepareKeyData(publicKey) {
|
||||||
|
try {
|
||||||
|
// Export public key
|
||||||
|
const exportedKey = await crypto.subtle.exportKey('spki', publicKey);
|
||||||
|
const keyArray = new Uint8Array(exportedKey);
|
||||||
|
|
||||||
|
// Create secure payload
|
||||||
|
const payload = {
|
||||||
|
type: 'public_key',
|
||||||
|
key: Array.from(keyArray),
|
||||||
|
timestamp: Date.now(),
|
||||||
|
protocolVersion: this.PROTOCOL_VERSION,
|
||||||
|
deviceId: await this.getDeviceId()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sign payload for integrity
|
||||||
|
const signature = await this.signPayload(payload);
|
||||||
|
payload.signature = signature;
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to prepare key data', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process received key data
|
||||||
|
*/
|
||||||
|
async processReceivedData(data, deviceId) {
|
||||||
|
try {
|
||||||
|
const payload = JSON.parse(data);
|
||||||
|
|
||||||
|
// Validate payload
|
||||||
|
if (!this.validatePayload(payload)) {
|
||||||
|
throw new Error('Invalid payload received');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify signature
|
||||||
|
if (!await this.verifyPayload(payload)) {
|
||||||
|
throw new Error('Payload signature verification failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import public key
|
||||||
|
const publicKey = await crypto.subtle.importKey(
|
||||||
|
'spki',
|
||||||
|
new Uint8Array(payload.key),
|
||||||
|
{ name: 'ECDH', namedCurve: 'P-384' },
|
||||||
|
false,
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
this.log('info', 'Successfully processed received key data');
|
||||||
|
return {
|
||||||
|
publicKey,
|
||||||
|
deviceId,
|
||||||
|
timestamp: payload.timestamp,
|
||||||
|
protocolVersion: payload.protocolVersion
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to process received data', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign payload for integrity
|
||||||
|
*/
|
||||||
|
async signPayload(payload) {
|
||||||
|
try {
|
||||||
|
// Use WebRTC manager's signing key if available
|
||||||
|
if (this.webrtcManager && this.webrtcManager.signingKeyPair) {
|
||||||
|
const data = new TextEncoder().encode(JSON.stringify(payload));
|
||||||
|
const signature = await crypto.subtle.sign(
|
||||||
|
{ name: 'ECDSA', hash: 'SHA-384' },
|
||||||
|
this.webrtcManager.signingKeyPair.privateKey,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
return Array.from(new Uint8Array(signature));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: simple hash
|
||||||
|
const data = new TextEncoder().encode(JSON.stringify(payload));
|
||||||
|
const hash = await crypto.subtle.digest('SHA-256', data);
|
||||||
|
return Array.from(new Uint8Array(hash));
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to sign payload', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify payload signature
|
||||||
|
*/
|
||||||
|
async verifyPayload(payload) {
|
||||||
|
try {
|
||||||
|
const { signature, ...payloadWithoutSig } = payload;
|
||||||
|
|
||||||
|
// Use WebRTC manager's signing key if available
|
||||||
|
if (this.webrtcManager && this.webrtcManager.signingKeyPair) {
|
||||||
|
const data = new TextEncoder().encode(JSON.stringify(payloadWithoutSig));
|
||||||
|
const isValid = await crypto.subtle.verify(
|
||||||
|
{ name: 'ECDSA', hash: 'SHA-384' },
|
||||||
|
this.webrtcManager.signingKeyPair.publicKey,
|
||||||
|
new Uint8Array(signature),
|
||||||
|
data
|
||||||
|
);
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: simple hash comparison
|
||||||
|
const data = new TextEncoder().encode(JSON.stringify(payloadWithoutSig));
|
||||||
|
const hash = await crypto.subtle.digest('SHA-256', data);
|
||||||
|
const expectedHash = Array.from(new Uint8Array(hash));
|
||||||
|
return JSON.stringify(signature) === JSON.stringify(expectedHash);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to verify payload', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate received payload
|
||||||
|
*/
|
||||||
|
validatePayload(payload) {
|
||||||
|
return (
|
||||||
|
payload &&
|
||||||
|
payload.type === 'public_key' &&
|
||||||
|
payload.key &&
|
||||||
|
Array.isArray(payload.key) &&
|
||||||
|
payload.timestamp &&
|
||||||
|
payload.protocolVersion &&
|
||||||
|
payload.signature &&
|
||||||
|
Array.isArray(payload.signature)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get unique device ID
|
||||||
|
*/
|
||||||
|
async getDeviceId() {
|
||||||
|
try {
|
||||||
|
// Try to get a unique device identifier
|
||||||
|
if (navigator.userAgentData && navigator.userAgentData.getHighEntropyValues) {
|
||||||
|
const values = await navigator.userAgentData.getHighEntropyValues(['model']);
|
||||||
|
return values.model || 'unknown-device';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to user agent hash
|
||||||
|
const userAgent = navigator.userAgent;
|
||||||
|
const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(userAgent));
|
||||||
|
return Array.from(new Uint8Array(hash)).slice(0, 8).map(b => b.toString(16).padStart(2, '0')).join('');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return 'unknown-device';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send connection data (offer, answer, verification)
|
||||||
|
*/
|
||||||
|
async sendConnectionData(data, deviceId, type) {
|
||||||
|
try {
|
||||||
|
const device = this.connectedDevices.get(deviceId);
|
||||||
|
if (!device) {
|
||||||
|
throw new Error('Device not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
const connectionData = {
|
||||||
|
type: type,
|
||||||
|
data: data,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
protocolVersion: this.PROTOCOL_VERSION
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.sendData(connectionData, device);
|
||||||
|
this.log('info', `Sent ${type} to device:`, deviceId);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', `Failed to send ${type}`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for specific connection data type
|
||||||
|
*/
|
||||||
|
async waitForConnectionData(deviceId, type, timeout = 30000) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
reject(new Error(`Timeout waiting for ${type}`));
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
const checkForData = () => {
|
||||||
|
if (this.connectionDataBuffer &&
|
||||||
|
this.connectionDataBuffer.has(deviceId) &&
|
||||||
|
this.connectionDataBuffer.get(deviceId).has(type)) {
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
const data = this.connectionDataBuffer.get(deviceId).get(type);
|
||||||
|
this.connectionDataBuffer.get(deviceId).delete(type);
|
||||||
|
resolve(data.data);
|
||||||
|
} else {
|
||||||
|
setTimeout(checkForData, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkForData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create verification data
|
||||||
|
*/
|
||||||
|
async createVerificationData() {
|
||||||
|
try {
|
||||||
|
if (!this.webrtcManager || !this.webrtcManager.keyFingerprint) {
|
||||||
|
throw new Error('WebRTC Manager or key fingerprint not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fingerprint: this.webrtcManager.keyFingerprint,
|
||||||
|
verificationCode: this.webrtcManager.verificationCode || 'auto-verified',
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to create verification data', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete connection process
|
||||||
|
*/
|
||||||
|
async completeConnection(verification, deviceId) {
|
||||||
|
try {
|
||||||
|
// Validate verification data
|
||||||
|
if (verification.fingerprint && this.webrtcManager.keyFingerprint) {
|
||||||
|
if (verification.fingerprint !== this.webrtcManager.keyFingerprint) {
|
||||||
|
throw new Error('Key fingerprint mismatch');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify auto connection completion
|
||||||
|
this.onAutoConnection?.({
|
||||||
|
deviceId,
|
||||||
|
fingerprint: verification.fingerprint,
|
||||||
|
verificationCode: verification.verificationCode,
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
this.log('info', 'Connection completed successfully');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to complete connection', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split string into chunks
|
||||||
|
*/
|
||||||
|
chunkString(str, chunkSize) {
|
||||||
|
const chunks = [];
|
||||||
|
for (let i = 0; i < str.length; i += chunkSize) {
|
||||||
|
chunks.push(str.slice(i, i + chunkSize));
|
||||||
|
}
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logging utility
|
||||||
|
*/
|
||||||
|
log(level, message, data = null) {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const logMessage = `[BluetoothKeyTransfer ${timestamp}] ${message}`;
|
||||||
|
|
||||||
|
switch (level) {
|
||||||
|
case 'error':
|
||||||
|
console.error(logMessage, data);
|
||||||
|
break;
|
||||||
|
case 'warn':
|
||||||
|
console.warn(logMessage, data);
|
||||||
|
break;
|
||||||
|
case 'info':
|
||||||
|
console.info(logMessage, data);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log(logMessage, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// CLEANUP METHODS
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from all devices
|
||||||
|
*/
|
||||||
|
async disconnectAll() {
|
||||||
|
try {
|
||||||
|
for (const [deviceId, device] of this.connectedDevices) {
|
||||||
|
if (device.connected && device.server) {
|
||||||
|
device.server.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.connectedDevices.clear();
|
||||||
|
this.log('info', 'Disconnected from all devices');
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to disconnect devices', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup resources
|
||||||
|
*/
|
||||||
|
async cleanup() {
|
||||||
|
try {
|
||||||
|
await this.stopAdvertising();
|
||||||
|
await this.stopScanning();
|
||||||
|
await this.disconnectAll();
|
||||||
|
this.log('info', 'Bluetooth Key Transfer cleaned up');
|
||||||
|
} catch (error) {
|
||||||
|
this.log('error', 'Failed to cleanup Bluetooth Key Transfer', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export for use in other modules
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = BluetoothKeyTransfer;
|
||||||
|
} else if (typeof window !== 'undefined') {
|
||||||
|
window.BluetoothKeyTransfer = BluetoothKeyTransfer;
|
||||||
|
}
|
||||||
@@ -27,7 +27,11 @@ const STATIC_ASSETS = [
|
|||||||
'/src/pwa/pwa-manager.js',
|
'/src/pwa/pwa-manager.js',
|
||||||
'/src/pwa/install-prompt.js',
|
'/src/pwa/install-prompt.js',
|
||||||
'/src/scripts/pwa-register.js',
|
'/src/scripts/pwa-register.js',
|
||||||
'/src/scripts/pwa-offline-test.js'
|
'/src/scripts/pwa-offline-test.js',
|
||||||
|
|
||||||
|
// Bluetooth key transfer (PWA feature)
|
||||||
|
'/src/transfer/BluetoothKeyTransfer.js',
|
||||||
|
'/src/components/ui/BluetoothKeyTransfer.jsx'
|
||||||
];
|
];
|
||||||
|
|
||||||
// Sensitive files that should never be cached
|
// Sensitive files that should never be cached
|
||||||
|
|||||||
@@ -0,0 +1,455 @@
|
|||||||
|
// src/components/ui/BluetoothKeyTransfer.jsx
|
||||||
|
var BluetoothKeyTransfer = ({
|
||||||
|
webrtcManager,
|
||||||
|
onKeyReceived,
|
||||||
|
onStatusChange,
|
||||||
|
onAutoConnection,
|
||||||
|
isVisible = false,
|
||||||
|
onClose
|
||||||
|
}) => {
|
||||||
|
const [bluetoothManager, setBluetoothManager] = React.useState(null);
|
||||||
|
const [isSupported, setIsSupported] = React.useState(false);
|
||||||
|
const [isAvailable, setIsAvailable] = React.useState(false);
|
||||||
|
const [isScanning, setIsScanning] = React.useState(false);
|
||||||
|
const [isAdvertising, setIsAdvertising] = React.useState(false);
|
||||||
|
const [connectedDevices, setConnectedDevices] = React.useState([]);
|
||||||
|
const [status, setStatus] = React.useState("idle");
|
||||||
|
const [error, setError] = React.useState(null);
|
||||||
|
const [logs, setLogs] = React.useState([]);
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isVisible && !bluetoothManager) {
|
||||||
|
initializeBluetooth();
|
||||||
|
}
|
||||||
|
}, [isVisible]);
|
||||||
|
React.useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (bluetoothManager) {
|
||||||
|
bluetoothManager.cleanup();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [bluetoothManager]);
|
||||||
|
const initializeBluetooth = async () => {
|
||||||
|
try {
|
||||||
|
const manager = new window.BluetoothKeyTransfer(
|
||||||
|
webrtcManager,
|
||||||
|
handleStatusChange,
|
||||||
|
handleKeyReceived,
|
||||||
|
handleError,
|
||||||
|
handleAutoConnection
|
||||||
|
);
|
||||||
|
setBluetoothManager(manager);
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsSupported(manager.isSupported);
|
||||||
|
setIsAvailable(manager.isAvailable);
|
||||||
|
}, 100);
|
||||||
|
} catch (error2) {
|
||||||
|
console.error("Failed to initialize Bluetooth manager:", error2);
|
||||||
|
setError("Failed to initialize Bluetooth: " + error2.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleStatusChange = (statusType, data) => {
|
||||||
|
setStatus(statusType);
|
||||||
|
addLog(`Status: ${statusType}`, data);
|
||||||
|
switch (statusType) {
|
||||||
|
case "bluetooth_ready":
|
||||||
|
setIsSupported(data.supported);
|
||||||
|
setIsAvailable(data.available);
|
||||||
|
break;
|
||||||
|
case "scanning_active":
|
||||||
|
setIsScanning(true);
|
||||||
|
break;
|
||||||
|
case "scanning_stopped":
|
||||||
|
setIsScanning(false);
|
||||||
|
break;
|
||||||
|
case "advertising_active":
|
||||||
|
setIsAdvertising(true);
|
||||||
|
break;
|
||||||
|
case "advertising_stopped":
|
||||||
|
setIsAdvertising(false);
|
||||||
|
break;
|
||||||
|
case "connected":
|
||||||
|
setConnectedDevices((prev) => [...prev, {
|
||||||
|
id: data.deviceId,
|
||||||
|
name: data.deviceName,
|
||||||
|
connected: true
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
onStatusChange?.(statusType, data);
|
||||||
|
};
|
||||||
|
const handleKeyReceived = (keyData, deviceId) => {
|
||||||
|
addLog("Key received from device", { deviceId });
|
||||||
|
onKeyReceived?.(keyData, deviceId);
|
||||||
|
};
|
||||||
|
const handleError = (error2) => {
|
||||||
|
console.error("Bluetooth error:", error2);
|
||||||
|
setError(error2.message);
|
||||||
|
addLog("Error", error2.message);
|
||||||
|
};
|
||||||
|
const handleAutoConnection = (connectionData) => {
|
||||||
|
console.log("Auto connection completed:", connectionData);
|
||||||
|
addLog("Auto Connection Completed", connectionData);
|
||||||
|
onAutoConnection?.(connectionData);
|
||||||
|
};
|
||||||
|
const addLog = (message, data = null) => {
|
||||||
|
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
||||||
|
const logEntry = {
|
||||||
|
timestamp,
|
||||||
|
message,
|
||||||
|
data: data ? JSON.stringify(data, null, 2) : null
|
||||||
|
};
|
||||||
|
setLogs((prev) => [...prev.slice(-9), logEntry]);
|
||||||
|
};
|
||||||
|
const startScanning = async () => {
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
await bluetoothManager.startScanning();
|
||||||
|
} catch (error2) {
|
||||||
|
setError("Failed to start scanning: " + error2.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const stopScanning = async () => {
|
||||||
|
try {
|
||||||
|
await bluetoothManager.stopScanning();
|
||||||
|
} catch (error2) {
|
||||||
|
setError("Failed to stop scanning: " + error2.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const startAdvertising = async () => {
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
if (!webrtcManager || !webrtcManager.ecdhKeyPair) {
|
||||||
|
throw new Error("No public key available for advertising");
|
||||||
|
}
|
||||||
|
await bluetoothManager.startAdvertising(
|
||||||
|
webrtcManager.ecdhKeyPair.publicKey,
|
||||||
|
"SecureBit Device"
|
||||||
|
);
|
||||||
|
} catch (error2) {
|
||||||
|
setError("Failed to start advertising: " + error2.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const stopAdvertising = async () => {
|
||||||
|
try {
|
||||||
|
await bluetoothManager.stopAdvertising();
|
||||||
|
} catch (error2) {
|
||||||
|
setError("Failed to stop advertising: " + error2.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const sendPublicKey = async (deviceId) => {
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
if (!webrtcManager || !webrtcManager.ecdhKeyPair) {
|
||||||
|
throw new Error("No public key available for sending");
|
||||||
|
}
|
||||||
|
await bluetoothManager.sendPublicKey(
|
||||||
|
webrtcManager.ecdhKeyPair.publicKey,
|
||||||
|
deviceId
|
||||||
|
);
|
||||||
|
} catch (error2) {
|
||||||
|
setError("Failed to send public key: " + error2.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const clearLogs = () => {
|
||||||
|
setLogs([]);
|
||||||
|
};
|
||||||
|
const startAutoConnection = async (deviceId) => {
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
await bluetoothManager.startAutoConnection(deviceId);
|
||||||
|
} catch (error2) {
|
||||||
|
setError("Failed to start auto connection: " + error2.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const startAutoConnectionAsResponder = async (deviceId) => {
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
await bluetoothManager.startAutoConnectionAsResponder(deviceId);
|
||||||
|
} catch (error2) {
|
||||||
|
setError("Failed to start auto connection as responder: " + error2.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (!isVisible) return null;
|
||||||
|
return React.createElement("div", {
|
||||||
|
className: "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
|
||||||
|
}, [
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "modal",
|
||||||
|
className: "bg-gray-900 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-hidden"
|
||||||
|
}, [
|
||||||
|
// Header
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "header",
|
||||||
|
className: "flex items-center justify-between p-6 border-b border-gray-700"
|
||||||
|
}, [
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "title",
|
||||||
|
className: "flex items-center space-x-3"
|
||||||
|
}, [
|
||||||
|
React.createElement("i", {
|
||||||
|
key: "icon",
|
||||||
|
className: "fas fa-bluetooth text-blue-400 text-xl"
|
||||||
|
}),
|
||||||
|
React.createElement("h2", {
|
||||||
|
key: "text",
|
||||||
|
className: "text-xl font-semibold text-white"
|
||||||
|
}, "Bluetooth Key Transfer")
|
||||||
|
]),
|
||||||
|
React.createElement("button", {
|
||||||
|
key: "close",
|
||||||
|
onClick: onClose,
|
||||||
|
className: "text-gray-400 hover:text-white transition-colors"
|
||||||
|
}, [
|
||||||
|
React.createElement("i", {
|
||||||
|
className: "fas fa-times text-xl"
|
||||||
|
})
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
// Content
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "content",
|
||||||
|
className: "p-6 space-y-6 overflow-y-auto max-h-[calc(90vh-200px)]"
|
||||||
|
}, [
|
||||||
|
// Status Section
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "status",
|
||||||
|
className: "space-y-4"
|
||||||
|
}, [
|
||||||
|
React.createElement("h3", {
|
||||||
|
key: "title",
|
||||||
|
className: "text-lg font-medium text-white"
|
||||||
|
}, "Bluetooth Status"),
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "indicators",
|
||||||
|
className: "grid grid-cols-2 gap-4"
|
||||||
|
}, [
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "support",
|
||||||
|
className: "flex items-center space-x-2"
|
||||||
|
}, [
|
||||||
|
React.createElement("div", {
|
||||||
|
className: `w-3 h-3 rounded-full ${isSupported ? "bg-green-500" : "bg-red-500"}`
|
||||||
|
}),
|
||||||
|
React.createElement("span", {
|
||||||
|
className: "text-sm text-gray-300"
|
||||||
|
}, "Bluetooth Supported")
|
||||||
|
]),
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "availability",
|
||||||
|
className: "flex items-center space-x-2"
|
||||||
|
}, [
|
||||||
|
React.createElement("div", {
|
||||||
|
className: `w-3 h-3 rounded-full ${isAvailable ? "bg-green-500" : "bg-red-500"}`
|
||||||
|
}),
|
||||||
|
React.createElement("span", {
|
||||||
|
className: "text-sm text-gray-300"
|
||||||
|
}, "Bluetooth Available")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
// Controls Section
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "controls",
|
||||||
|
className: "space-y-4"
|
||||||
|
}, [
|
||||||
|
React.createElement("h3", {
|
||||||
|
key: "title",
|
||||||
|
className: "text-lg font-medium text-white"
|
||||||
|
}, "Key Exchange"),
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "buttons",
|
||||||
|
className: "grid grid-cols-1 sm:grid-cols-2 gap-4"
|
||||||
|
}, [
|
||||||
|
// Scanning Controls
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "scanning",
|
||||||
|
className: "space-y-2"
|
||||||
|
}, [
|
||||||
|
React.createElement("h4", {
|
||||||
|
key: "title",
|
||||||
|
className: "text-sm font-medium text-gray-300"
|
||||||
|
}, "Discover Devices"),
|
||||||
|
React.createElement("button", {
|
||||||
|
key: "scan",
|
||||||
|
onClick: isScanning ? stopScanning : startScanning,
|
||||||
|
disabled: !isSupported || !isAvailable,
|
||||||
|
className: `w-full px-4 py-2 rounded-lg font-medium transition-colors ${isScanning ? "bg-red-600 hover:bg-red-700 text-white" : "bg-blue-600 hover:bg-blue-700 text-white disabled:bg-gray-600 disabled:cursor-not-allowed"}`
|
||||||
|
}, [
|
||||||
|
React.createElement("i", {
|
||||||
|
key: "icon",
|
||||||
|
className: `fas ${isScanning ? "fa-stop" : "fa-search"} mr-2`
|
||||||
|
}),
|
||||||
|
isScanning ? "Stop Scanning" : "Start Scanning"
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
// Advertising Controls
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "advertising",
|
||||||
|
className: "space-y-2"
|
||||||
|
}, [
|
||||||
|
React.createElement("h4", {
|
||||||
|
key: "title",
|
||||||
|
className: "text-sm font-medium text-gray-300"
|
||||||
|
}, "Share Your Key"),
|
||||||
|
React.createElement("button", {
|
||||||
|
key: "advertise",
|
||||||
|
onClick: isAdvertising ? stopAdvertising : startAdvertising,
|
||||||
|
disabled: !isSupported || !isAvailable,
|
||||||
|
className: `w-full px-4 py-2 rounded-lg font-medium transition-colors ${isAdvertising ? "bg-red-600 hover:bg-red-700 text-white" : "bg-green-600 hover:bg-green-700 text-white disabled:bg-gray-600 disabled:cursor-not-allowed"}`
|
||||||
|
}, [
|
||||||
|
React.createElement("i", {
|
||||||
|
key: "icon",
|
||||||
|
className: `fas ${isAdvertising ? "fa-stop" : "fa-broadcast-tower"} mr-2`
|
||||||
|
}),
|
||||||
|
isAdvertising ? "Stop Sharing" : "Start Sharing"
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
// Connected Devices
|
||||||
|
connectedDevices.length > 0 && React.createElement("div", {
|
||||||
|
key: "devices",
|
||||||
|
className: "space-y-4"
|
||||||
|
}, [
|
||||||
|
React.createElement("h3", {
|
||||||
|
key: "title",
|
||||||
|
className: "text-lg font-medium text-white"
|
||||||
|
}, "Connected Devices"),
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "list",
|
||||||
|
className: "space-y-2"
|
||||||
|
}, connectedDevices.map(
|
||||||
|
(device) => React.createElement("div", {
|
||||||
|
key: device.id,
|
||||||
|
className: "flex items-center justify-between p-3 bg-gray-800 rounded-lg"
|
||||||
|
}, [
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "info",
|
||||||
|
className: "flex items-center space-x-3"
|
||||||
|
}, [
|
||||||
|
React.createElement("i", {
|
||||||
|
key: "icon",
|
||||||
|
className: "fas fa-mobile-alt text-blue-400"
|
||||||
|
}),
|
||||||
|
React.createElement("span", {
|
||||||
|
key: "name",
|
||||||
|
className: "text-white"
|
||||||
|
}, device.name)
|
||||||
|
]),
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "buttons",
|
||||||
|
className: "flex space-x-2"
|
||||||
|
}, [
|
||||||
|
React.createElement("button", {
|
||||||
|
key: "auto-connect",
|
||||||
|
onClick: () => startAutoConnection(device.id),
|
||||||
|
className: "px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-sm rounded transition-colors"
|
||||||
|
}, "Auto Connect"),
|
||||||
|
React.createElement("button", {
|
||||||
|
key: "auto-respond",
|
||||||
|
onClick: () => startAutoConnectionAsResponder(device.id),
|
||||||
|
className: "px-3 py-1 bg-purple-600 hover:bg-purple-700 text-white text-sm rounded transition-colors"
|
||||||
|
}, "Auto Respond"),
|
||||||
|
React.createElement("button", {
|
||||||
|
key: "send",
|
||||||
|
onClick: () => sendPublicKey(device.id),
|
||||||
|
className: "px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded transition-colors"
|
||||||
|
}, "Send Key")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
))
|
||||||
|
]),
|
||||||
|
// Error Display
|
||||||
|
error && React.createElement("div", {
|
||||||
|
key: "error",
|
||||||
|
className: "p-4 bg-red-900 border border-red-700 rounded-lg"
|
||||||
|
}, [
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "header",
|
||||||
|
className: "flex items-center space-x-2 mb-2"
|
||||||
|
}, [
|
||||||
|
React.createElement("i", {
|
||||||
|
key: "icon",
|
||||||
|
className: "fas fa-exclamation-triangle text-red-400"
|
||||||
|
}),
|
||||||
|
React.createElement("h4", {
|
||||||
|
key: "title",
|
||||||
|
className: "text-red-400 font-medium"
|
||||||
|
}, "Error")
|
||||||
|
]),
|
||||||
|
React.createElement("p", {
|
||||||
|
key: "message",
|
||||||
|
className: "text-red-300 text-sm"
|
||||||
|
}, error)
|
||||||
|
]),
|
||||||
|
// Logs Section
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "logs",
|
||||||
|
className: "space-y-4"
|
||||||
|
}, [
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "header",
|
||||||
|
className: "flex items-center justify-between"
|
||||||
|
}, [
|
||||||
|
React.createElement("h3", {
|
||||||
|
key: "title",
|
||||||
|
className: "text-lg font-medium text-white"
|
||||||
|
}, "Activity Log"),
|
||||||
|
React.createElement("button", {
|
||||||
|
key: "clear",
|
||||||
|
onClick: clearLogs,
|
||||||
|
className: "text-sm text-gray-400 hover:text-white transition-colors"
|
||||||
|
}, "Clear")
|
||||||
|
]),
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
key: "log-list",
|
||||||
|
className: "bg-gray-800 rounded-lg p-4 max-h-40 overflow-y-auto"
|
||||||
|
},
|
||||||
|
logs.length === 0 ? React.createElement("p", {
|
||||||
|
key: "empty",
|
||||||
|
className: "text-gray-400 text-sm text-center"
|
||||||
|
}, "No activity yet") : logs.map(
|
||||||
|
(log, index) => React.createElement("div", {
|
||||||
|
key: index,
|
||||||
|
className: "text-xs text-gray-300 mb-1"
|
||||||
|
}, [
|
||||||
|
React.createElement("span", {
|
||||||
|
key: "time",
|
||||||
|
className: "text-gray-500"
|
||||||
|
}, `[${log.timestamp}] `),
|
||||||
|
React.createElement("span", {
|
||||||
|
key: "message",
|
||||||
|
className: "text-gray-300"
|
||||||
|
}, log.message),
|
||||||
|
log.data && React.createElement("pre", {
|
||||||
|
key: "data",
|
||||||
|
className: "text-gray-400 mt-1 ml-4"
|
||||||
|
}, log.data)
|
||||||
|
])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
// Footer
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "footer",
|
||||||
|
className: "flex items-center justify-between p-6 border-t border-gray-700"
|
||||||
|
}, [
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "info",
|
||||||
|
className: "text-sm text-gray-400"
|
||||||
|
}, "Bluetooth key exchange provides secure device-to-device communication"),
|
||||||
|
React.createElement("button", {
|
||||||
|
key: "close-footer",
|
||||||
|
onClick: onClose,
|
||||||
|
className: "px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition-colors"
|
||||||
|
}, "Close")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
window.BluetoothKeyTransfer = BluetoothKeyTransfer;
|
||||||
|
}
|
||||||
+575
@@ -0,0 +1,575 @@
|
|||||||
|
// src/components/ui/Header.jsx
|
||||||
|
var EnhancedMinimalHeader = ({
|
||||||
|
status,
|
||||||
|
fingerprint,
|
||||||
|
verificationCode,
|
||||||
|
onDisconnect,
|
||||||
|
isConnected,
|
||||||
|
securityLevel,
|
||||||
|
webrtcManager
|
||||||
|
}) => {
|
||||||
|
const [realSecurityLevel, setRealSecurityLevel] = React.useState(null);
|
||||||
|
const [lastSecurityUpdate, setLastSecurityUpdate] = React.useState(0);
|
||||||
|
React.useEffect(() => {
|
||||||
|
let isUpdating = false;
|
||||||
|
let lastUpdateAttempt = 0;
|
||||||
|
const updateRealSecurityStatus = async () => {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastUpdateAttempt < 1e4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isUpdating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isUpdating = true;
|
||||||
|
lastUpdateAttempt = now;
|
||||||
|
try {
|
||||||
|
if (!webrtcManager || !isConnected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const activeWebrtcManager = webrtcManager;
|
||||||
|
let realSecurityData = null;
|
||||||
|
if (typeof activeWebrtcManager.getRealSecurityLevel === "function") {
|
||||||
|
realSecurityData = await activeWebrtcManager.getRealSecurityLevel();
|
||||||
|
} else if (typeof activeWebrtcManager.calculateAndReportSecurityLevel === "function") {
|
||||||
|
realSecurityData = await activeWebrtcManager.calculateAndReportSecurityLevel();
|
||||||
|
} else {
|
||||||
|
realSecurityData = await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(activeWebrtcManager);
|
||||||
|
}
|
||||||
|
if (realSecurityData && realSecurityData.isRealData !== false) {
|
||||||
|
const currentScore = realSecurityLevel?.score || 0;
|
||||||
|
const newScore = realSecurityData.score || 0;
|
||||||
|
if (currentScore !== newScore || !realSecurityLevel) {
|
||||||
|
setRealSecurityLevel(realSecurityData);
|
||||||
|
setLastSecurityUpdate(now);
|
||||||
|
} else if (window.DEBUG_MODE) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn(" Security calculation returned invalid data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(" Error in real security calculation:", error);
|
||||||
|
} finally {
|
||||||
|
isUpdating = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (isConnected) {
|
||||||
|
updateRealSecurityStatus();
|
||||||
|
if (!realSecurityLevel || realSecurityLevel.score < 50) {
|
||||||
|
const retryInterval = setInterval(() => {
|
||||||
|
if (!realSecurityLevel || realSecurityLevel.score < 50) {
|
||||||
|
updateRealSecurityStatus();
|
||||||
|
} else {
|
||||||
|
clearInterval(retryInterval);
|
||||||
|
}
|
||||||
|
}, 5e3);
|
||||||
|
setTimeout(() => clearInterval(retryInterval), 3e4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const interval = setInterval(updateRealSecurityStatus, 3e4);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [webrtcManager, isConnected]);
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handleSecurityUpdate = (event) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setLastSecurityUpdate(0);
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
const handleRealSecurityCalculated = (event) => {
|
||||||
|
if (event.detail && event.detail.securityData) {
|
||||||
|
setRealSecurityLevel(event.detail.securityData);
|
||||||
|
setLastSecurityUpdate(Date.now());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener("security-level-updated", handleSecurityUpdate);
|
||||||
|
document.addEventListener("real-security-calculated", handleRealSecurityCalculated);
|
||||||
|
window.forceHeaderSecurityUpdate = (webrtcManager2) => {
|
||||||
|
if (webrtcManager2 && window.EnhancedSecureCryptoUtils) {
|
||||||
|
window.EnhancedSecureCryptoUtils.calculateSecurityLevel(webrtcManager2).then((securityData) => {
|
||||||
|
if (securityData && securityData.isRealData !== false) {
|
||||||
|
setRealSecurityLevel(securityData);
|
||||||
|
setLastSecurityUpdate(Date.now());
|
||||||
|
console.log("\u2705 Header security level force-updated");
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error("\u274C Force update failed:", error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setLastSecurityUpdate(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("security-level-updated", handleSecurityUpdate);
|
||||||
|
document.removeEventListener("real-security-calculated", handleRealSecurityCalculated);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
React.useEffect(() => {
|
||||||
|
setHasActiveSession(true);
|
||||||
|
setCurrentTimeLeft(0);
|
||||||
|
setSessionType("premium");
|
||||||
|
}, []);
|
||||||
|
React.useEffect(() => {
|
||||||
|
setHasActiveSession(true);
|
||||||
|
setCurrentTimeLeft(0);
|
||||||
|
setSessionType("premium");
|
||||||
|
}, [sessionTimeLeft]);
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handleForceUpdate = (event) => {
|
||||||
|
setHasActiveSession(true);
|
||||||
|
setCurrentTimeLeft(0);
|
||||||
|
setSessionType("premium");
|
||||||
|
};
|
||||||
|
const handleConnectionCleaned = () => {
|
||||||
|
if (window.DEBUG_MODE) {
|
||||||
|
console.log("\u{1F9F9} Connection cleaned - clearing security data in header");
|
||||||
|
}
|
||||||
|
setRealSecurityLevel(null);
|
||||||
|
setLastSecurityUpdate(0);
|
||||||
|
setHasActiveSession(false);
|
||||||
|
setCurrentTimeLeft(0);
|
||||||
|
setSessionType("unknown");
|
||||||
|
};
|
||||||
|
const handlePeerDisconnect = () => {
|
||||||
|
if (window.DEBUG_MODE) {
|
||||||
|
console.log("\u{1F44B} Peer disconnect detected - clearing security data in header");
|
||||||
|
}
|
||||||
|
setRealSecurityLevel(null);
|
||||||
|
setLastSecurityUpdate(0);
|
||||||
|
};
|
||||||
|
const handleDisconnected = () => {
|
||||||
|
setRealSecurityLevel(null);
|
||||||
|
setLastSecurityUpdate(0);
|
||||||
|
setHasActiveSession(false);
|
||||||
|
setCurrentTimeLeft(0);
|
||||||
|
setSessionType("unknown");
|
||||||
|
};
|
||||||
|
document.addEventListener("force-header-update", handleForceUpdate);
|
||||||
|
document.addEventListener("peer-disconnect", handlePeerDisconnect);
|
||||||
|
document.addEventListener("connection-cleaned", handleConnectionCleaned);
|
||||||
|
document.addEventListener("disconnected", handleDisconnected);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("force-header-update", handleForceUpdate);
|
||||||
|
document.removeEventListener("peer-disconnect", handlePeerDisconnect);
|
||||||
|
document.removeEventListener("connection-cleaned", handleConnectionCleaned);
|
||||||
|
document.removeEventListener("disconnected", handleDisconnected);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
const handleSecurityClick = async (event) => {
|
||||||
|
if (event && (event.button === 2 || event.ctrlKey || event.metaKey)) {
|
||||||
|
if (onDisconnect && typeof onDisconnect === "function") {
|
||||||
|
onDisconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
let realTestResults = null;
|
||||||
|
if (webrtcManager && window.EnhancedSecureCryptoUtils) {
|
||||||
|
try {
|
||||||
|
realTestResults = await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(webrtcManager);
|
||||||
|
console.log("\u2705 Real security tests completed:", realTestResults);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("\u274C Real security tests failed:", error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("\u26A0\uFE0F Cannot run security tests:", {
|
||||||
|
webrtcManager: !!webrtcManager,
|
||||||
|
cryptoUtils: !!window.EnhancedSecureCryptoUtils
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!realTestResults && !realSecurityLevel) {
|
||||||
|
alert("Security verification in progress...\nPlease wait for real-time cryptographic verification to complete.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let securityData = realTestResults || realSecurityLevel;
|
||||||
|
if (!securityData) {
|
||||||
|
securityData = {
|
||||||
|
level: "UNKNOWN",
|
||||||
|
score: 0,
|
||||||
|
color: "gray",
|
||||||
|
verificationResults: {},
|
||||||
|
timestamp: Date.now(),
|
||||||
|
details: "Security verification not available",
|
||||||
|
isRealData: false,
|
||||||
|
passedChecks: 0,
|
||||||
|
totalChecks: 0
|
||||||
|
};
|
||||||
|
console.log("Using fallback security data:", securityData);
|
||||||
|
}
|
||||||
|
let message = `REAL-TIME SECURITY VERIFICATION
|
||||||
|
|
||||||
|
`;
|
||||||
|
message += `Security Level: ${securityData.level} (${securityData.score}%)
|
||||||
|
`;
|
||||||
|
message += `Verification Time: ${new Date(securityData.timestamp).toLocaleTimeString()}
|
||||||
|
`;
|
||||||
|
message += `Data Source: ${securityData.isRealData ? "Real Cryptographic Tests" : "Simulated Data"}
|
||||||
|
|
||||||
|
`;
|
||||||
|
if (securityData.verificationResults) {
|
||||||
|
message += "DETAILED CRYPTOGRAPHIC TESTS:\n";
|
||||||
|
message += "=" + "=".repeat(40) + "\n";
|
||||||
|
const passedTests = Object.entries(securityData.verificationResults).filter(([key, result]) => result.passed);
|
||||||
|
const failedTests = Object.entries(securityData.verificationResults).filter(([key, result]) => !result.passed);
|
||||||
|
if (passedTests.length > 0) {
|
||||||
|
message += "PASSED TESTS:\n";
|
||||||
|
passedTests.forEach(([key, result]) => {
|
||||||
|
const testName = key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase());
|
||||||
|
message += ` ${testName}: ${result.details || "Test passed"}
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
message += "\n";
|
||||||
|
}
|
||||||
|
if (failedTests.length > 0) {
|
||||||
|
message += "FAILED/UNAVAILABLE TESTS:\n";
|
||||||
|
failedTests.forEach(([key, result]) => {
|
||||||
|
const testName = key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase());
|
||||||
|
message += ` ${testName}: ${result.details || "Test failed or unavailable"}
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
message += "\n";
|
||||||
|
}
|
||||||
|
message += `SUMMARY:
|
||||||
|
`;
|
||||||
|
message += `Passed: ${securityData.passedChecks}/${securityData.totalChecks} tests
|
||||||
|
`;
|
||||||
|
message += `Score: ${securityData.score}/${securityData.maxPossibleScore || 100} points
|
||||||
|
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
message += `SECURITY FEATURES STATUS:
|
||||||
|
`;
|
||||||
|
message += "=" + "=".repeat(40) + "\n";
|
||||||
|
if (securityData.verificationResults) {
|
||||||
|
const features = {
|
||||||
|
"ECDSA Digital Signatures": securityData.verificationResults.verifyECDSASignatures?.passed || false,
|
||||||
|
"ECDH Key Exchange": securityData.verificationResults.verifyECDHKeyExchange?.passed || false,
|
||||||
|
"AES-GCM Encryption": securityData.verificationResults.verifyEncryption?.passed || false,
|
||||||
|
"Message Integrity (HMAC)": securityData.verificationResults.verifyMessageIntegrity?.passed || false,
|
||||||
|
"Perfect Forward Secrecy": securityData.verificationResults.verifyPerfectForwardSecrecy?.passed || false,
|
||||||
|
"Replay Protection": securityData.verificationResults.verifyReplayProtection?.passed || false,
|
||||||
|
"DTLS Fingerprint": securityData.verificationResults.verifyDTLSFingerprint?.passed || false,
|
||||||
|
"SAS Verification": securityData.verificationResults.verifySASVerification?.passed || false,
|
||||||
|
"Metadata Protection": securityData.verificationResults.verifyMetadataProtection?.passed || false,
|
||||||
|
"Traffic Obfuscation": securityData.verificationResults.verifyTrafficObfuscation?.passed || false
|
||||||
|
};
|
||||||
|
Object.entries(features).forEach(([feature, isEnabled]) => {
|
||||||
|
message += `${isEnabled ? "\u2705" : "\u274C"} ${feature}
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
message += `\u2705 ECDSA Digital Signatures
|
||||||
|
`;
|
||||||
|
message += `\u2705 ECDH Key Exchange
|
||||||
|
`;
|
||||||
|
message += `\u2705 AES-GCM Encryption
|
||||||
|
`;
|
||||||
|
message += `\u2705 Message Integrity (HMAC)
|
||||||
|
`;
|
||||||
|
message += `\u2705 Perfect Forward Secrecy
|
||||||
|
`;
|
||||||
|
message += `\u2705 Replay Protection
|
||||||
|
`;
|
||||||
|
message += `\u2705 DTLS Fingerprint
|
||||||
|
`;
|
||||||
|
message += `\u2705 SAS Verification
|
||||||
|
`;
|
||||||
|
message += `\u2705 Metadata Protection
|
||||||
|
`;
|
||||||
|
message += `\u2705 Traffic Obfuscation
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
message += `
|
||||||
|
${securityData.details || "Real cryptographic verification completed"}`;
|
||||||
|
if (securityData.isRealData) {
|
||||||
|
message += "\n\n\u2705 This is REAL-TIME verification using actual cryptographic functions.";
|
||||||
|
} else {
|
||||||
|
message += "\n\n\u26A0\uFE0F Warning: This data may be simulated. Connection may not be fully established.";
|
||||||
|
}
|
||||||
|
const modal = document.createElement("div");
|
||||||
|
modal.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0,0,0,0.8);
|
||||||
|
z-index: 10000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-family: monospace;
|
||||||
|
`;
|
||||||
|
const content = document.createElement("div");
|
||||||
|
content.style.cssText = `
|
||||||
|
background: #1a1a1a;
|
||||||
|
color: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
max-width: 80%;
|
||||||
|
max-height: 80%;
|
||||||
|
overflow-y: auto;
|
||||||
|
white-space: pre-line;
|
||||||
|
border: 1px solid #333;
|
||||||
|
`;
|
||||||
|
content.textContent = message;
|
||||||
|
modal.appendChild(content);
|
||||||
|
modal.addEventListener("click", (e) => {
|
||||||
|
if (e.target === modal) {
|
||||||
|
document.body.removeChild(modal);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const handleKeyDown = (e) => {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
document.body.removeChild(modal);
|
||||||
|
document.removeEventListener("keydown", handleKeyDown);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener("keydown", handleKeyDown);
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
};
|
||||||
|
const getStatusConfig = () => {
|
||||||
|
switch (status) {
|
||||||
|
case "connected":
|
||||||
|
return {
|
||||||
|
text: "Connected",
|
||||||
|
className: "status-connected",
|
||||||
|
badgeClass: "bg-green-500/10 text-green-400 border-green-500/20"
|
||||||
|
};
|
||||||
|
case "verifying":
|
||||||
|
return {
|
||||||
|
text: "Verifying...",
|
||||||
|
className: "status-verifying",
|
||||||
|
badgeClass: "bg-purple-500/10 text-purple-400 border-purple-500/20"
|
||||||
|
};
|
||||||
|
case "connecting":
|
||||||
|
return {
|
||||||
|
text: "Connecting...",
|
||||||
|
className: "status-connecting",
|
||||||
|
badgeClass: "bg-blue-500/10 text-blue-400 border-blue-500/20"
|
||||||
|
};
|
||||||
|
case "retrying":
|
||||||
|
return {
|
||||||
|
text: "Retrying...",
|
||||||
|
className: "status-connecting",
|
||||||
|
badgeClass: "bg-yellow-500/10 text-yellow-400 border-yellow-500/20"
|
||||||
|
};
|
||||||
|
case "failed":
|
||||||
|
return {
|
||||||
|
text: "Error",
|
||||||
|
className: "status-failed",
|
||||||
|
badgeClass: "bg-red-500/10 text-red-400 border-red-500/20"
|
||||||
|
};
|
||||||
|
case "reconnecting":
|
||||||
|
return {
|
||||||
|
text: "Reconnecting...",
|
||||||
|
className: "status-connecting",
|
||||||
|
badgeClass: "bg-yellow-500/10 text-yellow-400 border-yellow-500/20"
|
||||||
|
};
|
||||||
|
case "peer_disconnected":
|
||||||
|
return {
|
||||||
|
text: "Peer disconnected",
|
||||||
|
className: "status-failed",
|
||||||
|
badgeClass: "bg-orange-500/10 text-orange-400 border-orange-500/20"
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
text: "Not connected",
|
||||||
|
className: "status-disconnected",
|
||||||
|
badgeClass: "bg-gray-500/10 text-gray-400 border-gray-500/20"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const config = getStatusConfig();
|
||||||
|
const displaySecurityLevel = isConnected ? realSecurityLevel || securityLevel : null;
|
||||||
|
const getSecurityIndicatorDetails = () => {
|
||||||
|
if (!displaySecurityLevel) {
|
||||||
|
return {
|
||||||
|
tooltip: "Security verification in progress...",
|
||||||
|
isVerified: false,
|
||||||
|
dataSource: "loading"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const isRealData = displaySecurityLevel.isRealData !== false;
|
||||||
|
const baseTooltip = `${displaySecurityLevel.level} (${displaySecurityLevel.score}%)`;
|
||||||
|
if (isRealData) {
|
||||||
|
return {
|
||||||
|
tooltip: `${baseTooltip} - Real-time verification \u2705
|
||||||
|
Right-click or Ctrl+click to disconnect`,
|
||||||
|
isVerified: true,
|
||||||
|
dataSource: "real"
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
tooltip: `${baseTooltip} - Estimated (connection establishing...)
|
||||||
|
Right-click or Ctrl+click to disconnect`,
|
||||||
|
isVerified: false,
|
||||||
|
dataSource: "estimated"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const securityDetails = getSecurityIndicatorDetails();
|
||||||
|
React.useEffect(() => {
|
||||||
|
window.debugHeaderSecurity = () => {
|
||||||
|
console.log("\u{1F50D} Header Security Debug:", {
|
||||||
|
realSecurityLevel,
|
||||||
|
lastSecurityUpdate,
|
||||||
|
isConnected,
|
||||||
|
webrtcManagerProp: !!webrtcManager,
|
||||||
|
windowWebrtcManager: !!window.webrtcManager,
|
||||||
|
cryptoUtils: !!window.EnhancedSecureCryptoUtils,
|
||||||
|
displaySecurityLevel,
|
||||||
|
securityDetails
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return () => {
|
||||||
|
delete window.debugHeaderSecurity;
|
||||||
|
};
|
||||||
|
}, [realSecurityLevel, lastSecurityUpdate, isConnected, webrtcManager, displaySecurityLevel, securityDetails]);
|
||||||
|
return React.createElement("header", {
|
||||||
|
className: "header-minimal sticky top-0 z-50"
|
||||||
|
}, [
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "container",
|
||||||
|
className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"
|
||||||
|
}, [
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "content",
|
||||||
|
className: "flex items-center justify-between h-16"
|
||||||
|
}, [
|
||||||
|
// Logo and Title
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "logo-section",
|
||||||
|
className: "flex items-center space-x-2 sm:space-x-3"
|
||||||
|
}, [
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "logo",
|
||||||
|
className: "icon-container w-8 h-8 sm:w-10 sm:h-10"
|
||||||
|
}, [
|
||||||
|
React.createElement("i", {
|
||||||
|
className: "fas fa-shield-halved accent-orange text-sm sm:text-base"
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "title-section"
|
||||||
|
}, [
|
||||||
|
React.createElement("h1", {
|
||||||
|
key: "title",
|
||||||
|
className: "text-lg sm:text-xl font-semibold text-primary"
|
||||||
|
}, "SecureBit.chat"),
|
||||||
|
React.createElement("p", {
|
||||||
|
key: "subtitle",
|
||||||
|
className: "text-xs sm:text-sm text-muted hidden sm:block"
|
||||||
|
}, "End-to-end freedom v4.3.120")
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
// Status and Controls - Responsive
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "status-section",
|
||||||
|
className: "flex items-center space-x-2 sm:space-x-3"
|
||||||
|
}, [
|
||||||
|
displaySecurityLevel && React.createElement("div", {
|
||||||
|
key: "security-level",
|
||||||
|
className: "hidden md:flex items-center space-x-2 cursor-pointer hover:opacity-80 transition-opacity duration-200",
|
||||||
|
onClick: handleSecurityClick,
|
||||||
|
onContextMenu: (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (onDisconnect && typeof onDisconnect === "function") {
|
||||||
|
onDisconnect();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: securityDetails.tooltip
|
||||||
|
}, [
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "security-icon",
|
||||||
|
className: `w-6 h-6 rounded-full flex items-center justify-center relative ${displaySecurityLevel.color === "green" ? "bg-green-500/20" : displaySecurityLevel.color === "orange" ? "bg-orange-500/20" : displaySecurityLevel.color === "yellow" ? "bg-yellow-500/20" : "bg-red-500/20"} ${securityDetails.isVerified ? "" : "animate-pulse"}`
|
||||||
|
}, [
|
||||||
|
React.createElement("i", {
|
||||||
|
className: `fas fa-shield-alt text-xs ${displaySecurityLevel.color === "green" ? "text-green-400" : displaySecurityLevel.color === "orange" ? "text-orange-400" : displaySecurityLevel.color === "yellow" ? "text-yellow-400" : "text-red-400"}`
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "security-info",
|
||||||
|
className: "flex flex-col"
|
||||||
|
}, [
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "security-level-text",
|
||||||
|
className: "text-xs font-medium text-primary flex items-center space-x-1"
|
||||||
|
}, [
|
||||||
|
React.createElement("span", {}, `${displaySecurityLevel.level} (${displaySecurityLevel.score}%)`)
|
||||||
|
]),
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
key: "security-details",
|
||||||
|
className: "text-xs text-muted mt-1 hidden lg:block"
|
||||||
|
},
|
||||||
|
securityDetails.dataSource === "real" ? `${displaySecurityLevel.passedChecks || 0}/${displaySecurityLevel.totalChecks || 0} tests` : displaySecurityLevel.details || `Stage ${displaySecurityLevel.stage || 1}`
|
||||||
|
),
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "security-progress",
|
||||||
|
className: "w-16 h-1 bg-gray-600 rounded-full overflow-hidden"
|
||||||
|
}, [
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "progress-bar",
|
||||||
|
className: `h-full transition-all duration-500 ${displaySecurityLevel.color === "green" ? "bg-green-400" : displaySecurityLevel.color === "orange" ? "bg-orange-400" : displaySecurityLevel.color === "yellow" ? "bg-yellow-400" : "bg-red-400"}`,
|
||||||
|
style: { width: `${displaySecurityLevel.score}%` }
|
||||||
|
})
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
// Mobile Security Indicator
|
||||||
|
displaySecurityLevel && React.createElement("div", {
|
||||||
|
key: "mobile-security",
|
||||||
|
className: "md:hidden flex items-center"
|
||||||
|
}, [
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "mobile-security-icon",
|
||||||
|
className: `w-8 h-8 rounded-full flex items-center justify-center cursor-pointer hover:opacity-80 transition-opacity duration-200 relative ${displaySecurityLevel.color === "green" ? "bg-green-500/20" : displaySecurityLevel.color === "orange" ? "bg-orange-500/20" : displaySecurityLevel.color === "yellow" ? "bg-yellow-500/20" : "bg-red-500/20"} ${securityDetails.isVerified ? "" : "animate-pulse"}`,
|
||||||
|
title: securityDetails.tooltip,
|
||||||
|
onClick: handleSecurityClick,
|
||||||
|
onContextMenu: (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (onDisconnect && typeof onDisconnect === "function") {
|
||||||
|
onDisconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
React.createElement("i", {
|
||||||
|
className: `fas fa-shield-alt text-sm ${displaySecurityLevel.color === "green" ? "text-green-400" : displaySecurityLevel.color === "orange" ? "text-orange-400" : displaySecurityLevel.color === "yellow" ? "text-yellow-400" : "text-red-400"}`
|
||||||
|
})
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
// Status Badge
|
||||||
|
React.createElement("div", {
|
||||||
|
key: "status-badge",
|
||||||
|
className: `px-2 sm:px-3 py-1.5 rounded-lg border ${config.badgeClass} flex items-center space-x-1 sm:space-x-2`
|
||||||
|
}, [
|
||||||
|
React.createElement("span", {
|
||||||
|
key: "status-dot",
|
||||||
|
className: `status-dot ${config.className}`
|
||||||
|
}),
|
||||||
|
React.createElement("span", {
|
||||||
|
key: "status-text",
|
||||||
|
className: "text-xs sm:text-sm font-medium"
|
||||||
|
}, config.text)
|
||||||
|
]),
|
||||||
|
// Disconnect Button
|
||||||
|
isConnected && React.createElement("button", {
|
||||||
|
key: "disconnect-btn",
|
||||||
|
onClick: onDisconnect,
|
||||||
|
className: "p-1.5 sm:px-3 sm:py-1.5 bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/20 rounded-lg transition-all duration-200 text-sm"
|
||||||
|
}, [
|
||||||
|
React.createElement("i", {
|
||||||
|
className: "fas fa-power-off sm:mr-2"
|
||||||
|
}),
|
||||||
|
React.createElement("span", {
|
||||||
|
className: "hidden sm:inline"
|
||||||
|
}, "Disconnect")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
window.EnhancedMinimalHeader = EnhancedMinimalHeader;
|
||||||
Reference in New Issue
Block a user