bugfix: Implement Graceful WebSocket Server Shutdown Mechanism

- Added shutdown method to BrowserConnector for controlled WebSocket server closure
- Improved process signal handling for SIGINT and SIGTERM
- Updated Chrome extension to handle server shutdown signal
- Bumped package versions to 1.1.0 for browser-tools-mcp and browser-tools-server
This commit is contained in:
wangzengdi 2025-03-11 00:46:44 +08:00
parent 04bbbdaf34
commit e55410bc5c
4 changed files with 103 additions and 16 deletions

View File

@ -1,12 +1,12 @@
{ {
"name": "@agentdeskai/browser-tools-mcp", "name": "@agentdeskai/browser-tools-mcp",
"version": "1.0.11", "version": "1.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@agentdeskai/browser-tools-mcp", "name": "@agentdeskai/browser-tools-mcp",
"version": "1.0.11", "version": "1.1.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.4.1", "@modelcontextprotocol/sdk": "^1.4.1",

View File

@ -667,6 +667,47 @@ export class BrowserConnector {
}); });
} }
} }
// Add shutdown method
public shutdown() {
return new Promise<void>((resolve) => {
console.log("Shutting down WebSocket server...");
// Send close message to client if connection is active
if (this.activeConnection && this.activeConnection.readyState === WebSocket.OPEN) {
console.log("Notifying client to close connection...");
try {
this.activeConnection.send(JSON.stringify({ type: "server-shutdown" }));
} catch (err) {
console.error("Error sending shutdown message to client:", err);
}
}
// Set a timeout to force close after 2 seconds
const forceCloseTimeout = setTimeout(() => {
console.log("Force closing connections after timeout...");
if (this.activeConnection) {
this.activeConnection.terminate(); // Force close the connection
this.activeConnection = null;
}
this.wss.close();
resolve();
}, 2000);
// Close active WebSocket connection if exists
if (this.activeConnection) {
this.activeConnection.close(1000, "Server shutting down");
this.activeConnection = null;
}
// Close WebSocket server
this.wss.close(() => {
clearTimeout(forceCloseTimeout);
console.log("WebSocket server closed gracefully");
resolve();
});
});
}
} }
// Move the server creation before BrowserConnector instantiation // Move the server creation before BrowserConnector instantiation
@ -677,10 +718,41 @@ const server = app.listen(PORT, () => {
// Initialize the browser connector with the existing app AND server // Initialize the browser connector with the existing app AND server
const browserConnector = new BrowserConnector(app, server); const browserConnector = new BrowserConnector(app, server);
// Handle shutdown gracefully // Handle shutdown gracefully with improved error handling
process.on("SIGINT", () => { process.on("SIGINT", async () => {
server.close(() => { console.log("\nReceived SIGINT signal. Starting graceful shutdown...");
console.log("Server shut down");
try {
// First shutdown WebSocket connections
await browserConnector.shutdown();
// Then close the HTTP server
await new Promise<void>((resolve, reject) => {
server.close((err) => {
if (err) {
console.error("Error closing HTTP server:", err);
reject(err);
} else {
console.log("HTTP server closed successfully");
resolve();
}
});
});
// Clear all logs
clearAllLogs();
console.log("Shutdown completed successfully");
process.exit(0); process.exit(0);
} catch (error) {
console.error("Error during shutdown:", error);
// Force exit in case of error
process.exit(1);
}
}); });
// Also handle SIGTERM
process.on("SIGTERM", () => {
console.log("\nReceived SIGTERM signal");
process.emit("SIGINT");
}); });

View File

@ -1,12 +1,12 @@
{ {
"name": "@agentdeskai/browser-tools-server", "name": "@agentdeskai/browser-tools-server",
"version": "1.0.5", "version": "1.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@agentdeskai/browser-tools-server", "name": "@agentdeskai/browser-tools-server",
"version": "1.0.5", "version": "1.1.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.4.1", "@modelcontextprotocol/sdk": "^1.4.1",

View File

@ -536,6 +536,18 @@ function setupWebSocket() {
const message = JSON.parse(event.data); const message = JSON.parse(event.data);
console.log("Chrome Extension: Received WebSocket message:", message); console.log("Chrome Extension: Received WebSocket message:", message);
if (message.type === "server-shutdown") {
console.log("Chrome Extension: Received server shutdown signal");
// Clear any reconnection attempts
if (wsReconnectTimeout) {
clearTimeout(wsReconnectTimeout);
wsReconnectTimeout = null;
}
// Close the connection gracefully
ws.close(1000, "Server shutting down");
return;
}
if (message.type === "take-screenshot") { if (message.type === "take-screenshot") {
console.log("Chrome Extension: Taking screenshot..."); console.log("Chrome Extension: Taking screenshot...");
// Capture screenshot of the current tab // Capture screenshot of the current tab
@ -574,10 +586,7 @@ function setupWebSocket() {
}); });
} }
} catch (error) { } catch (error) {
console.error( console.error("Chrome Extension: Error processing WebSocket message:", error);
"Chrome Extension: Error processing WebSocket message:",
error
);
} }
}; };
@ -589,11 +598,17 @@ function setupWebSocket() {
} }
}; };
ws.onclose = () => { ws.onclose = (event) => {
console.log( console.log(
"Chrome Extension: WebSocket disconnected, attempting to reconnect..." `Chrome Extension: WebSocket disconnected (${event.code}: ${event.reason})`
); );
// Only attempt to reconnect if it wasn't a server shutdown
if (event.reason !== "Server shutting down") {
console.log("Chrome Extension: Attempting to reconnect...");
wsReconnectTimeout = setTimeout(setupWebSocket, WS_RECONNECT_DELAY); wsReconnectTimeout = setTimeout(setupWebSocket, WS_RECONNECT_DELAY);
} else {
console.log("Chrome Extension: Server shutdown detected, not reconnecting");
}
}; };
ws.onerror = (error) => { ws.onerror = (error) => {