@olane/o-client-limited 0.7.52 → 0.7.54

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,3 +1,4 @@
1
1
  export * from './o-limited-connection.js';
2
2
  export * from './o-limited-connection-manager.js';
3
+ export * from './o-limited.stream-manager.js';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/connection/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mCAAmC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/connection/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mCAAmC,CAAC;AAClD,cAAc,+BAA+B,CAAC"}
@@ -1,2 +1,3 @@
1
1
  export * from './o-limited-connection.js';
2
2
  export * from './o-limited-connection-manager.js';
3
+ export * from './o-limited.stream-manager.js';
@@ -18,7 +18,7 @@ export class oLimitedConnectionManager extends oNodeConnectionManager {
18
18
  address: address,
19
19
  p2pConnection: p2pConnection,
20
20
  callerAddress: callerAddress,
21
- runOnLimitedConnection: this.config.runOnLimitedConnection ?? true,
21
+ runOnLimitedConnection: true,
22
22
  requestHandler: config.requestHandler,
23
23
  });
24
24
  return connection;
@@ -1,29 +1,40 @@
1
1
  import { oNodeConnection } from '@olane/o-node';
2
2
  import type { oNodeConnectionConfig } from '@olane/o-node';
3
3
  import { oLimitedStreamManager } from './o-limited.stream-manager.js';
4
+ import type { oRequest, oResponse } from '@olane/o-core';
4
5
  /**
5
- * oLimitedConnection extends oNodeConnection with stream reuse for limited connections.
6
+ * oLimitedConnection extends oNodeConnection with dual persistent streams for limited connections.
6
7
  *
7
- * This is optimized for limited connections where creating new streams is expensive
8
- * (mobile clients, browsers, resource-constrained environments).
8
+ * This is optimized for limited connections where the caller cannot be dialed
9
+ * (browser clients, mobile clients, resource-constrained environments).
9
10
  *
10
11
  * Stream Architecture:
11
- * - 1 dedicated reader stream for receiving requests from receiver
12
- * - Reuses outbound streams for multiple request-response cycles
12
+ * - 1 dedicated reader stream for receiving ALL inbound data from receiver
13
+ * (receiver-initiated requests AND responses to limited client's outbound requests)
14
+ * - 1 dedicated writer stream for sending ALL outbound data to receiver
15
+ * (responses to receiver's requests AND limited client's outbound requests)
16
+ * - No ephemeral streams - all communication uses persistent streams for efficiency
13
17
  *
14
18
  * Features:
15
- * - Automatic reader stream creation and maintenance
19
+ * - Automatic reader/writer stream creation and maintenance
16
20
  * - Single retry on reader failure
17
- * - Stream reuse for outbound requests
18
- * - Event emission for monitoring (reader-started, reader-failed, reader-recovered)
21
+ * - Stream reuse for all outbound requests (no ephemeral streams)
22
+ * - Response routing via _streamId and _responseConnectionId in request params
23
+ * - Event emission for monitoring (reader-started, writer-started, reader-failed, reader-recovered)
19
24
  *
20
25
  * Default Behavior:
21
26
  * - Uses 'reuse' stream policy by default
22
- * - Automatically initializes dedicated reader on connection start
27
+ * - Automatically initializes persistent streams on connection start
23
28
  */
