"use strict"; /* import express from 'express'; import http from 'http'; import path from 'path'; import fs from 'fs'; import { Server as SocketIOServer } from 'socket.io'; import { RTCPeerConnection, RTCSessionDescription, RTCIceCandidate } from 'wrtc'; // Define types interface FileTransferSession { peerConnection: RTCPeerConnection; dataChannel?: RTCDataChannel; fileStream?: fs.ReadStream; filePath: string; fileSize: number; chunkSize: number; sentBytes: number; } // Initialize express app const app = express(); const server = http.createServer(app); const io = new SocketIOServer(server); const PORT = process.env.PORT || 3000; // Serve static files app.use(express.static(path.join(__dirname, 'public'))); // Store active file transfer sessions const sessions: Map = new Map(); // Configure WebRTC ICE servers (STUN/TURN) const iceServers = [ { urls: 'stun:stun.l.google.com:19302' }, { urls: 'stun:stun1.l.google.com:19302' } ]; io.on('connection', (socket) => { console.log('Client connected:', socket.id); // Handle request for available files socket.on('get-files', () => { const filesDirectory = path.join(__dirname, 'files'); try { const files = fs.readdirSync(filesDirectory) .filter(file => fs.statSync(path.join(filesDirectory, file)).isFile()) .map(file => { const filePath = path.join(filesDirectory, file); const stats = fs.statSync(filePath); return { name: file, size: stats.size, modified: stats.mtime }; }); socket.emit('files-list', files); } catch (err) { console.error('Error reading files directory:', err); socket.emit('error', 'Failed to retrieve files list'); } }); // Handle file transfer request socket.on('request-file', (fileName: string) => { const filePath = path.join(__dirname, 'files', fileName); // Check if file exists if (!fs.existsSync(filePath)) { return socket.emit('error', 'File not found'); } const fileSize = fs.statSync(filePath).size; const chunkSize = 16384; // 16KB chunks // Create and configure peer connection const peerConnection = new RTCPeerConnection({ iceServers }); // Create data channel const dataChannel = peerConnection.createDataChannel('fileTransfer', { ordered: true }); // Store session info sessions.set(socket.id, { peerConnection, dataChannel, filePath, fileSize, chunkSize, sentBytes: 0 }); // Handle ICE candidates peerConnection.onicecandidate = (event) => { if (event.candidate) { socket.emit('ice-candidate', event.candidate); } }; // Set up data channel handlers dataChannel.onopen = () => { console.log(`Data channel opened for client ${socket.id}`); startFileTransfer(socket.id); }; dataChannel.onclose = () => { console.log(`Data channel closed for client ${socket.id}`); cleanupSession(socket.id); }; dataChannel.onerror = (error) => { console.error(`Data channel error for client ${socket.id}:`, error); cleanupSession(socket.id); }; // Create offer peerConnection.createOffer() .then(offer => peerConnection.setLocalDescription(offer)) .then(() => { socket.emit('offer', { sdp: peerConnection.localDescription, fileInfo: { name: path.basename(filePath), size: fileSize } }); }) .catch(err => { console.error('Error creating offer:', err); socket.emit('error', 'Failed to create connection offer'); cleanupSession(socket.id); }); }); // Handle answer from browser socket.on('answer', async (answer: RTCSessionDescription) => { try { const session = sessions.get(socket.id); if (!session) return; await session.peerConnection.setRemoteDescription(new RTCSessionDescription(answer)); console.log(`Connection established with client ${socket.id}`); } catch (err) { console.error('Error setting remote description:', err); socket.emit('error', 'Failed to establish connection'); cleanupSession(socket.id); } }); // Handle ICE candidates from browser socket.on('ice-candidate', async (candidate: RTCIceCandidate) => { try { const session = sessions.get(socket.id); if (!session) return; await session.peerConnection.addIceCandidate(new RTCIceCandidate(candidate)); } catch (err) { console.error('Error adding ICE candidate:', err); } }); // Handle client disconnection socket.on('disconnect', () => { console.log('Client disconnected:', socket.id); cleanupSession(socket.id); }); }); // Start file transfer function startFileTransfer(socketId: string): void { const session = sessions.get(socketId); if (!session || !session.dataChannel) return; // Send file info first session.dataChannel.send(JSON.stringify({ type: 'file-info', name: path.basename(session.filePath), size: session.fileSize })); // Open file stream session.fileStream = fs.createReadStream(session.filePath, { highWaterMark: session.chunkSize }); // Process file chunks session.fileStream.on('data', (chunk: Buffer) => { // Check if data channel is still open and ready if (session.dataChannel?.readyState === 'open') { // Pause the stream to handle backpressure session.fileStream?.pause(); // Send chunk as ArrayBuffer session.dataChannel.send(chunk); session.sentBytes += chunk.length; // Report progress if (session.sentBytes % (5 * 1024 * 1024) === 0) { // Every 5MB console.log(`Sent ${session.sentBytes / (1024 * 1024)}MB of ${session.fileSize / (1024 * 1024)}MB`); } // Check buffer status before resuming const bufferAmount = session.dataChannel.bufferedAmount; if (bufferAmount < session.chunkSize * 2) { // Resume reading if buffer is below threshold session.fileStream?.resume(); } else { // Wait for buffer to drain const checkBuffer = setInterval(() => { if (session.dataChannel?.bufferedAmount < session.chunkSize) { clearInterval(checkBuffer); session.fileStream?.resume(); } }, 100); } } }); // Handle end of file session.fileStream.on('end', () => { if (session.dataChannel?.readyState === 'open') { session.dataChannel.send(JSON.stringify({ type: 'file-complete' })); console.log(`File transfer complete for client ${socketId}`); } }); // Handle file stream errors session.fileStream.on('error', (err) => { console.error(`File stream error for client ${socketId}:`, err); if (session.dataChannel?.readyState === 'open') { session.dataChannel.send(JSON.stringify({ type: 'error', message: 'File read error on server' })); } cleanupSession(socketId); }); } // Clean up session resources function cleanupSession(socketId: string): void { const session = sessions.get(socketId); if (!session) return; if (session.fileStream) { session.fileStream.destroy(); } if (session.dataChannel && session.dataChannel.readyState === 'open') { session.dataChannel.close(); } session.peerConnection.close(); sessions.delete(socketId); console.log(`Cleaned up session for client ${socketId}`); } // Start the server server.listen(PORT, () => { console.log(`Server running on port ${PORT}`); // Create files directory if it doesn't exist const filesDir = path.join(__dirname, 'files'); if (!fs.existsSync(filesDir)) { fs.mkdirSync(filesDir); console.log('Created files directory'); } }); */ //# sourceMappingURL=index.js.map