fix(electron): properly cleanup gRPC message stream after keygen/sign

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 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-31 13:07:43 -08:00
parent d051178801
commit 66a718ea72
2 changed files with 34 additions and 5 deletions

View File

@ -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}`);
}
}
}
});
});

View File

@ -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();
}
/**