From d051178801909924d340dcef3da6e5740550b40a Mon Sep 17 00:00:00 2001 From: hailin Date: Wed, 31 Dec 2025 13:04:19 -0800 Subject: [PATCH] fix(electron): add gRPC connection check before subscribing to messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The app was crashing with "CANCELLED: Cancelled on client" error when opening the app a second time. This happened because: 1. When window was reopened, old gRPC streams were in cancelled state 2. prepareForSign/prepareForKeygen tried to subscribe on cancelled streams 3. The error was unhandled and crashed the app Changes: - Add isConnected() check in prepareForSign() and prepareForKeygen() - Throw meaningful error when gRPC client is not connected - Wrap all prepareFor* calls in try-catch in main.ts - Return user-friendly error message instead of crashing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../service-party-app/electron/main.ts | 36 ++++++++++++++----- .../electron/modules/tss-handler.ts | 12 +++++++ 2 files changed, 40 insertions(+), 8 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 a760ac9a..314bdb23 100644 --- a/backend/mpc-system/services/service-party-app/electron/main.ts +++ b/backend/mpc-system/services/service-party-app/electron/main.ts @@ -1174,8 +1174,13 @@ function setupIpcHandlers() { // 这确保在其他方开始发送 TSS 消息时,我们已经准备好接收和缓冲 // 即使 keygen 进程还没启动,消息也不会丢失 if (tssHandler && 'prepareForKeygen' in tssHandler) { - debugLog.info('tss', `Preparing for keygen: subscribing to messages for session ${sessionId}`); - (tssHandler as { prepareForKeygen: (sessionId: string, partyId: string) => void }).prepareForKeygen(sessionId, partyId); + try { + debugLog.info('tss', `Preparing for keygen: subscribing to messages for session ${sessionId}`); + (tssHandler as { prepareForKeygen: (sessionId: string, partyId: string) => void }).prepareForKeygen(sessionId, partyId); + } catch (prepareErr) { + debugLog.error('tss', `Failed to prepare for keygen: ${(prepareErr as Error).message}`); + return { success: false, error: `消息订阅失败: ${(prepareErr as Error).message}` }; + } } // 方案 B: 检查 JoinSession 响应中的 session 状态 @@ -1287,8 +1292,13 @@ function setupIpcHandlers() { // 关键步骤:立即预订阅消息流 if (tssHandler && 'prepareForKeygen' in tssHandler) { - debugLog.info('tss', `Initiator preparing for keygen: subscribing to messages for session ${result.session_id}`); - (tssHandler as { prepareForKeygen: (sessionId: string, partyId: string) => void }).prepareForKeygen(result.session_id, partyId); + try { + debugLog.info('tss', `Initiator preparing for keygen: subscribing to messages for session ${result.session_id}`); + (tssHandler as { prepareForKeygen: (sessionId: string, partyId: string) => void }).prepareForKeygen(result.session_id, partyId); + } catch (prepareErr) { + debugLog.error('tss', `Failed to prepare for keygen: ${(prepareErr as Error).message}`); + return { success: false, error: `消息订阅失败: ${(prepareErr as Error).message}` }; + } } // 方案 B: 检查 JoinSession 响应中的 session 状态 @@ -1707,8 +1717,13 @@ function setupIpcHandlers() { // 预订阅消息流 if (tssHandler && 'prepareForSign' in tssHandler) { - debugLog.info('tss', `Initiator preparing for sign: subscribing to messages for session ${result.session_id}`); - (tssHandler as TSSHandler).prepareForSign(result.session_id, partyId); + try { + debugLog.info('tss', `Initiator preparing for sign: subscribing to messages for session ${result.session_id}`); + (tssHandler as TSSHandler).prepareForSign(result.session_id, partyId); + } catch (prepareErr) { + debugLog.error('tss', `Failed to prepare for sign: ${(prepareErr as Error).message}`); + return { success: false, error: `消息订阅失败: ${(prepareErr as Error).message}` }; + } } // 检查会话状态 @@ -1873,8 +1888,13 @@ function setupIpcHandlers() { // 预订阅消息流 if (tssHandler && 'prepareForSign' in tssHandler) { - debugLog.info('tss', `Preparing for sign: subscribing to messages for session ${params.sessionId}`); - (tssHandler as TSSHandler).prepareForSign(params.sessionId, partyId); + try { + debugLog.info('tss', `Preparing for sign: subscribing to messages for session ${params.sessionId}`); + (tssHandler as TSSHandler).prepareForSign(params.sessionId, partyId); + } catch (prepareErr) { + debugLog.error('tss', `Failed to prepare for sign: ${(prepareErr as Error).message}`); + return { success: false, error: `消息订阅失败: ${(prepareErr 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 422950be..4678a3b5 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 @@ -123,6 +123,12 @@ export class TSSHandler extends EventEmitter { * @param partyId 自己的 party ID */ prepareForKeygen(sessionId: string, partyId: string): void { + // 检查 gRPC 连接状态 + if (!this.grpcClient.isConnected()) { + console.error('[TSS] Cannot prepare for keygen: gRPC client not connected'); + throw new Error('gRPC client not connected'); + } + // 如果已经为同一个 session 准备过,跳过 if (this.isPrepared && this.sessionId === sessionId) { console.log('[TSS] Already prepared for same session, skip'); @@ -489,6 +495,12 @@ export class TSSHandler extends EventEmitter { * @param partyId 自己的 party ID */ prepareForSign(sessionId: string, partyId: string): void { + // 检查 gRPC 连接状态 + if (!this.grpcClient.isConnected()) { + console.error('[TSS-SIGN] Cannot prepare for sign: gRPC client not connected'); + throw new Error('gRPC client not connected'); + } + // 如果已经为同一个 session 准备过,跳过 if (this.isPrepared && this.sessionId === sessionId) { console.log('[TSS-SIGN] Already prepared for same session, skip');