feat(service-party-app): 实现启动时自动注册到 Message Router,状态验证真实化

## 主要变更

### 1. 启动时自动连接并注册
- main.ts: 添加 `connectAndRegisterToMessageRouter()` 函数
- 应用启动时自动连接到 Message Router 并注册为 temporary 角色
- 自动生成并持久化 partyId(使用 crypto.randomUUID)
- 自动订阅会话事件

### 2. 状态验证真实化
- appStore.ts: 重写 `checkAllServices()` 消息路由检测逻辑
- 不再只检测连接成功,而是:
  1. 调用 isConnected() 检查连接状态
  2. 调用 getPartyId() 检查是否已注册
  3. 调用 getRegisteredParties() 从 Message Router 获取注册列表
  4. 验证自己的 partyId 是否在列表中
- 状态显示更准确:
  - "未连接到 xxx" - 未连接
  - "已连接但未注册" - 已连接但没注册
  - "已注册 (在线)" - 完全正常
  - "注册验证失败" - 注册了但验证失败

### 3. 新增 IPC API
- grpc:getPartyId - 获取当前 partyId
- grpc:isConnected - 检查连接状态
- grpc:connect - 连接到 Message Router
- grpc:register - 注册为参与方

### 修改的文件
- electron/main.ts
- electron/preload.ts
- src/stores/appStore.ts
- src/types/electron.d.ts

🤖 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-28 23:29:04 -08:00
parent 73034c072c
commit 28da7f6807
4 changed files with 139 additions and 15 deletions

View File

