diff --git a/background.js b/background.js index daf9d40..6576915 100644 --- a/background.js +++ b/background.js @@ -12,6 +12,17 @@ var serverSocketId = null; var isListening = false; var isInitialized = false; // Prevent double initialization var handlersSetup = false; // Prevent duplicate event listeners +var appWindow = null; // Keep app window reference to prevent suspension + +// Connection statistics +var stats = { + totalConnections: 0, + activeConnections: 0, + bytesReceived: 0, + bytesSent: 0, + startTime: Date.now(), + connections: {} // socketId -> { connectTime, bytesReceived, bytesSent } +}; // SOCKS5 Connection States var ConnectionState = { @@ -78,7 +89,18 @@ function initConnection(socketId) { buffer: new Uint8Array(0), destinationSocketId: null }; + + // Track statistics + stats.totalConnections++; + stats.activeConnections++; + stats.connections[socketId] = { + connectTime: Date.now(), + bytesReceived: 0, + bytesSent: 0 + }; + console.log('[SOCKS] Initialized connection ' + socketId); + sendStatusToWindow(); } function removeConnection(socketId) { @@ -90,7 +112,15 @@ function removeConnection(socketId) { } catch(e) {} } delete connections[socketId]; + + // Update statistics + if (stats.connections[socketId]) { + delete stats.connections[socketId]; + } + stats.activeConnections--; + console.log('[SOCKS] Removed connection ' + socketId); + sendStatusToWindow(); } function handleSocksData(clientSocketId, data) { @@ -102,11 +132,25 @@ function handleSocksData(clientSocketId, data) { // If we're in tunnel mode, forward data to destination if (conn.state === ConnectionState.TUNNEL && conn.destinationSocketId) { - console.log('[Tunnel] Client -> Destination: ' + data.byteLength + ' bytes'); + var byteLength = data.byteLength; + console.log('[Tunnel] Client -> Destination: ' + byteLength + ' bytes'); + + // Track statistics + stats.bytesReceived += byteLength; + if (stats.connections[clientSocketId]) { + stats.connections[clientSocketId].bytesReceived += byteLength; + } + chrome.sockets.tcp.send(conn.destinationSocketId, data, function(sendInfo) { if (sendInfo.resultCode < 0) { console.error('[Tunnel] Failed to send to destination: ' + sendInfo.resultCode); closeConnection(clientSocketId); + return; + } + // Track sent bytes + stats.bytesSent += byteLength; + if (stats.connections[clientSocketId]) { + stats.connections[clientSocketId].bytesSent += byteLength; } }); return; @@ -343,7 +387,14 @@ function handleDestinationData(destSocketId, data) { return; } - console.log('[Tunnel] Destination -> Client: ' + data.byteLength + ' bytes'); + var byteLength = data.byteLength; + console.log('[Tunnel] Destination -> Client: ' + byteLength + ' bytes'); + + // Track statistics + stats.bytesReceived += byteLength; + if (stats.connections[clientSocketId]) { + stats.connections[clientSocketId].bytesReceived += byteLength; + } // Pause destination to prevent event spam while sending chrome.sockets.tcp.setPaused(destSocketId, true); @@ -356,6 +407,12 @@ function handleDestinationData(destSocketId, data) { console.error('[Tunnel] Failed to send to client: ' + sendInfo.resultCode); // Close both connections closeConnection(clientSocketId); + return; + } + // Track sent bytes + stats.bytesSent += byteLength; + if (stats.connections[clientSocketId]) { + stats.connections[clientSocketId].bytesSent += byteLength; } }); } @@ -462,8 +519,51 @@ function initializeProxy() { chrome.app.runtime.onLaunched.addListener(function() { console.log('[Hammer] App launched'); initializeProxy(); + createAppWindow(); }); +// Create a minimal app window to prevent Chrome from suspending the app +function createAppWindow() { + // Only create if we don't already have one + if (appWindow) { + try { + appWindow.focus(); + return; + } catch(e) { + appWindow = null; + } + } + + chrome.app.window.create('window.html', { + 'outerBounds': { + 'width': 400, + 'height': 500, + 'minWidth': 300, + 'minHeight': 400 + }, + 'resizable': true, + 'alwaysOnTop': false + }, function(createdWindow) { + if (chrome.runtime.lastError) { + console.error('[Hammer] Failed to create window:', chrome.runtime.lastError); + return; + } + appWindow = createdWindow; + console.log('[Hammer] App window created - prevents suspension'); + + // Re-create window if closed + appWindow.onClosed.addListener(function() { + console.log('[Hammer] Window closed, recreating in 2 seconds...'); + appWindow = null; + setTimeout(function() { + if (!appWindow) { + createAppWindow(); + } + }, 2000); + }); + }); +} + // Chrome App lifecycle - restart when app is restarted chrome.app.runtime.onRestarted.addListener(function() { console.log('[Hammer] App restarted'); @@ -500,6 +600,13 @@ chrome.app.window.onClosed.addListener(function() { // Also initialize on startup (for apps that are already running) initializeProxy(); +// Create window on startup to prevent suspension +setTimeout(function() { + if (!appWindow) { + createAppWindow(); + } +}, 1000); + // ============================================================================ // KEEP-ALIVE MECHANISM // ============================================================================ @@ -512,6 +619,9 @@ chrome.alarms.onAlarm.addListener(function(alarm) { // Heartbeat: log activity to keep the app alive console.log('[Hammer] Heartbeat - Proxy is ' + (isListening ? 'active' : 'inactive')); + // Send status to window + sendStatusToWindow(); + // Check if server is still listening, restart if needed if (!isListening && !isInitialized) { console.log('[Hammer] Server not listening, reinitializing...'); @@ -523,6 +633,64 @@ chrome.alarms.onAlarm.addListener(function(alarm) { } }); +// Send status to app window +function sendStatusToWindow() { + // Format bytes for display + function formatBytes(bytes) { + if (bytes < 1024) return bytes + ' B'; + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; + return (bytes / (1024 * 1024)).toFixed(2) + ' MB'; + } + + // Format duration for display + function formatDuration(ms) { + var seconds = Math.floor(ms / 1000); + var minutes = Math.floor(seconds / 60); + var hours = Math.floor(minutes / 60); + if (hours > 0) return hours + 'h ' + (minutes % 60) + 'm'; + if (minutes > 0) return minutes + 'm ' + (seconds % 60) + 's'; + return seconds + 's'; + } + + // Calculate connection durations + var connectionDurations = []; + for (var socketId in stats.connections) { + var conn = stats.connections[socketId]; + var duration = Date.now() - conn.connectTime; + connectionDurations.push(formatDuration(duration)); + } + + chrome.runtime.sendMessage({ + type: 'status', + active: isListening, + stats: { + totalConnections: stats.totalConnections, + activeConnections: stats.activeConnections, + bytesReceived: formatBytes(stats.bytesReceived), + bytesSent: formatBytes(stats.bytesSent), + uptime: formatDuration(Date.now() - stats.startTime), + connectionDurations: connectionDurations + } + }); + chrome.runtime.sendMessage({ + type: 'heartbeat' + }); +} + +// Listen for status requests from window +chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { + if (message.type === 'getStatus') { + sendResponse({ + type: 'status', + active: isListening, + stats: { + totalConnections: stats.totalConnections, + activeConnections: stats.activeConnections + } + }); + } +}); + // Handle when app is suspended and resumed chrome.runtime.onSuspend.addListener(function() { console.log('[Hammer] App being suspended...'); diff --git a/window.html b/window.html new file mode 100644 index 0000000..b74db0d --- /dev/null +++ b/window.html @@ -0,0 +1,202 @@ + + +
+ +