4 Commits
1.0.0 ... main

Author SHA1 Message Date
e3067c3404 Add event for multiaddresses changed (#2)
Co-authored-by: Chris Daßler <chris.dassler@me.com>
Reviewed-on: #2
2025-09-20 02:43:55 +02:00
Chris Daßler
2a854aea5b Fix logger reference error in Libp2pComponent
Add missing LoggerComponent import that was causing ReferenceError
when trying to use logger in start() method
2025-09-18 23:51:51 +02:00
Chris Daßler
fee70c18f4 Add some more logging 2025-09-18 23:42:17 +02:00
9122949a6e Merge feat/logger (#1)
Fixed encoding issue: changed from btoa(String.fromCharCode(...)) to Buffer.from().toString('base64')
Maintains persistent identity through private key storage
Provides better state management and error tracking
Supports connection acceptance/rejection flow
Properly handles node lifecycle events
Can be cleanly shut down with resource cleanup

Co-authored-by: Chris Daßler <chris.dassler@me.com>
Reviewed-on: #1
2025-09-18 15:25:17 +02:00
2 changed files with 207 additions and 34 deletions

View File

@@ -3,6 +3,8 @@
*/ */
import { NativeEventEmitter, NativeModules } from 'react-native'; import { NativeEventEmitter, NativeModules } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { LoggerComponent } from 'ior:gitea:gitea.metatrom.net:universal-components/logger@1.0.0';
import { LIBP2P_CONFIG } from '../utils/constants'; import { LIBP2P_CONFIG } from '../utils/constants';
import type { ConnectionStatusEvent, PeerDiscoveredEvent, PeerInfoEvent } from '../utils/types'; import type { ConnectionStatusEvent, PeerDiscoveredEvent, PeerInfoEvent } from '../utils/types';
import type { import type {
@@ -15,6 +17,9 @@ import type {
PeerInfo, PeerInfo,
} from '../interfaces/ILibp2pComponent'; } from '../interfaces/ILibp2pComponent';
// Create logger instance for this module
const logger = new LoggerComponent('Libp2pComponent');
// Helper class to create PeerId-like objects from strings // Helper class to create PeerId-like objects from strings
export class SimplePeerId implements PeerId { export class SimplePeerId implements PeerId {
constructor(private id: string) {} constructor(private id: string) {}
@@ -74,8 +79,13 @@ export class Libp2pComponent implements ILibp2pComponent {
private _peerId?: PeerId; private _peerId?: PeerId;
private _multiaddrs: Multiaddr[] = []; private _multiaddrs: Multiaddr[] = [];
private _started: boolean = false; private _started: boolean = false;
private _starting: boolean = false;
private _error: string | null = null;
private cachedConnections: Connection[] = []; private cachedConnections: Connection[] = [];
private options: Libp2pOptions; private options: Libp2pOptions;
private nodeStartedListener: any = null;
private nodeStoppedListener: any = null;
private errorListener: any = null;
constructor(options?: Libp2pOptions, nativeModules?: typeof NativeModules) { constructor(options?: Libp2pOptions, nativeModules?: typeof NativeModules) {
// Allow dependency injection of NativeModules for testing // Allow dependency injection of NativeModules for testing
@@ -90,6 +100,7 @@ export class Libp2pComponent implements ILibp2pComponent {
this.setupNativeEventListeners(); this.setupNativeEventListeners();
this.setupProtocolHandlers(); this.setupProtocolHandlers();
this.setupStateListeners();
} }
get peerId(): PeerId | null { get peerId(): PeerId | null {
@@ -100,6 +111,14 @@ export class Libp2pComponent implements ILibp2pComponent {
return this._multiaddrs; return this._multiaddrs;
} }
get isStarting(): boolean {
return this._starting;
}
get error(): string | null {
return this._error;
}
private setupNativeEventListeners(): void { private setupNativeEventListeners(): void {
// Map native events to js-libp2p style events // Map native events to js-libp2p style events
@@ -134,6 +153,15 @@ export class Libp2pComponent implements ILibp2pComponent {
}); });
}); });
// Multiaddresses changed event
this.eventEmitter.addListener('onMultiaddressesChanged', ({ multiaddrs }: { multiaddrs: string[] }) => {
logger.debug('[Libp2pComponent] Multiaddresses changed:', multiaddrs);
this._multiaddrs = multiaddrs.map((addr: string) => new SimpleMultiaddr(addr));
this.emit('multiaddresses:changed', {
multiaddrs: this._multiaddrs,
});
});
// Connection events // Connection events
this.eventEmitter.addListener( this.eventEmitter.addListener(
'onConnectionStatus', 'onConnectionStatus',
@@ -195,6 +223,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<K extends keyof Libp2pEvents>(event: K, detail: unknown): void { private emit<K extends keyof Libp2pEvents>(event: K, detail: unknown): void {
const handlers = this.eventHandlers.get(event); const handlers = this.eventHandlers.get(event);
if (handlers) { if (handlers) {
@@ -206,6 +255,18 @@ export class Libp2pComponent implements ILibp2pComponent {
} }
async start(): Promise<void> { async start(): Promise<void> {
logger.info('[TypeScript] start() called, _started:', this._started, '_starting:', this._starting);
if (this._started || this._starting) {
logger.info('[TypeScript] Already started or starting, returning early');
return; // Already started or starting
}
this._starting = true;
this._error = null;
logger.info('[TypeScript] Setting _starting to true, proceeding with start');
try {
// Register protocols if any // Register protocols if any
if (this.options.protocols && this.nativeModule.registerProtocolHandler) { if (this.options.protocols && this.nativeModule.registerProtocolHandler) {
for (const protocol of this.options.protocols) { for (const protocol of this.options.protocols) {
@@ -213,27 +274,76 @@ export class Libp2pComponent implements ILibp2pComponent {
} }
} }
// 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 // Pass configuration options to native module including keypair
const config = { const config: any = {
tcpPort: this.options.config?.tcpPort, tcpPort: this.options.config?.tcpPort,
wsPort: this.options.config?.wsPort, wsPort: this.options.config?.wsPort,
// Convert Uint8Array to base64 for passing to native module
keypair: this.options.keypair
? {
privateKey: btoa(String.fromCharCode(...this.options.keypair.privateKey)),
publicKey: btoa(String.fromCharCode(...this.options.keypair.publicKey)),
}
: undefined,
}; };
await this.nativeModule.startLibp2p(config); if (privateKeyBytes) {
// Convert Uint8Array to base64 for passing to native module
config.privateKey = Buffer.from(privateKeyBytes).toString('base64');
}
logger.debug('[Libp2pComponent] About to call nativeModule.startLibp2p with config:', config);
logger.debug('[Libp2pComponent] nativeModule exists:', !!this.nativeModule);
logger.debug('[Libp2pComponent] nativeModule.startLibp2p exists:', !!this.nativeModule?.startLibp2p);
let result;
try {
result = await this.nativeModule.startLibp2p(config);
logger.info('[Libp2pComponent] startLibp2p returned:', result);
} catch (error) {
logger.error('[Libp2pComponent] startLibp2p failed:', error);
throw error;
}
// 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._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<void> { async stop(): Promise<void> {
try { try {
await this.nativeModule.stopLibp2p(); await this.nativeModule.stopLibp2p();
this._started = false; this._started = false;
this._peerId = undefined;
this._multiaddrs = [];
} catch (error) { } catch (error) {
// If libp2p wasn't running, that's okay - just update our state // If libp2p wasn't running, that's okay - just update our state
const errorMessage = error instanceof Error ? error.message : String(error); const errorMessage = error instanceof Error ? error.message : String(error);
@@ -247,23 +357,65 @@ 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<Connection> { async dial(multiaddr: string): Promise<Connection> {
await this.nativeModule.connectToPeer(multiaddr); console.log(`[Libp2pComponent] dial() called with multiaddr: ${multiaddr}`);
try {
const result = await this.nativeModule.connectToPeer(multiaddr);
console.log('[Libp2pComponent] connectToPeer result:', result);
// Create connection object // Create connection object
const peerId = multiaddr.match(/\/p2p\/([^/]+)/)?.[1] || ''; // Try to extract peer ID from result or multiaddr
return { let peerId = result?.peer_id || result?.peerId || '';
if (!peerId) {
// Try to extract from multiaddr if not in result
peerId = multiaddr.match(/\/p2p\/([^/]+)/)?.[1] || '';
}
// If still no peer ID, this might be a connection without known peer ID
// The real peer ID will be determined during handshake
if (!peerId || peerId === 'pending') {
console.log('[Libp2pComponent] Dialing without known peer ID, will be determined during handshake');
peerId = 'pending';
}
const connection = {
id: `${peerId}-${Date.now()}`, id: `${peerId}-${Date.now()}`,
remotePeer: new SimplePeerId(peerId), remotePeer: new SimplePeerId(peerId),
remoteAddr: new SimpleMultiaddr(multiaddr), remoteAddr: new SimpleMultiaddr(multiaddr),
stat: { stat: {
direction: 'outbound', direction: 'outbound' as const,
status: 'open', status: 'open' as const,
timeline: { timeline: {
open: Date.now(), open: Date.now(),
}, },
}, },
}; };
console.log('[Libp2pComponent] Returning connection:', connection);
return connection;
} catch (error) {
console.error('[Libp2pComponent] dial error:', error);
throw error;
}
} }
async hangUp(peerId: string): Promise<void> { async hangUp(peerId: string): Promise<void> {
@@ -370,6 +522,20 @@ export class Libp2pComponent implements ILibp2pComponent {
throw new Error('Protocol sending not supported on this platform'); throw new Error('Protocol sending not supported on this platform');
} }
} }
async acceptConnection(peerId: string): Promise<void> {
if (!this.nativeModule.acceptConnection) {
throw new Error('acceptConnection not supported by native module');
}
await this.nativeModule.acceptConnection(peerId);
}
async rejectConnection(peerId: string): Promise<void> {
if (!this.nativeModule.rejectConnection) {
throw new Error('rejectConnection not supported by native module');
}
await this.nativeModule.rejectConnection(peerId);
}
} }
// Type definition for CustomEventInit // Type definition for CustomEventInit

View File

@@ -43,6 +43,9 @@ export interface ProtocolHandler {
export interface ILibp2pComponent { export interface ILibp2pComponent {
peerId: PeerId | null; peerId: PeerId | null;
multiaddrs: Multiaddr[]; multiaddrs: Multiaddr[];
isStarted: boolean;
isStarting: boolean;
error: string | null;
start(): Promise<void>; start(): Promise<void>;
stop(): Promise<void>; stop(): Promise<void>;
@@ -52,6 +55,9 @@ export interface ILibp2pComponent {
sendProtocolData(peerId: string, protocolId: string, data: Uint8Array): Promise<void>; sendProtocolData(peerId: string, protocolId: string, data: Uint8Array): Promise<void>;
refreshDiscovery(): Promise<void>; refreshDiscovery(): Promise<void>;
pingPeer(peerId: string): Promise<{ success: boolean; rtt?: number; peerId: string }>; pingPeer(peerId: string): Promise<{ success: boolean; rtt?: number; peerId: string }>;
acceptConnection(peerId: string): Promise<void>;
rejectConnection(peerId: string): Promise<void>;
cleanup(): void;
addEventListener<K extends keyof Libp2pEvents>( addEventListener<K extends keyof Libp2pEvents>(
event: K, event: K,
@@ -72,6 +78,7 @@ export interface Libp2pEvents {
'connection:open': CustomEvent<Connection>; 'connection:open': CustomEvent<Connection>;
'connection:close': CustomEvent<Connection>; 'connection:close': CustomEvent<Connection>;
'self:peer:update': CustomEvent<{ peerId: PeerId; multiaddrs: Multiaddr[] }>; 'self:peer:update': CustomEvent<{ peerId: PeerId; multiaddrs: Multiaddr[] }>;
'multiaddresses:changed': CustomEvent<{ multiaddrs: Multiaddr[] }>;
} }
export interface Libp2pOptions { export interface Libp2pOptions {