@ -1,6 +1,7 @@
import { app, BrowserWindow, ipcMain, shell, dialog } from 'electron';
import * as path from 'path';
import * as fs from 'fs';
import * as crypto from 'crypto';
import express from 'express';
import { GrpcClient } from './modules/grpc-client';
import { DatabaseManager } from './modules/database';
@ -83,14 +84,27 @@ function startHttpServer() {
});
}
// 生成或获取持久化的 partyId
function getOrCreatePartyId(db: DatabaseManager): string {
const settings = db.getAllSettings();
let partyId = settings['party_id'];
if (!partyId) {
// 生成一个新的 UUID 作为 partyId
partyId = crypto.randomUUID();
db.setSetting('party_id', partyId);
console.log('Generated new partyId:', partyId);
}
return partyId;
}
// 初始化服务
async function initServices() {
// 初始化数据库 (必须首先初始化)
database = new DatabaseManager();
// 初始化 gRPC 客户端
grpcClient = new GrpcClient();
// 初始化数据库
database = new DatabaseManager();
// 初始化 Kava 交易服务
kavaTxService = new KavaTxService(KAVA_MAINNET_TX_CONFIG);
@ -102,6 +116,39 @@ async function initServices() {
// 设置 IPC 处理器
setupIpcHandlers();
// 启动时自动连接并注册到 Message Router
await connectAndRegisterToMessageRouter();
}
// 连接并注册到 Message Router
async function connectAndRegisterToMessageRouter() {
if (!grpcClient || !database) {
console.error('gRPC client or database not initialized');
return;
}
try {
const settings = database.getAllSettings();
const routerUrl = settings['message_router_url'] || 'mpc-grpc.szaiai.com:443';
const partyId = getOrCreatePartyId(database);
const role = 'temporary'; // Service-Party-App 使用 temporary 角色
console.log(`Connecting to Message Router: ${routerUrl}...`);
await grpcClient.connect(routerUrl);
console.log('Connected to Message Router');
console.log(`Registering as party: ${partyId} (role: ${role})...`);
await grpcClient.registerParty(partyId, role);
console.log('Registered to Message Router successfully');
// 订阅会话事件
grpcClient.subscribeSessionEvents(partyId);
console.log('Subscribed to session events');
} catch (error) {
console.error('Failed to connect/register to Message Router:', (error as Error).message);
// 不抛出错误,允许应用继续启动,用户可以稍后手动重试
}
}
// 设置 IPC 通信处理器
@ -111,9 +158,9 @@ function setupIpcHandlers() {
// ===========================================================================
// gRPC 连接
ipcMain.handle('grpc:connect', async (_event, { host, port }) => {
ipcMain.handle('grpc:connect', async (_event, { url }) => {
try {
await grpcClient?.connect(host, port);
await grpcClient?.connect(url);
return { success: true };
} catch (error) {
return { success: false, error: (error as Error).message };
@ -227,6 +274,26 @@ function setupIpcHandlers() {
}
});
// gRPC - 获取当前 partyId
ipcMain.handle('grpc:getPartyId', async () => {
try {
const partyId = grpcClient?.getPartyId();
return { success: true, partyId: partyId || null };
} catch (error) {
return { success: false, error: (error as Error).message, partyId: null };
}
});
// gRPC - 检查连接状态
ipcMain.handle('grpc:isConnected', async () => {
try {
const connected = grpcClient?.isConnected() || false;
return { success: true, connected };
} catch (error) {
return { success: false, error: (error as Error).message, connected: false };
}
});
// gRPC - 验证签名会话 (通过 Account 服务 HTTP API)
ipcMain.handle('grpc:validateSigningSession', async (_event, { code }) => {
try {

View File

@ -46,6 +46,19 @@ contextBridge.exposeInMainWorld('electronAPI', {
getRegisteredParties: (roleFilter?: string, onlyOnline?: boolean) =>
ipcRenderer.invoke('grpc:getRegisteredParties', { roleFilter, onlyOnline }),
// 获取当前 partyId
getPartyId: () => ipcRenderer.invoke('grpc:getPartyId'),
// 检查连接状态
isConnected: () => ipcRenderer.invoke('grpc:isConnected'),
// 连接到 Message Router
connect: (url: string) => ipcRenderer.invoke('grpc:connect', { url }),
// 注册为参与方
register: (partyId: string, role: string) =>
ipcRenderer.invoke('grpc:register', { partyId, role }),
// 签名相关
validateSigningSession: (code: string) =>
ipcRenderer.invoke('grpc:validateSigningSession', { code }),

View File

@ -101,25 +101,61 @@ export const useAppStore = create<AppState>((set, get) => ({
checkAllServices: async () => {
const { setMessageRouterStatus, setKavaApiStatus, setDatabaseStatus, setAppReady } = get();
// 检测 Message Router
// 检测 Message Router - 真实验证注册状态
setMessageRouterStatus({ status: 'checking', message: '正在检测...' });
try {
if (window.electronAPI) {
const settings = await window.electronAPI.storage.getSettings();
const routerUrl = settings?.messageRouterUrl || 'mpc-grpc.szaiai.com:443';
const result = await window.electronAPI.grpc.testConnection(routerUrl);
if (result.success) {
// 1. 检查是否已连接
const connResult = await window.electronAPI.grpc.isConnected();
if (!connResult.connected) {
setMessageRouterStatus({
status: 'connected',
message: routerUrl,
status: 'disconnected',
message: '未连接到 ' + routerUrl,
lastChecked: new Date(),
});
} else {
setMessageRouterStatus({
status: 'error',
message: result.error || '连接失败',
lastChecked: new Date(),
});
// 2. 已连接,检查是否已注册
const partyIdResult = await window.electronAPI.grpc.getPartyId();
if (!partyIdResult.partyId) {
setMessageRouterStatus({
status: 'disconnected',
message: '已连接但未注册',
lastChecked: new Date(),
});
} else {
// 3. 已注册,从 Message Router 验证自己是否在注册列表中
const partiesResult = await window.electronAPI.grpc.getRegisteredParties();
if (partiesResult.success) {
const selfRegistered = partiesResult.parties.some(
(p) => p.partyId === partyIdResult.partyId
);
if (selfRegistered) {
const selfParty = partiesResult.parties.find(
(p) => p.partyId === partyIdResult.partyId
);
setMessageRouterStatus({
status: 'connected',
message: `已注册 (${selfParty?.online ? '在线' : '离线'})`,
lastChecked: new Date(),
});
} else {
setMessageRouterStatus({
status: 'error',
message: '注册验证失败',
lastChecked: new Date(),
});
}
} else {
setMessageRouterStatus({
status: 'error',
message: partiesResult.error || '获取注册列表失败',
lastChecked: new Date(),
});
}
}
}
} else {
setMessageRouterStatus({

View File

@ -430,6 +430,14 @@ interface ElectronAPI {
testConnection: (url: string) => Promise<TestConnectionResult>;
// 获取已注册的参与方列表
getRegisteredParties: (roleFilter?: string, onlyOnline?: boolean) => Promise<GetRegisteredPartiesResult>;
// 获取当前 partyId
getPartyId: () => Promise<{ success: boolean; partyId: string | null; error?: string }>;
// 检查连接状态
isConnected: () => Promise<{ success: boolean; connected: boolean; error?: string }>;
// 连接到 Message Router
connect: (url: string) => Promise<{ success: boolean; error?: string }>;
// 注册为参与方
register: (partyId: string, role: string) => Promise<{ success: boolean; error?: string }>;
// 签名相关
validateSigningSession: (code: string) => Promise<ValidateSigningSessionResult>;
joinSigningSession: (params: JoinSigningSessionParams) => Promise<JoinSigningSessionResult>;