abu-browser-bridge 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/wsServer.d.ts +6 -2
- package/dist/wsServer.js +84 -7
- package/package.json +2 -2
package/dist/wsServer.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WebSocket server that accepts connections from the Chrome Extension.
|
|
3
3
|
* Routes requests from MCP tools to the extension and returns responses.
|
|
4
|
+
*
|
|
5
|
+
* Also exposes a lightweight HTTP discovery endpoint on a fixed port
|
|
6
|
+
* so the Chrome Extension can reliably find the WS port.
|
|
4
7
|
*/
|
|
5
8
|
import type { BridgeResponse } from './types.js';
|
|
6
9
|
/**
|
|
7
|
-
* Start the WebSocket server. If the default port is occupied
|
|
8
|
-
* try a few alternative ports. The Extension
|
|
10
|
+
* Start the WebSocket server. If the default port is occupied,
|
|
11
|
+
* try a few alternative ports. The Extension discovers the port
|
|
12
|
+
* via the HTTP discovery endpoint.
|
|
9
13
|
*/
|
|
10
14
|
export declare function startWSServer(port?: number): Promise<number>;
|
|
11
15
|
/**
|
package/dist/wsServer.js
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WebSocket server that accepts connections from the Chrome Extension.
|
|
3
3
|
* Routes requests from MCP tools to the extension and returns responses.
|
|
4
|
+
*
|
|
5
|
+
* Also exposes a lightweight HTTP discovery endpoint on a fixed port
|
|
6
|
+
* so the Chrome Extension can reliably find the WS port.
|
|
4
7
|
*/
|
|
5
8
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
9
|
+
import { createServer } from 'http';
|
|
6
10
|
import { createConnection } from 'net';
|
|
7
|
-
const
|
|
8
|
-
const
|
|
11
|
+
const DEFAULT_WS_PORT = 9876;
|
|
12
|
+
const DISCOVERY_PORT = 9875;
|
|
13
|
+
const HEARTBEAT_INTERVAL = 15_000; // 15s — more frequent for faster disconnect detection
|
|
14
|
+
const PONG_TIMEOUT = 5_000; // If no pong within 5s, consider dead
|
|
9
15
|
let wss = null;
|
|
16
|
+
let discoveryServer = null;
|
|
10
17
|
let extensionSocket = null;
|
|
11
18
|
let heartbeatTimer = null;
|
|
19
|
+
let pongReceived = true;
|
|
12
20
|
const pendingRequests = new Map();
|
|
13
21
|
let requestCounter = 0;
|
|
14
22
|
let activePort = null;
|
|
@@ -17,7 +25,6 @@ function generateId() {
|
|
|
17
25
|
}
|
|
18
26
|
/**
|
|
19
27
|
* Check if a port is in use by trying to connect to it.
|
|
20
|
-
* Cross-platform — works on macOS, Windows, Linux.
|
|
21
28
|
*/
|
|
22
29
|
function isPortInUse(port, host) {
|
|
23
30
|
return new Promise((resolve) => {
|
|
@@ -36,11 +43,65 @@ function isPortInUse(port, host) {
|
|
|
36
43
|
});
|
|
37
44
|
});
|
|
38
45
|
}
|
|
46
|
+
// --- HTTP Discovery Endpoint ---
|
|
39
47
|
/**
|
|
40
|
-
* Start the
|
|
41
|
-
*
|
|
48
|
+
* Start the HTTP discovery server on a fixed well-known port.
|
|
49
|
+
* Chrome Extension queries this to find the actual WS port.
|
|
50
|
+
*
|
|
51
|
+
* GET /status → { wsPort, pid, extensionConnected, uptime }
|
|
42
52
|
*/
|
|
43
|
-
|
|
53
|
+
function startDiscoveryServer() {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const startTime = Date.now();
|
|
56
|
+
discoveryServer = createServer((req, res) => {
|
|
57
|
+
// CORS headers for Chrome Extension fetch
|
|
58
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
59
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
|
60
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
61
|
+
if (req.method === 'OPTIONS') {
|
|
62
|
+
res.writeHead(204);
|
|
63
|
+
res.end();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (req.url === '/status' || req.url === '/') {
|
|
67
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
68
|
+
res.end(JSON.stringify({
|
|
69
|
+
wsPort: activePort,
|
|
70
|
+
pid: process.pid,
|
|
71
|
+
extensionConnected: isExtensionConnected(),
|
|
72
|
+
uptime: Math.round((Date.now() - startTime) / 1000),
|
|
73
|
+
version: '0.1.1',
|
|
74
|
+
}));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
res.writeHead(404);
|
|
78
|
+
res.end('Not found');
|
|
79
|
+
});
|
|
80
|
+
discoveryServer.on('error', (err) => {
|
|
81
|
+
if (err.code === 'EADDRINUSE') {
|
|
82
|
+
console.error(`[abu-bridge] Discovery port ${DISCOVERY_PORT} in use — old bridge still running?`);
|
|
83
|
+
// Not fatal — extension can still fall back to port scanning
|
|
84
|
+
resolve();
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
reject(err);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
discoveryServer.listen(DISCOVERY_PORT, '127.0.0.1', () => {
|
|
91
|
+
console.error(`[abu-bridge] Discovery endpoint: http://127.0.0.1:${DISCOVERY_PORT}/status`);
|
|
92
|
+
resolve();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
// --- WebSocket Server ---
|
|
97
|
+
/**
|
|
98
|
+
* Start the WebSocket server. If the default port is occupied,
|
|
99
|
+
* try a few alternative ports. The Extension discovers the port
|
|
100
|
+
* via the HTTP discovery endpoint.
|
|
101
|
+
*/
|
|
102
|
+
export async function startWSServer(port = DEFAULT_WS_PORT) {
|
|
103
|
+
// Start discovery endpoint first (non-blocking if port taken)
|
|
104
|
+
await startDiscoveryServer();
|
|
44
105
|
const MAX_RETRIES = 5;
|
|
45
106
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
46
107
|
const tryPort = port + attempt;
|
|
@@ -56,7 +117,6 @@ export async function startWSServer(port = DEFAULT_PORT) {
|
|
|
56
117
|
}
|
|
57
118
|
catch (err) {
|
|
58
119
|
const msg = err instanceof Error ? err.message : String(err);
|
|
59
|
-
// EADDRINUSE can still happen in a race condition
|
|
60
120
|
if (msg.includes('EADDRINUSE') && attempt < MAX_RETRIES - 1) {
|
|
61
121
|
console.error(`[abu-bridge] Port ${tryPort} race condition, trying next...`);
|
|
62
122
|
continue;
|
|
@@ -87,6 +147,11 @@ function listenOnPort(port) {
|
|
|
87
147
|
extensionSocket.close(1000, 'Replaced by new connection');
|
|
88
148
|
}
|
|
89
149
|
extensionSocket = ws;
|
|
150
|
+
pongReceived = true;
|
|
151
|
+
// Handle pong responses for heartbeat
|
|
152
|
+
ws.on('pong', () => {
|
|
153
|
+
pongReceived = true;
|
|
154
|
+
});
|
|
90
155
|
ws.on('message', (data) => {
|
|
91
156
|
try {
|
|
92
157
|
const msg = JSON.parse(data.toString());
|
|
@@ -117,6 +182,14 @@ function listenOnPort(port) {
|
|
|
117
182
|
function startHeartbeat() {
|
|
118
183
|
heartbeatTimer = setInterval(() => {
|
|
119
184
|
if (extensionSocket && extensionSocket.readyState === WebSocket.OPEN) {
|
|
185
|
+
if (!pongReceived) {
|
|
186
|
+
// No pong since last ping — connection is dead
|
|
187
|
+
console.error('[abu-bridge] Extension not responding to heartbeat, closing connection');
|
|
188
|
+
extensionSocket.terminate();
|
|
189
|
+
extensionSocket = null;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
pongReceived = false;
|
|
120
193
|
extensionSocket.ping();
|
|
121
194
|
}
|
|
122
195
|
}, HEARTBEAT_INTERVAL);
|
|
@@ -180,5 +253,9 @@ export function stopWSServer() {
|
|
|
180
253
|
wss.close();
|
|
181
254
|
wss = null;
|
|
182
255
|
}
|
|
256
|
+
if (discoveryServer) {
|
|
257
|
+
discoveryServer.close();
|
|
258
|
+
discoveryServer = null;
|
|
259
|
+
}
|
|
183
260
|
activePort = null;
|
|
184
261
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abu-browser-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP Server that bridges Abu AI assistant with Chrome Extension for browser automation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"keywords": ["mcp", "browser", "automation", "chrome-extension", "abu"],
|
|
13
13
|
"bin": {
|
|
14
|
-
"abu-browser-bridge": "
|
|
14
|
+
"abu-browser-bridge": "dist/index.js"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"dist"
|