fix(electron): add gRPC connection check before subscribing to messages

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 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-31 13:04:19 -08:00
parent c0229a1139
commit d051178801
2 changed files with 40 additions and 8 deletions

View File

@ -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}` };
}
}
// 检查会话状态

View File

@ -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');