wip(encryption): experimental support for encrypted file transfer via chunks
Added an early implementation of secure file transfer using chunk-based encryption. Files are split into encrypted chunks and transmitted over the chat channel. This feature is still under active development and requires further changes and testing.
This commit is contained in:
146
index.html
146
index.html
@@ -2321,10 +2321,11 @@
|
|||||||
keyFingerprint,
|
keyFingerprint,
|
||||||
isVerified,
|
isVerified,
|
||||||
chatMessagesRef,
|
chatMessagesRef,
|
||||||
scrollToBottom
|
scrollToBottom,
|
||||||
|
webrtcManager
|
||||||
}) => {
|
}) => {
|
||||||
const [showScrollButton, setShowScrollButton] = React.useState(false);
|
const [showScrollButton, setShowScrollButton] = React.useState(false);
|
||||||
|
const [showFileTransfer, setShowFileTransfer] = React.useState(false);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (chatMessagesRef.current && messages.length > 0) {
|
if (chatMessagesRef.current && messages.length > 0) {
|
||||||
const { scrollTop, scrollHeight, clientHeight } = chatMessagesRef.current;
|
const { scrollTop, scrollHeight, clientHeight } = chatMessagesRef.current;
|
||||||
@@ -2472,7 +2473,28 @@
|
|||||||
className: 'fas fa-chevron-down'
|
className: 'fas fa-chevron-down'
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'file-section',
|
||||||
|
className: "file-transfer-section max-w-4xl mx-auto p-4 border-t border-gray-500/10"
|
||||||
|
}, [
|
||||||
|
React.createElement('button', {
|
||||||
|
key: 'toggle-files',
|
||||||
|
onClick: () => setShowFileTransfer(!showFileTransfer),
|
||||||
|
className: `flex items-center text-sm text-secondary hover:text-primary transition-colors ${showFileTransfer ? 'mb-4' : ''}`
|
||||||
|
}, [
|
||||||
|
React.createElement('i', {
|
||||||
|
key: 'icon',
|
||||||
|
className: `fas fa-${showFileTransfer ? 'chevron-up' : 'paperclip'} mr-2`
|
||||||
|
}),
|
||||||
|
showFileTransfer ? 'Hide file transfer' : 'Send files'
|
||||||
|
]),
|
||||||
|
|
||||||
|
showFileTransfer && React.createElement(FileTransferComponent, {
|
||||||
|
key: 'file-transfer',
|
||||||
|
webrtcManager: webrtcManager,
|
||||||
|
isConnected: isVerified
|
||||||
|
})
|
||||||
|
]),
|
||||||
// Enhanced Chat Input Area
|
// Enhanced Chat Input Area
|
||||||
React.createElement('div', {
|
React.createElement('div', {
|
||||||
key: 'chat-input',
|
key: 'chat-input',
|
||||||
@@ -2646,6 +2668,9 @@
|
|||||||
}, [sessionManager]);
|
}, [sessionManager]);
|
||||||
|
|
||||||
const webrtcManagerRef = React.useRef(null);
|
const webrtcManagerRef = React.useRef(null);
|
||||||
|
// Expose for modules/UI that run outside this closure (e.g., inline handlers)
|
||||||
|
// Safe because it's a ref object and we maintain it centrally here
|
||||||
|
window.webrtcManagerRef = webrtcManagerRef;
|
||||||
|
|
||||||
const addMessageWithAutoScroll = (message, type) => {
|
const addMessageWithAutoScroll = (message, type) => {
|
||||||
const newMessage = {
|
const newMessage = {
|
||||||
@@ -2880,30 +2905,17 @@
|
|||||||
updateSecurityLevel().catch(console.error);
|
updateSecurityLevel().catch(console.error);
|
||||||
}
|
}
|
||||||
} else if (status === 'disconnected') {
|
} else if (status === 'disconnected') {
|
||||||
if (sessionManager && sessionManager.hasActiveSession()) {
|
// При ошибках соединения не сбрасываем сессию полностью
|
||||||
sessionManager.resetSession();
|
// только обновляем статус соединения
|
||||||
setSessionTimeLeft(0);
|
setConnectionStatus('disconnected');
|
||||||
setHasActiveSession(false);
|
|
||||||
}
|
|
||||||
document.dispatchEvent(new CustomEvent('peer-disconnect'));
|
|
||||||
|
|
||||||
// Complete UI reset on disconnect
|
|
||||||
setKeyFingerprint('');
|
|
||||||
setVerificationCode('');
|
|
||||||
setSecurityLevel(null);
|
|
||||||
setIsVerified(false);
|
setIsVerified(false);
|
||||||
setShowVerification(false);
|
setShowVerification(false);
|
||||||
setConnectionStatus('disconnected');
|
|
||||||
|
|
||||||
setMessages([]);
|
// Не очищаем консоль и не сбрасываем сообщения
|
||||||
|
// чтобы пользователь мог видеть ошибки
|
||||||
|
|
||||||
if (typeof console.clear === 'function') {
|
// Не сбрасываем сессию при ошибках соединения
|
||||||
console.clear();
|
// только при намеренном отключении
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
setSessionManager(null);
|
|
||||||
}, 1000);
|
|
||||||
} else if (status === 'peer_disconnected') {
|
} else if (status === 'peer_disconnected') {
|
||||||
if (sessionManager && sessionManager.hasActiveSession()) {
|
if (sessionManager && sessionManager.hasActiveSession()) {
|
||||||
sessionManager.resetSession();
|
sessionManager.resetSession();
|
||||||
@@ -2922,11 +2934,12 @@
|
|||||||
setShowVerification(false);
|
setShowVerification(false);
|
||||||
setConnectionStatus('disconnected');
|
setConnectionStatus('disconnected');
|
||||||
|
|
||||||
setMessages([]);
|
// Не очищаем сообщения и консоль при отключении пира
|
||||||
|
// чтобы сохранить историю соединения
|
||||||
if (typeof console.clear === 'function') {
|
// setMessages([]);
|
||||||
console.clear();
|
// if (typeof console.clear === 'function') {
|
||||||
}
|
// console.clear();
|
||||||
|
// }
|
||||||
|
|
||||||
setSessionManager(null);
|
setSessionManager(null);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
@@ -3058,6 +3071,43 @@
|
|||||||
|
|
||||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||||
|
|
||||||
|
// Setup file transfer callbacks
|
||||||
|
if (webrtcManagerRef.current) {
|
||||||
|
webrtcManagerRef.current.setFileTransferCallbacks(
|
||||||
|
// Progress callback
|
||||||
|
(progress) => {
|
||||||
|
console.log('File progress:', progress);
|
||||||
|
},
|
||||||
|
|
||||||
|
// File received callback
|
||||||
|
(fileData) => {
|
||||||
|
// Auto-download received file
|
||||||
|
const url = URL.createObjectURL(fileData.fileBlob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = fileData.fileName;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
addMessageWithAutoScroll(`📥 Файл загружен: ${fileData.fileName}`, 'system');
|
||||||
|
},
|
||||||
|
|
||||||
|
// Error callback
|
||||||
|
(error) => {
|
||||||
|
// Более мягкая обработка ошибок файлового трансфера - не закрываем сессию
|
||||||
|
console.error('File transfer error:', error);
|
||||||
|
|
||||||
|
if (error.includes('Connection not ready')) {
|
||||||
|
addMessageWithAutoScroll(`⚠️ Ошибка передачи файла: соединение не готово. Попробуйте позже.`, 'system');
|
||||||
|
} else if (error.includes('File too large')) {
|
||||||
|
addMessageWithAutoScroll(`⚠️ Файл слишком большой. Максимальный размер: 100 МБ`, 'system');
|
||||||
|
} else {
|
||||||
|
addMessageWithAutoScroll(`❌ Ошибка передачи файла: ${error}`, 'system');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||||
@@ -3426,9 +3476,11 @@
|
|||||||
setOfferPassword('');
|
setOfferPassword('');
|
||||||
setAnswerPassword('');
|
setAnswerPassword('');
|
||||||
|
|
||||||
if (typeof console.clear === 'function') {
|
// Не очищаем консоль при очистке данных
|
||||||
console.clear();
|
// чтобы пользователь мог видеть ошибки
|
||||||
}
|
// if (typeof console.clear === 'function') {
|
||||||
|
// console.clear();
|
||||||
|
// }
|
||||||
|
|
||||||
// Cleanup pay-per-session state
|
// Cleanup pay-per-session state
|
||||||
if (sessionManager) {
|
if (sessionManager) {
|
||||||
@@ -3467,9 +3519,11 @@
|
|||||||
|
|
||||||
setMessages([]);
|
setMessages([]);
|
||||||
|
|
||||||
if (typeof console.clear === 'function') {
|
// Не очищаем консоль при отключении
|
||||||
console.clear();
|
// чтобы пользователь мог видеть ошибки
|
||||||
}
|
// if (typeof console.clear === 'function') {
|
||||||
|
// console.clear();
|
||||||
|
// }
|
||||||
|
|
||||||
document.dispatchEvent(new CustomEvent('peer-disconnect'));
|
document.dispatchEvent(new CustomEvent('peer-disconnect'));
|
||||||
|
|
||||||
@@ -3565,7 +3619,8 @@
|
|||||||
keyFingerprint: keyFingerprint,
|
keyFingerprint: keyFingerprint,
|
||||||
isVerified: isVerified,
|
isVerified: isVerified,
|
||||||
chatMessagesRef: chatMessagesRef,
|
chatMessagesRef: chatMessagesRef,
|
||||||
scrollToBottom: scrollToBottom
|
scrollToBottom: scrollToBottom,
|
||||||
|
webrtcManager: webrtcManagerRef.current
|
||||||
})
|
})
|
||||||
: React.createElement(EnhancedConnectionSetup, {
|
: React.createElement(EnhancedConnectionSetup, {
|
||||||
onCreateOffer: handleCreateOffer,
|
onCreateOffer: handleCreateOffer,
|
||||||
@@ -3629,10 +3684,11 @@
|
|||||||
try {
|
try {
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
|
|
||||||
const [cryptoModule, webrtcModule, paymentModule] = await Promise.all([
|
const [cryptoModule, webrtcModule, paymentModule, fileTransferModule] = await Promise.all([
|
||||||
import(`./src/crypto/EnhancedSecureCryptoUtils.js?v=${timestamp}`),
|
import(`./src/crypto/EnhancedSecureCryptoUtils.js?v=${timestamp}`),
|
||||||
import(`./src/network/EnhancedSecureWebRTCManager.js?v=${timestamp}`),
|
import(`./src/network/EnhancedSecureWebRTCManager.js?v=${timestamp}`),
|
||||||
import(`./src/session/PayPerSessionManager.js?v=${timestamp}`)
|
import(`./src/session/PayPerSessionManager.js?v=${timestamp}`),
|
||||||
|
import(`./src/transfer/EnhancedSecureFileTransfer.js?v=${timestamp}`)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { EnhancedSecureCryptoUtils } = cryptoModule;
|
const { EnhancedSecureCryptoUtils } = cryptoModule;
|
||||||
@@ -3644,6 +3700,9 @@
|
|||||||
const { PayPerSessionManager } = paymentModule;
|
const { PayPerSessionManager } = paymentModule;
|
||||||
window.PayPerSessionManager = PayPerSessionManager;
|
window.PayPerSessionManager = PayPerSessionManager;
|
||||||
|
|
||||||
|
const { EnhancedSecureFileTransfer } = fileTransferModule;
|
||||||
|
window.EnhancedSecureFileTransfer = EnhancedSecureFileTransfer;
|
||||||
|
|
||||||
async function loadReactComponent(path, componentName) {
|
async function loadReactComponent(path, componentName) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
@@ -3669,7 +3728,8 @@
|
|||||||
loadReactComponent('./src/components/ui/SessionTypeSelector.jsx', 'SessionTypeSelector'),
|
loadReactComponent('./src/components/ui/SessionTypeSelector.jsx', 'SessionTypeSelector'),
|
||||||
loadReactComponent('./src/components/ui/LightningPayment.jsx', 'LightningPayment'),
|
loadReactComponent('./src/components/ui/LightningPayment.jsx', 'LightningPayment'),
|
||||||
loadReactComponent('./src/components/ui/PaymentModal.jsx', 'PaymentModal'),
|
loadReactComponent('./src/components/ui/PaymentModal.jsx', 'PaymentModal'),
|
||||||
loadReactComponent('./src/components/ui/DownloadApps.jsx', 'DownloadApps')
|
loadReactComponent('./src/components/ui/DownloadApps.jsx', 'DownloadApps'),
|
||||||
|
loadReactComponent('./src/components/ui/FileTransfer.jsx', 'FileTransferComponent')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (typeof initializeApp === 'function') {
|
if (typeof initializeApp === 'function') {
|
||||||
@@ -3711,6 +3771,18 @@ document.addEventListener('session-activated', (event) => {
|
|||||||
if (window.forceUpdateHeader) {
|
if (window.forceUpdateHeader) {
|
||||||
window.forceUpdateHeader(event.detail.timeLeft, event.detail.sessionType);
|
window.forceUpdateHeader(event.detail.timeLeft, event.detail.sessionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify WebRTC Manager about session activation
|
||||||
|
if (window.webrtcManager && window.webrtcManager.handleSessionActivation) {
|
||||||
|
console.log('🔐 Notifying WebRTC Manager about session activation');
|
||||||
|
window.webrtcManager.handleSessionActivation({
|
||||||
|
sessionId: event.detail.sessionId,
|
||||||
|
sessionType: event.detail.sessionType,
|
||||||
|
timeLeft: event.detail.timeLeft,
|
||||||
|
isDemo: event.detail.isDemo,
|
||||||
|
sessionManager: window.sessionManager
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (window.DEBUG_MODE) {
|
if (window.DEBUG_MODE) {
|
||||||
|
|||||||
310
src/components/ui/FileTransfer.jsx
Normal file
310
src/components/ui/FileTransfer.jsx
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
// File Transfer Component for Chat Interface
|
||||||
|
const FileTransferComponent = ({ webrtcManager, isConnected }) => {
|
||||||
|
const [dragOver, setDragOver] = React.useState(false);
|
||||||
|
const [transfers, setTransfers] = React.useState({ sending: [], receiving: [] });
|
||||||
|
const fileInputRef = React.useRef(null);
|
||||||
|
|
||||||
|
// Update transfers periodically
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!isConnected || !webrtcManager) return;
|
||||||
|
|
||||||
|
const updateTransfers = () => {
|
||||||
|
const currentTransfers = webrtcManager.getFileTransfers();
|
||||||
|
setTransfers(currentTransfers);
|
||||||
|
};
|
||||||
|
|
||||||
|
const interval = setInterval(updateTransfers, 500);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [isConnected, webrtcManager]);
|
||||||
|
|
||||||
|
// Setup file transfer callbacks
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!webrtcManager) return;
|
||||||
|
|
||||||
|
webrtcManager.setFileTransferCallbacks(
|
||||||
|
// Progress callback
|
||||||
|
(progress) => {
|
||||||
|
const currentTransfers = webrtcManager.getFileTransfers();
|
||||||
|
setTransfers(currentTransfers);
|
||||||
|
},
|
||||||
|
|
||||||
|
// File received callback
|
||||||
|
(fileData) => {
|
||||||
|
// Auto-download received file
|
||||||
|
const url = URL.createObjectURL(fileData.fileBlob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = fileData.fileName;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
// Update transfer list
|
||||||
|
const currentTransfers = webrtcManager.getFileTransfers();
|
||||||
|
setTransfers(currentTransfers);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Error callback
|
||||||
|
(error) => {
|
||||||
|
console.error('File transfer error:', error);
|
||||||
|
const currentTransfers = webrtcManager.getFileTransfers();
|
||||||
|
setTransfers(currentTransfers);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, [webrtcManager]);
|
||||||
|
|
||||||
|
const handleFileSelect = async (files) => {
|
||||||
|
if (!isConnected || !webrtcManager) {
|
||||||
|
alert('Соединение не установлено. Сначала установите соединение.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Дополнительная проверка состояния соединения
|
||||||
|
if (!webrtcManager.isConnected() || !webrtcManager.isVerified) {
|
||||||
|
alert('Соединение не готово для передачи файлов. Дождитесь завершения установки соединения.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
try {
|
||||||
|
await webrtcManager.sendFile(file);
|
||||||
|
} catch (error) {
|
||||||
|
// Более мягкая обработка ошибок - не закрываем сессию
|
||||||
|
console.error(`Failed to send ${file.name}:`, error);
|
||||||
|
|
||||||
|
// Показываем пользователю ошибку, но не закрываем соединение
|
||||||
|
if (error.message.includes('Connection not ready')) {
|
||||||
|
alert(`Файл ${file.name} не может быть отправлен сейчас. Проверьте соединение и попробуйте снова.`);
|
||||||
|
} else if (error.message.includes('File too large')) {
|
||||||
|
alert(`Файл ${file.name} слишком большой. Максимальный размер: 100 MB`);
|
||||||
|
} else if (error.message.includes('Maximum concurrent transfers')) {
|
||||||
|
alert(`Достигнут лимит одновременных передач. Дождитесь завершения текущих передач.`);
|
||||||
|
} else {
|
||||||
|
alert(`Ошибка отправки файла ${file.name}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrop = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setDragOver(false);
|
||||||
|
|
||||||
|
const files = Array.from(e.dataTransfer.files);
|
||||||
|
handleFileSelect(files);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragOver = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setDragOver(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragLeave = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setDragOver(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileInputChange = (e) => {
|
||||||
|
const files = Array.from(e.target.files);
|
||||||
|
handleFileSelect(files);
|
||||||
|
e.target.value = ''; // Reset input
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatFileSize = (bytes) => {
|
||||||
|
if (bytes === 0) return '0 B';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isConnected) {
|
||||||
|
return React.createElement('div', {
|
||||||
|
className: "p-4 text-center text-muted"
|
||||||
|
}, 'Передача файлов доступна только при установленном соединении');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем дополнительное состояние соединения
|
||||||
|
const isConnectionReady = webrtcManager && webrtcManager.isConnected() && webrtcManager.isVerified;
|
||||||
|
|
||||||
|
if (!isConnectionReady) {
|
||||||
|
return React.createElement('div', {
|
||||||
|
className: "p-4 text-center text-yellow-600"
|
||||||
|
}, [
|
||||||
|
React.createElement('i', {
|
||||||
|
key: 'icon',
|
||||||
|
className: 'fas fa-exclamation-triangle mr-2'
|
||||||
|
}),
|
||||||
|
'Соединение устанавливается... Передача файлов будет доступна после завершения установки.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return React.createElement('div', {
|
||||||
|
className: "file-transfer-component"
|
||||||
|
}, [
|
||||||
|
// File Drop Zone
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'drop-zone',
|
||||||
|
className: `file-drop-zone ${dragOver ? 'drag-over' : ''}`,
|
||||||
|
onDrop: handleDrop,
|
||||||
|
onDragOver: handleDragOver,
|
||||||
|
onDragLeave: handleDragLeave,
|
||||||
|
onClick: () => fileInputRef.current?.click()
|
||||||
|
}, [
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'drop-content',
|
||||||
|
className: "drop-content"
|
||||||
|
}, [
|
||||||
|
React.createElement('i', {
|
||||||
|
key: 'icon',
|
||||||
|
className: 'fas fa-cloud-upload-alt text-2xl mb-2 text-blue-400'
|
||||||
|
}),
|
||||||
|
React.createElement('p', {
|
||||||
|
key: 'text',
|
||||||
|
className: "text-primary font-medium"
|
||||||
|
}, 'Перетащите файлы сюда или нажмите для выбора'),
|
||||||
|
React.createElement('p', {
|
||||||
|
key: 'subtext',
|
||||||
|
className: "text-muted text-sm"
|
||||||
|
}, 'Максимальный размер: 100 МБ на файл')
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Hidden file input
|
||||||
|
React.createElement('input', {
|
||||||
|
key: 'file-input',
|
||||||
|
ref: fileInputRef,
|
||||||
|
type: 'file',
|
||||||
|
multiple: true,
|
||||||
|
className: 'hidden',
|
||||||
|
onChange: handleFileInputChange
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Active Transfers
|
||||||
|
(transfers.sending.length > 0 || transfers.receiving.length > 0) && React.createElement('div', {
|
||||||
|
key: 'transfers',
|
||||||
|
className: "active-transfers mt-4"
|
||||||
|
}, [
|
||||||
|
React.createElement('h4', {
|
||||||
|
key: 'title',
|
||||||
|
className: "text-primary font-medium mb-3 flex items-center"
|
||||||
|
}, [
|
||||||
|
React.createElement('i', {
|
||||||
|
key: 'icon',
|
||||||
|
className: 'fas fa-exchange-alt mr-2'
|
||||||
|
}),
|
||||||
|
'Передача файлов'
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Sending files
|
||||||
|
...transfers.sending.map(transfer =>
|
||||||
|
React.createElement('div', {
|
||||||
|
key: `send-${transfer.fileId}`,
|
||||||
|
className: "transfer-item bg-blue-500/10 border border-blue-500/20 rounded-lg p-3 mb-2"
|
||||||
|
}, [
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'header',
|
||||||
|
className: "flex items-center justify-between mb-2"
|
||||||
|
}, [
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'info',
|
||||||
|
className: "flex items-center"
|
||||||
|
}, [
|
||||||
|
React.createElement('i', {
|
||||||
|
key: 'icon',
|
||||||
|
className: 'fas fa-upload text-blue-400 mr-2'
|
||||||
|
}),
|
||||||
|
React.createElement('span', {
|
||||||
|
key: 'name',
|
||||||
|
className: "text-primary font-medium text-sm"
|
||||||
|
}, transfer.fileName),
|
||||||
|
React.createElement('span', {
|
||||||
|
key: 'size',
|
||||||
|
className: "text-muted text-xs ml-2"
|
||||||
|
}, formatFileSize(transfer.fileSize))
|
||||||
|
]),
|
||||||
|
React.createElement('button', {
|
||||||
|
key: 'cancel',
|
||||||
|
onClick: () => webrtcManager.cancelFileTransfer(transfer.fileId),
|
||||||
|
className: "text-red-400 hover:text-red-300 text-xs"
|
||||||
|
}, [
|
||||||
|
React.createElement('i', {
|
||||||
|
className: 'fas fa-times'
|
||||||
|
})
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'progress',
|
||||||
|
className: "progress-bar"
|
||||||
|
}, [
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'fill',
|
||||||
|
className: "progress-fill bg-blue-400",
|
||||||
|
style: { width: `${transfer.progress}%` }
|
||||||
|
}),
|
||||||
|
React.createElement('span', {
|
||||||
|
key: 'text',
|
||||||
|
className: "progress-text text-xs"
|
||||||
|
}, `${transfer.progress.toFixed(1)}% • ${transfer.status}`)
|
||||||
|
])
|
||||||
|
])
|
||||||
|
),
|
||||||
|
|
||||||
|
// Receiving files
|
||||||
|
...transfers.receiving.map(transfer =>
|
||||||
|
React.createElement('div', {
|
||||||
|
key: `recv-${transfer.fileId}`,
|
||||||
|
className: "transfer-item bg-green-500/10 border border-green-500/20 rounded-lg p-3 mb-2"
|
||||||
|
}, [
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'header',
|
||||||
|
className: "flex items-center justify-between mb-2"
|
||||||
|
}, [
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'info',
|
||||||
|
className: "flex items-center"
|
||||||
|
}, [
|
||||||
|
React.createElement('i', {
|
||||||
|
key: 'icon',
|
||||||
|
className: 'fas fa-download text-green-400 mr-2'
|
||||||
|
}),
|
||||||
|
React.createElement('span', {
|
||||||
|
key: 'name',
|
||||||
|
className: "text-primary font-medium text-sm"
|
||||||
|
}, transfer.fileName),
|
||||||
|
React.createElement('span', {
|
||||||
|
key: 'size',
|
||||||
|
className: "text-muted text-xs ml-2"
|
||||||
|
}, formatFileSize(transfer.fileSize))
|
||||||
|
]),
|
||||||
|
React.createElement('button', {
|
||||||
|
key: 'cancel',
|
||||||
|
onClick: () => webrtcManager.cancelFileTransfer(transfer.fileId),
|
||||||
|
className: "text-red-400 hover:text-red-300 text-xs"
|
||||||
|
}, [
|
||||||
|
React.createElement('i', {
|
||||||
|
className: 'fas fa-times'
|
||||||
|
})
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'progress',
|
||||||
|
className: "progress-bar"
|
||||||
|
}, [
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'fill',
|
||||||
|
className: "progress-fill bg-green-400",
|
||||||
|
style: { width: `${transfer.progress}%` }
|
||||||
|
}),
|
||||||
|
React.createElement('span', {
|
||||||
|
key: 'text',
|
||||||
|
className: "progress-text text-xs"
|
||||||
|
}, `${transfer.progress.toFixed(1)}% • ${transfer.status}`)
|
||||||
|
])
|
||||||
|
])
|
||||||
|
)
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export
|
||||||
|
window.FileTransferComponent = FileTransferComponent;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,91 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* FILE TRANSFER STYLES */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
.file-transfer-component {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-drop-zone {
|
||||||
|
border: 2px dashed #4b5563;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: rgba(55, 65, 81, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-drop-zone:hover {
|
||||||
|
border-color: #3b82f6;
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-drop-zone.drag-over {
|
||||||
|
border-color: #10b981;
|
||||||
|
background: rgba(16, 185, 129, 0.1);
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-content {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-transfers {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-item {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-item:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
position: relative;
|
||||||
|
height: 6px;
|
||||||
|
background: rgba(75, 85, 99, 0.3);
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill {
|
||||||
|
height: 100%;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-text {
|
||||||
|
position: absolute;
|
||||||
|
top: -20px;
|
||||||
|
right: 0;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-transfer-section {
|
||||||
|
border-top: 1px solid rgba(75, 85, 99, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.file-drop-zone {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-item {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-text {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.header-minimal {
|
.header-minimal {
|
||||||
background: rgb(35 36 35 / 13%);
|
background: rgb(35 36 35 / 13%);
|
||||||
backdrop-filter: blur(5px);
|
backdrop-filter: blur(5px);
|
||||||
|
|||||||
1288
src/transfer/EnhancedSecureFileTransfer.js
Normal file
1288
src/transfer/EnhancedSecureFileTransfer.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user