rwadurian/backend/mpc-system/services/service-party-app/electron/modules/account-client.ts

380 lines
9.4 KiB
TypeScript

/**
* Account Service HTTP Client
*
* 用于 Service-Party-App 调用 Account 服务的 HTTP API
* 主要用于创建/查询 keygen 和 sign 会话
*/
// =============================================================================
// 类型定义
// =============================================================================
// Keygen 会话相关
export interface CreateKeygenSessionRequest {
wallet_name: string;
threshold_t: number;
threshold_n: number;
initiator_party_id: string;
initiator_name?: string;
persistent_count: number;
external_count: number;
expires_in_seconds?: number;
}
export interface CreateKeygenSessionResponse {
session_id: string;
invite_code: string;
wallet_name: string;
threshold_n: number;
threshold_t: number;
selected_server_parties: string[];
join_tokens: Record<string, string>;
join_token?: string; // Wildcard token for backward compatibility
expires_at: number;
}
export interface JoinSessionRequest {
party_id: string;
join_token: string;
device_type?: string;
device_id?: string;
}
export interface PartyInfo {
party_id: string;
party_index: number;
}
export interface SessionInfo {
session_id: string;
session_type: string;
threshold_n: number;
threshold_t: number;
status: string;
wallet_name: string;
invite_code: string;
keygen_session_id?: string;
}
export interface JoinSessionResponse {
success: boolean;
party_index: number;
session_info: SessionInfo;
other_parties: PartyInfo[];
}
// Participant status information with party_index
export interface ParticipantStatusInfo {
party_id: string;
party_index: number;
status: string;
}
export interface GetSessionStatusResponse {
session_id: string;
status: string;
threshold_t: number; // Minimum parties needed to sign (e.g., 2 in 2-of-3)
threshold_n: number; // Total number of parties required (e.g., 3 in 2-of-3)
completed_parties: number;
total_parties: number;
session_type: string;
public_key?: string;
signature?: string;
has_delegate: boolean;
// participants contains detailed participant information including party_index
// Used for co_managed_keygen sessions to build correct participant list
participants?: ParticipantStatusInfo[];
}
export interface GetSessionByInviteCodeResponse {
session_id: string;
wallet_name: string;
threshold_n: number;
threshold_t: number;
status: string;
invite_code: string;
expires_at: number;
joined_parties: number;
completed_parties?: number;
total_parties?: number;
join_token?: string;
}
// Sign 会话相关
export interface SignPartyInfo {
party_id: string;
party_index: number;
}
export interface CreateSignSessionRequest {
keygen_session_id: string;
wallet_name: string;
message_hash: string;
parties: SignPartyInfo[];
threshold_t: number;
initiator_name?: string;
}
export interface CreateSignSessionResponse {
session_id: string;
invite_code: string;
keygen_session_id: string;
wallet_name: string;
threshold_t: number;
selected_parties: string[];
expires_at: number;
join_token?: string; // Backward compatible: wildcard token (may be empty)
join_tokens: Record<string, string>; // New: all join tokens (map[partyID]token)
}
export interface GetSignSessionByInviteCodeResponse {
session_id: string;
keygen_session_id: string;
wallet_name: string;
message_hash: string;
threshold_t: number;
threshold_n: number;
status: string;
invite_code: string;
expires_at: number;
parties: SignPartyInfo[];
joined_count: number;
join_token?: string;
}
export interface GetSignSessionStatusResponse {
session_id: string;
status: string;
session_type: string;
threshold_t: number;
threshold_n: number;
completed_parties: number;
total_parties: number;
joined_count?: number;
parties?: SignPartyInfo[];
participants?: Array<{ party_id: string; party_index: number; status: string }>;
message_hash?: string;
signature?: string;
}
// 错误响应
export interface ErrorResponse {
error: string;
message?: string;
}
// =============================================================================
// HTTP 客户端类
// =============================================================================
export class AccountClient {
private baseUrl: string;
private timeout: number;
/**
* 构造函数
* @param baseUrl Account 服务的基础 URL (例如: https://api.szaiai.com 或 http://localhost:8080)
* @param timeout 请求超时时间 (毫秒)
*/
constructor(baseUrl: string, timeout: number = 30000) {
// 移除末尾的斜杠
this.baseUrl = baseUrl.replace(/\/$/, '');
this.timeout = timeout;
}
/**
* 更新基础 URL
*/
setBaseUrl(baseUrl: string): void {
this.baseUrl = baseUrl.replace(/\/$/, '');
}
/**
* 获取当前基础 URL
*/
getBaseUrl(): string {
return this.baseUrl;
}
/**
* 发送 HTTP 请求
*/
private async request<T>(
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
path: string,
body?: unknown
): Promise<T> {
const url = `${this.baseUrl}${path}`;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const options: RequestInit = {
method,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
signal: controller.signal,
};
if (body) {
options.body = JSON.stringify(body);
}
console.log(`[AccountClient] ${method} ${url}`, body ? JSON.stringify(body) : '');
const response = await fetch(url, options);
const text = await response.text();
let data: T | ErrorResponse;
try {
data = JSON.parse(text);
} catch {
throw new Error(`Invalid JSON response: ${text}`);
}
if (!response.ok) {
const errorData = data as ErrorResponse;
throw new Error(errorData.message || errorData.error || `HTTP ${response.status}`);
}
console.log(`[AccountClient] Response:`, data);
return data as T;
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
throw new Error(`Request timeout after ${this.timeout}ms`);
}
throw error;
} finally {
clearTimeout(timeoutId);
}
}
// ===========================================================================
// Keygen 会话 API
// ===========================================================================
/**
* 创建 Keygen 会话
*/
async createKeygenSession(
params: CreateKeygenSessionRequest
): Promise<CreateKeygenSessionResponse> {
return this.request<CreateKeygenSessionResponse>(
'POST',
'/api/v1/co-managed/sessions',
params
);
}
/**
* 加入会话
*/
async joinSession(
sessionId: string,
params: JoinSessionRequest
): Promise<JoinSessionResponse> {
return this.request<JoinSessionResponse>(
'POST',
`/api/v1/co-managed/sessions/${sessionId}/join`,
params
);
}
/**
* 获取会话状态
*/
async getSessionStatus(sessionId: string): Promise<GetSessionStatusResponse> {
return this.request<GetSessionStatusResponse>(
'GET',
`/api/v1/co-managed/sessions/${sessionId}`
);
}
/**
* 通过邀请码查询 Keygen 会话
*/
async getSessionByInviteCode(inviteCode: string): Promise<GetSessionByInviteCodeResponse> {
return this.request<GetSessionByInviteCodeResponse>(
'GET',
`/api/v1/co-managed/sessions/by-invite-code/${inviteCode}`
);
}
// ===========================================================================
// Sign 会话 API
// ===========================================================================
/**
* 创建 Sign 会话
*/
async createSignSession(
params: CreateSignSessionRequest
): Promise<CreateSignSessionResponse> {
return this.request<CreateSignSessionResponse>(
'POST',
'/api/v1/co-managed/sign',
params
);
}
/**
* 通过邀请码查询 Sign 会话
*/
async getSignSessionByInviteCode(inviteCode: string): Promise<GetSignSessionByInviteCodeResponse> {
return this.request<GetSignSessionByInviteCodeResponse>(
'GET',
`/api/v1/co-managed/sign/by-invite-code/${inviteCode}`
);
}
/**
* 获取 Sign 会话状态
*/
async getSignSessionStatus(sessionId: string): Promise<GetSignSessionStatusResponse> {
return this.request<GetSignSessionStatusResponse>(
'GET',
`/api/v1/co-managed/sign/${sessionId}`
);
}
// ===========================================================================
// 健康检查
// ===========================================================================
/**
* 健康检查
*/
async healthCheck(): Promise<{ status: string; service: string }> {
return this.request<{ status: string; service: string }>(
'GET',
'/health'
);
}
/**
* 测试连接
*/
async testConnection(): Promise<boolean> {
try {
const result = await this.healthCheck();
return result.status === 'healthy';
} catch {
return false;
}
}
}
// =============================================================================
// 默认实例
// =============================================================================
// 默认使用生产环境地址
const DEFAULT_ACCOUNT_SERVICE_URL = 'https://rwaapi.szaiai.com';
// 创建默认客户端实例
export const accountClient = new AccountClient(DEFAULT_ACCOUNT_SERVICE_URL);