24
29
  export declare class oLimitedConnection extends oNodeConnection {
25
30
  streamManager: oLimitedStreamManager;
26
31
  constructor(config: oNodeConnectionConfig);
32
+ /**
33
+ * Override transmit to inject response routing params
34
+ * Adds _streamId and _responseConnectionId to enable receiver to route responses
35
+ * to the persistent reader stream
36
+ */
37
+ transmit(request: oRequest): Promise<oResponse>;
27
38
  /**
28
39
  * Override close to cleanup stream manager properly
29
40
  */
@@ -1 +1 @@
1
- {"version":3,"file":"o-limited-connection.d.ts","sourceRoot":"","sources":["../../../src/connection/o-limited-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EACL,qBAAqB,EAEtB,MAAM,+BAA+B,CAAC;AAGvC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,kBAAmB,SAAQ,eAAe;IAC7C,aAAa,EAAE,qBAAqB,CAAC;gBAEjC,MAAM,EAAE,qBAAqB;IAmCzC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAK7B"}
1
+ {"version":3,"file":"o-limited-connection.d.ts","sourceRoot":"","sources":["../../../src/connection/o-limited-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EACL,qBAAqB,EAEtB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,kBAAmB,SAAQ,eAAe;IAC7C,aAAa,EAAE,qBAAqB,CAAC;gBAEjC,MAAM,EAAE,qBAAqB;IAsCzC;;;;OAIG;IACG,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IA6BrD;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAK7B"}
@@ -2,24 +2,28 @@ import { oNodeConnection } from '@olane/o-node';
2
2
  import { oLimitedStreamManager, } from './o-limited.stream-manager.js';
3
3
  import { StreamManagerEvent } from '@olane/o-node';
4
4
  /**
5
- * oLimitedConnection extends oNodeConnection with stream reuse for limited connections.
5
+ * oLimitedConnection extends oNodeConnection with dual persistent streams for limited connections.
6
6
  *
7
- * This is optimized for limited connections where creating new streams is expensive
8
- * (mobile clients, browsers, resource-constrained environments).
7
+ * This is optimized for limited connections where the caller cannot be dialed
8
+ * (browser clients, mobile clients, resource-constrained environments).
9
9
  *
10
10
  * Stream Architecture:
11
- * - 1 dedicated reader stream for receiving requests from receiver
12
- * - Reuses outbound streams for multiple request-response cycles
11
+ * - 1 dedicated reader stream for receiving ALL inbound data from receiver
12
+ * (receiver-initiated requests AND responses to limited client's outbound requests)
13
+ * - 1 dedicated writer stream for sending ALL outbound data to receiver
14
+ * (responses to receiver's requests AND limited client's outbound requests)
15
+ * - No ephemeral streams - all communication uses persistent streams for efficiency
13
16
  *
14
17
  * Features:
15
- * - Automatic reader stream creation and maintenance
18
+ * - Automatic reader/writer stream creation and maintenance
16
19
  * - Single retry on reader failure
17
- * - Stream reuse for outbound requests
18
- * - Event emission for monitoring (reader-started, reader-failed, reader-recovered)
20
+ * - Stream reuse for all outbound requests (no ephemeral streams)
21
+ * - Response routing via _streamId and _responseConnectionId in request params
22
+ * - Event emission for monitoring (reader-started, writer-started, reader-failed, reader-recovered)
19
23
  *
20
24
  * Default Behavior:
21
25
  * - Uses 'reuse' stream policy by default
22
- * - Automatically initializes dedicated reader on connection start
26
+ * - Automatically initializes persistent streams on connection start
23
27
  */
24
28
  export class oLimitedConnection extends oNodeConnection {
25
29
  constructor(config) {
@@ -29,8 +33,7 @@ export class oLimitedConnection extends oNodeConnection {
29
33
  reusePolicy,
30
34
  });
31
35
  // Replace the base streamManager with our limited version
32
- const protocol = this.nextHopAddress.protocol +
33
- (reusePolicy === 'reuse' ? '/reuse' : '');
36
+ const protocol = this.nextHopAddress.protocol + (reusePolicy === 'reuse' ? '/reuse' : '');
34
37
  const limitedConfig = {
35
38
  p2pConnection: this.p2pConnection,
36
39
  protocol,
@@ -42,6 +45,9 @@ export class oLimitedConnection extends oNodeConnection {
42
45
  this.streamManager.on(StreamManagerEvent.ReaderStarted, (data) => {
43
46
  this.logger.info('Reader stream started', data);
44
47
  });
48
+ this.streamManager.on(StreamManagerEvent.WriterStarted, (data) => {
49
+ this.logger.info('Writer stream started', data);
50
+ });
45
51
  this.streamManager.on(StreamManagerEvent.ReaderFailed, (data) => {
46
52
  this.logger.warn('Reader stream failed', data);
47
53
  });
@@ -49,6 +55,35 @@ export class oLimitedConnection extends oNodeConnection {
49
55
  this.logger.info('Reader stream recovered', data);
50
56
  });
51
57
  }
58
+ /**
59
+ * Override transmit to inject response routing params
60
+ * Adds _streamId and _responseConnectionId to enable receiver to route responses
61
+ * to the persistent reader stream
62
+ */
63
+ async transmit(request) {
64
+ // Wait for stream manager initialization to ensure streams are available
65
+ if (!this.streamManager.isInitialized) {
66
+ await this.streamManager.initialize();
67
+ }
68
+ // Inject response routing params to route responses to reader stream
69
+ // Writer stream sends the request, reader stream receives the response
70
+ if (this.streamManager.readerStream) {
71
+ request.params._streamId = this.streamManager.readerStream.p2pStream.id;
72
+ request.params._responseConnectionId = this.p2pConnection.id;
73
+ if (request.params.payload) {
74
+ request.params.payload.params._streamId = request.params._streamId;
75
+ }
76
+ this.logger.debug('Injected response routing params', {
77
+ streamId: request.params._streamId,
78
+ responseConnectionId: request.params._responseConnectionId,
79
+ });
80
+ }
81
+ else {
82
+ this.logger.warn('Reader stream not available, response routing params not injected');
83
+ }
84
+ // Call parent transmit with modified request
85
+ return await super.transmit(request);
86
+ }
52
87
  /**
53
88
  * Override close to cleanup stream manager properly
54
89
  */
@@ -21,16 +21,19 @@ export interface oLimitedStreamManagerConfig extends StreamManagerConfig {
21
21
  /**
22
22
  * Limited Stream Manager handles stream lifecycle for limited connections
23
23
  * Features:
24
- * - Maintains dedicated reader stream for receiving requests from receiver
25
- * - Reuses outbound streams (no close after use)
24
+ * - Maintains dedicated reader stream for receiving all inbound data from receiver
25
+ * (both receiver-initiated requests and responses to limited client's outbound requests)
26
+ * - Maintains dedicated writer stream for sending all outbound data to receiver
27
+ * (both responses to receiver's requests and limited client's outbound requests)
28
+ * - No ephemeral streams - all communication uses persistent streams
26
29
  * - Single retry on reader failure
27
30
  * - No health checks (error handling on use)
28
31
  */
29
32
  export declare class oLimitedStreamManager extends oNodeStreamManager {
30
- private readerStream?;
33
+ readerStream?: oNodeStream;
34
+ writerStream?: oNodeStream;
31
35
  private isReaderLoopActive;
32
36
  private readerLoopAbortController?;
33
- private outboundStream?;
34
37
  private limitedConfig;
35
38
  private closing;
36
39
  constructor(config: oLimitedStreamManagerConfig);
@@ -44,7 +47,12 @@ export declare class oLimitedStreamManager extends oNodeStreamManager {
44
47
  */
45
48
  private createReaderStream;
46
49
  /**
47
- * Sends stream initialization message to identify this as a reader stream
50
+ * Creates and initializes the dedicated writer stream
51
+ */
52
+ private createWriterStream;
53
+ /**
54
+ * Sends stream initialization message to identify the stream role
55
+ * Waits for acknowledgment from receiver before proceeding
48
56
  */
49
57
  private sendStreamInitMessage;
50
58
  /**
@@ -61,16 +69,25 @@ export declare class oLimitedStreamManager extends oNodeStreamManager {
61
69
  */
62
70
  private recoverReaderStream;
63
71
  /**
64
- * Override getOrCreateStream to reuse outbound streams
65
- * Does not create new stream for every request
72
+ * Override getOrCreateStream to use persistent writer stream for outbound requests
73
+ * This eliminates ephemeral stream creation for limited connections
66
74
  * Auto-initializes on first use
67
75
  */
68
76
  getOrCreateStream(protocol: string, remoteAddress: any, config?: StreamHandlerConfig): Promise<oNodeStream>;
69
77
  /**
70
- * Override releaseStream to NOT close the stream
71
- * Keep streams open for reuse
78
+ * Override releaseStream to protect persistent streams from being closed
79
+ * Persistent streams (reader/writer) should never be released via this method
80
+ * Only closes streams that are not the dedicated reader or writer streams
72
81
  */
73
82
  releaseStream(streamId: string): Promise<void>;
83
+ /**
84
+ * Override getStreamById to check persistent streams first
85
+ * Provides access to reader and writer streams by ID
86
+ *
87
+ * @param streamId - The ID of the stream to retrieve
88
+ * @returns The libp2p Stream or undefined if not found
89
+ */
90
+ getStreamById(streamId: string): Stream | undefined;
74
91
  /**
75
92
  * Close the stream manager and cleanup all resources
76
93
  */
@@ -1 +1 @@
1
- {"version":3,"file":"o-limited.stream-manager.d.ts","sourceRoot":"","sources":["../../../src/connection/o-limited.stream-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EACL,kBAAkB,EAElB,WAAW,EACX,mBAAmB,EAGpB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,2BAA4B,SAAQ,mBAAmB;IACtE;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,aAAa,EAAE,GAAG,CAAC;IAEnB;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CACjE;AAED;;;;;;;GAOG;AACH,qBAAa,qBAAsB,SAAQ,kBAAkB;IAC3D,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,kBAAkB,CAAkB;IAC5C,OAAO,CAAC,yBAAyB,CAAC,CAAkB;IACpD,OAAO,CAAC,cAAc,CAAC,CAAc;IACrC,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,OAAO,CAAkB;gBAErB,MAAM,EAAE,2BAA2B;IAK/C;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAqDjC;;OAEG;YACW,kBAAkB;IA4BhC;;OAEG;YACW,qBAAqB;IAiBnC;;;OAGG;YACW,eAAe;IAe7B;;OAEG;YACW,aAAa;IAwB3B;;OAEG;YACW,mBAAmB;IAgCjC;;;;OAIG;IACG,iBAAiB,CACrB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,GAAG,EAClB,MAAM,GAAE,mBAAwB,GAC/B,OAAO,CAAC,WAAW,CAAC;IA6BvB;;;OAGG;IACG,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMpD;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAqC7B"}
1
+ {"version":3,"file":"o-limited.stream-manager.d.ts","sourceRoot":"","sources":["../../../src/connection/o-limited.stream-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EACL,kBAAkB,EAElB,WAAW,EACX,mBAAmB,EAKpB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAGzD;;GAEG;AACH,MAAM,WAAW,2BAA4B,SAAQ,mBAAmB;IACtE;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,aAAa,EAAE,GAAG,CAAC;IAEnB;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CACjE;AAED;;;;;;;;;;GAUG;AACH,qBAAa,qBAAsB,SAAQ,kBAAkB;IACpD,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B,YAAY,CAAC,EAAE,WAAW,CAAC;IAClC,OAAO,CAAC,kBAAkB,CAAkB;IAC5C,OAAO,CAAC,yBAAyB,CAAC,CAAkB;IACpD,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,OAAO,CAAkB;gBAErB,MAAM,EAAE,2BAA2B;IAK/C;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAsDjC;;OAEG;YACW,kBAAkB;IA4BhC;;OAEG;YACW,kBAAkB;IAyBhC;;;OAGG;YACW,qBAAqB;IA0EnC;;;OAGG;YACW,eAAe;IAe7B;;OAEG;YACW,aAAa;IAqB3B;;OAEG;YACW,mBAAmB;IAgCjC;;;;OAIG;IACG,iBAAiB,CACrB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,GAAG,EAClB,MAAM,GAAE,mBAAwB,GAC/B,OAAO,CAAC,WAAW,CAAC;IAqBvB;;;;OAIG;IACG,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAepD;;;;;;OAMG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAcnD;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAqC7B"}
@@ -1,10 +1,14 @@
1
1
  import { oError, oErrorCodes } from '@olane/o-core';
2
- import { oNodeStreamManager, StreamManagerEvent, } from '@olane/o-node';
2
+ import { oNodeStreamManager, StreamManagerEvent, isStreamInitAckMessage, } from '@olane/o-node';
3
+ import { lpStream } from '@olane/o-config';
3
4
  /**
4
5
  * Limited Stream Manager handles stream lifecycle for limited connections
5
6
  * Features:
6
- * - Maintains dedicated reader stream for receiving requests from receiver
7
- * - Reuses outbound streams (no close after use)
7
+ * - Maintains dedicated reader stream for receiving all inbound data from receiver
8
+ * (both receiver-initiated requests and responses to limited client's outbound requests)
9
+ * - Maintains dedicated writer stream for sending all outbound data to receiver
10
+ * (both responses to receiver's requests and limited client's outbound requests)
11
+ * - No ephemeral streams - all communication uses persistent streams
8
12
  * - Single retry on reader failure
9
13
  * - No health checks (error handling on use)
10
14
  */
@@ -40,8 +44,9 @@ export class oLimitedStreamManager extends oNodeStreamManager {
40
44
  this.logger.debug('Registered InboundRequest handler for limited connection');
41
45
  }
42
46
  try {
43
- // Create dedicated reader stream
47
+ // Create dedicated reader and writer streams
44
48
  await this.createReaderStream();
49
+ await this.createWriterStream();
45
50
  this.isInitialized = true;
46
51
  this.logger.info('Limited stream manager initialized', {
47
52
  remotePeer: this.limitedConfig.p2pConnection.remotePeer.toString(),
@@ -60,7 +65,7 @@ export class oLimitedStreamManager extends oNodeStreamManager {
60
65
  // Create reader stream using parent's createStream method
61
66
  this.readerStream = await this.createStream(this.limitedConfig.protocol, this.limitedConfig.remoteAddress, {});
62
67
  // Send self-identification message
63
- await this.sendStreamInitMessage(this.readerStream.p2pStream);
68
+ await this.sendStreamInitMessage(this.readerStream.p2pStream, 'reader');
64
69
  // Start background reader loop
65
70
  await this.startReaderLoop();
66
71
  this.eventEmitter.emit(StreamManagerEvent.ReaderStarted, {
@@ -76,21 +81,80 @@ export class oLimitedStreamManager extends oNodeStreamManager {
76
81
  }
77
82
  }
78
83
  /**
79
- * Sends stream initialization message to identify this as a reader stream
84
+ * Creates and initializes the dedicated writer stream
80
85
  */
81
- async sendStreamInitMessage(stream) {
86
+ async createWriterStream() {
87
+ try {
88
+ // Create writer stream using parent's createStream method
89
+ this.writerStream = await this.createStream(this.limitedConfig.protocol, this.limitedConfig.remoteAddress, {});
90
+ // Send self-identification message
91
+ await this.sendStreamInitMessage(this.writerStream.p2pStream, 'writer');
92
+ this.eventEmitter.emit(StreamManagerEvent.WriterStarted, {
93
+ streamId: this.writerStream.p2pStream.id,
94
+ });
95
+ this.logger.info('Writer stream created', {
96
+ streamId: this.writerStream.p2pStream.id,
97
+ });
98
+ }
99
+ catch (error) {
100
+ this.logger.error('Failed to create writer stream:', error);
101
+ throw error;
102
+ }
103
+ }
104
+ /**
105
+ * Sends stream initialization message to identify the stream role
106
+ * Waits for acknowledgment from receiver before proceeding
107
+ */
108
+ async sendStreamInitMessage(stream, role) {
82
109
  const initMessage = {
83
110
  type: 'stream-init',
84
- role: 'reader',
111
+ role,
85
112
  timestamp: Date.now(),
86
113
  connectionId: this.limitedConfig.p2pConnection.id,
87
114
  };
88
115
  const messageBytes = new TextEncoder().encode(JSON.stringify(initMessage));
89
116
  await this.sendLengthPrefixed(stream, messageBytes, {});
90
- this.logger.debug('Sent stream-init message', {
117
+ this.logger.debug('Sent stream-init message, waiting for ack', {
91
118
  streamId: stream.id,
92
- role: 'reader',
119
+ role,
93
120
  });
121
+ // Wait for acknowledgment with timeout
122
+ const ackTimeout = 5000; // 5 seconds
123
+ const abortController = new AbortController();
124
+ const timeoutId = setTimeout(() => {
125
+ abortController.abort();
126
+ }, ackTimeout);
127
+ try {
128
+ const lp = lpStream(stream);
129
+ // Read acknowledgment message
130
+ const ackBytes = await lp.read({ signal: abortController.signal });
131
+ const ackDecoded = new TextDecoder().decode(ackBytes.subarray());
132
+ const ackMessage = this.extractAndParseJSON(ackDecoded);
133
+ // Validate acknowledgment
134
+ if (!isStreamInitAckMessage(ackMessage)) {
135
+ throw new oError(oErrorCodes.INTERNAL_ERROR, `Invalid stream-init-ack message received: ${JSON.stringify(ackMessage)}`);
136
+ }
137
+ if (ackMessage.status === 'error') {
138
+ throw new oError(oErrorCodes.INTERNAL_ERROR, `Stream initialization failed: ${ackMessage.error || 'Unknown error'}`);
139
+ }
140
+ if (ackMessage.role !== role) {
141
+ throw new oError(oErrorCodes.INTERNAL_ERROR, `Stream role mismatch: expected ${role}, got ${ackMessage.role}`);
142
+ }
143
+ this.logger.info('Received stream-init-ack', {
144
+ streamId: stream.id,
145
+ role,
146
+ ackStreamId: ackMessage.streamId,
147
+ });
148
+ }
149
+ catch (error) {
150
+ if (error.name === 'AbortError') {
151
+ throw new oError(oErrorCodes.TIMEOUT, `Stream initialization acknowledgment timeout after ${ackTimeout}ms`);
152
+ }
153
+ throw error;
154
+ }
155
+ finally {
156
+ clearTimeout(timeoutId);
157
+ }
94
158
  }
95
159
  /**
96
160
  * Starts the background reader loop
@@ -163,8 +227,8 @@ export class oLimitedStreamManager extends oNodeStreamManager {
163
227
  }
164
228
  }
165
229
  /**
166
- * Override getOrCreateStream to reuse outbound streams
167
- * Does not create new stream for every request
230
+ * Override getOrCreateStream to use persistent writer stream for outbound requests
231
+ * This eliminates ephemeral stream creation for limited connections
168
232
  * Auto-initializes on first use
169
233
  */
170
234
  async getOrCreateStream(protocol, remoteAddress, config = {}) {
@@ -172,28 +236,49 @@ export class oLimitedStreamManager extends oNodeStreamManager {
172
236
  if (!this.isInitialized) {
173
237
  await this.initialize();
174
238
  }
175
- // Check if we have an existing outbound stream that's still valid
176
- if (this.outboundStream &&
177
- this.outboundStream.p2pStream.status === 'open' &&
178
- this.outboundStream.p2pStream.writeStatus === 'writable') {
179
- this.logger.debug('Reusing existing outbound stream', {
180
- streamId: this.outboundStream.p2pStream.id,
239
+ // Use persistent writer stream for all outbound requests
240
+ if (this.writerStream && this.writerStream.p2pStream.status === 'open') {
241
+ this.logger.debug('Reusing writer stream for outbound request', {
242
+ streamId: this.writerStream.p2pStream.id,
181
243
  });
182
- return this.outboundStream;
244
+ return this.writerStream;
183
245
  }
184
- // Create new outbound stream if none exists or current is invalid
185
- this.logger.debug('Creating new outbound stream (no valid stream to reuse)');
186
- this.outboundStream = await this.createStream(protocol, remoteAddress, config);
187
- return this.outboundStream;
246
+ // Writer stream should always be available after initialization
247
+ throw new oError(oErrorCodes.INTERNAL_ERROR, 'Writer stream not available or not open');
188
248
  }
189
249
  /**
190
- * Override releaseStream to NOT close the stream
191
- * Keep streams open for reuse
250
+ * Override releaseStream to protect persistent streams from being closed
251
+ * Persistent streams (reader/writer) should never be released via this method
252
+ * Only closes streams that are not the dedicated reader or writer streams
192
253
  */
193
254
  async releaseStream(streamId) {
194
- // Don't close the stream - just log that we're keeping it open
195
- this.logger.debug('Keeping stream open for reuse', { streamId });
196
- // Do NOT call super.releaseStream() as that would close the stream
255
+ // Check if this is a persistent stream - don't close those
256
+ if ((this.readerStream && this.readerStream.p2pStream.id === streamId) ||
257
+ (this.writerStream && this.writerStream.p2pStream.id === streamId)) {
258
+ this.logger.debug('Skipping release of persistent stream', { streamId });
259
+ return;
260
+ }
261
+ // Close any other streams (should not occur in normal operation)
262
+ this.logger.debug('Releasing non-persistent stream', { streamId });
263
+ await super.releaseStream(streamId);
264
+ }
265
+ /**
266
+ * Override getStreamById to check persistent streams first
267
+ * Provides access to reader and writer streams by ID
268
+ *
269
+ * @param streamId - The ID of the stream to retrieve
270
+ * @returns The libp2p Stream or undefined if not found
271
+ */
272
+ getStreamById(streamId) {
273
+ // Check our persistent streams first
274
+ if (this.writerStream?.p2pStream.id === streamId) {
275
+ return this.writerStream.p2pStream;
276
+ }
277
+ if (this.readerStream?.p2pStream.id === streamId) {
278
+ return this.readerStream.p2pStream;
279
+ }
280
+ // Fall back to parent implementation (checks tracked ephemeral streams)
281
+ return super.getStreamById(streamId);
197
282
  }
198
283
  /**
199
284
  * Close the stream manager and cleanup all resources
@@ -219,15 +304,15 @@ export class oLimitedStreamManager extends oNodeStreamManager {
219
304
  }
220
305
  this.readerStream = undefined;
221
306
  }
222
- // Close outbound stream
223
- if (this.outboundStream) {
307
+ // Close writer stream
308
+ if (this.writerStream) {
224
309
  try {
225
- await this.outboundStream.p2pStream.close();
310
+ await this.writerStream.p2pStream.close();
226
311
  }
227
312
  catch (error) {
228
- this.logger.warn('Error closing outbound stream:', error);
313
+ this.logger.warn('Error closing writer stream:', error);
229
314
  }
230
- this.outboundStream = undefined;
315
+ this.writerStream = undefined;
231
316
  }
232
317
  this.eventEmitter.emit(StreamManagerEvent.ManagerClosed, undefined);
233
318
  }
@@ -1 +1 @@
1
- {"version":3,"file":"o-limited.tool.d.ts","sourceRoot":"","sources":["../../src/o-limited.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,SAAS,EACV,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,qBAAa,YAAa,SAAQ,SAAS;gBAC7B,MAAM,EAAE,WAAW;IAUzB,OAAO,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC;IAkBhE,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;CAQ7C"}
1
+ {"version":3,"file":"o-limited.tool.d.ts","sourceRoot":"","sources":["../../src/o-limited.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,SAAS,EACV,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,qBAAa,YAAa,SAAQ,SAAS;gBAC7B,MAAM,EAAE,WAAW;IAWzB,OAAO,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC;IAkBhE,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;CAQ7C"}
@@ -8,6 +8,7 @@ export class oLimitedTool extends oNodeTool {
8
8
  ...(config.network || {}),
9
9
  listeners: config.network?.listeners || [], // default to no listeners
10
10
  },
11
+ runOnLimitedConnection: true,
11
12
  });
12
13
  }
13
14
  async connect(config) {
@@ -1,50 +1,123 @@
1
1
  import { expect } from 'chai';
2
2
  import { TestEnvironment, createConnectionSpy, } from '@olane/o-node/test/helpers';
3
- import { oNodeAddress } from '@olane/o-node';
3
+ import { oNodeAddress, oNodeTransport } from '@olane/o-node';
4
4
  import { LimitedTestTool, ReceiverTestTool } from './helpers/index.js';
5
+ import { RelayTestTool } from './helpers/relay-test-tool.js';
5
6
  describe('Bidirectional Communication', () => {
6
7
  const env = new TestEnvironment();
7
8
  let caller;
8
9
  let receiver;
10
+ let relay;
9
11
  afterEach(async () => {
10
12
  await env.cleanup();
11
13
  });
12
- describe('Caller → Receiver', () => {
13
- it('should send request and receive response', async () => {
14
+ // describe('Caller → Receiver', () => {
15
+ // it('should send request and receive response', async () => {
16
+ // caller = await env.createNode<any>(LimitedTestTool, {
17
+ // address: new oNodeAddress('o://caller'),
18
+ // });
19
+ // receiver = await env.createNode<any>(ReceiverTestTool, {
20
+ // address: new oNodeAddress('o://receiver'),
21
+ // });
22
+ // const response = await caller.use(
23
+ // new oNodeAddress(
24
+ // receiver.address.toString(),
25
+ // receiver.address.libp2pTransports,
26
+ // ),
27
+ // {
28
+ // method: 'echo',
29
+ // params: { message: 'hello-receiver' },
30
+ // },
31
+ // );
32
+ // expect(response.result.success).to.be.true;
33
+ // expect(response.result.data.message).to.equal('hello-receiver');
34
+ // expect(receiver.receivedRequests).to.have.lengthOf(1);
35
+ // });
36
+ // it('should handle multiple sequential requests', async () => {
37
+ // caller = await env.createNode<any>(LimitedTestTool, {
38
+ // address: new oNodeAddress('o://caller'),
39
+ // });
40
+ // receiver = await env.createNode<any>(ReceiverTestTool, {
41
+ // address: new oNodeAddress('o://receiver'),
42
+ // });
43
+ // const receiverAddr = new oNodeAddress(
44
+ // receiver.address.toString(),
45
+ // receiver.address.libp2pTransports,
46
+ // );
47
+ // for (let i = 0; i < 5; i++) {
48
+ // const response = await caller.use(receiverAddr, {
49
+ // method: 'echo',
50
+ // params: { message: `request-${i}` },
51
+ // });
52
+ // expect(response.result.success).to.be.true;
53
+ // expect(response.result.data.message).to.equal(`request-${i}`);
54
+ // }
55
+ // expect(receiver.receivedRequests).to.have.lengthOf(5);
56
+ // });
57
+ // });
58
+ describe('Receiver → Caller', () => {
59
+ it('should fail when receiver tries to dial limited caller directly', async () => {
14
60
  caller = await env.createNode(LimitedTestTool, {
15
61
  address: new oNodeAddress('o://caller'),
62
+ runOnLimitedConnection: true,
16
63
  });
17
64
  receiver = await env.createNode(ReceiverTestTool, {
18
65
  address: new oNodeAddress('o://receiver'),
66
+ runOnLimitedConnection: true,
67
+ network: {
68
+ listeners: ['/p2p-circuit'],
69
+ },
19
70
  });
20
- const response = await caller.use(new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports), {
21
- method: 'echo',
22
- params: { message: 'hello-receiver' },
71
+ relay = await env.createNode(RelayTestTool, {
72
+ address: new oNodeAddress('o://relay'),
73
+ runOnLimitedConnection: true,
23
74
  });
24
- expect(response.result.success).to.be.true;
25
- expect(response.result.data.message).to.equal('hello-receiver');
26
- expect(receiver.receivedRequests).to.have.lengthOf(1);
27
- });
28
- it('should handle multiple sequential requests', async () => {
29
- caller = await env.createNode(LimitedTestTool, {
30
- address: new oNodeAddress('o://caller'),
75
+ await receiver.use(relay.address, {
76
+ method: 'ping',
77
+ params: {},
31
78
  });
32
- receiver = await env.createNode(ReceiverTestTool, {
33
- address: new oNodeAddress('o://receiver'),
79
+ const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.p2pNode
80
+ .getMultiaddrs()
81
+ .filter((t) => t.toString().includes('p2p-circuit'))
82
+ .map((ma) => new oNodeTransport(ma.toString())));
83
+ console.log('Receiver connected to relay', receiverAddr.libp2pTransports);
84
+ const callerAddr = new oNodeAddress(caller.address.toString(), caller.address.libp2pTransports);
85
+ // First: Caller establishes connection to receiver (this should succeed)
86
+ const initialResponse = await caller.use(receiverAddr, {
87
+ method: 'echo',
88
+ params: { message: 'initial' },
34
89
  });
35
- const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
36
- for (let i = 0; i < 5; i++) {
37
- const response = await caller.use(receiverAddr, {
38
- method: 'echo',
39
- params: { message: `request-${i}` },
90
+ console.log('Initial response from receiver:', initialResponse);
91
+ expect(initialResponse.result.success).to.be.true;
92
+ // Now: Receiver attempts to create a stream directly to caller - should fail
93
+ // Get the underlying libp2p connection from receiver side
94
+ const receiverConn = receiver.getFirstConnection();
95
+ const receiverP2pConnection = receiverConn.p2pConnection;
96
+ console.log('receiverP2pConnection:', receiverP2pConnection.streams[0].protocol);
97
+ expect(receiverP2pConnection.direction).to.equal('inbound');
98
+ expect(receiverP2pConnection).to.exist;
99
+ // Get caller's protocol string (e.g., "/o/caller")
100
+ const callerProtocol = caller.address.protocol;
101
+ // Try to manually spawn a new stream to the limited caller
102
+ // This should fail because LimitedTestTool has no protocol handlers registered
103
+ let errorThrown = false;
104
+ try {
105
+ await receiverP2pConnection.newStream('/o/caller', {
106
+ runOnLimitedConnection: true,
107
+ maxOutboundStreams: 1000,
40
108
  });
41
- expect(response.result.success).to.be.true;
42
- expect(response.result.data.message).to.equal(`request-${i}`);
109
+ console.log('Created new stream!');
110
+ // Should not reach here
111
+ expect.fail('Should have thrown UnsupportedProtocolError');
112
+ }
113
+ catch (error) {
114
+ console.log('Error thrown as expected:', error);
115
+ errorThrown = true;
116
+ // libp2p throws UnsupportedProtocolError when peer has no handler for protocol
117
+ expect(error.name).to.equal('UnsupportedProtocolError');
43
118
  }
44
- expect(receiver.receivedRequests).to.have.lengthOf(5);
119
+ expect(errorThrown).to.be.true;
45
120
  });
46
- });
47
- describe('Receiver → Caller', () => {
48
121
  it('should send request via reader stream and receive response', async () => {
49
122
  caller = await env.createNode(LimitedTestTool, {
50
123
  address: new oNodeAddress('o://caller'),
@@ -63,13 +136,14 @@ describe('Bidirectional Communication', () => {
63
136
  const callerConn = caller.getFirstConnection();
64
137
  const receiverConn = receiver.getFirstConnection();
65
138
  await new Promise((resolve) => setTimeout(resolve, 1000));
139
+ // the following code will not work since the stream listeners are setup after the connections are made
66
140
  // if (callerConn) {
67
141
  // caller.setupEventListeners(callerConn);
68
142
  // }
69
143
  // if (receiverConn) {
70
144
  // receiver.setupStreamListeners(receiverConn);
71
145
  // }
72
- // // Wait for receiver to identify reader stream
146
+ // Wait for receiver to identify reader stream
73
147
  // await env.waitFor(() => receiver.identifiedStreams.length > 0, 5000, 100);
74
148
  // expect(receiver.identifiedStreams).to.have.lengthOf(1);
75
149
  // expect(receiver.identifiedStreams[0].role).to.equal('reader');
@@ -1,5 +1,6 @@
1
1
  import type { oRequest } from '@olane/o-core';
2
2
  import { oLimitedTool } from '../../src/o-limited.tool.js';
3
+ import { Libp2pConfig } from '@olane/o-config';
3
4
  /**
4
5
  * Test tool that uses limited connections
5
6
  * Tracks events and call counts for verification
@@ -12,6 +13,7 @@ export declare class LimitedTestTool extends oLimitedTool {
12
13
  }>;
13
14
  private eventListenersSetup;
14
15
  initialize(): Promise<void>;
16
+ configure(): Promise<Libp2pConfig>;
15
17
  /**
16
18
  * Set up event listeners for stream manager events
17
19
  * Call this after first connection is established
@@ -1 +1 @@
1
- {"version":3,"file":"limited-test-tool.d.ts","sourceRoot":"","sources":["../../../test/helpers/limited-test-tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAI9C,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,YAAY;IACxC,SAAS,SAAK;IACd,mBAAmB,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAE,CAAC,CAAM;IACpE,OAAO,CAAC,mBAAmB,CAAS;IAE9B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IASjC;;;OAGG;IACH,mBAAmB,CAAC,UAAU,EAAE,GAAG,GAAG,IAAI;IAiD1C;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;IAUjD;;;OAGG;IACG,kBAAkB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;IAczD;;OAEG;IACH,eAAe,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAE,CAAC;IAIrD;;OAEG;IACH,kBAAkB,IAAI,GAAG;CAO1B"}
1
+ {"version":3,"file":"limited-test-tool.d.ts","sourceRoot":"","sources":["../../../test/helpers/limited-test-tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAI9C,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAEL,YAAY,EAKb,MAAM,iBAAiB,CAAC;AAGzB;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,YAAY;IACxC,SAAS,SAAK;IACd,mBAAmB,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAE,CAAC,CAAM;IACpE,OAAO,CAAC,mBAAmB,CAAS;IAE9B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAS3B,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC;IA+BxC;;;OAGG;IACH,mBAAmB,CAAC,UAAU,EAAE,GAAG,GAAG,IAAI;IAiD1C;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;IAUjD;;;OAGG;IACG,kBAAkB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;IAczD;;OAEG;IACH,eAAe,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAE,CAAC;IAIrD;;OAEG;IACH,kBAAkB,IAAI,GAAG;CAO1B"}
@@ -1,6 +1,8 @@
1
1
  import { oLimitedConnectionManager } from '../../src/connection/o-limited-connection-manager.js';
2
2
  import { StreamManagerEvent } from '@olane/o-node';
3
3
  import { oLimitedTool } from '../../src/o-limited.tool.js';
4
+ import { identify, memory, ping, webSockets, webTransport, } from '@olane/o-config';
5
+ import { circuitRelayTransport } from '@libp2p/circuit-relay-v2';
4
6
  /**
5
7
  * Test tool that uses limited connections
6
8
  * Tracks events and call counts for verification
@@ -20,6 +22,35 @@ export class LimitedTestTool extends oLimitedTool {
20
22
  runOnLimitedConnection: true,
21
23
  });
22
24
  }
25
+ async configure() {
26
+ const config = await super.configure();
27
+ config.transports = [
28
+ memory(),
29
+ webSockets(),
30
+ webTransport(),
31
+ circuitRelayTransport({
32
+ reservationCompletionTimeout: 30000,
33
+ }),
34
+ ];
35
+ config.services = {
36
+ identify: identify(),
37
+ ping: ping({
38
+ maxOutboundStreams: 1000,
39
+ maxInboundStreams: 1000,
40
+ }),
41
+ };
42
+ config.connectionGater = {
43
+ ...config.connectionGater,
44
+ denyDialPeer: (peerId) => {
45
+ // as a leader, we can dial anything
46
+ return false;
47
+ },
48
+ denyDialMultiaddr: (multiaddr) => {
49
+ return false;
50
+ },
51
+ };
52
+ return config;
53
+ }
23
54
  /**
24
55
  * Set up event listeners for stream manager events
25
56
  * Call this after first connection is established
@@ -93,6 +124,6 @@ export class LimitedTestTool extends oLimitedTool {
93
124
  */
94
125
  getFirstConnection() {
95
126
  const firstEntry = Array.from(this.connectionManager?.cachedConnections?.values() || []);
96
- return firstEntry?.[0];
127
+ return (firstEntry?.[0])[0];
97
128
  }
98
129
  }
@@ -1,5 +1,6 @@
1
1
  import { oNodeTool } from '@olane/o-node';
2
2
  import type { oRequest } from '@olane/o-core';
3
+ import { Libp2pConfig } from '@olane/o-config';
3
4
  /**
4
5
  * Standard receiver tool (uses base connection manager)
5
6
  * Tracks received requests and identified streams
@@ -12,6 +13,7 @@ export declare class ReceiverTestTool extends oNodeTool {
12
13
  }>;
13
14
  private eventListenersSetup;
14
15
  constructor(config: any);
16
+ configure(): Promise<Libp2pConfig>;
15
17
  /**
16
18
  * Set up event listeners for stream identification
17
19
  * Call this after tool is started
@@ -1 +1 @@
1
- {"version":3,"file":"receiver-test-tool.d.ts","sourceRoot":"","sources":["../../../test/helpers/receiver-test-tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAI9C;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,SAAS;IACtC,gBAAgB,EAAE,QAAQ,EAAE,CAAM;IAClC,iBAAiB,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAM;IACzE,OAAO,CAAC,mBAAmB,CAAS;gBAExB,MAAM,EAAE,GAAG;IAIvB;;;OAGG;IACH,oBAAoB,CAAC,UAAU,EAAE,GAAG,GAAG,IAAI;IAmB3C;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;IAUjD;;;OAGG;IACG,iBAAiB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;IAiBxD;;OAEG;IACH,kBAAkB,IAAI,GAAG;CAM1B"}
1
+ {"version":3,"file":"receiver-test-tool.d.ts","sourceRoot":"","sources":["../../../test/helpers/receiver-test-tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,SAAS,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG9C,OAAO,EAEL,YAAY,EAKb,MAAM,iBAAiB,CAAC;AAGzB;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,SAAS;IACtC,gBAAgB,EAAE,QAAQ,EAAE,CAAM;IAClC,iBAAiB,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAM;IACzE,OAAO,CAAC,mBAAmB,CAAS;gBAExB,MAAM,EAAE,GAAG;IAOjB,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC;IAyBxC;;;OAGG;IACH,oBAAoB,CAAC,UAAU,EAAE,GAAG,GAAG,IAAI;IAmB3C;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;IAWjD;;;OAGG;IACG,iBAAiB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;IAiBxD;;OAEG;IACH,kBAAkB,IAAI,GAAG;CAY1B"}
@@ -1,16 +1,45 @@
1
1
  import { oNodeTool } from '@olane/o-node';
2
2
  import { StreamManagerEvent } from '@olane/o-node';
3
+ import { identify, memory, ping, tcp, webSockets, } from '@olane/o-config';
4
+ import { circuitRelayTransport } from '@libp2p/circuit-relay-v2';
3
5
  /**
4
6
  * Standard receiver tool (uses base connection manager)
5
7
  * Tracks received requests and identified streams
6
8
  */
7
9
  export class ReceiverTestTool extends oNodeTool {
8
10
  constructor(config) {
9
- super(config);
11
+ super({
12
+ ...config,
13
+ runOnLimitedConnection: true,
14
+ });
10
15
  this.receivedRequests = [];
11
16
  this.identifiedStreams = [];
12
17
  this.eventListenersSetup = false;
13
18
  }
19
+ async configure() {
20
+ const config = await super.configure();
21
+ config.transports = [
22
+ webSockets(),
23
+ circuitRelayTransport({
24
+ reservationCompletionTimeout: 30000,
25
+ }),
26
+ tcp(),
27
+ memory(),
28
+ ];
29
+ config.services = {
30
+ identify: identify({
31
+ maxOutboundStreams: 1000,
32
+ maxInboundStreams: 1000,
33
+ runOnLimitedConnection: true,
34
+ }),
35
+ ping: ping({
36
+ maxOutboundStreams: 1000,
37
+ maxInboundStreams: 1000,
38
+ runOnLimitedConnection: true,
39
+ }),
40
+ };
41
+ return config;
42
+ }
14
43
  /**
15
44
  * Set up event listeners for stream identification
16
45
  * Call this after tool is started
@@ -32,6 +61,7 @@ export class ReceiverTestTool extends oNodeTool {
32
61
  * Simple echo method
33
62
  */
34
63
  async _tool_echo(request) {
64
+ this.logger.info('Echo request received');
35
65
  this.receivedRequests.push(request);
36
66
  return {
37
67
  message: request.params.message,
@@ -62,6 +92,10 @@ export class ReceiverTestTool extends oNodeTool {
62
92
  */
63
93
  getFirstConnection() {
64
94
  const firstEntry = Array.from(this.connectionManager?.cachedConnections?.values() || []);
65
- return firstEntry?.[0];
95
+ // filter out relay addresses if there are some
96
+ const nonRelayConnections = firstEntry?.filter((entry) => {
97
+ return entry.some((conn) => conn.nextHopAddress.value !== 'o://relay');
98
+ });
99
+ return (nonRelayConnections?.[0])[0];
66
100
  }
67
101
  }
@@ -0,0 +1,27 @@
1
+ import { oNodeTool } from '@olane/o-node';
2
+ import type { oRequest } from '@olane/o-core';
3
+ import { Libp2pConfig } from '@olane/o-config';
4
+ /**
5
+ * Standard receiver tool (uses base connection manager)
6
+ * Tracks received requests and identified streams
7
+ */
8
+ export declare class RelayTestTool extends oNodeTool {
9
+ receivedRequests: oRequest[];
10
+ identifiedStreams: Array<{
11
+ streamId: string;
12
+ role: string;
13
+ }>;
14
+ private eventListenersSetup;
15
+ constructor(config: any);
16
+ configure(): Promise<Libp2pConfig>;
17
+ /**
18
+ * Set up event listeners for stream identification
19
+ * Call this after tool is started
20
+ */
21
+ setupStreamListeners(connection: any): void;
22
+ /**
23
+ * Get the first connection (for accessing stream manager)
24
+ */
25
+ getFirstConnection(): any;
26
+ }
27
+ //# sourceMappingURL=relay-test-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay-test-tool.d.ts","sourceRoot":"","sources":["../../../test/helpers/relay-test-tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EAAY,YAAY,EAAQ,MAAM,iBAAiB,CAAC;AAG/D;;;GAGG;AACH,qBAAa,aAAc,SAAQ,SAAS;IACnC,gBAAgB,EAAE,QAAQ,EAAE,CAAM;IAClC,iBAAiB,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAM;IACzE,OAAO,CAAC,mBAAmB,CAAS;gBAExB,MAAM,EAAE,GAAG;IAOjB,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC;IAmCxC;;;OAGG;IACH,oBAAoB,CAAC,UAAU,EAAE,GAAG,GAAG,IAAI;IAmB3C;;OAEG;IACH,kBAAkB,IAAI,GAAG;CAM1B"}
@@ -0,0 +1,75 @@
1
+ import { oNodeTool } from '@olane/o-node';
2
+ import { StreamManagerEvent } from '@olane/o-node';
3
+ import { identify, ping } from '@olane/o-config';
4
+ import { circuitRelayServer } from '@libp2p/circuit-relay-v2';
5
+ /**
6
+ * Standard receiver tool (uses base connection manager)
7
+ * Tracks received requests and identified streams
8
+ */
9
+ export class RelayTestTool extends oNodeTool {
10
+ constructor(config) {
11
+ super({
12
+ ...config,
13
+ runOnLimitedConnection: true,
14
+ });
15
+ this.receivedRequests = [];
16
+ this.identifiedStreams = [];
17
+ this.eventListenersSetup = false;
18
+ }
19
+ async configure() {
20
+ const config = await super.configure();
21
+ config.services = {
22
+ identify: identify({
23
+ maxOutboundStreams: Infinity,
24
+ maxInboundStreams: Infinity,
25
+ runOnLimitedConnection: true,
26
+ }),
27
+ ping: ping({
28
+ maxOutboundStreams: Infinity,
29
+ maxInboundStreams: Infinity,
30
+ runOnLimitedConnection: true,
31
+ }),
32
+ relay: circuitRelayServer({
33
+ reservations: {
34
+ maxReservations: Infinity, // need to figure this out
35
+ reservationClearInterval: 60000 * 5, // 15 minutes
36
+ defaultDurationLimit: 60000 * 15, // 15 minutes
37
+ defaultDataLimit: BigInt(1024 * 1024 * 10000), // 10GB
38
+ },
39
+ }),
40
+ };
41
+ config.listeners = config.listeners?.filter((l) => l.indexOf('/memory/') === -1);
42
+ config.connectionManager = {
43
+ ...(config?.connectionManager || {}),
44
+ maxConnections: 500,
45
+ maxParallelDials: 100,
46
+ maxDialQueueLength: 100,
47
+ maxPeerAddrsToDial: 25,
48
+ };
49
+ return config;
50
+ }
51
+ /**
52
+ * Set up event listeners for stream identification
53
+ * Call this after tool is started
54
+ */
55
+ setupStreamListeners(connection) {
56
+ if (this.eventListenersSetup || !connection?.streamManager) {
57
+ return;
58
+ }
59
+ this.eventListenersSetup = true;
60
+ // Listen for stream identification events
61
+ connection.streamManager.on(StreamManagerEvent.StreamIdentified, (data) => {
62
+ this.identifiedStreams.push({
63
+ streamId: data.streamId,
64
+ role: data.role,
65
+ });
66
+ });
67
+ }
68
+ /**
69
+ * Get the first connection (for accessing stream manager)
70
+ */
71
+ getFirstConnection() {
72
+ const firstEntry = Array.from(this.connectionManager?.cachedConnections?.values() || []);
73
+ return (firstEntry?.[0])[0];
74
+ }
75
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olane/o-client-limited",
3
- "version": "0.7.52",
3
+ "version": "0.7.54",
4
4
  "type": "module",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -35,6 +35,7 @@
35
35
  "devDependencies": {
36
36
  "@eslint/eslintrc": "^3.3.1",
37
37
  "@eslint/js": "^9.29.0",
38
+ "@libp2p/circuit-relay-v2": "^4.0.5",
38
39
  "@tsconfig/node20": "^20.1.6",
39
40
  "@types/jest": "^30.0.0",
40
41
  "@typescript-eslint/eslint-plugin": "^8.34.1",
@@ -53,13 +54,13 @@
53
54
  "typescript": "5.4.5"
54
55
  },
55
56
  "dependencies": {
56
- "@olane/o-config": "0.7.52",
57
- "@olane/o-core": "0.7.52",
58
- "@olane/o-node": "0.7.52",
59
- "@olane/o-protocol": "0.7.52",
60
- "@olane/o-tool": "0.7.52",
57
+ "@olane/o-config": "0.7.54",
58
+ "@olane/o-core": "0.7.54",
59
+ "@olane/o-node": "0.7.54",
60
+ "@olane/o-protocol": "0.7.54",
61
+ "@olane/o-tool": "0.7.54",
61
62
  "debug": "^4.4.1",
62
63
  "dotenv": "^16.5.0"
63
64
  },
64
- "gitHead": "706ee0d5b546e96b6c05270b39035ee7e08dadbd"
65
+ "gitHead": "8e527a1481c6aeaf0840ea30198500a9baef1e98"
65
66
  }