Status Window
This commit is contained in:
172
background.js
172
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...');
|
||||
|
||||
202
window.html
Normal file
202
window.html
Normal 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
34
window.js
Normal 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' });
|
||||
Reference in New Issue
Block a user