From 6077826815f2de8c5b989286f9a7bb1690f26d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Da=C3=9Fler?= Date: Thu, 18 Sep 2025 15:22:12 +0200 Subject: [PATCH] Enhanced features for new rust libraries --- implementations/Libp2pComponent.ts | 150 +++++++++++++++++++++++++---- interfaces/ILibp2pComponent.ts | 6 ++ 2 files changed, 139 insertions(+), 17 deletions(-) diff --git a/implementations/Libp2pComponent.ts b/implementations/Libp2pComponent.ts index 9364cfb..23657e3 100644 --- a/implementations/Libp2pComponent.ts +++ b/implementations/Libp2pComponent.ts @@ -3,6 +3,7 @@ */ import { NativeEventEmitter, NativeModules } from 'react-native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; import { LIBP2P_CONFIG } from '../utils/constants'; import type { ConnectionStatusEvent, PeerDiscoveredEvent, PeerInfoEvent } from '../utils/types'; import type { @@ -74,8 +75,13 @@ export class Libp2pComponent implements ILibp2pComponent { private _peerId?: PeerId; private _multiaddrs: Multiaddr[] = []; private _started: boolean = false; + private _starting: boolean = false; + private _error: string | null = null; private cachedConnections: Connection[] = []; private options: Libp2pOptions; + private nodeStartedListener: any = null; + private nodeStoppedListener: any = null; + private errorListener: any = null; constructor(options?: Libp2pOptions, nativeModules?: typeof NativeModules) { // Allow dependency injection of NativeModules for testing @@ -90,6 +96,7 @@ export class Libp2pComponent implements ILibp2pComponent { this.setupNativeEventListeners(); this.setupProtocolHandlers(); + this.setupStateListeners(); } get peerId(): PeerId | null { @@ -100,6 +107,14 @@ export class Libp2pComponent implements ILibp2pComponent { return this._multiaddrs; } + get isStarting(): boolean { + return this._starting; + } + + get error(): string | null { + return this._error; + } + private setupNativeEventListeners(): void { // Map native events to js-libp2p style events @@ -195,6 +210,27 @@ export class Libp2pComponent implements ILibp2pComponent { ); } + private setupStateListeners(): void { + // Listen for node started event + this.nodeStartedListener = this.eventEmitter.addListener('onNodeStarted', () => { + this._started = true; + this._starting = false; + this._error = null; + }); + + // Listen for node stopped event + this.nodeStoppedListener = this.eventEmitter.addListener('onNodeStopped', () => { + this._started = false; + this._starting = false; + }); + + // Listen for error events + this.errorListener = this.eventEmitter.addListener('onError', (event: any) => { + this._error = event.error || 'Unknown error'; + this._starting = false; + }); + } + private emit(event: K, detail: unknown): void { const handlers = this.eventHandlers.get(event); if (handlers) { @@ -206,32 +242,80 @@ export class Libp2pComponent implements ILibp2pComponent { } async start(): Promise { - // Register protocols if any - if (this.options.protocols && this.nativeModule.registerProtocolHandler) { - for (const protocol of this.options.protocols) { - await this.nativeModule.registerProtocolHandler(protocol.protocolId); - } + if (this._started || this._starting) { + return; // Already started or starting } - // Pass configuration options to native module including keypair - const config = { - tcpPort: this.options.config?.tcpPort, - wsPort: this.options.config?.wsPort, - // Convert Uint8Array to base64 for passing to native module - // Using Buffer.from for proper encoding of binary data - privateKey: this.options.keypair?.privateKey - ? Buffer.from(this.options.keypair.privateKey).toString('base64') - : undefined, - }; + this._starting = true; + this._error = null; - await this.nativeModule.startLibp2p(config); - this._started = true; + try { + // Register protocols if any + if (this.options.protocols && this.nativeModule.registerProtocolHandler) { + for (const protocol of this.options.protocols) { + await this.nativeModule.registerProtocolHandler(protocol.protocolId); + } + } + + // Load stored private key if available and not provided + let privateKeyBytes = this.options.keypair?.privateKey; + if (!privateKeyBytes) { + try { + const storedKey = await AsyncStorage.getItem('libp2p_private_key'); + if (storedKey) { + privateKeyBytes = new Uint8Array(JSON.parse(storedKey)); + } + } catch (err) { + // Failed to load stored key, will generate new one + } + } + + // Pass configuration options to native module including keypair + const config: any = { + tcpPort: this.options.config?.tcpPort, + wsPort: this.options.config?.wsPort, + }; + + if (privateKeyBytes) { + // Convert Uint8Array to base64 for passing to native module + config.privateKey = Buffer.from(privateKeyBytes).toString('base64'); + } + + const result = await this.nativeModule.startLibp2p(config); + + // Update internal state from result + if (result.peerId) { + this._peerId = new SimplePeerId(result.peerId); + } + if (result.multiaddrs) { + this._multiaddrs = result.multiaddrs.map((addr: string) => new SimpleMultiaddr(addr)); + } + + // Store the private key if we generated a new one + if (result.privateKey && !privateKeyBytes) { + try { + const keyBytes = Buffer.from(result.privateKey, 'base64'); + await AsyncStorage.setItem('libp2p_private_key', JSON.stringify(Array.from(keyBytes))); + } catch (err) { + // Failed to store key, but continue + } + } + + this._started = true; + this._starting = false; + } catch (error) { + this._starting = false; + this._error = error instanceof Error ? error.message : 'Failed to start'; + throw error; + } } async stop(): Promise { try { await this.nativeModule.stopLibp2p(); this._started = false; + this._peerId = undefined; + this._multiaddrs = []; } catch (error) { // If libp2p wasn't running, that's okay - just update our state const errorMessage = error instanceof Error ? error.message : String(error); @@ -245,6 +329,24 @@ export class Libp2pComponent implements ILibp2pComponent { } } + cleanup(): void { + // Remove state listeners + if (this.nodeStartedListener) { + this.nodeStartedListener.remove(); + this.nodeStartedListener = null; + } + if (this.nodeStoppedListener) { + this.nodeStoppedListener.remove(); + this.nodeStoppedListener = null; + } + if (this.errorListener) { + this.errorListener.remove(); + this.errorListener = null; + } + // Clear event handlers + this.eventHandlers.clear(); + } + async dial(multiaddr: string): Promise { await this.nativeModule.connectToPeer(multiaddr); @@ -368,6 +470,20 @@ export class Libp2pComponent implements ILibp2pComponent { throw new Error('Protocol sending not supported on this platform'); } } + + async acceptConnection(peerId: string): Promise { + if (!this.nativeModule.acceptConnection) { + throw new Error('acceptConnection not supported by native module'); + } + await this.nativeModule.acceptConnection(peerId); + } + + async rejectConnection(peerId: string): Promise { + if (!this.nativeModule.rejectConnection) { + throw new Error('rejectConnection not supported by native module'); + } + await this.nativeModule.rejectConnection(peerId); + } } // Type definition for CustomEventInit diff --git a/interfaces/ILibp2pComponent.ts b/interfaces/ILibp2pComponent.ts index 96cdf60..3df2d1d 100644 --- a/interfaces/ILibp2pComponent.ts +++ b/interfaces/ILibp2pComponent.ts @@ -43,6 +43,9 @@ export interface ProtocolHandler { export interface ILibp2pComponent { peerId: PeerId | null; multiaddrs: Multiaddr[]; + isStarted: boolean; + isStarting: boolean; + error: string | null; start(): Promise; stop(): Promise; @@ -52,6 +55,9 @@ export interface ILibp2pComponent { sendProtocolData(peerId: string, protocolId: string, data: Uint8Array): Promise; refreshDiscovery(): Promise; pingPeer(peerId: string): Promise<{ success: boolean; rtt?: number; peerId: string }>; + acceptConnection(peerId: string): Promise; + rejectConnection(peerId: string): Promise; + cleanup(): void; addEventListener( event: K,