Remove experimental Bluetooth key transfer module
- Deleted BluetoothKeyTransfer.js and related classes - Removed BluetoothKeyTransfer.jsx UI component - Cleaned up Bluetooth imports from app-boot.js and bootstrap-modules.js - Removed Bluetooth buttons and handlers from main app - Eliminated all Bluetooth functionality due to Web Bluetooth API limitations - Browsers cannot create GATT servers or advertise devices - Reduced bundle size by ~78KB - Application now focuses on supported browser technologies (QR codes, manual key exchange, WebRTC)
This commit is contained in:
-232
@@ -592,26 +592,6 @@
|
||||
})(),
|
||||
className: "flex-1 px-3 py-2 bg-orange-500/10 hover:bg-orange-500/20 text-orange-400 border border-orange-500/20 rounded text-sm font-medium"
|
||||
}, 'Copy invitation code'),
|
||||
React.createElement('button', {
|
||||
key: 'bluetooth-offer',
|
||||
onClick: () => {
|
||||
try {
|
||||
document.dispatchEvent(new CustomEvent('open-bluetooth-transfer', {
|
||||
detail: {
|
||||
role: 'initiator',
|
||||
offerData: offerData
|
||||
}
|
||||
}));
|
||||
} catch {}
|
||||
},
|
||||
className: "flex-1 px-3 py-2 bg-blue-500/10 hover:bg-blue-500/20 text-blue-400 border border-blue-500/20 rounded text-sm font-medium transition-all duration-200"
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
key: 'icon',
|
||||
className: 'fas fa-bluetooth mr-2'
|
||||
}),
|
||||
'Send via Bluetooth'
|
||||
])
|
||||
]),
|
||||
showQRCode && qrCodeUrl && React.createElement('div', {
|
||||
key: 'qr-container',
|
||||
@@ -766,17 +746,6 @@
|
||||
}),
|
||||
'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', {
|
||||
key: 'input',
|
||||
@@ -890,17 +859,6 @@
|
||||
}),
|
||||
'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', {
|
||||
key: 'process-btn',
|
||||
onClick: onCreateAnswer,
|
||||
@@ -1453,25 +1411,7 @@
|
||||
const [showQRScanner, setShowQRScanner] = 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);
|
||||
const [bluetoothOfferData, setBluetoothOfferData] = React.useState(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
const openBt = (e) => {
|
||||
try {
|
||||
const role = e?.detail?.role || null;
|
||||
const offerData = e?.detail?.offerData || null;
|
||||
setBluetoothAutoRole(role);
|
||||
setBluetoothOfferData(offerData);
|
||||
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 [securityLevel, setSecurityLevel] = React.useState(null);
|
||||
const [sessionTimeLeft, setSessionTimeLeft] = React.useState(0);
|
||||
@@ -2777,165 +2717,8 @@
|
||||
}
|
||||
};
|
||||
|
||||
// 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 (answerData, deviceId) => {
|
||||
try {
|
||||
console.log('Bluetooth auto connection - answer received:', answerData);
|
||||
|
||||
// Set the answer data
|
||||
setAnswerData(answerData);
|
||||
|
||||
// Process the answer to establish connection
|
||||
if (webrtcManagerRef.current) {
|
||||
await webrtcManagerRef.current.processAnswer(answerData);
|
||||
|
||||
setMessages(prev => [...prev, {
|
||||
message: '🔵 Bluetooth connection established successfully!',
|
||||
type: 'success',
|
||||
id: Date.now(),
|
||||
timestamp: Date.now()
|
||||
}]);
|
||||
|
||||
// Update connection status
|
||||
setConnectionStatus('connected');
|
||||
|
||||
// Close Bluetooth transfer modal
|
||||
setShowBluetoothTransfer(false);
|
||||
|
||||
// Show verification step
|
||||
setShowVerification(true);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to process auto connection:', error);
|
||||
setMessages(prev => [...prev, {
|
||||
message: 'Failed to process auto connection: ' + error.message,
|
||||
type: 'error',
|
||||
id: Date.now(),
|
||||
timestamp: Date.now()
|
||||
}]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateOffer = async () => {
|
||||
try {
|
||||
@@ -3515,8 +3298,6 @@
|
||||
setQrCodeUrl('');
|
||||
setShowQRScanner(false);
|
||||
setShowQRScannerModal(false);
|
||||
setShowBluetoothTransfer(false);
|
||||
setBluetoothAutoRole(null);
|
||||
|
||||
// Clear messages
|
||||
setMessages([]);
|
||||
@@ -3830,19 +3611,6 @@
|
||||
])
|
||||
]),
|
||||
|
||||
// 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),
|
||||
role: bluetoothAutoRole,
|
||||
offerData: bluetoothOfferData
|
||||
})
|
||||
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -1,629 +0,0 @@
|
||||
/**
|
||||
* 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,
|
||||
role = null,
|
||||
offerData = null
|
||||
}) => {
|
||||
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([]);
|
||||
const [isInitializing, setIsInitializing] = React.useState(false);
|
||||
|
||||
// Initialize Bluetooth manager
|
||||
React.useEffect(() => {
|
||||
if (isVisible && !bluetoothManager) {
|
||||
// Don't initialize immediately, wait for user action
|
||||
addLog('Bluetooth modal opened. Click a button to start.');
|
||||
}
|
||||
}, [isVisible]);
|
||||
|
||||
// Auto-start exchange process when role and offerData are provided
|
||||
React.useEffect(() => {
|
||||
if (isVisible && bluetoothManager && role && offerData) {
|
||||
if (role === 'initiator') {
|
||||
// Start advertising and waiting for connection
|
||||
addLog('Starting Bluetooth key exchange as initiator...');
|
||||
bluetoothManager.startAdvertising();
|
||||
} else if (role === 'responder') {
|
||||
// Start scanning for initiator
|
||||
addLog('Starting Bluetooth key exchange as responder...');
|
||||
bluetoothManager.startScanning();
|
||||
}
|
||||
}
|
||||
}, [isVisible, bluetoothManager, role, offerData]);
|
||||
|
||||
// Cleanup on unmount
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
if (bluetoothManager) {
|
||||
bluetoothManager.cleanup();
|
||||
}
|
||||
};
|
||||
}, [bluetoothManager]);
|
||||
|
||||
const initializeBluetooth = async () => {
|
||||
setIsInitializing(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// Check Bluetooth support first
|
||||
if (!navigator.bluetooth) {
|
||||
throw new Error('Bluetooth is not supported in this browser');
|
||||
}
|
||||
|
||||
addLog('Bluetooth is supported, initializing manager...');
|
||||
|
||||
// Check if BluetoothKeyTransfer class is available
|
||||
if (!window.BluetoothKeyTransfer && !window.createBluetoothKeyTransfer) {
|
||||
throw new Error('BluetoothKeyTransfer class not loaded. Please refresh the page.');
|
||||
}
|
||||
|
||||
// Additional check - make sure it's a constructor function
|
||||
if (window.BluetoothKeyTransfer && typeof window.BluetoothKeyTransfer !== 'function') {
|
||||
throw new Error('BluetoothKeyTransfer is not a constructor function. Type: ' + typeof window.BluetoothKeyTransfer);
|
||||
}
|
||||
|
||||
// Initialize Bluetooth manager first
|
||||
const manager = window.createBluetoothKeyTransfer ?
|
||||
window.createBluetoothKeyTransfer(webrtcManager, handleStatusChange, handleKeyReceived, handleError, handleAutoConnection, offerData) :
|
||||
new window.BluetoothKeyTransfer(webrtcManager, handleStatusChange, handleKeyReceived, handleError, handleAutoConnection, offerData);
|
||||
|
||||
setBluetoothManager(manager);
|
||||
|
||||
// Check support after initialization
|
||||
setTimeout(() => {
|
||||
setIsSupported(manager.isSupported);
|
||||
setIsAvailable(manager.isAvailable);
|
||||
setIsInitializing(false);
|
||||
addLog('Bluetooth manager initialized successfully');
|
||||
|
||||
if (!manager.isSupported) {
|
||||
setError('Bluetooth is not supported in this browser');
|
||||
} else if (!manager.isAvailable) {
|
||||
setError('Bluetooth is not available. Please enable Bluetooth on your device.');
|
||||
}
|
||||
}, 100);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize Bluetooth manager:', error);
|
||||
setError('Failed to initialize Bluetooth: ' + error.message);
|
||||
setIsInitializing(false);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
// Initialize Bluetooth manager if not already done
|
||||
if (!bluetoothManager) {
|
||||
await initializeBluetooth();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Initialize Bluetooth manager if not already done
|
||||
if (!bluetoothManager) {
|
||||
await initializeBluetooth();
|
||||
}
|
||||
|
||||
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)]'
|
||||
}, [
|
||||
// Loading state
|
||||
isInitializing && React.createElement('div', {
|
||||
key: 'loading',
|
||||
className: 'flex items-center justify-center py-8'
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'spinner',
|
||||
className: 'animate-spin rounded-full h-8 w-8 border-b-2 border-blue-400 mr-3'
|
||||
}),
|
||||
React.createElement('span', {
|
||||
key: 'text',
|
||||
className: 'text-white text-lg'
|
||||
}, 'Initializing Bluetooth...')
|
||||
]),
|
||||
|
||||
// Main content (only show when not initializing)
|
||||
!isInitializing && React.createElement('div', {
|
||||
key: 'main-content'
|
||||
}, [
|
||||
// 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')
|
||||
])
|
||||
])
|
||||
]),
|
||||
|
||||
// Info Section
|
||||
React.createElement('div', {
|
||||
key: 'info',
|
||||
className: 'p-4 bg-blue-900/20 border border-blue-500/30 rounded-lg'
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'header',
|
||||
className: 'flex items-center space-x-2 mb-2'
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
key: 'icon',
|
||||
className: 'fas fa-info-circle text-blue-400'
|
||||
}),
|
||||
React.createElement('h4', {
|
||||
key: 'title',
|
||||
className: 'text-blue-400 font-medium'
|
||||
}, 'How Bluetooth Key Exchange Works')
|
||||
]),
|
||||
React.createElement('div', {
|
||||
key: 'content',
|
||||
className: 'text-sm text-gray-300 space-y-2'
|
||||
}, [
|
||||
React.createElement('p', {
|
||||
key: 'p1'
|
||||
}, '• One device scans for nearby devices (responder)'),
|
||||
React.createElement('p', {
|
||||
key: 'p2'
|
||||
}, '• The other device waits for connection (initiator)'),
|
||||
React.createElement('p', {
|
||||
key: 'p3'
|
||||
}, '• Keys are exchanged automatically once connected'),
|
||||
React.createElement('p', {
|
||||
key: 'p4',
|
||||
className: 'text-blue-300 font-medium'
|
||||
}, 'Note: Both devices must be close to each other (within Bluetooth range)')
|
||||
])
|
||||
]),
|
||||
|
||||
// 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: !bluetoothManager,
|
||||
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: !bluetoothManager,
|
||||
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)
|
||||
])
|
||||
)
|
||||
)
|
||||
])
|
||||
]),
|
||||
]), // Close main-content div
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
/**
|
||||
* Web Bluetooth API Examples
|
||||
*
|
||||
* This file contains examples of how to use the Web Bluetooth API
|
||||
* for device discovery and connection.
|
||||
*/
|
||||
|
||||
// Example 1: Basic device discovery
|
||||
async function discoverBluetoothDevices() {
|
||||
try {
|
||||
console.log('Requesting Bluetooth device...');
|
||||
|
||||
const device = await navigator.bluetooth.requestDevice({
|
||||
filters: [
|
||||
{ services: ['battery_service'] },
|
||||
{ services: ['device_information'] },
|
||||
{ name: 'MyDevice' },
|
||||
{ namePrefix: 'My' }
|
||||
],
|
||||
optionalServices: ['battery_service', 'device_information']
|
||||
});
|
||||
|
||||
console.log('Device selected:', device.name);
|
||||
console.log('Device ID:', device.id);
|
||||
|
||||
return device;
|
||||
} catch (error) {
|
||||
console.error('Error discovering device:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Example 2: Connect to device and read characteristics
|
||||
async function connectAndReadData(device) {
|
||||
try {
|
||||
console.log('Connecting to device...');
|
||||
const server = await device.gatt.connect();
|
||||
|
||||
console.log('Getting primary service...');
|
||||
const service = await server.getPrimaryService('battery_service');
|
||||
|
||||
console.log('Getting characteristic...');
|
||||
const characteristic = await service.getCharacteristic('battery_level');
|
||||
|
||||
console.log('Reading value...');
|
||||
const value = await characteristic.readValue();
|
||||
const batteryLevel = value.getUint8(0);
|
||||
|
||||
console.log('Battery level:', batteryLevel + '%');
|
||||
|
||||
return batteryLevel;
|
||||
} catch (error) {
|
||||
console.error('Error connecting to device:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Example 3: Listen for characteristic changes
|
||||
async function listenForChanges(device) {
|
||||
try {
|
||||
const server = await device.gatt.connect();
|
||||
const service = await server.getPrimaryService('battery_service');
|
||||
const characteristic = await service.getCharacteristic('battery_level');
|
||||
|
||||
// Start notifications
|
||||
await characteristic.startNotifications();
|
||||
|
||||
// Listen for changes
|
||||
characteristic.addEventListener('characteristicvaluechanged', (event) => {
|
||||
const value = event.target.value;
|
||||
const batteryLevel = value.getUint8(0);
|
||||
console.log('Battery level changed:', batteryLevel + '%');
|
||||
});
|
||||
|
||||
console.log('Listening for battery level changes...');
|
||||
} catch (error) {
|
||||
console.error('Error setting up notifications:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Example 4: Write data to characteristic
|
||||
async function writeData(device, data) {
|
||||
try {
|
||||
const server = await device.gatt.connect();
|
||||
const service = await server.getPrimaryService('custom_service');
|
||||
const characteristic = await service.getCharacteristic('custom_characteristic');
|
||||
|
||||
// Convert data to ArrayBuffer
|
||||
const buffer = new TextEncoder().encode(data);
|
||||
|
||||
// Write data
|
||||
await characteristic.writeValue(buffer);
|
||||
console.log('Data written successfully');
|
||||
} catch (error) {
|
||||
console.error('Error writing data:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Example 5: Complete workflow
|
||||
async function completeBluetoothWorkflow() {
|
||||
try {
|
||||
// Step 1: Discover device
|
||||
const device = await discoverBluetoothDevices();
|
||||
|
||||
// Step 2: Connect and read data
|
||||
const batteryLevel = await connectAndReadData(device);
|
||||
|
||||
// Step 3: Listen for changes
|
||||
await listenForChanges(device);
|
||||
|
||||
// Step 4: Write data (if needed)
|
||||
// await writeData(device, 'Hello Bluetooth!');
|
||||
|
||||
console.log('Bluetooth workflow completed successfully');
|
||||
return { device, batteryLevel };
|
||||
} catch (error) {
|
||||
console.error('Bluetooth workflow failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Export functions for use in other modules
|
||||
export {
|
||||
discoverBluetoothDevices,
|
||||
connectAndReadData,
|
||||
listenForChanges,
|
||||
writeData,
|
||||
completeBluetoothWorkflow
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
import { EnhancedSecureCryptoUtils } from '../crypto/EnhancedSecureCryptoUtils.js';
|
||||
import { EnhancedSecureWebRTCManager } from '../network/EnhancedSecureWebRTCManager.js';
|
||||
import { EnhancedSecureFileTransfer } from '../transfer/EnhancedSecureFileTransfer.js';
|
||||
import BluetoothKeyTransfer from '../transfer/BluetoothKeyTransfer.js';
|
||||
|
||||
// Import UI components (side-effect: they attach themselves to window.*)
|
||||
import '../components/ui/Header.jsx';
|
||||
@@ -12,19 +11,11 @@ import '../components/ui/Testimonials.jsx';
|
||||
import '../components/ui/ComparisonTable.jsx';
|
||||
import '../components/ui/Roadmap.jsx';
|
||||
import '../components/ui/FileTransfer.jsx';
|
||||
import '../components/ui/BluetoothKeyTransfer.jsx';
|
||||
|
||||
// Expose to global for legacy usage inside app code
|
||||
window.EnhancedSecureCryptoUtils = EnhancedSecureCryptoUtils;
|
||||
window.EnhancedSecureWebRTCManager = EnhancedSecureWebRTCManager;
|
||||
window.EnhancedSecureFileTransfer = EnhancedSecureFileTransfer;
|
||||
window.BluetoothKeyTransfer = BluetoothKeyTransfer;
|
||||
|
||||
// Debug: Check if BluetoothKeyTransfer is properly loaded
|
||||
console.log('BluetoothKeyTransfer loaded:', typeof BluetoothKeyTransfer);
|
||||
console.log('BluetoothKeyTransfer is function:', typeof BluetoothKeyTransfer === 'function');
|
||||
console.log('BluetoothKeyTransfer on window:', typeof window.BluetoothKeyTransfer);
|
||||
console.log('createBluetoothKeyTransfer on window:', typeof window.createBluetoothKeyTransfer);
|
||||
|
||||
// Mount application once DOM and modules are ready
|
||||
const start = () => {
|
||||
|
||||
Vendored
+16
-24
@@ -1,13 +1,12 @@
|
||||
// Temporary bootstrap that still uses eval for JSX components fetched as text.
|
||||
// Next step is to replace this with proper ESM imports of prebuilt JS.
|
||||
// Bootstrap that loads modules using dynamic ESM imports.
|
||||
// This approach is CSP-compliant and doesn't use eval().
|
||||
(async () => {
|
||||
try {
|
||||
const timestamp = Date.now();
|
||||
const [cryptoModule, webrtcModule, fileTransferModule, bluetoothModule] = await Promise.all([
|
||||
const [cryptoModule, webrtcModule, fileTransferModule] = await Promise.all([
|
||||
import(`../crypto/EnhancedSecureCryptoUtils.js?v=${timestamp}`),
|
||||
import(`../network/EnhancedSecureWebRTCManager.js?v=${timestamp}`),
|
||||
import(`../transfer/EnhancedSecureFileTransfer.js?v=${timestamp}`),
|
||||
import(`../transfer/BluetoothKeyTransfer.js?v=${timestamp}`),
|
||||
]);
|
||||
|
||||
const { EnhancedSecureCryptoUtils } = cryptoModule;
|
||||
@@ -16,29 +15,22 @@
|
||||
window.EnhancedSecureWebRTCManager = EnhancedSecureWebRTCManager;
|
||||
const { EnhancedSecureFileTransfer } = fileTransferModule;
|
||||
window.EnhancedSecureFileTransfer = EnhancedSecureFileTransfer;
|
||||
const { default: BluetoothKeyTransfer } = bluetoothModule;
|
||||
window.BluetoothKeyTransfer = BluetoothKeyTransfer;
|
||||
|
||||
async function loadReactComponent(path) {
|
||||
const response = await fetch(`${path}?v=${timestamp}`);
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
const code = await response.text();
|
||||
// eslint-disable-next-line no-eval
|
||||
eval(code);
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
loadReactComponent('../components/ui/Header.jsx'),
|
||||
loadReactComponent('../components/ui/DownloadApps.jsx'),
|
||||
loadReactComponent('../components/ui/ComparisonTable.jsx'),
|
||||
loadReactComponent('../components/ui/UniqueFeatureSlider.jsx'),
|
||||
loadReactComponent('../components/ui/SecurityFeatures.jsx'),
|
||||
loadReactComponent('../components/ui/Testimonials.jsx'),
|
||||
loadReactComponent('../components/ui/Roadmap.jsx'),
|
||||
loadReactComponent('../components/ui/FileTransfer.jsx'),
|
||||
loadReactComponent('../components/ui/BluetoothKeyTransfer.jsx'),
|
||||
// Load React components using dynamic imports instead of eval
|
||||
const componentModules = await Promise.all([
|
||||
import(`../components/ui/Header.jsx?v=${timestamp}`),
|
||||
import(`../components/ui/DownloadApps.jsx?v=${timestamp}`),
|
||||
import(`../components/ui/ComparisonTable.jsx?v=${timestamp}`),
|
||||
import(`../components/ui/UniqueFeatureSlider.jsx?v=${timestamp}`),
|
||||
import(`../components/ui/SecurityFeatures.jsx?v=${timestamp}`),
|
||||
import(`../components/ui/Testimonials.jsx?v=${timestamp}`),
|
||||
import(`../components/ui/Roadmap.jsx?v=${timestamp}`),
|
||||
import(`../components/ui/FileTransfer.jsx?v=${timestamp}`),
|
||||
]);
|
||||
|
||||
// Components are automatically registered on window by their respective modules
|
||||
console.log('✅ All React components loaded successfully');
|
||||
|
||||
if (typeof window.initializeApp === 'function') {
|
||||
window.initializeApp();
|
||||
} else {
|
||||
|
||||
@@ -1,816 +0,0 @@
|
||||
/**
|
||||
* 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, offerData = null) {
|
||||
this.webrtcManager = webrtcManager;
|
||||
this.onStatusChange = onStatusChange;
|
||||
this.onKeyReceived = onKeyReceived;
|
||||
this.onError = onError;
|
||||
this.onAutoConnection = onAutoConnection;
|
||||
this.offerData = offerData;
|
||||
|
||||
// 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 = null, 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 });
|
||||
|
||||
// Use offerData if available, otherwise use provided publicKey
|
||||
const keyToAdvertise = this.offerData || publicKey;
|
||||
|
||||
// For web browsers, we can't actually advertise BLE
|
||||
// Instead, we'll wait for devices to connect to us
|
||||
// This is a limitation of the Web Bluetooth API
|
||||
|
||||
this.isAdvertising = true;
|
||||
this.onStatusChange?.('advertising_active', { deviceName });
|
||||
|
||||
this.log('info', 'Bluetooth advertising mode activated (waiting for connections)');
|
||||
this.log('info', 'Note: Web browsers cannot actively advertise BLE. Waiting for incoming connections...');
|
||||
|
||||
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);
|
||||
|
||||
// Auto-start exchange process based on role
|
||||
if (this.offerData) {
|
||||
// We are initiator - send offer immediately
|
||||
this.log('info', 'Auto-sending offer as initiator...');
|
||||
await this.sendConnectionData({
|
||||
type: 'offer',
|
||||
data: this.offerData,
|
||||
timestamp: Date.now()
|
||||
}, device.id);
|
||||
} else {
|
||||
// We are responder - wait for offer
|
||||
this.log('info', 'Waiting for offer as responder...');
|
||||
this.onStatusChange?.('waiting_for_offer', { deviceId: device.id });
|
||||
}
|
||||
|
||||
} 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);
|
||||
|
||||
// Auto-handle offer if we are responder
|
||||
if (connectionData.type === 'offer' && !this.offerData) {
|
||||
this.log('info', 'Auto-creating answer for received offer...');
|
||||
try {
|
||||
// Create answer using WebRTC manager
|
||||
const answer = await this.webrtcManager.createSecureAnswer(connectionData.data);
|
||||
|
||||
// Send answer back
|
||||
await this.sendConnectionData({
|
||||
type: 'answer',
|
||||
data: answer,
|
||||
timestamp: Date.now()
|
||||
}, deviceId);
|
||||
|
||||
this.log('info', 'Answer sent successfully');
|
||||
this.onStatusChange?.('answer_sent', { deviceId });
|
||||
} catch (error) {
|
||||
this.log('error', 'Failed to create answer:', error);
|
||||
this.onError?.(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-handle answer if we are initiator
|
||||
if (connectionData.type === 'answer' && this.offerData) {
|
||||
this.log('info', 'Answer received, establishing connection...');
|
||||
this.onStatusChange?.('answer_received', { deviceId, data: connectionData });
|
||||
|
||||
// Trigger auto-connection callback
|
||||
this.onAutoConnection?.(connectionData.data, deviceId);
|
||||
}
|
||||
|
||||
// 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
|
||||
export { BluetoothKeyTransfer };
|
||||
export default BluetoothKeyTransfer;
|
||||
|
||||
// Also expose on window for global access
|
||||
if (typeof window !== 'undefined') {
|
||||
window.BluetoothKeyTransfer = BluetoothKeyTransfer;
|
||||
// Also create a factory function for easier usage
|
||||
window.createBluetoothKeyTransfer = function(...args) {
|
||||
return new BluetoothKeyTransfer(...args);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user