@olane/o-node 0.7.54 → 0.7.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,9 @@
1
- import { Connection } from '@olane/o-config';
1
+ import { Connection, Libp2p } from '@olane/o-config';
2
2
  import { oConnectionConfig } from '@olane/o-core';
3
3
  import type { StreamReusePolicy } from '../stream-handler.config.js';
4
4
  export interface oNodeConnectionConfig extends oConnectionConfig {
5
5
  p2pConnection: Connection;
6
+ p2pNode?: Libp2p;
6
7
  runOnLimitedConnection?: boolean;
7
8
  reusePolicy?: StreamReusePolicy;
8
9
  }
@@ -1 +1 @@
1
- {"version":3,"file":"o-node-connection.config.d.ts","sourceRoot":"","sources":["../../../../src/connection/interfaces/o-node-connection.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAErE,MAAM,WAAW,qBAAsB,SAAQ,iBAAiB;IAC9D,aAAa,EAAE,UAAU,CAAC;IAC1B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,WAAW,CAAC,EAAE,iBAAiB,CAAC;CACjC"}
1
+ {"version":3,"file":"o-node-connection.config.d.ts","sourceRoot":"","sources":["../../../../src/connection/interfaces/o-node-connection.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAErE,MAAM,WAAW,qBAAsB,SAAQ,iBAAiB;IAC9D,aAAa,EAAE,UAAU,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,WAAW,CAAC,EAAE,iBAAiB,CAAC;CACjC"}
@@ -1,27 +1,73 @@
1
- import { Connection } from '@olane/o-config';
2
- import { oConnection, oRequest, oResponse } from '@olane/o-core';
1
+ import { Connection, Libp2p, Multiaddr } from '@olane/o-config';
2
+ import { oAddress, oConnection, oRequest, oResponse } from '@olane/o-core';
3
3
  import { oNodeConnectionConfig } from './interfaces/o-node-connection.config.js';
4
4
  import type { StreamReusePolicy } from './stream-handler.config.js';
5
5
  import { oNodeAddress } from '../router/o-node.address.js';
6
6
  import { oNodeStream } from './o-node-stream.js';
7
7
  import { oNodeStreamManager } from './o-node-stream.manager.js';
8
+ interface CachedIdentifyData {
9
+ protocols: string[];
10
+ agentVersion?: string;
11
+ protocolVersion?: string;
12
+ listenAddrs: Multiaddr[];
13
+ observedAddr?: Multiaddr;
14
+ timestamp: number;
15
+ }
8
16
  export declare class oNodeConnection extends oConnection {
9
17
  protected readonly config: oNodeConnectionConfig;
10
18
  p2pConnection: Connection;
11
19
  protected reusePolicy: StreamReusePolicy;
12
20
  streamManager: oNodeStreamManager;
21
+ protected p2pNode?: Libp2p;
22
+ protected identifyData?: CachedIdentifyData;
23
+ protected identifyListener?: (evt: any) => void;
13
24
  constructor(config: oNodeConnectionConfig);
14
25
  get remotePeerId(): import("@olane/o-config").PeerId;
15
- get remoteAddr(): import("@olane/o-config").Multiaddr;
26
+ get remoteAddr(): Multiaddr;
16
27
  /**
17
28
  * Get the connection configuration for compatibility checking.
18
29
  */
19
30
  get connectionConfig(): oNodeConnectionConfig;
20
31
  supportsAddress(address: oNodeAddress): boolean;
21
32
  get streams(): oNodeStream[];
33
+ /**
34
+ * Get cached identify data for the remote peer.
35
+ * Returns undefined if identify event hasn't been received yet.
36
+ */
37
+ get cachedIdentifyData(): CachedIdentifyData | undefined;
38
+ /**
39
+ * Get protocols supported by the remote peer from cached identify data.
40
+ * Returns empty array if identify data not available yet.
41
+ */
42
+ get remoteProtocols(): string[];
43
+ /**
44
+ * Get agent version of the remote peer from cached identify data.
45
+ */
46
+ get remoteAgentVersion(): string | undefined;
47
+ /**
48
+ * Get protocol version of the remote peer from cached identify data.
49
+ */
50
+ get remoteProtocolVersion(): string | undefined;
51
+ /**
52
+ * Get listen addresses advertised by the remote peer.
53
+ * Returns empty array if identify data not available yet.
54
+ */
55
+ get remoteListenAddrs(): Multiaddr[];
56
+ /**
57
+ * Get our observed address as seen by the remote peer.
58
+ */
59
+ get observedAddress(): Multiaddr | undefined;
60
+ get address(): oAddress;
61
+ get nextHopAddress(): oAddress;
62
+ get callerAddress(): oAddress | undefined;
22
63
  transmit(request: oRequest): Promise<oResponse>;
23
64
  postTransmit(stream: oNodeStream): Promise<void>;
65
+ /**
66
+ * Set up persistent listener for identify events from the remote peer
67
+ */
68
+ protected setupIdentifyListener(): void;
24
69
  abort(error: Error): Promise<void>;
25
70
  close(): Promise<void>;
26
71
  }
72
+ export {};
27
73
  //# sourceMappingURL=o-node-connection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"o-node-connection.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAU,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAEL,WAAW,EAGX,QAAQ,EACR,SAAS,EACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAEV,iBAAiB,EAClB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAEhE,qBAAa,eAAgB,SAAQ,WAAW;IAKlC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,qBAAqB;IAJrD,aAAa,EAAE,UAAU,CAAC;IACjC,SAAS,CAAC,WAAW,EAAE,iBAAiB,CAAC;IAClC,aAAa,EAAE,kBAAkB,CAAC;gBAEV,MAAM,EAAE,qBAAqB;IAW5D,IAAI,YAAY,qCAEf;IAED,IAAI,UAAU,wCAEb;IAED;;OAEG;IACH,IAAI,gBAAgB,IAAI,qBAAqB,CAE5C;IAED,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO;IAM/C,IAAI,OAAO,IAAI,WAAW,EAAE,CAE3B;IAEK,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAkF/C,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAKhD,KAAK,CAAC,KAAK,EAAE,KAAK;IAMlB,KAAK;CASZ"}
1
+ {"version":3,"file":"o-node-connection.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAU,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAEL,QAAQ,EACR,WAAW,EAGX,QAAQ,EACR,SAAS,EACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAEV,iBAAiB,EAClB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAEhE,UAAU,kBAAkB;IAC1B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,SAAS,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,eAAgB,SAAQ,WAAW;IAQlC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,qBAAqB;IAPrD,aAAa,EAAE,UAAU,CAAC;IACjC,SAAS,CAAC,WAAW,EAAE,iBAAiB,CAAC;IAClC,aAAa,EAAE,kBAAkB,CAAC;IACzC,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAC5C,SAAS,CAAC,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;gBAEjB,MAAM,EAAE,qBAAqB;IAe5D,IAAI,YAAY,qCAEf;IAED,IAAI,UAAU,cAEb;IAED;;OAEG;IACH,IAAI,gBAAgB,IAAI,qBAAqB,CAE5C;IAED,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO;IAM/C,IAAI,OAAO,IAAI,WAAW,EAAE,CAE3B;IAED;;;OAGG;IACH,IAAI,kBAAkB,IAAI,kBAAkB,GAAG,SAAS,CAEvD;IAED;;;OAGG;IACH,IAAI,eAAe,IAAI,MAAM,EAAE,CAE9B;IAED;;OAEG;IACH,IAAI,kBAAkB,IAAI,MAAM,GAAG,SAAS,CAE3C;IAED;;OAEG;IACH,IAAI,qBAAqB,IAAI,MAAM,GAAG,SAAS,CAE9C;IAED;;;OAGG;IACH,IAAI,iBAAiB,IAAI,SAAS,EAAE,CAEnC;IAED;;OAEG;IACH,IAAI,eAAe,IAAI,SAAS,GAAG,SAAS,CAE3C;IAED,IAAI,OAAO,IAAI,QAAQ,CActB;IAED,IAAI,cAAc,IAAI,QAAQ,CAK7B;IAED,IAAI,aAAa,IAAI,QAAQ,GAAG,SAAS,CAMxC;IAEK,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAkF/C,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtD;;OAEG;IACH,SAAS,CAAC,qBAAqB,IAAI,IAAI;IAsCjC,KAAK,CAAC,KAAK,EAAE,KAAK;IAMlB,KAAK;CAcZ"}
@@ -1,4 +1,5 @@
1
1
  import { oConnection, oError, oErrorCodes, } from '@olane/o-core';
2
+ import { oNodeAddress } from '../router/o-node.address.js';
2
3
  import { oNodeStreamManager } from './o-node-stream.manager.js';
3
4
  export class oNodeConnection extends oConnection {
4
5
  constructor(config) {
@@ -6,10 +7,13 @@ export class oNodeConnection extends oConnection {
6
7
  this.config = config;
7
8
  this.p2pConnection = config.p2pConnection;
8
9
  this.reusePolicy = config.reusePolicy ?? 'none';
10
+ this.p2pNode = config.p2pNode;
9
11
  // Initialize oNodeStreamManager (stream lifecycle and protocol management)
10
12
  this.streamManager = new oNodeStreamManager({
11
13
  p2pConnection: this.p2pConnection,
12
14
  });
15
+ // Set up persistent identify event listener
16
+ this.setupIdentifyListener();
13
17
  }
14
18
  get remotePeerId() {
15
19
  return this.p2pConnection.remotePeer;
@@ -31,6 +35,68 @@ export class oNodeConnection extends oConnection {
31
35
  get streams() {
32
36
  return this.streamManager.getAllStreams();
33
37
  }
38
+ /**
39
+ * Get cached identify data for the remote peer.
40
+ * Returns undefined if identify event hasn't been received yet.
41
+ */
42
+ get cachedIdentifyData() {
43
+ return this.identifyData;
44
+ }
45
+ /**
46
+ * Get protocols supported by the remote peer from cached identify data.
47
+ * Returns empty array if identify data not available yet.
48
+ */
49
+ get remoteProtocols() {
50
+ return this.identifyData?.protocols || [];
51
+ }
52
+ /**
53
+ * Get agent version of the remote peer from cached identify data.
54
+ */
55
+ get remoteAgentVersion() {
56
+ return this.identifyData?.agentVersion;
57
+ }
58
+ /**
59
+ * Get protocol version of the remote peer from cached identify data.
60
+ */
61
+ get remoteProtocolVersion() {
62
+ return this.identifyData?.protocolVersion;
63
+ }
64
+ /**
65
+ * Get listen addresses advertised by the remote peer.
66
+ * Returns empty array if identify data not available yet.
67
+ */
68
+ get remoteListenAddrs() {
69
+ return this.identifyData?.listenAddrs || [];
70
+ }
71
+ /**
72
+ * Get our observed address as seen by the remote peer.
73
+ */
74
+ get observedAddress() {
75
+ return this.identifyData?.observedAddr;
76
+ }
77
+ get address() {
78
+ if (this.identifyData?.protocols &&
79
+ this.identifyData.protocols.length > 0) {
80
+ const filtered = this.identifyData.protocols.filter((p) => p.startsWith('/o/'));
81
+ if (filtered.length > 0) {
82
+ const remoteAddress = oNodeAddress.fromProtocol(filtered[0]);
83
+ return remoteAddress;
84
+ }
85
+ }
86
+ return this.config.address;
87
+ }
88
+ get nextHopAddress() {
89
+ if (this.config.nextHopAddress.value === 'o://unknown') {
90
+ return this.address;
91
+ }
92
+ return this.config.nextHopAddress;
93
+ }
94
+ get callerAddress() {
95
+ if (this.p2pConnection.direction === 'inbound') {
96
+ return this.address;
97
+ }
98
+ return this.config.callerAddress;
99
+ }
34
100
  async transmit(request) {
35
101
  try {
36
102
  // Build protocol string
@@ -90,6 +156,35 @@ export class oNodeConnection extends oConnection {
90
156
  // Always cleanup streams (no reuse at base layer)
91
157
  await this.streamManager.releaseStream(stream.p2pStream.id);
92
158
  }
159
+ /**
160
+ * Set up persistent listener for identify events from the remote peer
161
+ */
162
+ setupIdentifyListener() {
163
+ if (!this.p2pNode) {
164
+ return;
165
+ }
166
+ this.identifyListener = (evt) => {
167
+ const { peerId, protocols, agentVersion, protocolVersion, listenAddrs, observedAddr, } = evt.detail;
168
+ // Only cache if this event is for our remote peer
169
+ if (peerId?.toString() === this.remotePeerId.toString()) {
170
+ this.identifyData = {
171
+ protocols: protocols || [],
172
+ agentVersion,
173
+ protocolVersion,
174
+ listenAddrs: listenAddrs || [],
175
+ observedAddr,
176
+ timestamp: Date.now(),
177
+ };
178
+ this.logger.debug('Cached identify data for peer', {
179
+ peerId: peerId.toString(),
180
+ protocols: this.identifyData.protocols,
181
+ agentVersion: this.identifyData.agentVersion,
182
+ });
183
+ }
184
+ };
185
+ this.p2pNode.addEventListener('peer:identify', this.identifyListener);
186
+ this.logger.debug('Identify listener set up for connection');
187
+ }
93
188
  async abort(error) {
94
189
  this.logger.debug('Aborting connection');
95
190
  await this.p2pConnection.abort(error);
@@ -97,6 +192,10 @@ export class oNodeConnection extends oConnection {
97
192
  }
98
193
  async close() {
99
194
  this.logger.debug('Closing connection');
195
+ // Remove identify listener
196
+ if (this.p2pNode && this.identifyListener) {
197
+ this.p2pNode.removeEventListener('peer:identify', this.identifyListener);
198
+ }
100
199
  // Close stream manager (handles all stream cleanup)
101
200
  await this.streamManager.close();
102
201
  await this.p2pConnection.close();
@@ -2,53 +2,34 @@ import { oAddress, oConnectionConfig, oConnectionManager } from '@olane/o-core';
2
2
  import { Libp2p, Connection } from '@olane/o-config';
3
3
  import { oNodeConnectionManagerConfig } from './interfaces/o-node-connection-manager.config.js';
4
4
  import { oNodeConnection } from './o-node-connection.js';
5
+ /**
6
+ * Manages oNodeConnection instances, reusing connections when possible.
7
+ */
5
8
  export declare class oNodeConnectionManager extends oConnectionManager {
6
9
  readonly config: oNodeConnectionManagerConfig;
7
10
  protected p2pNode: Libp2p;
8
11
  protected defaultReadTimeoutMs?: number;
9
12
  protected defaultDrainTimeoutMs?: number;
10
13
  /** Single cache of oNodeConnection instances keyed by address */
11
- protected cachedConnections: Map<string, oNodeConnection[]>;
14
+ protected cachedConnections: Map<string, oNodeConnection>;
12
15
  protected pendingDialsByAddress: Map<string, Promise<Connection>>;
13
16
  constructor(config: oNodeConnectionManagerConfig);
14
17
  /**
15
18
  * Set up listeners to maintain connection cache state
16
19
  */
17
20
  protected setupConnectionListeners(): void;
18
- /**
19
- * Build a stable cache key from an address.
20
- *
21
- * We key the cache by address value (e.g., "o://my-tool") to maintain
22
- * a simple one-to-one mapping between addresses and connections.
23
- */
24
- protected getAddressKey(address: oAddress): string | null;
25
21
  /**
26
22
  * Extract peer ID string from an address
27
23
  * @param address - The address to extract peer ID from
28
24
  * @returns The peer ID string or null if not found
29
25
  */
30
26
  protected getPeerIdFromAddress(address: oAddress): string | null;
31
- /**
32
- * Get the first valid (open) connection for the given address key.
33
- * Cleans up stale connections from the cache automatically.
34
- *
35
- * @param addressKey - The address key to look up
36
- * @returns A valid oNodeConnection or null if none found
37
- */
38
- protected getValidConnection(addressKey: string): oNodeConnection | null;
39
27
  /**
40
28
  * Cache an oNodeConnection by its address key.
41
29
  * @param conn - The oNodeConnection to cache
42
30
  * @param addressKey - The address key to cache under
43
31
  */
44
- protected cacheConnection(conn: oNodeConnection, addressKey: string): void;
45
- /**
46
- * Get oNodeConnection by libp2p Connection reference
47
- * Used to find the correct oNodeConnection for incoming streams
48
- * @param p2pConnection - The libp2p connection to search for
49
- * @returns The oNodeConnection or undefined if not found
50
- */
51
- getConnectionByP2pConnection(p2pConnection: Connection): oNodeConnection | undefined;
32
+ protected cacheConnection(conn: oNodeConnection): void;
52
33
  /**
53
34
  * Get or create a raw p2p connection to the given address.
54
35
  * Subclasses can override connect() and use this method to get the underlying p2p connection.
@@ -59,42 +40,12 @@ export declare class oNodeConnectionManager extends oConnectionManager {
59
40
  p2pConnection: Connection;
60
41
  reuse?: boolean;
61
42
  }): Promise<oNodeConnection>;
43
+ getConnectionFromAddress(address: oAddress): oNodeConnection | null;
62
44
  /**
63
45
  * Connect to a given address, reusing oNodeConnection when possible
64
46
  * @param config - Connection configuration
65
47
  * @returns The connection object
66
48
  */
67
49
  connect(config: oConnectionConfig): Promise<oNodeConnection>;
68
- /**
69
- * Check if we have an active connection to the target peer
70
- * @param address - The address to check
71
- * @returns true if an active connection exists
72
- */
73
- isCached(address: oAddress): boolean;
74
- /**
75
- * Get an existing cached oNodeConnection for the target address
76
- * @param address - The address to get a connection for
77
- * @returns The oNodeConnection or null if not found
78
- */
79
- getCachedConnection(address: oAddress): oNodeConnection | null;
80
- /**
81
- * Get cache statistics for monitoring and debugging
82
- * @returns Object containing cache statistics
83
- */
84
- getCacheStats(): {
85
- cachedAddresses: number;
86
- totalCachedConnections: number;
87
- pendingDials: number;
88
- connectionsByPeer: Array<{
89
- peerId: string;
90
- status: string;
91
- addressKey: string;
92
- }>;
93
- };
94
- /**
95
- * Clean up all stale (non-open) connections from cache
96
- * @returns Number of connections removed
97
- */
98
- cleanupStaleConnections(): number;
99
50
  }
100
51
  //# sourceMappingURL=o-node-connection.manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"o-node-connection.manager.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,4BAA4B,EAAE,MAAM,kDAAkD,CAAC;AAEhG,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,qBAAa,sBAAuB,SAAQ,kBAAkB;IAQhD,QAAQ,CAAC,MAAM,EAAE,4BAA4B;IAPzD,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACxC,SAAS,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IACzC,iEAAiE;IACjE,SAAS,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAAa;IACxE,SAAS,CAAC,qBAAqB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAa;gBAEzD,MAAM,EAAE,4BAA4B;IAWzD;;OAEG;IACH,SAAS,CAAC,wBAAwB,IAAI,IAAI;IA+B1C;;;;;OAKG;IACH,SAAS,CAAC,aAAa,CAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI;IASzD;;;;OAIG;IACH,SAAS,CAAC,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI;IAahE;;;;;;OAMG;IACH,SAAS,CAAC,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAkBxE;;;;OAIG;IACH,SAAS,CAAC,eAAe,CAAC,IAAI,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAY1E;;;;;OAKG;IACH,4BAA4B,CAC1B,aAAa,EAAE,UAAU,GACxB,eAAe,GAAG,SAAS;IAa9B;;;OAGG;cACa,wBAAwB,CACtC,cAAc,EAAE,QAAQ,EACxB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,UAAU,CAAC;cAoCN,WAAW,CACzB,cAAc,EAAE,QAAQ,EACxB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,UAAU,CAAC;IAqBhB,MAAM,CACV,MAAM,EAAE,iBAAiB,GAAG;QAAE,aAAa,EAAE,UAAU,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GACzE,OAAO,CAAC,eAAe,CAAC;IA4D3B;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IAwDlE;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO;IAcpC;;;;OAIG;IACH,mBAAmB,CAAC,OAAO,EAAE,QAAQ,GAAG,eAAe,GAAG,IAAI;IAc9D;;;OAGG;IACH,aAAa,IAAI;QACf,eAAe,EAAE,MAAM,CAAC;QACxB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,YAAY,EAAE,MAAM,CAAC;QACrB,iBAAiB,EAAE,KAAK,CAAC;YACvB,MAAM,EAAE,MAAM,CAAC;YACf,MAAM,EAAE,MAAM,CAAC;YACf,UAAU,EAAE,MAAM,CAAC;SACpB,CAAC,CAAC;KACJ;IAyBD;;;OAGG;IACH,uBAAuB,IAAI,MAAM;CAuBlC"}
1
+ {"version":3,"file":"o-node-connection.manager.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,4BAA4B,EAAE,MAAM,kDAAkD,CAAC;AAEhG,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,kBAAkB;IAQhD,QAAQ,CAAC,MAAM,EAAE,4BAA4B;IAPzD,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACxC,SAAS,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IACzC,iEAAiE;IACjE,SAAS,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAa;IACtE,SAAS,CAAC,qBAAqB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAa;gBAEzD,MAAM,EAAE,4BAA4B;IAWzD;;OAEG;IACH,SAAS,CAAC,wBAAwB,IAAI,IAAI;IAY1C;;;;OAIG;IACH,SAAS,CAAC,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI;IAahE;;;;OAIG;IACH,SAAS,CAAC,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI;IAWtD;;;OAGG;cACa,wBAAwB,CACtC,cAAc,EAAE,QAAQ,EACxB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,UAAU,CAAC;cAoBN,WAAW,CACzB,cAAc,EAAE,QAAQ,EACxB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,UAAU,CAAC;IAqBhB,MAAM,CACV,MAAM,EAAE,iBAAiB,GAAG;QAAE,aAAa,EAAE,UAAU,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GACzE,OAAO,CAAC,eAAe,CAAC;IAgD3B,wBAAwB,CAAC,OAAO,EAAE,QAAQ,GAAG,eAAe,GAAG,IAAI;IAkBnE;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;CAqDnE"}
@@ -1,5 +1,8 @@
1
1
  import { oConnectionManager } from '@olane/o-core';
2
2
  import { oNodeConnection } from './o-node-connection.js';
3
+ /**
4
+ * Manages oNodeConnection instances, reusing connections when possible.
5
+ */
3
6
  export class oNodeConnectionManager extends oConnectionManager {
4
7
  constructor(config) {
5
8
  super(config);
@@ -24,35 +27,9 @@ export class oNodeConnectionManager extends oConnectionManager {
24
27
  return;
25
28
  }
26
29
  const connectionId = connection.id;
27
- // Clean up cached connections by filtering out the closed connection
28
- for (const [key, conns] of this.cachedConnections.entries()) {
29
- const filtered = conns.filter((c) => c.p2pConnection.id !== connectionId);
30
- if (filtered.length === 0) {
31
- this.logger.debug('Connection closed, removing all cached connections for address:', key);
32
- this.cachedConnections.delete(key);
33
- }
34
- else if (filtered.length !== conns.length) {
35
- this.logger.debug('Connection closed, updating cached connections for address:', key);
36
- this.cachedConnections.set(key, filtered);
37
- }
38
- }
30
+ this.cachedConnections.delete(connectionId);
39
31
  });
40
32
  }
41
- /**
42
- * Build a stable cache key from an address.
43
- *
44
- * We key the cache by address value (e.g., "o://my-tool") to maintain
45
- * a simple one-to-one mapping between addresses and connections.
46
- */
47
- getAddressKey(address) {
48
- try {
49
- return address.value || null;
50
- }
51
- catch (error) {
52
- this.logger.debug('Error extracting address key from address:', error);
53
- return null;
54
- }
55
- }
56
33
  /**
57
34
  * Extract peer ID string from an address
58
35
  * @param address - The address to extract peer ID from
@@ -71,71 +48,22 @@ export class oNodeConnectionManager extends oConnectionManager {
71
48
  return null;
72
49
  }
73
50
  }
74
- /**
75
- * Get the first valid (open) connection for the given address key.
76
- * Cleans up stale connections from the cache automatically.
77
- *
78
- * @param addressKey - The address key to look up
79
- * @returns A valid oNodeConnection or null if none found
80
- */
81
- getValidConnection(addressKey) {
82
- const connections = this.cachedConnections.get(addressKey) || [];
83
- // Filter to open connections
84
- const valid = connections.filter((c) => c.p2pConnection?.status === 'open');
85
- // Update cache if we cleaned any stale connections
86
- if (valid.length !== connections.length) {
87
- if (valid.length === 0) {
88
- this.cachedConnections.delete(addressKey);
89
- }
90
- else {
91
- this.cachedConnections.set(addressKey, valid);
92
- }
93
- }
94
- return valid[0] ?? null;
95
- }
96
51
  /**
97
52
  * Cache an oNodeConnection by its address key.
98
53
  * @param conn - The oNodeConnection to cache
99
54
  * @param addressKey - The address key to cache under
100
55
  */
101
- cacheConnection(conn, addressKey) {
102
- this.logger.debug('Caching connection for address:', addressKey, conn.p2pConnection.id, conn.p2pConnection.direction);
103
- const existing = this.cachedConnections.get(addressKey) || [];
104
- existing.push(conn);
105
- this.cachedConnections.set(addressKey, existing);
106
- }
107
- /**
108
- * Get oNodeConnection by libp2p Connection reference
109
- * Used to find the correct oNodeConnection for incoming streams
110
- * @param p2pConnection - The libp2p connection to search for
111
- * @returns The oNodeConnection or undefined if not found
112
- */
113
- getConnectionByP2pConnection(p2pConnection) {
114
- // Search through all cached connections
115
- for (const connections of this.cachedConnections.values()) {
116
- const found = connections.find((conn) => conn.p2pConnection.id === p2pConnection.id);
117
- if (found) {
118
- return found;
119
- }
120
- }
121
- return undefined;
56
+ cacheConnection(conn) {
57
+ this.logger.debug('Caching connection for address:', conn.p2pConnection.id, conn.p2pConnection.direction, conn.nextHopAddress.value, conn.p2pConnection.streams.map((s) => s.protocol).join(', '));
58
+ this.cachedConnections.set(conn.p2pConnection.id, conn);
122
59
  }
123
60
  /**
124
61
  * Get or create a raw p2p connection to the given address.
125
62
  * Subclasses can override connect() and use this method to get the underlying p2p connection.
126
63
  */
127
64
  async getOrCreateP2pConnection(nextHopAddress, addressKey) {
128
- // Check if libp2p already has an active connection for this peer
129
- const peerId = this.getPeerIdFromAddress(nextHopAddress);
130
- if (peerId) {
131
- const connections = this.p2pNode.getConnections();
132
- const existingConnection = connections.find((conn) => conn.remotePeer?.toString() === peerId && conn.status === 'open');
133
- if (existingConnection) {
134
- this.logger.debug('Found existing libp2p connection for address:', addressKey);
135
- return existingConnection;
136
- }
137
- }
138
65
  // Check if dial is already in progress for this address key
66
+ this.logger.debug('Checking for pending dial for address:', addressKey);
139
67
  const pendingDial = this.pendingDialsByAddress.get(addressKey);
140
68
  if (pendingDial) {
141
69
  this.logger.debug('Awaiting existing dial for address:', addressKey);
@@ -166,25 +94,23 @@ export class oNodeConnectionManager extends oConnectionManager {
166
94
  }
167
95
  async answer(config) {
168
96
  const { address, nextHopAddress, callerAddress, readTimeoutMs, drainTimeoutMs, p2pConnection, reuse, } = config;
169
- const addressKey = this.getAddressKey(nextHopAddress);
170
- if (!addressKey) {
171
- this.logger.error('Failed to generate an address key for address:', nextHopAddress);
172
- throw new Error(`Unable to extract address key from address: ${nextHopAddress.toString()}`);
173
- }
97
+ this.logger.debug('Answering connection for address:', {
98
+ address: nextHopAddress?.value,
99
+ connectionId: p2pConnection.id,
100
+ direction: p2pConnection.direction,
101
+ reuse,
102
+ });
174
103
  // Check if we already have a cached connection for this address with the same connection id
175
- const connections = this.cachedConnections.get(addressKey) || [];
176
- // Filter to open connections
177
- const validConnections = connections.filter((c) => c.p2pConnection?.id === p2pConnection.id &&
178
- c.p2pConnection?.status === 'open');
179
- if (validConnections.length > 0) {
180
- const existingConnection = validConnections[0];
181
- this.logger.debug('Reusing cached connection for answer:', addressKey, existingConnection.p2pConnection.id);
104
+ const existingConnection = this.cachedConnections.get(p2pConnection.id);
105
+ if (existingConnection) {
106
+ this.logger.debug('Reusing cached connection for answer:', existingConnection.p2pConnection.id);
182
107
  return existingConnection;
183
108
  }
184
109
  const connection = new oNodeConnection({
185
110
  nextHopAddress: nextHopAddress,
186
111
  address: address,
187
112
  p2pConnection: p2pConnection,
113
+ p2pNode: this.p2pNode,
188
114
  callerAddress: callerAddress,
189
115
  readTimeoutMs: readTimeoutMs ?? this.defaultReadTimeoutMs,
190
116
  drainTimeoutMs: drainTimeoutMs ?? this.defaultDrainTimeoutMs,
@@ -194,9 +120,26 @@ export class oNodeConnectionManager extends oConnectionManager {
194
120
  reusePolicy: reuse ? 'reuse' : 'none',
195
121
  });
196
122
  // Cache the new connection
197
- this.cacheConnection(connection, addressKey);
123
+ this.cacheConnection(connection);
198
124
  return connection;
199
125
  }
126
+ getConnectionFromAddress(address) {
127
+ const protocol = address.protocol;
128
+ this.logger.debug('Searching cached connections for protocol:', protocol);
129
+ for (const conn of this.cachedConnections.values()) {
130
+ // if nextHopAddress protocol matches, return conn
131
+ if (conn.nextHopAddress.protocol === protocol) {
132
+ this.logger.debug('local reuse cache found:', protocol);
133
+ return conn;
134
+ }
135
+ // if remote protocols include protocol, return conn
136
+ if (conn.remoteProtocols.includes(protocol)) {
137
+ this.logger.debug('remote reuse cache found', protocol);
138
+ return conn;
139
+ }
140
+ }
141
+ return null;
142
+ }
200
143
  /**
201
144
  * Connect to a given address, reusing oNodeConnection when possible
202
145
  * @param config - Connection configuration
@@ -207,22 +150,24 @@ export class oNodeConnectionManager extends oConnectionManager {
207
150
  if (!nextHopAddress) {
208
151
  throw new Error('Invalid address passed');
209
152
  }
210
- const addressKey = this.getAddressKey(nextHopAddress);
211
- if (!addressKey) {
212
- throw new Error(`Unable to extract address key from address: ${nextHopAddress.toString()}`);
213
- }
214
153
  // Check for existing valid cached connection
215
- const existingConnection = this.getValidConnection(addressKey);
154
+ const existingConnection = this.getConnectionFromAddress(nextHopAddress);
216
155
  if (existingConnection) {
217
- this.logger.debug('Reusing cached connection for address:', addressKey, existingConnection.p2pConnection.id);
156
+ this.logger.debug('Reusing cached connection for address:', existingConnection.p2pConnection.id);
218
157
  return existingConnection;
219
158
  }
159
+ else {
160
+ this.logger.debug('No cached connection found for address:', {
161
+ address: nextHopAddress.value,
162
+ });
163
+ }
220
164
  // Get or create the underlying p2p connection
221
- const p2pConnection = await this.getOrCreateP2pConnection(nextHopAddress, addressKey);
165
+ const p2pConnection = await this.getOrCreateP2pConnection(nextHopAddress, nextHopAddress.value);
222
166
  // Create new oNodeConnection
223
167
  const connection = new oNodeConnection({
224
168
  nextHopAddress: nextHopAddress,
225
169
  address: address,
170
+ p2pNode: this.p2pNode,
226
171
  p2pConnection: p2pConnection,
227
172
  callerAddress: callerAddress,
228
173
  readTimeoutMs: readTimeoutMs ?? this.defaultReadTimeoutMs,
@@ -230,91 +175,10 @@ export class oNodeConnectionManager extends oConnectionManager {
230
175
  isStream: config.isStream ?? false,
231
176
  abortSignal: config.abortSignal,
232
177
  runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
178
+ requestHandler: config.requestHandler,
233
179
  });
234
180
  // Cache the new connection
235
- this.cacheConnection(connection, addressKey);
181
+ this.cacheConnection(connection);
236
182
  return connection;
237
183
  }
238
- /**
239
- * Check if we have an active connection to the target peer
240
- * @param address - The address to check
241
- * @returns true if an active connection exists
242
- */
243
- isCached(address) {
244
- try {
245
- const addressKey = this.getAddressKey(address);
246
- if (!addressKey) {
247
- return false;
248
- }
249
- return this.getValidConnection(addressKey) !== null;
250
- }
251
- catch (error) {
252
- this.logger.debug('Error checking cached connection:', error);
253
- return false;
254
- }
255
- }
256
- /**
257
- * Get an existing cached oNodeConnection for the target address
258
- * @param address - The address to get a connection for
259
- * @returns The oNodeConnection or null if not found
260
- */
261
- getCachedConnection(address) {
262
- try {
263
- const addressKey = this.getAddressKey(address);
264
- if (!addressKey) {
265
- return null;
266
- }
267
- return this.getValidConnection(addressKey);
268
- }
269
- catch (error) {
270
- this.logger.debug('Error getting cached connection:', error);
271
- return null;
272
- }
273
- }
274
- /**
275
- * Get cache statistics for monitoring and debugging
276
- * @returns Object containing cache statistics
277
- */
278
- getCacheStats() {
279
- const allConnections = [];
280
- for (const [addressKey, connections] of this.cachedConnections.entries()) {
281
- for (const conn of connections) {
282
- allConnections.push({
283
- peerId: conn.p2pConnection?.remotePeer?.toString() ?? 'unknown',
284
- status: conn.p2pConnection?.status ?? 'unknown',
285
- addressKey,
286
- });
287
- }
288
- }
289
- return {
290
- cachedAddresses: this.cachedConnections.size,
291
- totalCachedConnections: allConnections.length,
292
- pendingDials: this.pendingDialsByAddress.size,
293
- connectionsByPeer: allConnections,
294
- };
295
- }
296
- /**
297
- * Clean up all stale (non-open) connections from cache
298
- * @returns Number of connections removed
299
- */
300
- cleanupStaleConnections() {
301
- let removed = 0;
302
- for (const [addressKey, connections] of this.cachedConnections.entries()) {
303
- const openConnections = connections.filter((conn) => conn.p2pConnection?.status === 'open');
304
- const staleCount = connections.length - openConnections.length;
305
- if (staleCount > 0) {
306
- removed += staleCount;
307
- if (openConnections.length === 0) {
308
- this.cachedConnections.delete(addressKey);
309
- }
310
- else {
311
- this.cachedConnections.set(addressKey, openConnections);
312
- }
313
- }
314
- }
315
- if (removed > 0) {
316
- this.logger.debug(`Cleaned up ${removed} stale connections`);
317
- }
318
- return removed;
319
- }
320
184
  }
@@ -1 +1 @@
1
- {"version":3,"file":"o-node-stream.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAuB,OAAO,EAAiB,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAEzE;;;;;;;;GAQG;AACH,qBAAa,WAAY,SAAQ,OAAO;aAIpB,SAAS,EAAE,MAAM;aACjB,MAAM,EAAE,iBAAiB;IAJ3C,SAAgB,SAAS,EAAE,MAAM,CAAC;gBAGhB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,iBAAiB;IAO3C,QAAQ;IA6BR;;;;;;;OAOG;IACH,IAAI,OAAO,IAAI,OAAO,CAMrB;IAED;;OAEG;IACH,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED;;OAEG;IACH,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED;;OAEG;IACH,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IAED;;OAEG;IACH,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAgB7B"}
1
+ {"version":3,"file":"o-node-stream.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAuB,OAAO,EAAiB,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAEzE;;;;;;;;GAQG;AACH,qBAAa,WAAY,SAAQ,OAAO;aAIpB,SAAS,EAAE,MAAM;aACjB,MAAM,EAAE,iBAAiB;IAJ3C,SAAgB,SAAS,EAAE,MAAM,CAAC;gBAGhB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,iBAAiB;IAO3C,QAAQ;IA6BR;;;;;;;OAOG;IACH,IAAI,OAAO,IAAI,OAAO,CAMrB;IAED;;OAEG;IACH,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED;;OAEG;IACH,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED;;OAEG;IACH,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IAED;;OAEG;IACH,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAiB7B"}
@@ -76,6 +76,7 @@ export class oNodeStream extends oObject {
76
76
  if (this.p2pStream.status === 'open') {
77
77
  try {
78
78
  // force the close for now until we can implement a proper close
79
+ this.logger.debug('Closing p2p stream');
79
80
  await this.p2pStream.abort(new Error('Stream closed'));
80
81
  }
81
82
  catch (error) {
@@ -26,6 +26,8 @@ export declare class oNodeStreamManager extends oObject {
26
26
  private activeStreamHandlers;
27
27
  protected callerReaderStream?: Stream;
28
28
  protected callerWriterStream?: Stream;
29
+ private streamMonitoringIntervals;
30
+ private id;
29
31
  constructor(config: StreamManagerConfig);
30
32
  /**
31
33
  * Initialize the stream manager
@@ -74,6 +76,14 @@ export declare class oNodeStreamManager extends oObject {
74
76
  * @returns The libp2p Stream or undefined if not found
75
77
  */
76
78
  getStreamById(streamId: string): Stream | undefined;
79
+ /**
80
+ * Sets up monitoring for stream closure and emits events when detected
81
+ * Periodically checks stream status and cleans up when stream closes
82
+ *
83
+ * @param stream - The stream to monitor
84
+ * @param role - The role of the stream ('reader' or 'writer')
85
+ */
86
+ private setupStreamCloseMonitoring;
77
87
  /**
78
88
  * Emits an async event and waits for the first listener to return a result
79
89
  * This enables event-based request handling with async responses
@@ -1 +1 @@
1
- {"version":3,"file":"o-node-stream.manager.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-stream.manager.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EACL,OAAO,EAGP,QAAQ,EACR,SAAS,EAGV,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,iBAAiB,EAIlB,MAAM,qCAAqC,CAAC;AAI7C;;;;;;;;GAQG;AACH,qBAAa,kBAAmB,SAAQ,OAAO;IAYjC,QAAQ,CAAC,MAAM,EAAE,mBAAmB;IAXhD,OAAO,CAAC,OAAO,CAAuC;IACtD,SAAS,CAAC,YAAY,EAAE,YAAY,CAAsB;IACnD,aAAa,EAAE,OAAO,CAAS;IACtC,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,oBAAoB,CAGd;IACd,SAAS,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACtC,SAAS,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;gBAEjB,MAAM,EAAE,mBAAmB;IAKhD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBjC;;;;;;;;;;;;OAYG;IACG,iBAAiB,CACrB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,GAAG,EAClB,MAAM,GAAE,mBAAwB,GAC/B,OAAO,CAAC,WAAW,CAAC;IAsCvB;;;;;;;OAOG;IACG,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,GAAG,EAClB,MAAM,GAAE,mBAAwB,GAC/B,OAAO,CAAC,WAAW,CAAC;IAiCvB;;;;OAIG;IACG,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BpD;;;;OAIG;IACH,aAAa,IAAI,WAAW,EAAE;IAI9B;;;;;;OAMG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAgBnD;;;OAGG;YACW,SAAS;IAevB;;;OAGG;IACH,SAAS,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO;IAIhC;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO;IAIjC;;;OAGG;IACH,YAAY,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,IAAI,iBAAiB;IAIxD;;;;;;;OAOG;cACa,uBAAuB,CACrC,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC;IAsEhB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,GAAG,GAAG;IAqCzD;;;;;;;;OAQG;IACG,kBAAkB,CACtB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,UAAU,EAChB,MAAM,GAAE,mBAAwB,GAC/B,OAAO,CAAC,IAAI,CAAC;IAKhB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAI5B;;;;;;;OAOG;IACG,oBAAoB,CACxB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,IAAI,CAAC;IA2ChB;;;;;;;OAOG;IACH,SAAS,CAAC,iBAAiB,CACzB,OAAO,EAAE,QAAQ,EACjB,aAAa,EAAE,MAAM,GACpB,MAAM;IAoCT;;;;;;OAMG;cACa,oBAAoB,CAClC,OAAO,EAAE,GAAG,EACZ,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,IAAI,CAAC;IAwChB;;;;;;;;;OASG;IACG,oBAAoB,CACxB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,YAAY,EACrB,MAAM,GAAE,mBAAwB,EAChC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAC1B,OAAO,CAAC,SAAS,CAAC;IAwFrB;;;;;;;OAOG;IACG,cAAc,CAClB,OAAO,EAAE,cAAc,EACvB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,GAChD,OAAO,CAAC,IAAI,CAAC;IA0BhB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyC5B;;OAEG;IACH,EAAE,CAAC,CAAC,SAAS,kBAAkB,EAC7B,KAAK,EAAE,CAAC,GAAG,MAAM,EACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,sBAAsB,CAAC,CAAC,CAAC,KAAK,IAAI,GAClD,IAAI;IAIP;;OAEG;IACH,GAAG,CAAC,CAAC,SAAS,kBAAkB,EAC9B,KAAK,EAAE,CAAC,GAAG,MAAM,EACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,sBAAsB,CAAC,CAAC,CAAC,KAAK,IAAI,GAClD,IAAI;IAIP;;OAEG;IACH,OAAO,CAAC,IAAI;CAMb"}
1
+ {"version":3,"file":"o-node-stream.manager.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-stream.manager.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ import { StreamManagerEvent, } from './stream-manager.events.js';
5
5
  import { isStreamInitMessage, } from './interfaces/stream-init-message.js';
6
6
  import { lpStream } from '@olane/o-config';
7
7
  import JSON5 from 'json5';
8
+ import { v4 as uuidv4 } from 'uuid';
8
9
  /**
9
10
  * oNodeStreamManager handles the lifecycle and tracking of streams for a single connection.
10
11
  * Features:
@@ -16,12 +17,15 @@ import JSON5 from 'json5';
16
17
  */
17
18
  export class oNodeStreamManager extends oObject {
18
19
  constructor(config) {
19
- super();
20
+ const id = uuidv4();
21
+ super('id:' + id);
20
22
  this.config = config;
21
23
  this.streams = new Map();
22
24
  this.eventEmitter = new EventEmitter();
23
25
  this.isInitialized = false;
24
26
  this.activeStreamHandlers = new Map();
27
+ this.streamMonitoringIntervals = new Map(); // Track monitoring intervals
28
+ this.id = id;
25
29
  this.p2pConnection = config.p2pConnection;
26
30
  }
27
31
  /**
@@ -59,17 +63,20 @@ export class oNodeStreamManager extends oObject {
59
63
  * @returns Wrapped stream
60
64
  */
61
65
  async getOrCreateStream(protocol, remoteAddress, config = {}) {
62
- this.logger.debug('Getting or creating stream', this.callerReaderStream?.protocol, 'and status', this.callerReaderStream?.status);
66
+ this.logger.debug('Getting or creating stream', this.callerReaderStream?.protocol, 'and status', this.callerReaderStream?.status, 'json:', JSON.stringify(this.callerReaderStream));
63
67
  // If we have a caller's reader stream (from limited connection), use it for sending requests
64
68
  if (this.callerReaderStream && this.callerReaderStream.status === 'open') {
65
69
  this.logger.debug('Using caller reader stream for limited connection', {
66
70
  streamId: this.callerReaderStream.id,
67
71
  });
72
+ // TODO: figure out why this would cause the node stream to be closed?
68
73
  // Wrap the reader stream for use (if not already wrapped)
69
- const existingWrapped = Array.from(this.streams.values()).find((s) => s.p2pStream.id === this.callerReaderStream.id);
70
- if (existingWrapped) {
71
- return existingWrapped;
72
- }
74
+ // const existingWrapped = Array.from(this.streams.values()).find(
75
+ // (s) => s.p2pStream.id === this.callerReaderStream!.id,
76
+ // );
77
+ // if (existingWrapped) {
78
+ // return existingWrapped;
79
+ // }
73
80
  // Wrap the reader stream
74
81
  const wrappedStream = new oNodeStream(this.callerReaderStream, {
75
82
  direction: 'inbound', // It's inbound to us, we write to it
@@ -77,7 +84,7 @@ export class oNodeStreamManager extends oObject {
77
84
  remoteAddress: remoteAddress,
78
85
  streamType: 'request-response',
79
86
  });
80
- this.streams.set(this.callerReaderStream.id, wrappedStream);
87
+ // this.streams.set(this.callerReaderStream.id, wrappedStream);
81
88
  return wrappedStream;
82
89
  }
83
90
  // Always create new stream (no reuse at base layer)
@@ -177,6 +184,52 @@ export class oNodeStreamManager extends oObject {
177
184
  const wrappedStream = this.streams.get(streamId);
178
185
  return wrappedStream?.p2pStream;
179
186
  }
187
+ /**
188
+ * Sets up monitoring for stream closure and emits events when detected
189
+ * Periodically checks stream status and cleans up when stream closes
190
+ *
191
+ * @param stream - The stream to monitor
192
+ * @param role - The role of the stream ('reader' or 'writer')
193
+ */
194
+ setupStreamCloseMonitoring(stream, role) {
195
+ const streamId = stream.id;
196
+ // Clear any existing monitoring for this stream
197
+ const existingInterval = this.streamMonitoringIntervals.get(streamId);
198
+ if (existingInterval) {
199
+ clearInterval(existingInterval);
200
+ }
201
+ // Check stream status every 5 seconds
202
+ const interval = setInterval(() => {
203
+ if (stream.status !== 'open') {
204
+ this.logger.info(`Caller ${role} stream closed`, {
205
+ streamId,
206
+ status: stream.status,
207
+ role,
208
+ });
209
+ // Emit stream closed event
210
+ this.emit(StreamManagerEvent.StreamClosed, {
211
+ streamId,
212
+ role,
213
+ status: stream.status,
214
+ });
215
+ // Clear the stream reference
216
+ if (role === 'reader') {
217
+ this.callerReaderStream = undefined;
218
+ this.logger.info('Limited connection reader stream closed, will create new streams for requests');
219
+ }
220
+ else if (role === 'writer') {
221
+ this.callerWriterStream = undefined;
222
+ this.logger.info('Limited connection writer stream closed, responses may be affected');
223
+ }
224
+ // Stop monitoring this stream
225
+ clearInterval(interval);
226
+ this.streamMonitoringIntervals.delete(streamId);
227
+ }
228
+ }, 5000);
229
+ // Track the interval for cleanup
230
+ this.streamMonitoringIntervals.set(streamId, interval);
231
+ this.logger.debug(`Started monitoring ${role} stream`, { streamId });
232
+ }
180
233
  /**
181
234
  * Emits an async event and waits for the first listener to return a result
182
235
  * This enables event-based request handling with async responses
@@ -232,6 +285,8 @@ export class oNodeStreamManager extends oObject {
232
285
  role: message.role,
233
286
  connectionId: message.connectionId,
234
287
  });
288
+ // Set up monitoring for reader stream closure
289
+ this.setupStreamCloseMonitoring(stream, 'reader');
235
290
  }
236
291
  else if (message.role === 'writer') {
237
292
  this.callerWriterStream = stream;
@@ -244,6 +299,8 @@ export class oNodeStreamManager extends oObject {
244
299
  role: message.role,
245
300
  connectionId: message.connectionId,
246
301
  });
302
+ // Set up monitoring for writer stream closure
303
+ this.setupStreamCloseMonitoring(stream, 'writer');
247
304
  }
248
305
  // Send acknowledgment back to caller
249
306
  const ackMessage = {
@@ -387,7 +444,6 @@ export class oNodeStreamManager extends oObject {
387
444
  catch (error) {
388
445
  // Stream closed or error occurred
389
446
  if (stream.status === 'open') {
390
- this.logger.error('Error in length-prefixed stream handler:', error);
391
447
  this.emit(StreamManagerEvent.StreamError, {
392
448
  streamId: stream.id,
393
449
  error,
@@ -585,7 +641,14 @@ export class oNodeStreamManager extends oObject {
585
641
  this.logger.info('Closing stream manager', {
586
642
  activeStreams: this.streams.size,
587
643
  activeHandlers: this.activeStreamHandlers.size,
644
+ monitoringIntervals: this.streamMonitoringIntervals.size,
588
645
  });
646
+ // Clear all stream monitoring intervals
647
+ for (const [streamId, interval,] of this.streamMonitoringIntervals.entries()) {
648
+ clearInterval(interval);
649
+ this.logger.debug('Cleared monitoring interval', { streamId });
650
+ }
651
+ this.streamMonitoringIntervals.clear();
589
652
  // Abort all active stream handlers
590
653
  for (const [streamId, { abortController },] of this.activeStreamHandlers.entries()) {
591
654
  abortController.abort();
@@ -12,6 +12,7 @@ export declare enum StreamManagerEvent {
12
12
  StreamReplaced = "stream-replaced",
13
13
  StreamFailed = "stream-failed",
14
14
  StreamIdentified = "stream-identified",
15
+ StreamClosed = "stream-closed",
15
16
  InboundRequest = "inbound-request",
16
17
  InboundResponse = "inbound-response",
17
18
  StreamError = "stream-error"
@@ -53,6 +54,11 @@ export interface StreamIdentifiedData {
53
54
  role: 'reader' | 'writer' | 'standard';
54
55
  connectionId?: string;
55
56
  }
57
+ export interface StreamClosedData {
58
+ streamId: string;
59
+ role: 'reader' | 'writer';
60
+ status: string;
61
+ }
56
62
  export interface InboundRequestData {
57
63
  request: any;
58
64
  stream: any;
@@ -81,6 +87,7 @@ export type StreamManagerEventData = {
81
87
  [StreamManagerEvent.StreamReplaced]: StreamReplacedData;
82
88
  [StreamManagerEvent.StreamFailed]: StreamFailedData;
83
89
  [StreamManagerEvent.StreamIdentified]: StreamIdentifiedData;
90
+ [StreamManagerEvent.StreamClosed]: StreamClosedData;
84
91
  [StreamManagerEvent.InboundRequest]: InboundRequestData;
85
92
  [StreamManagerEvent.InboundResponse]: InboundResponseData;
86
93
  [StreamManagerEvent.StreamError]: StreamErrorData;
@@ -1 +1 @@
1
- {"version":3,"file":"stream-manager.events.d.ts","sourceRoot":"","sources":["../../../src/connection/stream-manager.events.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,oBAAY,kBAAkB;IAC5B,kBAAkB,wBAAwB;IAC1C,aAAa,mBAAmB;IAChC,aAAa,mBAAmB;IAChC,aAAa,mBAAmB;IAChC,YAAY,kBAAkB;IAC9B,eAAe,qBAAqB;IACpC,cAAc,oBAAoB;IAClC,cAAc,oBAAoB;IAClC,YAAY,kBAAkB;IAC9B,gBAAgB,sBAAsB;IACtC,cAAc,oBAAoB;IAClC,eAAe,qBAAqB;IACpC,WAAW,iBAAiB;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;CAAG;AAEnC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,GAAG,CAAC;IACb,MAAM,EAAE,GAAG,CAAC;IACZ,UAAU,EAAE,GAAG,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,GAAG,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;CAC9C;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,EAAE,eAAe,CAAC;IACzD,CAAC,kBAAkB,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC;IACzC,CAAC,kBAAkB,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IACtD,CAAC,kBAAkB,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IACtD,CAAC,kBAAkB,CAAC,YAAY,CAAC,EAAE,gBAAgB,CAAC;IACpD,CAAC,kBAAkB,CAAC,eAAe,CAAC,EAAE,mBAAmB,CAAC;IAC1D,CAAC,kBAAkB,CAAC,cAAc,CAAC,EAAE,kBAAkB,CAAC;IACxD,CAAC,kBAAkB,CAAC,cAAc,CAAC,EAAE,kBAAkB,CAAC;IACxD,CAAC,kBAAkB,CAAC,YAAY,CAAC,EAAE,gBAAgB,CAAC;IACpD,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;IAC5D,CAAC,kBAAkB,CAAC,cAAc,CAAC,EAAE,kBAAkB,CAAC;IACxD,CAAC,kBAAkB,CAAC,eAAe,CAAC,EAAE,mBAAmB,CAAC;IAC1D,CAAC,kBAAkB,CAAC,WAAW,CAAC,EAAE,eAAe,CAAC;CACnD,CAAC"}
1
+ {"version":3,"file":"stream-manager.events.d.ts","sourceRoot":"","sources":["../../../src/connection/stream-manager.events.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,7 @@ export var StreamManagerEvent;
13
13
  StreamManagerEvent["StreamReplaced"] = "stream-replaced";
14
14
  StreamManagerEvent["StreamFailed"] = "stream-failed";
15
15
  StreamManagerEvent["StreamIdentified"] = "stream-identified";
16
+ StreamManagerEvent["StreamClosed"] = "stream-closed";
16
17
  StreamManagerEvent["InboundRequest"] = "inbound-request";
17
18
  StreamManagerEvent["InboundResponse"] = "inbound-response";
18
19
  StreamManagerEvent["StreamError"] = "stream-error";
@@ -1 +1 @@
1
- {"version":3,"file":"o-node.tool.d.ts","sourceRoot":"","sources":["../../src/o-node.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAoB,MAAM,eAAe,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;;AAOrD;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAAkB;IACzC,mBAAmB,CAAC,OAAO,EAAE,QAAQ;IAuBrC,cAAc,CAAC,OAAO,EAAE,QAAQ;IAsBhC,mBAAmB;IAWnB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,IAAI,CAAC;IAIV,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,KAAK,CAAC,EAAE,OAAO,GACd,OAAO,CAAC,IAAI,CAAC;IAwDV,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAQ9B,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;CAiC5D"}
1
+ {"version":3,"file":"o-node.tool.d.ts","sourceRoot":"","sources":["../../src/o-node.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAoB,MAAM,eAAe,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;;AAOrD;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAAkB;IACzC,mBAAmB,CAAC,OAAO,EAAE,QAAQ;IAuBrC,cAAc,CAAC,OAAO,EAAE,QAAQ;IAyBhC,mBAAmB;IAWnB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,IAAI,CAAC;IAIV,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,KAAK,CAAC,EAAE,OAAO,GACd,OAAO,CAAC,IAAI,CAAC;IA+CV,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAQ9B,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;CAiC5D"}
@@ -3,7 +3,6 @@ import { oTool } from '@olane/o-tool';
3
3
  import { oServerNode } from './nodes/server.node.js';
4
4
  import { oNodeTransport } from './router/o-node.transport.js';
5
5
  import { oNodeAddress } from './router/o-node.address.js';
6
- import { ConnectionUtils } from './utils/connection.utils.js';
7
6
  import { StreamManagerEvent } from './connection/stream-manager.events.js';
8
7
  /**
9
8
  * oTool is a mixin that extends the base class and implements the oTool interface
@@ -30,8 +29,11 @@ export class oNodeTool extends oTool(oServerNode) {
30
29
  this.logger.debug('Handled protocol reuse: ' + reuseProtocol);
31
30
  }
32
31
  async handleProtocol(address) {
32
+ if (!address || !address.protocol) {
33
+ throw new Error('Invalid address passed: ' + address);
34
+ }
33
35
  const protocols = this.p2pNode.getProtocols();
34
- if (protocols.find((p) => p === address.protocol)) {
36
+ if (protocols.find((p) => p === address?.protocol)) {
35
37
  // already handling
36
38
  return;
37
39
  }
@@ -66,29 +68,22 @@ export class oNodeTool extends oTool(oServerNode) {
66
68
  return this.handleStream(stream, connection, true);
67
69
  }
68
70
  async handleStream(stream, connection, reuse) {
69
- if (reuse) {
70
- this.logger.debug('Handle stream with reuse = true');
71
- // record inbound connection to manager
72
- }
73
- const remoteAddress = await ConnectionUtils.addressFromConnection({
74
- currentNode: this,
75
- connection: connection,
71
+ this.logger.debug('Handling incoming stream on connection:', {
72
+ connectionId: connection.id,
73
+ direction: connection.direction,
76
74
  });
77
- await this.connectionManager.answer({
78
- nextHopAddress: remoteAddress,
79
- address: remoteAddress,
75
+ const unknown = new oNodeAddress('o://unknown', []);
76
+ const oConnection = await this.connectionManager.answer({
77
+ nextHopAddress: unknown,
78
+ address: unknown,
80
79
  callerAddress: this.address,
81
80
  p2pConnection: connection,
82
81
  reuse,
83
82
  });
84
83
  // Get the oNodeConnection for this libp2p connection
85
- const oConnection = this.connectionManager.getConnectionByP2pConnection(connection);
86
84
  if (!oConnection) {
87
- this.logger.error('No oNodeConnection found for incoming stream', {
88
- remotePeer: connection.remotePeer.toString(),
89
- connectionId: connection.id,
90
- });
91
- return;
85
+ this.logger.error('Failed to process inbound connection');
86
+ throw new Error('Failed to process inbound connection');
92
87
  }
93
88
  // Subscribe to InboundRequest events from the stream manager
94
89
  // This follows an event-driven pattern for handling incoming requests
@@ -2,10 +2,10 @@ import { Connection } from '@olane/o-config';
2
2
  import { oObject } from '@olane/o-core';
3
3
  export declare class ConnectionUtils extends oObject {
4
4
  /**
5
- * Waits for a peer to appear in the peer store with sufficient protocol information.
6
- * Implements retry logic to handle race conditions where peer store is not immediately populated.
5
+ * Waits for a peer to be identified via the identify protocol.
6
+ * Uses event-driven approach listening to peer store protocol updates.
7
7
  */
8
- private static waitForPeerInStore;
8
+ private static waitForPeerIdentify;
9
9
  static addressFromConnection(options: {
10
10
  currentNode: any;
11
11
  connection: Connection;
@@ -1 +1 @@
1
- {"version":3,"file":"connection.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/connection.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA2B,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAuB,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7D,qBAAa,eAAgB,SAAQ,OAAO;IAC1C;;;OAGG;mBACkB,kBAAkB;WAgCnB,qBAAqB,CAAC,OAAO,EAAE;QACjD,WAAW,EAAE,GAAG,CAAC;QACjB,UAAU,EAAE,UAAU,CAAC;KACxB;CA0DF"}
1
+ {"version":3,"file":"connection.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/connection.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAKX,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAuB,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7D,qBAAa,eAAgB,SAAQ,OAAO;IAC1C;;;OAGG;mBACkB,mBAAmB;WA0EpB,qBAAqB,CAAC,OAAO,EAAE;QACjD,WAAW,EAAE,GAAG,CAAC;QACjB,UAAU,EAAE,UAAU,CAAC;KACxB;CA4DF"}
@@ -3,38 +3,64 @@ import { oNodeAddress } from '../router/o-node.address.js';
3
3
  import { oNodeTransport } from '../router/o-node.transport.js';
4
4
  export class ConnectionUtils extends oObject {
5
5
  /**
6
- * Waits for a peer to appear in the peer store with sufficient protocol information.
7
- * Implements retry logic to handle race conditions where peer store is not immediately populated.
6
+ * Waits for a peer to be identified via the identify protocol.
7
+ * Uses event-driven approach listening to peer store protocol updates.
8
8
  */
9
- static async waitForPeerInStore(p2pNode, remotePeerId) {
10
- const MAX_RETRIES = 100; // 5 seconds total (100 * 50ms)
11
- const RETRY_DELAY_MS = 50;
12
- const MIN_PROTOCOLS = 2;
13
- for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
9
+ static async waitForPeerIdentify(p2pNode, remotePeerId, nodeProtocol) {
10
+ const TIMEOUT_MS = 5000; // 5 seconds timeout
11
+ // Helper to check if peer has sufficient protocols
12
+ const checkPeerProtocols = async () => {
14
13
  const peers = await p2pNode.peerStore.all();
15
14
  const remotePeer = peers.find((peer) => {
16
15
  return peer.id.toString() === remotePeerId.toString();
17
16
  });
18
- // Check if peer exists and has sufficient protocol information
19
- if (remotePeer && remotePeer.protocols.length > MIN_PROTOCOLS) {
20
- return remotePeer;
21
- }
22
- // Wait before next retry
23
- if (attempt < MAX_RETRIES - 1) {
24
- await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
17
+ if (remotePeer) {
18
+ const oProtocols = remotePeer.protocols.filter((p) => p.startsWith('/o/') && p.startsWith(nodeProtocol) === false) || [];
19
+ console.log('Found o-protocols for peer:', oProtocols, 'with node address:', nodeProtocol);
20
+ if (oProtocols.length > 0) {
21
+ return remotePeer;
22
+ }
25
23
  }
24
+ return null;
25
+ };
26
+ // Check if peer already has sufficient protocols
27
+ const existingPeer = await checkPeerProtocols();
28
+ if (existingPeer) {
29
+ return existingPeer;
26
30
  }
27
- // Timeout exceeded
28
- throw new Error(`Timeout waiting for peer ${remotePeerId.toString()} to appear in peer store with sufficient protocols (waited ${MAX_RETRIES * RETRY_DELAY_MS}ms)`);
31
+ // Wait for peer store protocol update event
32
+ return new Promise((resolve, reject) => {
33
+ const timeoutId = setTimeout(() => {
34
+ // TypeScript doesn't have types for peerStore events, but they exist at runtime
35
+ p2pNode.removeEventListener('peer:identify', protocolChangeHandler);
36
+ reject(new Error(`Timeout waiting for peer ${remotePeerId.toString()} to be identified (waited ${TIMEOUT_MS}ms)`));
37
+ }, TIMEOUT_MS);
38
+ const protocolChangeHandler = async (evt) => {
39
+ const { peerId } = evt.detail;
40
+ console.log('evt.detail:', evt.detail);
41
+ // Check if this is the peer we're waiting for
42
+ if (peerId?.toString() === remotePeerId.toString()) {
43
+ const peer = await checkPeerProtocols();
44
+ if (peer) {
45
+ clearTimeout(timeoutId);
46
+ // TypeScript doesn't have types for peerStore events, but they exist at runtime
47
+ p2pNode.removeEventListener('peer:identify', protocolChangeHandler);
48
+ resolve(peer);
49
+ }
50
+ }
51
+ };
52
+ // TypeScript doesn't have types for peerStore events, but they exist at runtime
53
+ p2pNode.addEventListener('peer:identify', protocolChangeHandler);
54
+ });
29
55
  }
30
56
  // TODO: improve this logic (poor implementation for now)
31
57
  static async addressFromConnection(options) {
32
58
  try {
33
59
  const { currentNode, connection } = options;
34
60
  const p2pNode = currentNode.p2pNode;
35
- // Wait for peer to appear in peer store with sufficient protocol information
36
- // This handles race conditions where peer store is not immediately populated
37
- const remotePeer = await this.waitForPeerInStore(p2pNode, connection.remotePeer);
61
+ // Wait for peer to be identified via the identify protocol
62
+ // This uses an event-driven approach to detect when the peer store is updated
63
+ const remotePeer = await this.waitForPeerIdentify(p2pNode, connection.remotePeer, currentNode.address.protocol);
38
64
  // Get origin address for comparison
39
65
  const originAddress = currentNode.address?.value;
40
66
  if (!originAddress) {
@@ -43,6 +69,7 @@ export class ConnectionUtils extends oObject {
43
69
  const oProtocol = remotePeer.protocols.find((p) => p.startsWith('/o/') &&
44
70
  p.includes(currentNode?.address?.protocol) === false);
45
71
  if (!oProtocol) {
72
+ console.log('Remote peer protocols:', remotePeer.protocols);
46
73
  throw new Error('Failed to extract remote address, could not find o-protocol in peer protocols.');
47
74
  }
48
75
  const address = oNodeAddress.fromProtocol(oProtocol);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olane/o-node",
3
- "version": "0.7.54",
3
+ "version": "0.7.55",
4
4
  "type": "module",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -40,7 +40,7 @@
40
40
  "devDependencies": {
41
41
  "@eslint/eslintrc": "^3.3.1",
42
42
  "@eslint/js": "^9.29.0",
43
- "@olane/o-test": "0.7.54",
43
+ "@olane/o-test": "0.7.55",
44
44
  "@tsconfig/node20": "^20.1.6",
45
45
  "@types/jest": "^30.0.0",
46
46
  "@types/json5": "^2.2.0",
@@ -60,13 +60,13 @@
60
60
  "typescript": "5.4.5"
61
61
  },
62
62
  "dependencies": {
63
- "@olane/o-config": "0.7.54",
64
- "@olane/o-core": "0.7.54",
65
- "@olane/o-protocol": "0.7.54",
66
- "@olane/o-tool": "0.7.54",
63
+ "@olane/o-config": "0.7.55",
64
+ "@olane/o-core": "0.7.55",
65
+ "@olane/o-protocol": "0.7.55",
66
+ "@olane/o-tool": "0.7.55",
67
67
  "debug": "^4.4.1",
68
68
  "dotenv": "^16.5.0",
69
69
  "json5": "^2.2.3"
70
70
  },
71
- "gitHead": "8e527a1481c6aeaf0840ea30198500a9baef1e98"
71
+ "gitHead": "f7d02a1ecfb0d28b7b1009a7c3eb6d0d4863adc2"
72
72
  }