/** * 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; 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; // 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( method: 'GET' | 'POST' | 'PUT' | 'DELETE', path: string, body?: unknown ): Promise { 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 { return this.request( 'POST', '/api/v1/co-managed/sessions', params ); } /** * 加入会话 */ async joinSession( sessionId: string, params: JoinSessionRequest ): Promise { return this.request( 'POST', `/api/v1/co-managed/sessions/${sessionId}/join`, params ); } /** * 获取会话状态 */ async getSessionStatus(sessionId: string): Promise { return this.request( 'GET', `/api/v1/co-managed/sessions/${sessionId}` ); } /** * 通过邀请码查询 Keygen 会话 */ async getSessionByInviteCode(inviteCode: string): Promise { return this.request( 'GET', `/api/v1/co-managed/sessions/by-invite-code/${inviteCode}` ); } // =========================================================================== // Sign 会话 API // =========================================================================== /** * 创建 Sign 会话 */ async createSignSession( params: CreateSignSessionRequest ): Promise { return this.request( 'POST', '/api/v1/co-managed/sign', params ); } /** * 通过邀请码查询 Sign 会话 */ async getSignSessionByInviteCode(inviteCode: string): Promise { return this.request( 'GET', `/api/v1/co-managed/sign/by-invite-code/${inviteCode}` ); } /** * 获取 Sign 会话状态 */ async getSignSessionStatus(sessionId: string): Promise { return this.request( '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 { 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);