WIP
This commit is contained in:
parent
e98dbaca8d
commit
d80e490263
1703
src/.service.bak.ts
Executable file
1703
src/.service.bak.ts
Executable file
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,9 @@
|
||||
// Main entry point for the SDK Signer server
|
||||
export { Service } from './service';
|
||||
export { config } from './config';
|
||||
export { MessageType } from './models';
|
||||
export { MessageType, AnkFlag } from './models';
|
||||
export { isValid32ByteHex } from './utils';
|
||||
export { RelayManager } from './relay-manager';
|
||||
|
||||
// Re-export the main server class
|
||||
export { Server } from './simple-server';
|
@ -38,3 +38,23 @@ export enum MessageType {
|
||||
ADD_DEVICE = 'ADD_DEVICE',
|
||||
DEVICE_ADDED = 'DEVICE_ADDED',
|
||||
}
|
||||
|
||||
// Re-export AnkFlag from WASM for relay message typing
|
||||
export { AnkFlag } from '../pkg/sdk_client';
|
||||
|
||||
// Message priority levels
|
||||
export enum MessagePriority {
|
||||
LOW = 0,
|
||||
NORMAL = 1,
|
||||
HIGH = 2,
|
||||
CRITICAL = 3,
|
||||
}
|
||||
|
||||
// Message delivery status
|
||||
export enum DeliveryStatus {
|
||||
PENDING = 'PENDING',
|
||||
SENT = 'SENT',
|
||||
DELIVERED = 'DELIVERED',
|
||||
FAILED = 'FAILED',
|
||||
RETRY = 'RETRY',
|
||||
}
|
96
src/relay-example.ts
Normal file
96
src/relay-example.ts
Normal file
@ -0,0 +1,96 @@
|
||||
// Example usage of the RelayManager for outbound relay connections
|
||||
import { RelayManager } from './relay-manager';
|
||||
import { AnkFlag } from './models';
|
||||
|
||||
async function exampleRelayUsage() {
|
||||
const relayManager = RelayManager.getInstance();
|
||||
|
||||
// 1. Connect to relays
|
||||
console.log('🔗 Connecting to relays...');
|
||||
|
||||
// Connect to a faucet relay
|
||||
await relayManager.connectToRelay(
|
||||
'faucet-relay-1',
|
||||
'wss://faucet-relay.example.com/ws',
|
||||
'sp1qfaucetrelayaddress...'
|
||||
);
|
||||
|
||||
// Connect to a validator relay
|
||||
await relayManager.connectToRelay(
|
||||
'validator-relay-1',
|
||||
'wss://validator-relay.example.com/ws',
|
||||
'sp1qvalidatorrelayaddress...'
|
||||
);
|
||||
|
||||
// 2. Send messages using protocol-specific methods
|
||||
console.log('📤 Sending protocol messages...');
|
||||
|
||||
// Send faucet message
|
||||
relayManager.sendFaucetMessage('faucet_request_message');
|
||||
|
||||
// Send new transaction
|
||||
relayManager.sendNewTxMessage('new_transaction_data');
|
||||
|
||||
// Send commit message
|
||||
relayManager.sendCommitMessage('commit_data');
|
||||
|
||||
// Send cipher messages
|
||||
relayManager.sendCipherMessages(['cipher1', 'cipher2']);
|
||||
|
||||
// 3. Send messages using generic method with AnkFlag
|
||||
console.log('📤 Sending generic messages...');
|
||||
|
||||
relayManager.sendMessage('NewTx' as AnkFlag, 'transaction_data', 'validator-relay-1');
|
||||
relayManager.sendMessage('Faucet' as AnkFlag, 'faucet_data', 'faucet-relay-1');
|
||||
relayManager.sendMessage('Cipher' as AnkFlag, 'cipher_data'); // Broadcast to all
|
||||
|
||||
// 4. Check relay status
|
||||
const stats = relayManager.getStats();
|
||||
console.log('📊 Relay stats:', stats);
|
||||
|
||||
// 5. Get connected relays
|
||||
const connectedRelays = relayManager.getConnectedRelays();
|
||||
console.log('🔗 Connected relays:', connectedRelays.map(r => r.id));
|
||||
|
||||
// 6. Send to specific relay
|
||||
const success = relayManager.sendToRelay('faucet-relay-1', 'Faucet' as AnkFlag, 'specific_message');
|
||||
console.log('✅ Message sent to specific relay:', success);
|
||||
|
||||
// 7. Broadcast to all relays
|
||||
const sentCount = relayManager.broadcastToAllRelays('Sync' as AnkFlag, 'sync_data');
|
||||
console.log(`📡 Broadcasted to ${sentCount} relays`);
|
||||
}
|
||||
|
||||
// Example of how to add relay addresses to the service
|
||||
async function addRelayAddresses() {
|
||||
// This would typically be done in the service configuration
|
||||
// For now, we'll show the structure
|
||||
|
||||
const relayAddresses = {
|
||||
'wss://faucet-relay.example.com/ws': 'sp1qfaucetrelayaddress...',
|
||||
'wss://validator-relay.example.com/ws': 'sp1qvalidatorrelayaddress...',
|
||||
'wss://network-relay.example.com/ws': 'sp1qnetworkrelayaddress...'
|
||||
};
|
||||
|
||||
console.log('📝 Relay addresses configured:', Object.keys(relayAddresses));
|
||||
}
|
||||
|
||||
// Example of storage API usage (separate from relay system)
|
||||
async function storageExample() {
|
||||
// Storage is handled via REST API, not WebSocket
|
||||
console.log('💾 Storage operations would use REST API endpoints:');
|
||||
console.log(' - POST /storage/push');
|
||||
console.log(' - GET /storage/retrieve/{hash}');
|
||||
console.log(' - DELETE /storage/delete/{hash}');
|
||||
console.log(' - GET /storage/list');
|
||||
}
|
||||
|
||||
// Run examples
|
||||
if (require.main === module) {
|
||||
exampleRelayUsage()
|
||||
.then(() => addRelayAddresses())
|
||||
.then(() => storageExample())
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
export { exampleRelayUsage, addRelayAddresses, storageExample };
|
396
src/relay-manager.ts
Normal file
396
src/relay-manager.ts
Normal file
@ -0,0 +1,396 @@
|
||||
import WebSocket from 'ws';
|
||||
import { AnkFlag } from '../pkg/sdk_client';
|
||||
|
||||
interface RelayConnection {
|
||||
id: string;
|
||||
ws: WebSocket;
|
||||
url: string;
|
||||
spAddress: string;
|
||||
isConnected: boolean;
|
||||
lastHeartbeat: number;
|
||||
reconnectAttempts: number;
|
||||
maxReconnectAttempts: number;
|
||||
}
|
||||
|
||||
interface QueuedMessage {
|
||||
id: string;
|
||||
flag: AnkFlag;
|
||||
payload: any;
|
||||
targetRelayId?: string;
|
||||
timestamp: number;
|
||||
expiresAt?: number;
|
||||
retryCount: number;
|
||||
maxRetries: number;
|
||||
}
|
||||
|
||||
export class RelayManager {
|
||||
private static instance: RelayManager;
|
||||
private relays: Map<string, RelayConnection> = new Map();
|
||||
private messageQueue: QueuedMessage[] = [];
|
||||
private processingQueue = false;
|
||||
private heartbeatInterval: NodeJS.Timeout | null = null;
|
||||
private queueProcessingInterval: NodeJS.Timeout | null = null;
|
||||
private reconnectInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
private constructor() {
|
||||
this.startHeartbeat();
|
||||
this.startQueueProcessing();
|
||||
this.startReconnectMonitoring();
|
||||
}
|
||||
|
||||
static getInstance(): RelayManager {
|
||||
if (!RelayManager.instance) {
|
||||
RelayManager.instance = new RelayManager();
|
||||
}
|
||||
return RelayManager.instance;
|
||||
}
|
||||
|
||||
// Relay Management - Outbound Connections
|
||||
public async connectToRelay(relayId: string, wsUrl: string, spAddress: string): Promise<boolean> {
|
||||
try {
|
||||
console.log(`🔗 Connecting to relay ${relayId} at ${wsUrl}`);
|
||||
|
||||
const ws = new WebSocket(wsUrl);
|
||||
|
||||
const relay: RelayConnection = {
|
||||
id: relayId,
|
||||
ws,
|
||||
url: wsUrl,
|
||||
spAddress,
|
||||
isConnected: false,
|
||||
lastHeartbeat: Date.now(),
|
||||
reconnectAttempts: 0,
|
||||
maxReconnectAttempts: 5
|
||||
};
|
||||
|
||||
// Set up WebSocket event handlers
|
||||
ws.on('open', () => {
|
||||
console.log(`✅ Connected to relay ${relayId}`);
|
||||
relay.isConnected = true;
|
||||
relay.reconnectAttempts = 0;
|
||||
relay.lastHeartbeat = Date.now();
|
||||
});
|
||||
|
||||
ws.on('message', (data: WebSocket.Data) => {
|
||||
try {
|
||||
const message = JSON.parse(data.toString());
|
||||
this.handleRelayMessage(relayId, message);
|
||||
} catch (error) {
|
||||
console.error(`❌ Error parsing message from relay ${relayId}:`, error);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
console.log(`🔌 Disconnected from relay ${relayId}`);
|
||||
relay.isConnected = false;
|
||||
this.scheduleReconnect(relayId);
|
||||
});
|
||||
|
||||
ws.on('error', (error) => {
|
||||
console.error(`❌ WebSocket error for relay ${relayId}:`, error);
|
||||
relay.isConnected = false;
|
||||
});
|
||||
|
||||
this.relays.set(relayId, relay);
|
||||
|
||||
// Wait for connection to establish
|
||||
return new Promise((resolve) => {
|
||||
const timeout = setTimeout(() => {
|
||||
resolve(false);
|
||||
}, 5000);
|
||||
|
||||
ws.once('open', () => {
|
||||
clearTimeout(timeout);
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to connect to relay ${relayId}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public disconnectFromRelay(relayId: string): void {
|
||||
const relay = this.relays.get(relayId);
|
||||
if (relay) {
|
||||
relay.isConnected = false;
|
||||
relay.ws.close();
|
||||
this.relays.delete(relayId);
|
||||
console.log(`🔌 Disconnected from relay ${relayId}`);
|
||||
}
|
||||
}
|
||||
|
||||
public getRelayById(relayId: string): RelayConnection | undefined {
|
||||
return this.relays.get(relayId);
|
||||
}
|
||||
|
||||
public getConnectedRelays(): RelayConnection[] {
|
||||
return Array.from(this.relays.values()).filter(relay => relay.isConnected);
|
||||
}
|
||||
|
||||
// Message Sending Methods using AnkFlag
|
||||
public sendMessage(flag: AnkFlag, payload: any, targetRelayId?: string): void {
|
||||
const msg: QueuedMessage = {
|
||||
id: this.generateMessageId(),
|
||||
flag,
|
||||
payload,
|
||||
targetRelayId,
|
||||
timestamp: Date.now(),
|
||||
expiresAt: Date.now() + 30000, // 30 seconds
|
||||
retryCount: 0,
|
||||
maxRetries: 3
|
||||
};
|
||||
|
||||
this.queueMessage(msg);
|
||||
}
|
||||
|
||||
public sendToRelay(relayId: string, flag: AnkFlag, payload: any): boolean {
|
||||
const relay = this.relays.get(relayId);
|
||||
if (!relay || !relay.isConnected) {
|
||||
console.warn(`⚠️ Cannot send to relay ${relayId}: not connected`);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const message = {
|
||||
flag,
|
||||
payload,
|
||||
messageId: this.generateMessageId()
|
||||
};
|
||||
|
||||
relay.ws.send(JSON.stringify(message));
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to send message to relay ${relayId}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public broadcastToAllRelays(flag: AnkFlag, payload: any): number {
|
||||
const connectedRelays = this.getConnectedRelays();
|
||||
let sentCount = 0;
|
||||
|
||||
for (const relay of connectedRelays) {
|
||||
if (this.sendToRelay(relay.id, flag, payload)) {
|
||||
sentCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`📡 Broadcasted to ${sentCount}/${connectedRelays.length} relays`);
|
||||
return sentCount;
|
||||
}
|
||||
|
||||
// Protocol-Specific Message Methods
|
||||
public sendNewTxMessage(message: string, targetRelayId?: string): void {
|
||||
// Use appropriate AnkFlag for new transaction
|
||||
this.sendMessage("NewTx" as AnkFlag, message, targetRelayId);
|
||||
}
|
||||
|
||||
public sendCommitMessage(message: string, targetRelayId?: string): void {
|
||||
// Use appropriate AnkFlag for commit
|
||||
this.sendMessage("Commit" as AnkFlag, message, targetRelayId);
|
||||
}
|
||||
|
||||
public sendCipherMessages(ciphers: string[], targetRelayId?: string): void {
|
||||
for (const cipher of ciphers) {
|
||||
// Use appropriate AnkFlag for cipher
|
||||
this.sendMessage("Cipher" as AnkFlag, cipher, targetRelayId);
|
||||
}
|
||||
}
|
||||
|
||||
public sendFaucetMessage(message: string, targetRelayId?: string): void {
|
||||
// Use appropriate AnkFlag for faucet
|
||||
this.sendMessage("Faucet" as AnkFlag, message, targetRelayId);
|
||||
}
|
||||
|
||||
// Message Queue Management
|
||||
private queueMessage(message: QueuedMessage): void {
|
||||
this.messageQueue.push(message);
|
||||
console.log(`📬 Queued message ${message.id} with flag ${message.flag}`);
|
||||
}
|
||||
|
||||
private async processQueue(): Promise<void> {
|
||||
if (this.processingQueue || this.messageQueue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.processingQueue = true;
|
||||
|
||||
try {
|
||||
const message = this.messageQueue.shift();
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if message has expired
|
||||
if (message.expiresAt && Date.now() > message.expiresAt) {
|
||||
console.warn(`⏰ Message ${message.id} expired`);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.deliverMessage(message);
|
||||
} finally {
|
||||
this.processingQueue = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async deliverMessage(message: QueuedMessage): Promise<void> {
|
||||
try {
|
||||
let delivered = false;
|
||||
|
||||
if (message.targetRelayId) {
|
||||
// Send to specific relay
|
||||
delivered = this.sendToRelay(message.targetRelayId, message.flag, message.payload);
|
||||
} else {
|
||||
// Broadcast to all connected relays
|
||||
const sentCount = this.broadcastToAllRelays(message.flag, message.payload);
|
||||
delivered = sentCount > 0;
|
||||
}
|
||||
|
||||
if (!delivered) {
|
||||
throw new Error('No suitable relay available');
|
||||
}
|
||||
|
||||
console.log(`✅ Message ${message.id} delivered`);
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to deliver message ${message.id}:`, error);
|
||||
message.retryCount++;
|
||||
|
||||
if (message.retryCount < message.maxRetries) {
|
||||
// Re-queue with exponential backoff
|
||||
setTimeout(() => {
|
||||
this.queueMessage(message);
|
||||
}, Math.pow(2, message.retryCount) * 1000);
|
||||
} else {
|
||||
console.error(`💀 Message ${message.id} failed after ${message.maxRetries} retries`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Relay Message Handling
|
||||
private handleRelayMessage(relayId: string, message: any): void {
|
||||
console.log(`📨 Received message from relay ${relayId}:`, message);
|
||||
|
||||
// Handle different types of relay responses
|
||||
if (message.flag) {
|
||||
// Handle protocol-specific responses
|
||||
this.handleProtocolMessage(relayId, message);
|
||||
} else if (message.type === 'heartbeat') {
|
||||
// Update heartbeat
|
||||
const relay = this.relays.get(relayId);
|
||||
if (relay) {
|
||||
relay.lastHeartbeat = Date.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleProtocolMessage(relayId: string, message: any): void {
|
||||
// Handle different AnkFlag responses
|
||||
switch (message.flag) {
|
||||
case "NewTx":
|
||||
console.log(`📨 NewTx response from relay ${relayId}`);
|
||||
break;
|
||||
case "Commit":
|
||||
console.log(`📨 Commit response from relay ${relayId}`);
|
||||
break;
|
||||
case "Cipher":
|
||||
console.log(`📨 Cipher response from relay ${relayId}`);
|
||||
break;
|
||||
case "Faucet":
|
||||
console.log(`📨 Faucet response from relay ${relayId}`);
|
||||
break;
|
||||
default:
|
||||
console.log(`📨 Unknown flag response from relay ${relayId}:`, message.flag);
|
||||
}
|
||||
}
|
||||
|
||||
// Reconnection Logic
|
||||
private scheduleReconnect(relayId: string): void {
|
||||
const relay = this.relays.get(relayId);
|
||||
if (!relay || relay.reconnectAttempts >= relay.maxReconnectAttempts) {
|
||||
console.log(`💀 Max reconnection attempts reached for relay ${relayId}`);
|
||||
this.relays.delete(relayId);
|
||||
return;
|
||||
}
|
||||
|
||||
const delay = Math.pow(2, relay.reconnectAttempts) * 1000; // Exponential backoff
|
||||
console.log(`🔄 Scheduling reconnect to relay ${relayId} in ${delay}ms (attempt ${relay.reconnectAttempts + 1})`);
|
||||
|
||||
setTimeout(async () => {
|
||||
relay.reconnectAttempts++;
|
||||
await this.connectToRelay(relayId, relay.url, relay.spAddress);
|
||||
}, delay);
|
||||
}
|
||||
|
||||
private startReconnectMonitoring(): void {
|
||||
this.reconnectInterval = setInterval(() => {
|
||||
// Check for disconnected relays and attempt reconnection
|
||||
for (const [relayId, relay] of this.relays) {
|
||||
if (!relay.isConnected && relay.reconnectAttempts < relay.maxReconnectAttempts) {
|
||||
this.scheduleReconnect(relayId);
|
||||
}
|
||||
}
|
||||
}, 10000); // Check every 10 seconds
|
||||
}
|
||||
|
||||
// Heartbeat Management
|
||||
private startHeartbeat(): void {
|
||||
this.heartbeatInterval = setInterval(() => {
|
||||
const now = Date.now();
|
||||
const heartbeatMessage = {
|
||||
type: 'heartbeat',
|
||||
timestamp: now
|
||||
};
|
||||
|
||||
for (const [relayId, relay] of this.relays) {
|
||||
if (relay.isConnected) {
|
||||
try {
|
||||
relay.ws.send(JSON.stringify(heartbeatMessage));
|
||||
relay.lastHeartbeat = now;
|
||||
} catch (error) {
|
||||
console.error(`❌ Heartbeat failed for relay ${relayId}:`, error);
|
||||
relay.isConnected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 30000); // 30 seconds
|
||||
}
|
||||
|
||||
private startQueueProcessing(): void {
|
||||
this.queueProcessingInterval = setInterval(() => {
|
||||
this.processQueue();
|
||||
}, 100); // Process queue every 100ms
|
||||
}
|
||||
|
||||
// Utility Methods
|
||||
private generateMessageId(): string {
|
||||
return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
public getStats(): any {
|
||||
return {
|
||||
totalRelays: this.relays.size,
|
||||
connectedRelays: this.getConnectedRelays().length,
|
||||
queuedMessages: this.messageQueue.length
|
||||
};
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
if (this.heartbeatInterval) {
|
||||
clearInterval(this.heartbeatInterval);
|
||||
}
|
||||
if (this.queueProcessingInterval) {
|
||||
clearInterval(this.queueProcessingInterval);
|
||||
}
|
||||
if (this.reconnectInterval) {
|
||||
clearInterval(this.reconnectInterval);
|
||||
}
|
||||
|
||||
for (const [relayId] of this.relays) {
|
||||
this.disconnectFromRelay(relayId);
|
||||
}
|
||||
|
||||
console.log('🛑 Relay manager shutdown complete');
|
||||
}
|
||||
}
|
417
src/service.ts
417
src/service.ts
@ -2,14 +2,20 @@
|
||||
import Database from './database.service';
|
||||
import * as wasm from '../pkg/sdk_client';
|
||||
import { ApiReturn, Device, HandshakeMessage, Member, MerkleProofResult, OutPointProcessMap, Process, ProcessState, RoleDefinition, SecretsStore, UserDiff } from '../pkg/sdk_client';
|
||||
import { RelayManager } from './relay-manager';
|
||||
|
||||
const DEFAULTAMOUNT = 1000n;
|
||||
|
||||
export class Service {
|
||||
private static instance: Service;
|
||||
private processes: Map<string, any> = new Map();
|
||||
private membersList: any = {};
|
||||
private relayManager: RelayManager;
|
||||
private storages: string[] = []; // storage urls
|
||||
|
||||
private constructor() {
|
||||
console.log('🔧 Service initialized');
|
||||
this.relayManager = RelayManager.getInstance();
|
||||
this.initWasm();
|
||||
}
|
||||
|
||||
@ -31,6 +37,323 @@ export class Service {
|
||||
return Service.instance;
|
||||
}
|
||||
|
||||
public async connectToRelays(): Promise<void> {
|
||||
const relays = this.getAllRelays();
|
||||
|
||||
console.log(`🔗 Connecting to ${relays.length} relays...`);
|
||||
|
||||
for (const relay of relays) {
|
||||
try {
|
||||
const success = await this.relayManager.connectToRelay(
|
||||
relay.spAddress, // Use spAddress as relay ID
|
||||
relay.wsurl,
|
||||
relay.spAddress
|
||||
);
|
||||
|
||||
if (success) {
|
||||
console.log(`✅ Connected to relay: ${relay.spAddress}`);
|
||||
} else {
|
||||
console.warn(`⚠️ Failed to connect to relay: ${relay.spAddress}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Error connecting to relay ${relay.spAddress}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all connected relays from RelayManager.
|
||||
* @returns An array of objects containing relay information.
|
||||
*/
|
||||
public getAllRelays(): { wsurl: string; spAddress: string }[] {
|
||||
const connectedRelays = this.relayManager.getConnectedRelays();
|
||||
return connectedRelays.map(relay => ({
|
||||
wsurl: relay.url,
|
||||
spAddress: relay.spAddress,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relay address to the RelayManager.
|
||||
* @param relayId - Unique identifier for the relay
|
||||
* @param wsUrl - WebSocket URL of the relay
|
||||
* @param spAddress - Silent Payment address of the relay
|
||||
*/
|
||||
public async addRelayAddress(relayId: string, wsUrl: string, spAddress: string): Promise<boolean> {
|
||||
try {
|
||||
const success = await this.relayManager.connectToRelay(relayId, wsUrl, spAddress);
|
||||
if (success) {
|
||||
console.log(`✅ Added relay: ${relayId} at ${wsUrl}`);
|
||||
} else {
|
||||
console.warn(`⚠️ Failed to add relay: ${relayId}`);
|
||||
}
|
||||
return success;
|
||||
} catch (error) {
|
||||
console.error(`❌ Error adding relay ${relayId}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a relay from the RelayManager.
|
||||
* @param relayId - Unique identifier for the relay to remove
|
||||
*/
|
||||
public removeRelayAddress(relayId: string): void {
|
||||
this.relayManager.disconnectFromRelay(relayId);
|
||||
console.log(`🔌 Removed relay: ${relayId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get relay statistics from RelayManager.
|
||||
* @returns Statistics about connected relays
|
||||
*/
|
||||
public getRelayStats(): any {
|
||||
return this.relayManager.getStats();
|
||||
}
|
||||
|
||||
public async getSecretForAddress(address: string): Promise<string | null> {
|
||||
const db = await Database.getInstance();
|
||||
return await db.getObject('shared_secrets', address);
|
||||
}
|
||||
|
||||
private async getTokensFromFaucet(): Promise<void> {
|
||||
try {
|
||||
await this.ensureSufficientAmount();
|
||||
} catch (e) {
|
||||
console.error('Failed to get tokens from relay, check connection');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public getAllMembers(): Record<string, Member> {
|
||||
return this.membersList;
|
||||
}
|
||||
|
||||
public getAddressesForMemberId(memberId: string): string[] | null {
|
||||
try {
|
||||
return this.membersList[memberId].sp_addresses;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async checkConnections(members: Member[]): Promise<void> {
|
||||
// Ensure the amount is available before proceeding
|
||||
await this.getTokensFromFaucet();
|
||||
let unconnectedAddresses = [];
|
||||
const myAddress = this.getDeviceAddress();
|
||||
for (const member of members) {
|
||||
const sp_addresses = member.sp_addresses;
|
||||
if (!sp_addresses || sp_addresses.length === 0) continue;
|
||||
for (const address of sp_addresses) {
|
||||
// For now, we ignore our own device address, although there might be use cases for having a secret with ourselves
|
||||
if (address === myAddress) continue;
|
||||
const sharedSecret = await this.getSecretForAddress(address);
|
||||
if (!sharedSecret) {
|
||||
unconnectedAddresses.push(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (unconnectedAddresses && unconnectedAddresses.length != 0) {
|
||||
const apiResult = await this.connectAddresses(unconnectedAddresses);
|
||||
await this.handleApiReturn(apiResult);
|
||||
}
|
||||
}
|
||||
|
||||
public async connectAddresses(addresses: string[]): Promise<ApiReturn> {
|
||||
if (addresses.length === 0) {
|
||||
throw new Error('Trying to connect to empty addresses list');
|
||||
}
|
||||
|
||||
try {
|
||||
return wasm.create_transaction(addresses, 1);
|
||||
} catch (e) {
|
||||
console.error('Failed to connect member:', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private async ensureSufficientAmount(): Promise<void> {
|
||||
const availableAmt: BigInt = wasm.get_available_amount();
|
||||
const target: BigInt = DEFAULTAMOUNT * BigInt(10);
|
||||
|
||||
if (availableAmt < target) {
|
||||
try {
|
||||
const faucetMsg = wasm.create_faucet_msg();
|
||||
this.relayManager.sendFaucetMessage(faucetMsg);
|
||||
} catch (e) {
|
||||
throw new Error('Failed to create faucet message');
|
||||
}
|
||||
|
||||
await this.waitForAmount(target);
|
||||
}
|
||||
}
|
||||
|
||||
private async waitForAmount(target: BigInt): Promise<BigInt> {
|
||||
let attempts = 3;
|
||||
|
||||
while (attempts > 0) {
|
||||
const amount: BigInt = wasm.get_available_amount();
|
||||
if (amount >= target) {
|
||||
return amount;
|
||||
}
|
||||
|
||||
attempts--;
|
||||
if (attempts > 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait for 1 second
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Amount is still 0 after 3 attempts');
|
||||
}
|
||||
|
||||
|
||||
private isFileBlob(value: any): value is { type: string, data: Uint8Array } {
|
||||
return (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
typeof value.type === 'string' &&
|
||||
value.data instanceof Uint8Array
|
||||
);
|
||||
}
|
||||
|
||||
private splitData(obj: Record<string, any>) {
|
||||
const jsonCompatibleData: Record<string, any> = {};
|
||||
const binaryData: Record<string, { type: string; data: Uint8Array }> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (this.isFileBlob(value)) {
|
||||
binaryData[key] = value;
|
||||
} else {
|
||||
jsonCompatibleData[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return { jsonCompatibleData, binaryData };
|
||||
}
|
||||
|
||||
public async createNewDevice() {
|
||||
try {
|
||||
const spAddress = wasm.create_new_device(0, 'signet');
|
||||
const device = wasm.dump_device();
|
||||
await this.saveDeviceInDatabase(device);
|
||||
return spAddress;
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to create new device: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async saveDeviceInDatabase(device: Device): Promise<void> {
|
||||
const db = await Database.getInstance();
|
||||
const walletStore = 'wallet';
|
||||
try {
|
||||
const prevDevice = await this.getDeviceFromDatabase();
|
||||
if (prevDevice) {
|
||||
await db.deleteObject(walletStore, "1");
|
||||
}
|
||||
await db.addObject({
|
||||
storeName: walletStore,
|
||||
object: { pre_id: '1', device },
|
||||
key: null,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public getPairingProcessId(): string {
|
||||
try {
|
||||
return wasm.get_pairing_process_id();
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to get pairing process: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async createPairingProcess(userName: string, pairWith: string[]): Promise<ApiReturn> {
|
||||
if (wasm.is_paired()) {
|
||||
throw new Error('Device already paired');
|
||||
}
|
||||
const myAddress: string = wasm.get_address();
|
||||
pairWith.push(myAddress);
|
||||
const privateData = {
|
||||
description: 'pairing',
|
||||
counter: 0,
|
||||
};
|
||||
const publicData = {
|
||||
memberPublicName: userName,
|
||||
pairedAddresses: pairWith,
|
||||
};
|
||||
const validation_fields: string[] = [...Object.keys(privateData), ...Object.keys(publicData), 'roles'];
|
||||
const roles: Record<string, RoleDefinition> = {
|
||||
pairing: {
|
||||
members: [],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 1.0,
|
||||
fields: validation_fields,
|
||||
min_sig_member: 1.0,
|
||||
},
|
||||
],
|
||||
storages: this.storages
|
||||
},
|
||||
};
|
||||
try {
|
||||
return this.createProcess(
|
||||
privateData,
|
||||
publicData,
|
||||
roles
|
||||
);
|
||||
} catch (e) {
|
||||
throw new Error(`Creating process failed:, ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async createProcess(
|
||||
privateData: Record<string, any>,
|
||||
publicData: Record<string, any>,
|
||||
roles: Record<string, RoleDefinition>,
|
||||
): Promise<ApiReturn> {
|
||||
const relayAddress = this.getAllRelays()[0]['spAddress'];
|
||||
const feeRate = 1;
|
||||
|
||||
// We can't encode files as the rest because Uint8Array is not valid json
|
||||
// So we first take them apart and we will encode them separately and put them back in the right object
|
||||
// TODO encoding of relatively large binaries (=> 1M) is a bit long now and blocking
|
||||
const privateSplitData = this.splitData(privateData);
|
||||
const publicSplitData = this.splitData(publicData);
|
||||
const encodedPrivateData = {
|
||||
...wasm.encode_json(privateSplitData.jsonCompatibleData),
|
||||
...wasm.encode_binary(privateSplitData.binaryData)
|
||||
};
|
||||
const encodedPublicData = {
|
||||
...wasm.encode_json(publicSplitData.jsonCompatibleData),
|
||||
...wasm.encode_binary(publicSplitData.binaryData)
|
||||
};
|
||||
|
||||
let members: Set<Member> = new Set();
|
||||
for (const role of Object.values(roles!)) {
|
||||
for (const member of role.members) {
|
||||
// Check if we know the member that matches this id
|
||||
const memberAddresses = this.getAddressesForMemberId(member);
|
||||
if (memberAddresses && memberAddresses.length != 0) {
|
||||
members.add({ sp_addresses: memberAddresses });
|
||||
}
|
||||
}
|
||||
}
|
||||
await this.checkConnections([...members]);
|
||||
|
||||
const result = wasm.create_new_process (
|
||||
encodedPrivateData,
|
||||
roles,
|
||||
encodedPublicData,
|
||||
relayAddress,
|
||||
feeRate,
|
||||
this.getAllMembers()
|
||||
);
|
||||
|
||||
return(result);
|
||||
}
|
||||
|
||||
// Core protocol method: Create PRD Update
|
||||
async createPrdUpdate(processId: string, stateId: string): Promise<ApiReturn> {
|
||||
console.log(`📢 Creating PRD update for process ${processId}, state ${stateId}`);
|
||||
@ -234,8 +557,31 @@ export class Service {
|
||||
}
|
||||
}
|
||||
|
||||
// Utility method: Check if device is paired
|
||||
isPaired(): boolean {
|
||||
public async getDeviceFromDatabase(): Promise<Device | null> {
|
||||
const db = await Database.getInstance();
|
||||
const walletStore = 'wallet';
|
||||
try {
|
||||
const dbRes = await db.getObject(walletStore, '1');
|
||||
if (dbRes) {
|
||||
return dbRes['device'];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to retrieve device from db: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async restoreDeviceFromDatabase(device: Device): Promise<void> {
|
||||
try {
|
||||
wasm.restore_device(device);
|
||||
console.log('✅ Device restored in WASM successfully');
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to restore device in WASM: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
public isPaired(): boolean {
|
||||
try {
|
||||
return wasm.is_paired();
|
||||
} catch (error) {
|
||||
@ -244,29 +590,45 @@ export class Service {
|
||||
}
|
||||
}
|
||||
|
||||
// Utility method: Get last committed state
|
||||
getLastCommitedState(process: any): any {
|
||||
return process.states.find((s: any) => s.commited_in) || null;
|
||||
public getLastCommitedState(process: Process): ProcessState | null {
|
||||
const index = this.getLastCommitedStateIndex(process);
|
||||
if (index === null) return null;
|
||||
return process.states[index];
|
||||
}
|
||||
|
||||
// Utility method: Get last committed state index
|
||||
getLastCommitedStateIndex(process: any): number | null {
|
||||
const index = process.states.findIndex((s: any) => s.commited_in);
|
||||
return index >= 0 ? index : null;
|
||||
}
|
||||
|
||||
// Utility method: Check if roles contain current user
|
||||
rolesContainsUs(roles: Record<string, any>): boolean {
|
||||
try {
|
||||
// This would need to be implemented based on your user management
|
||||
// For now, return true for testing
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error checking roles:', error);
|
||||
return true; // Fallback to true for testing
|
||||
public getLastCommitedStateIndex(process: Process): number | null {
|
||||
if (process.states.length === 0) return null;
|
||||
const processTip = process.states[process.states.length - 1].commited_in;
|
||||
for (let i = process.states.length - 1; i >= 0; i--) {
|
||||
if (process.states[i].commited_in !== processTip) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public rolesContainsUs(roles: Record<string, RoleDefinition>): boolean {
|
||||
let us;
|
||||
try {
|
||||
us = wasm.get_pairing_process_id();
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
return this.rolesContainsMember(roles, us);
|
||||
}
|
||||
|
||||
public rolesContainsMember(roles: Record<string, RoleDefinition>, pairingProcessId: string): boolean {
|
||||
for (const roleDef of Object.values(roles)) {
|
||||
if (roleDef.members.includes(pairingProcessId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Utility method: Add member to the members list
|
||||
addMember(outpoint: string, member: any) {
|
||||
this.membersList[outpoint] = member;
|
||||
@ -282,24 +644,20 @@ export class Service {
|
||||
}
|
||||
}
|
||||
|
||||
// WebSocket message methods (stubs for now)
|
||||
// WebSocket message methods using Relay Manager
|
||||
async sendNewTxMessage(message: string) {
|
||||
console.log('📤 Sending NewTx message:', message);
|
||||
// TODO: Implement actual WebSocket sending
|
||||
this.relayManager.sendNewTxMessage(message);
|
||||
}
|
||||
|
||||
async sendCommitMessage(message: string) {
|
||||
console.log('<EFBFBD><EFBFBD> Sending Commit message:', message);
|
||||
// TODO: Implement actual WebSocket sending
|
||||
console.log('📤 Sending Commit message:', message);
|
||||
this.relayManager.sendCommitMessage(message);
|
||||
}
|
||||
|
||||
async sendCipherMessages(ciphers: string[]) {
|
||||
console.log('📤 Sending Cipher messages:', ciphers.length, 'ciphers');
|
||||
for (let i = 0; i < ciphers.length; i++) {
|
||||
const cipher = ciphers[i];
|
||||
console.log('📤 Sending Cipher:', cipher);
|
||||
// TODO: Implement actual WebSocket sending
|
||||
}
|
||||
this.relayManager.sendCipherMessages(ciphers);
|
||||
}
|
||||
|
||||
// Blob and data storage methods
|
||||
@ -368,7 +726,6 @@ export class Service {
|
||||
return uint8Array;
|
||||
}
|
||||
|
||||
// Main handleApiReturn method
|
||||
public async handleApiReturn(apiReturn: ApiReturn) {
|
||||
console.log('🔄 Handling API return:', apiReturn);
|
||||
|
||||
|
@ -2,7 +2,8 @@ import WebSocket from 'ws';
|
||||
import { MessageType } from './models';
|
||||
import { config } from './config';
|
||||
import { Service } from './service';
|
||||
import { ApiReturn } from '../pkg/sdk_client';
|
||||
import { ApiReturn, Process } from '../pkg/sdk_client';
|
||||
import { EMPTY32BYTES } from './utils';
|
||||
|
||||
interface ServerMessageEvent {
|
||||
data: {
|
||||
@ -58,15 +59,10 @@ class SimpleProcessHandlers {
|
||||
throw new Error('Device not paired');
|
||||
}
|
||||
|
||||
// Create test process if it doesn't exist
|
||||
let process = await this.service.getProcess(processId);
|
||||
if (!process) {
|
||||
process = this.service.createTestProcess(processId);
|
||||
}
|
||||
|
||||
let res: ApiReturn;
|
||||
try {
|
||||
res = await this.service.createPrdUpdate(processId, stateId);
|
||||
await this.service.handleApiReturn(res);
|
||||
} catch (e) {
|
||||
throw new Error(e as string);
|
||||
}
|
||||
@ -77,7 +73,7 @@ class SimpleProcessHandlers {
|
||||
};
|
||||
} catch (e) {
|
||||
const errorMsg = `Failed to notify update for process: ${e}`;
|
||||
return this.errorResponse(errorMsg, event.clientId, event.data.messageId);
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,17 +94,10 @@ class SimpleProcessHandlers {
|
||||
throw new Error('Device not paired');
|
||||
}
|
||||
|
||||
// Create test process if it doesn't exist
|
||||
let process = await this.service.getProcess(processId);
|
||||
if (!process) {
|
||||
process = this.service.createTestProcess(processId);
|
||||
}
|
||||
|
||||
// Execute actual protocol logic
|
||||
let res: ApiReturn;
|
||||
try {
|
||||
res = await this.service.approveChange(processId, stateId);
|
||||
|
||||
await this.service.handleApiReturn(res);
|
||||
} catch (e) {
|
||||
throw new Error(e as string);
|
||||
}
|
||||
@ -120,7 +109,7 @@ class SimpleProcessHandlers {
|
||||
};
|
||||
} catch (e) {
|
||||
const errorMsg = `Failed to validate process: ${e}`;
|
||||
return this.errorResponse(errorMsg, event.clientId, event.data.messageId);
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,93 +118,79 @@ class SimpleProcessHandlers {
|
||||
throw new Error('Invalid message type');
|
||||
}
|
||||
|
||||
if (!this.service.isPaired()) {
|
||||
throw new Error('Device not paired');
|
||||
}
|
||||
|
||||
try {
|
||||
// privateFields is only used if newData contains new fields
|
||||
// roles can be empty meaning that roles from the last commited state are kept
|
||||
const { processId, newData, privateFields, roles, apiKey } = event.data;
|
||||
|
||||
if (!apiKey || !this.validateApiKey(apiKey)) {
|
||||
throw new Error('Invalid API key');
|
||||
}
|
||||
|
||||
// Check if device is paired
|
||||
if (!this.service.isPaired()) {
|
||||
throw new Error('Device not paired');
|
||||
}
|
||||
|
||||
// Get or create the process
|
||||
let process = await this.service.getProcess(processId);
|
||||
// Check if the new data is already in the process or if it's a new field
|
||||
const process = await this.service.getProcess(processId);
|
||||
if (!process) {
|
||||
process = this.service.createTestProcess(processId);
|
||||
throw new Error('Process not found');
|
||||
}
|
||||
|
||||
// Get the last committed state
|
||||
let lastState = this.service.getLastCommitedState(process);
|
||||
const lastState = this.service.getLastCommitedState(process);
|
||||
if (!lastState) {
|
||||
const firstState = process.states[0];
|
||||
const roles = firstState.roles;
|
||||
if (this.service.rolesContainsUs(roles)) {
|
||||
const approveChangeRes = await this.service.approveChange(processId, firstState.state_id);
|
||||
const prdUpdateRes = await this.service.createPrdUpdate(processId, firstState.state_id);
|
||||
} else {
|
||||
if (firstState.validation_tokens.length > 0) {
|
||||
const res = await this.service.createPrdUpdate(processId, firstState.state_id);
|
||||
}
|
||||
}
|
||||
// Wait a couple seconds
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
const updatedProcess = await this.service.getProcess(processId);
|
||||
if (!updatedProcess) {
|
||||
throw new Error('Failed to get updated process');
|
||||
}
|
||||
process = updatedProcess;
|
||||
lastState = this.service.getLastCommitedState(process);
|
||||
if (!lastState) {
|
||||
throw new Error('Process doesn\'t have a committed state yet');
|
||||
}
|
||||
throw new Error('Process doesn\'t have a commited state yet');
|
||||
}
|
||||
|
||||
const lastStateIndex = this.service.getLastCommitedStateIndex(process);
|
||||
if (lastStateIndex === null) {
|
||||
throw new Error('Process doesn\'t have a committed state yet');
|
||||
throw new Error('Process doesn\'t have a commited state yet');
|
||||
}
|
||||
|
||||
// Split data into private and public
|
||||
const privateData: Record<string, any> = {};
|
||||
const publicData: Record<string, any> = {};
|
||||
|
||||
for (const field of Object.keys(newData)) {
|
||||
// Public data are carried along each new state
|
||||
// TODO I hope that at some point we stop doing that
|
||||
// So the first thing we can do is check if the new data is public data
|
||||
if (lastState.public_data[field]) {
|
||||
// Add it to public data
|
||||
publicData[field] = newData[field];
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it's not a public data, it may be either a private data update, or a new field
|
||||
// If it's not a public data, it may be either a private data update, or a new field (public of private)
|
||||
// Caller gave us a list of new private fields, if we see it here this is a new private field
|
||||
if (privateFields.includes(field)) {
|
||||
// Add it to private data
|
||||
privateData[field] = newData[field];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if field exists in previous states private data
|
||||
// Now it can be an update of private data or a new public data
|
||||
// We check that the field exists in previous states private data
|
||||
for (let i = lastStateIndex; i >= 0; i--) {
|
||||
const state = process.states[i];
|
||||
if (state.pcd_commitment[field]) {
|
||||
// We don't even check if it's a public field, we would have seen it in the last state
|
||||
// TODO maybe that's an issue if we remove a public field at some point? That's not explicitly forbidden but not really supported yet
|
||||
privateData[field] = newData[field];
|
||||
break;
|
||||
} else {
|
||||
// This attribute was not modified in that state, we go back to the previous state
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (privateData[field]) continue;
|
||||
|
||||
// It's a new public field
|
||||
// We've get back all the way to the first state without seeing it, it's a new public field
|
||||
publicData[field] = newData[field];
|
||||
}
|
||||
|
||||
let res: ApiReturn;
|
||||
try {
|
||||
res = await this.service.updateProcess(process, privateData, publicData, roles);
|
||||
} catch (e) {
|
||||
throw new Error(e as string);
|
||||
}
|
||||
// We'll let the wasm check if roles are consistent
|
||||
|
||||
const res = await this.service.updateProcess(process, privateData, publicData, roles);
|
||||
await this.service.handleApiReturn(res);
|
||||
|
||||
return {
|
||||
type: MessageType.PROCESS_UPDATED,
|
||||
@ -224,11 +199,11 @@ class SimpleProcessHandlers {
|
||||
};
|
||||
} catch (e) {
|
||||
const errorMsg = `Failed to update process: ${e}`;
|
||||
return this.errorResponse(errorMsg, event.clientId, event.data.messageId);
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
async handleMessage(event: ServerMessageEvent): Promise<ServerResponse> {
|
||||
async handleMessage(event: ServerMessageEvent): Promise<ServerResponse > {
|
||||
try {
|
||||
switch (event.data.type) {
|
||||
case MessageType.NOTIFY_UPDATE:
|
||||
@ -241,8 +216,7 @@ class SimpleProcessHandlers {
|
||||
throw new Error(`Unhandled message type: ${event.data.type}`);
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMsg = `Error handling message: ${error}`;
|
||||
return this.errorResponse(errorMsg, event.clientId, event.data.messageId);
|
||||
return this.errorResponse(error as string, event.clientId, event.data.messageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -270,6 +244,33 @@ export class Server {
|
||||
// Setup WebSocket handlers
|
||||
this.setupWebSocketHandlers();
|
||||
|
||||
// Check if we have a device
|
||||
const device = await service.getDeviceFromDatabase();
|
||||
if (!device) {
|
||||
const spAddress = await service.createNewDevice();
|
||||
console.log('🔑 New device created:', spAddress);
|
||||
} else {
|
||||
console.log('🔑 Device found, restoring from database...');
|
||||
await service.restoreDeviceFromDatabase(device);
|
||||
console.log('🔑 Device restored successfully');
|
||||
}
|
||||
|
||||
// Check if we are paired
|
||||
if (!service.isPaired()) {
|
||||
console.log('🔑 Not paired, creating pairing process...');
|
||||
try {
|
||||
const pairingResult = await service.createPairingProcess('', []);
|
||||
console.log('🔑 Pairing process created successfully');
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to create pairing process:', error);
|
||||
}
|
||||
} else {
|
||||
console.log('🔑 Already paired with id:', service.getPairingProcessId());
|
||||
}
|
||||
|
||||
// Connect to relays
|
||||
await service.connectToRelays();
|
||||
|
||||
console.log(`✅ Simple server running on port ${this.wss.options.port}`);
|
||||
console.log('📋 Supported operations: UPDATE_PROCESS, NOTIFY_UPDATE, VALIDATE_STATE');
|
||||
console.log('🔑 Authentication: API key required for all operations');
|
||||
@ -306,7 +307,6 @@ export class Server {
|
||||
|
||||
const response = await this.handlers.handleMessage(serverEvent);
|
||||
this.sendToClient(ws, response);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Error handling message from ${clientId}:`, error);
|
||||
this.sendToClient(ws, {
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Server-specific utility functions
|
||||
export const EMPTY32BYTES = String('').padStart(64, '0');
|
||||
|
||||
export function isValid32ByteHex(value: string): boolean {
|
||||
// Check if the value is a valid 32-byte hex string (64 characters)
|
||||
|
Loading…
x
Reference in New Issue
Block a user