mirror of
https://github.com/penpot/penpot.git
synced 2026-07-31 18:36:18 +00:00
🐛 Preserve established plugin connection when rejecting a duplicate #10961
In multi-user mode, rejecting a second plugin WebSocket connection for an already-registered user token performed the full removeConnection cleanup for the newcomer. Since the token-keyed cleanup is keyed by token rather than by socket, this deleted the clientsByToken entry and the Redis request-channel subscription of the established, healthy connection. That connection then remained open and heartbeating but was unroutable, so every subsequent MCP tool call for the user failed although a valid plugin connection existed. removeConnection now performs the token-keyed cleanup only if the removed connection actually owns the token registration, so rejecting a duplicate releases only the resources the newcomer itself registered. AI-assisted-by: claude-fable-5
This commit is contained in:
parent
1ae9334064
commit
b4659df5b2
@ -133,9 +133,10 @@ export class PluginBridge {
|
||||
/**
|
||||
* Removes a client connection and releases all resources associated with it.
|
||||
*
|
||||
* Clears the per-connection keep-alive interval and removes the connection
|
||||
* from both the socket-keyed and token-keyed indexes. Safe to call with a
|
||||
* socket that is not (or no longer) registered.
|
||||
* Clears the per-connection keep-alive interval and removes the connection from the
|
||||
* socket-keyed index. The token-keyed index entry (and, in multi-instance mode, the
|
||||
* token's Redis task subscription) is removed only if it is owned by the given
|
||||
* connection. Safe to call with a socket that is not (or no longer) registered.
|
||||
*
|
||||
* @param ws - The WebSocket whose connection state should be removed
|
||||
*/
|
||||
@ -147,12 +148,18 @@ export class PluginBridge {
|
||||
clearInterval(connection.pingInterval);
|
||||
this.connectedClients.delete(ws);
|
||||
if (connection.userToken) {
|
||||
this.clientsByToken.delete(connection.userToken);
|
||||
// Perform the token-keyed cleanup only if this connection owns the token registration.
|
||||
// A connection rejected as a duplicate carries the same token but must not remove token associations.
|
||||
if (this.clientsByToken.get(connection.userToken) !== connection) {
|
||||
this.logger.debug("Removed connection does not own its token registration; skipping token cleanup");
|
||||
} else {
|
||||
this.clientsByToken.delete(connection.userToken);
|
||||
|
||||
if (this.redisBridge) {
|
||||
this.redisBridge
|
||||
.unsubscribeFromTasks(connection.userToken)
|
||||
.catch((error) => this.logger.error(error, "Failed to unsubscribe from Redis task channel"));
|
||||
if (this.redisBridge) {
|
||||
this.redisBridge
|
||||
.unsubscribeFromTasks(connection.userToken)
|
||||
.catch((error) => this.logger.error(error, "Failed to unsubscribe from Redis task channel"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user