From 66a718ea72ab86444ec251d31a2846586acbb08c Mon Sep 17 00:00:00 2001 From: hailin Date: Wed, 31 Dec 2025 13:07:43 -0800 Subject: [PATCH] fix(electron): properly cleanup gRPC message stream after keygen/sign MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: After keygen/sign completion, the gRPC message stream was not unsubscribed. On the second operation, prepareForSign/prepareForKeygen would try to cancel the stale stream, causing "CANCELLED: Cancelled on client". Changes in tss-handler.ts: - Add grpcClient.unsubscribeMessages() in all cleanup paths: - participateKeygen close handler - participateKeygen error handler - participateSign close handler - participateSign error handler - cancel() method - Reset sessionId and partyId in all cleanup paths Changes in main.ts: - Add reconnection logic in app 'activate' event for macOS 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../service-party-app/electron/main.ts | 11 +++++++- .../electron/modules/tss-handler.ts | 28 ++++++++++++++++--- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/backend/mpc-system/services/service-party-app/electron/main.ts b/backend/mpc-system/services/service-party-app/electron/main.ts index 314bdb23..e848566d 100644 --- a/backend/mpc-system/services/service-party-app/electron/main.ts +++ b/backend/mpc-system/services/service-party-app/electron/main.ts @@ -2458,9 +2458,18 @@ app.whenReady().then(async () => { } createWindow(); - app.on('activate', () => { + app.on('activate', async () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); + // macOS: 重新激活时检查并恢复 gRPC 连接 + if (grpcClient && !grpcClient.isConnected()) { + debugLog.info('grpc', 'App activated, reconnecting to Message Router...'); + try { + await connectAndRegisterToMessageRouter(); + } catch (err) { + debugLog.error('grpc', `Failed to reconnect on activate: ${(err as Error).message}`); + } + } } }); }); diff --git a/backend/mpc-system/services/service-party-app/electron/modules/tss-handler.ts b/backend/mpc-system/services/service-party-app/electron/modules/tss-handler.ts index 4678a3b5..4686c30d 100644 --- a/backend/mpc-system/services/service-party-app/electron/modules/tss-handler.ts +++ b/backend/mpc-system/services/service-party-app/electron/modules/tss-handler.ts @@ -290,8 +290,11 @@ export class TSSHandler extends EventEmitter { this.isPrepared = false; this.messageBuffer = []; this.tssProcess = null; - // 清理消息监听器,防止下次 keygen 时重复注册 + this.sessionId = null; + this.partyId = null; + // 清理消息监听器和 gRPC 流订阅,防止下次 keygen 时出错 this.grpcClient.removeAllListeners('mpcMessage'); + this.grpcClient.unsubscribeMessages(); if (code === 0 && resultData) { try { @@ -327,14 +330,19 @@ export class TSSHandler extends EventEmitter { this.isPrepared = false; this.messageBuffer = []; this.tssProcess = null; - // 清理消息监听器 + this.sessionId = null; + this.partyId = null; + // 清理消息监听器和 gRPC 流订阅 this.grpcClient.removeAllListeners('mpcMessage'); + this.grpcClient.unsubscribeMessages(); reject(err); }); } catch (err) { this.isRunning = false; this.isPrepared = false; + this.sessionId = null; + this.partyId = null; reject(err); } }); @@ -674,8 +682,11 @@ export class TSSHandler extends EventEmitter { this.isPrepared = false; this.messageBuffer = []; this.tssProcess = null; - // 清理消息监听器 + this.sessionId = null; + this.partyId = null; + // 清理消息监听器和 gRPC 流订阅 this.grpcClient.removeAllListeners('mpcMessage'); + this.grpcClient.unsubscribeMessages(); if (code === 0 && resultData) { try { @@ -709,14 +720,19 @@ export class TSSHandler extends EventEmitter { this.isPrepared = false; this.messageBuffer = []; this.tssProcess = null; - // 清理消息监听器 + this.sessionId = null; + this.partyId = null; + // 清理消息监听器和 gRPC 流订阅 this.grpcClient.removeAllListeners('mpcMessage'); + this.grpcClient.unsubscribeMessages(); reject(err); }); } catch (err) { this.isRunning = false; this.isPrepared = false; + this.sessionId = null; + this.partyId = null; reject(err); } }); @@ -734,7 +750,11 @@ export class TSSHandler extends EventEmitter { this.isProcessReady = false; this.isPrepared = false; this.messageBuffer = []; + this.sessionId = null; + this.partyId = null; + // 清理消息监听器和 gRPC 流订阅 this.grpcClient.removeAllListeners('mpcMessage'); + this.grpcClient.unsubscribeMessages(); } /**