Status Window

This commit is contained in:
Joerg Dorgeist
2026-01-14 11:57:57 +01:00
parent 16be5a95ec
commit 97487ce060
3 changed files with 406 additions and 2 deletions

View File

@@ -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...');

202
window.html Normal file
View File

@@ -0,0 +1,202 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Chrome's Hammer</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: #2d3748;
color: white;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.container {
text-align: center;
max-width: 380px;
}
h1 {
font-size: 24px;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.icon {
font-size: 32px;
}
.status {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 20px;
margin: 20px 0;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.status-item {
margin: 10px 0;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
}
.label {
font-weight: 500;
opacity: 0.9;
}
.value {
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
background: rgba(0, 0, 0, 0.3);
padding: 4px 10px;
border-radius: 6px;
font-size: 13px;
}
.indicator {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 8px;
animation: pulse 2s infinite;
}
.indicator.active {
background: #48bb78;
box-shadow: 0 0 10px #48bb78;
}
.indicator.inactive {
background: #f56565;
box-shadow: 0 0 10px #f56565;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.info {
font-size: 12px;
opacity: 0.7;
margin-top: 15px;
line-height: 1.5;
}
.heartbeat {
font-size: 11px;
opacity: 0.5;
margin-top: 12px;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.stat-box {
background: rgba(0, 0, 0, 0.2);
padding: 10px;
border-radius: 8px;
}
.stat-label {
font-size: 10px;
opacity: 0.7;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 4px;
}
.stat-value {
font-size: 16px;
font-weight: 600;
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
}
.connections-list {
margin-top: 15px;
text-align: left;
}
.connection-item {
font-size: 11px;
opacity: 0.8;
padding: 4px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.connection-item:last-child {
border-bottom: none;
}
</style>
</head>
<body>
<div class="container">
<h1><span class="icon">🔨</span> Chrome's Hammer</h1>
<div class="status">
<div class="status-item">
<span class="label">Status</span>
<span class="value">
<span class="indicator active" id="indicator"></span>
<span id="status">Running</span>
</span>
</div>
<div class="status-item">
<span class="label">Host</span>
<span class="value">127.0.0.1</span>
</div>
<div class="status-item">
<span class="label">Port</span>
<span class="value">1080</span>
</div>
<div class="status-item">
<span class="label">Protocol</span>
<span class="value">SOCKS5</span>
</div>
<div class="stats-grid">
<div class="stat-box">
<div class="stat-label">Active Connections</div>
<div class="stat-value" id="activeConnections">0</div>
</div>
<div class="stat-box">
<div class="stat-label">Total Connections</div>
<div class="stat-value" id="totalConnections">0</div>
</div>
<div class="stat-box">
<div class="stat-label">Data Received</div>
<div class="stat-value" id="bytesReceived">0 B</div>
</div>
<div class="stat-box">
<div class="stat-label">Data Sent</div>
<div class="stat-value" id="bytesSent">0 B</div>
</div>
</div>
<div class="status-item" style="margin-top: 15px;">
<span class="label">Uptime</span>
<span class="value" id="uptime">0s</span>
</div>
</div>
<div class="connections-list" id="connectionsList" style="display: none;">
<div style="font-size: 11px; opacity: 0.7; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px;">
Active Connections
</div>
</div>
<div class="info">
Keep this window open to maintain connection.
</div>
<div class="heartbeat" id="heartbeat">
Last heartbeat: --
</div>
</div>
<script src="window.js"></script>
</body>
</html>

34
window.js Normal file
View File

@@ -0,0 +1,34 @@
// Listen for status updates from background script
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if (message.type === 'status' && message.stats) {
var s = message.stats;
document.getElementById('status').textContent = message.active ? 'Running' : 'Inactive';
document.getElementById('indicator').className = 'indicator ' + (message.active ? 'active' : 'inactive');
document.getElementById('activeConnections').textContent = s.activeConnections;
document.getElementById('totalConnections').textContent = s.totalConnections;
document.getElementById('bytesReceived').textContent = s.bytesReceived;
document.getElementById('bytesSent').textContent = s.bytesSent;
document.getElementById('uptime').textContent = s.uptime;
// Show connection durations
var list = document.getElementById('connectionsList');
if (s.connectionDurations && s.connectionDurations.length > 0) {
list.style.display = 'block';
list.innerHTML = '<div style="font-size: 11px; opacity: 0.7; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px;">Active Connections</div>';
s.connectionDurations.forEach(function(duration) {
var div = document.createElement('div');
div.className = 'connection-item';
div.textContent = 'Connection ' + (s.connectionDurations.indexOf(duration) + 1) + ': ' + duration;
list.appendChild(div);
});
} else {
list.style.display = 'none';
}
} else if (message.type === 'heartbeat') {
var now = new Date();
document.getElementById('heartbeat').textContent = 'Last heartbeat: ' + now.toLocaleTimeString();
}
});
// Request initial status
chrome.runtime.sendMessage({ type: 'getStatus' });