@olane/o-client-limited 0.7.55 → 0.7.57

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,4 +1,2 @@
1
- export * from './o-limited-connection.js';
2
1
  export * from './o-limited-connection-manager.js';
3
- export * from './o-limited.stream-manager.js';
4
2
  //# 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;AAClD,cAAc,+BAA+B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/connection/index.ts"],"names":[],"mappings":"AAAA,cAAc,mCAAmC,CAAC"}
@@ -1,3 +1 @@
1
- export * from './o-limited-connection.js';
2
1
  export * from './o-limited-connection-manager.js';
3
- export * from './o-limited.stream-manager.js';
@@ -1 +1 @@
1
- {"version":3,"file":"o-limited-connection-manager.d.ts","sourceRoot":"","sources":["../../../src/connection/o-limited-connection-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAI9E,qBAAa,yBAA0B,SAAQ,sBAAsB;IACnE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;CAClC"}
1
+ {"version":3,"file":"o-limited-connection-manager.d.ts","sourceRoot":"","sources":["../../../src/connection/o-limited-connection-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAE9E,qBAAa,yBAA0B,SAAQ,sBAAsB;IACnE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;CAClC"}
@@ -1,8 +1,6 @@
1
- import { oNodeConnection, oNodeConnectionConfig, oNodeTool } from '@olane/o-node';
1
+ import { oNodeTool } from '@olane/o-node';
2
2
  import { oNodeConfig } from '@olane/o-node';
3
3
  export declare class oLimitedTool extends oNodeTool {
4
4
  constructor(config: oNodeConfig);
5
- connect(config: oNodeConnectionConfig): Promise<oNodeConnection>;
6
- initConnectionManager(): Promise<void>;
7
5
  }
8
6
  //# sourceMappingURL=o-limited.tool.d.ts.map
@@ -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;IAWzB,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,EAGL,SAAS,EACV,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,qBAAa,YAAa,SAAQ,SAAS;gBAC7B,MAAM,EAAE,WAAW;CAqChC"}
@@ -1,5 +1,4 @@
1
1
  import { oNodeTool, } from '@olane/o-node';
2
- import { oLimitedConnectionManager } from './connection/o-limited-connection-manager.js';
3
2
  export class oLimitedTool extends oNodeTool {
4
3
  constructor(config) {
5
4
  super({
@@ -11,26 +10,4 @@ export class oLimitedTool extends oNodeTool {
11
10
  runOnLimitedConnection: true,
12
11
  });
13
12
  }
14
- async connect(config) {
15
- this.handleProtocol(config.nextHopAddress).catch((error) => {
16
- this.logger.error('Error handling protocol:', error);
17
- });
18
- // Inject requestHandler to enable bidirectional stream processing
19
- // This allows incoming router requests to be processed through the tool's execute method
20
- // The StreamPoolManager (in oLimitedConnection) will use this handler for the dedicated reader
21
- const configWithHandler = {
22
- ...config,
23
- requestHandler: this.execute.bind(this),
24
- };
25
- const connection = await super.connect(configWithHandler);
26
- return connection;
27
- }
28
- async initConnectionManager() {
29
- this.connectionManager = new oLimitedConnectionManager({
30
- p2pNode: this.p2pNode,
31
- defaultReadTimeoutMs: this.config.connectionTimeouts?.readTimeoutMs,
32
- defaultDrainTimeoutMs: this.config.connectionTimeouts?.drainTimeoutMs,
33
- runOnLimitedConnection: true,
34
- });
35
- }
36
13
  }
@@ -55,6 +55,7 @@ describe('Bidirectional Communication', () => {
55
55
  // expect(receiver.receivedRequests).to.have.lengthOf(5);
56
56
  // });
57
57
  // });
58
+ // TODO: verify that the read only caller stream can receive requests from receiver
58
59
  describe('Receiver → Caller', () => {
59
60
  it('should fail when receiver tries to dial limited caller directly', async () => {
60
61
  caller = await env.createNode(LimitedTestTool, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olane/o-client-limited",
3
- "version": "0.7.55",
3
+ "version": "0.7.57",
4
4
  "type": "module",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -54,13 +54,13 @@
54
54
  "typescript": "5.4.5"
55
55
  },
56
56
  "dependencies": {
57
- "@olane/o-config": "0.7.55",
58
- "@olane/o-core": "0.7.55",
59
- "@olane/o-node": "0.7.55",
60
- "@olane/o-protocol": "0.7.55",
61
- "@olane/o-tool": "0.7.55",
57
+ "@olane/o-config": "0.7.57",
58
+ "@olane/o-core": "0.7.57",
59
+ "@olane/o-node": "0.7.57",
60
+ "@olane/o-protocol": "0.7.57",
61
+ "@olane/o-tool": "0.7.57",
62
62
  "debug": "^4.4.1",
63
63
  "dotenv": "^16.5.0"
64
64
  },
65
- "gitHead": "f7d02a1ecfb0d28b7b1009a7c3eb6d0d4863adc2"
65
+ "gitHead": "2528b14a811472157ca94927ef6f3b39ccd31886"
66
66
  }
@@ -1,43 +0,0 @@
1
- import { oNodeConnection } from '@olane/o-node';
2
- import type { oNodeConnectionConfig } from '@olane/o-node';
3
- import { oLimitedStreamManager } from './o-limited.stream-manager.js';
4
- import type { oRequest, oResponse } from '@olane/o-core';
5
- /**
6
- * oLimitedConnection extends oNodeConnection with dual persistent streams for limited connections.
7
- *
8
- * This is optimized for limited connections where the caller cannot be dialed
9
- * (browser clients, mobile clients, resource-constrained environments).
10
- *
11
- * Stream Architecture:
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
17
- *
18
- * Features:
19
- * - Automatic reader/writer stream creation and maintenance
20
- * - Single retry on reader failure
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)
24
- *
25
- * Default Behavior:
26
- * - Uses 'reuse' stream policy by default
27
- * - Automatically initializes persistent streams on connection start
28
- */
29
- export declare class oLimitedConnection extends oNodeConnection {
30
- streamManager: oLimitedStreamManager;
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>;
38
- /**
39
- * Override close to cleanup stream manager properly
40
- */
41
- close(): Promise<void>;
42
- }
43
- //# sourceMappingURL=o-limited-connection.d.ts.map
@@ -1 +0,0 @@
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"}
@@ -1,95 +0,0 @@
1
- import { oNodeConnection } from '@olane/o-node';
2
- import { oLimitedStreamManager, } from './o-limited.stream-manager.js';
3
- import { StreamManagerEvent } from '@olane/o-node';
4
- /**
5
- * oLimitedConnection extends oNodeConnection with dual persistent streams for limited connections.
6
- *
7
- * This is optimized for limited connections where the caller cannot be dialed
8
- * (browser clients, mobile clients, resource-constrained environments).
9
- *
10
- * Stream Architecture:
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
16
- *
17
- * Features:
18
- * - Automatic reader/writer stream creation and maintenance
19
- * - Single retry on reader failure
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)
23
- *
24
- * Default Behavior:
25
- * - Uses 'reuse' stream policy by default
26
- * - Automatically initializes persistent streams on connection start
27
- */
28
- export class oLimitedConnection extends oNodeConnection {
29
- constructor(config) {
30
- const reusePolicy = config.reusePolicy ?? 'reuse';
31
- super({
32
- ...config,
33
- reusePolicy,
34
- });
35
- // Replace the base streamManager with our limited version
36
- const protocol = this.nextHopAddress.protocol + (reusePolicy === 'reuse' ? '/reuse' : '');
37
- const limitedConfig = {
38
- p2pConnection: this.p2pConnection,
39
- protocol,
40
- remoteAddress: this.nextHopAddress,
41
- requestHandler: config.requestHandler,
42
- };
43
- this.streamManager = new oLimitedStreamManager(limitedConfig);
44
- // Set up event listeners for monitoring
45
- this.streamManager.on(StreamManagerEvent.ReaderStarted, (data) => {
46
- this.logger.info('Reader stream started', data);
47
- });
48
- this.streamManager.on(StreamManagerEvent.WriterStarted, (data) => {
49
- this.logger.info('Writer stream started', data);
50
- });
51
- this.streamManager.on(StreamManagerEvent.ReaderFailed, (data) => {
52
- this.logger.warn('Reader stream failed', data);
53
- });
54
- this.streamManager.on(StreamManagerEvent.ReaderRecovered, (data) => {
55
- this.logger.info('Reader stream recovered', data);
56
- });
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
- }
87
- /**
88
- * Override close to cleanup stream manager properly
89
- */
90
- async close() {
91
- this.logger.debug('Closing limited connection');
92
- await this.streamManager.close();
93
- await super.close();
94
- }
95
- }
@@ -1,96 +0,0 @@
1
- import type { Stream } from '@libp2p/interface';
2
- import { oNodeStreamManager, oNodeStream, StreamManagerConfig } from '@olane/o-node';
3
- import type { StreamHandlerConfig } from '@olane/o-node';
4
- /**
5
- * Extended configuration for limited stream manager
6
- */
7
- export interface oLimitedStreamManagerConfig extends StreamManagerConfig {
8
- /**
9
- * Protocol string for creating streams
10
- */
11
- protocol: string;
12
- /**
13
- * Remote address for the connection
14
- */
15
- remoteAddress: any;
16
- /**
17
- * Optional request handler for bidirectional communication
18
- */
19
- requestHandler?: (request: any, stream: Stream) => Promise<any>;
20
- }
21
- /**
22
- * Limited Stream Manager handles stream lifecycle for limited connections
23
- * Features:
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
29
- * - Single retry on reader failure
30
- * - No health checks (error handling on use)
31
- */
32
- export declare class oLimitedStreamManager extends oNodeStreamManager {
33
- readerStream?: oNodeStream;
34
- writerStream?: oNodeStream;
35
- private isReaderLoopActive;
36
- private readerLoopAbortController?;
37
- private limitedConfig;
38
- private closing;
39
- constructor(config: oLimitedStreamManagerConfig);
40
- /**
41
- * Initialize the limited stream manager
42
- * Creates dedicated reader stream and starts background read loop
43
- */
44
- initialize(): Promise<void>;
45
- /**
46
- * Creates and initializes the dedicated reader stream
47
- */
48
- private createReaderStream;
49
- /**
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
56
- */
57
- private sendStreamInitMessage;
58
- /**
59
- * Starts the background reader loop
60
- * Continuously listens for incoming requests on the reader stream
61
- */
62
- private startReaderLoop;
63
- /**
64
- * Background reader loop implementation
65
- */
66
- private runReaderLoop;
67
- /**
68
- * Attempts to recover the reader stream (single retry)
69
- */
70
- private recoverReaderStream;
71
- /**
72
- * Override getOrCreateStream to use persistent writer stream for outbound requests
73
- * This eliminates ephemeral stream creation for limited connections
74
- * Auto-initializes on first use
75
- */
76
- getOrCreateStream(protocol: string, remoteAddress: any, config?: StreamHandlerConfig): Promise<oNodeStream>;
77
- /**
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
81
- */
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;
91
- /**
92
- * Close the stream manager and cleanup all resources
93
- */
94
- close(): Promise<void>;
95
- }
96
- //# sourceMappingURL=o-limited.stream-manager.d.ts.map
@@ -1 +0,0 @@
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,319 +0,0 @@
1
- import { oError, oErrorCodes } from '@olane/o-core';
2
- import { oNodeStreamManager, StreamManagerEvent, isStreamInitAckMessage, } from '@olane/o-node';
3
- import { lpStream } from '@olane/o-config';
4
- /**
5
- * Limited Stream Manager handles stream lifecycle for limited connections
6
- * Features:
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
12
- * - Single retry on reader failure
13
- * - No health checks (error handling on use)
14
- */
15
- export class oLimitedStreamManager extends oNodeStreamManager {
16
- constructor(config) {
17
- super(config);
18
- this.isReaderLoopActive = false;
19
- this.closing = false;
20
- this.limitedConfig = config;
21
- }
22
- /**
23
- * Initialize the limited stream manager
24
- * Creates dedicated reader stream and starts background read loop
25
- */
26
- async initialize() {
27
- if (this.isInitialized) {
28
- return;
29
- }
30
- await super.initialize();
31
- // Register InboundRequest listener if requestHandler provided
32
- // This enables bidirectional communication - receiver can send requests to caller via reader stream
33
- if (this.limitedConfig.requestHandler) {
34
- this.eventEmitter.on(StreamManagerEvent.InboundRequest, async (data) => {
35
- try {
36
- const result = await this.limitedConfig.requestHandler(data.request, data.stream);
37
- return result;
38
- }
39
- catch (error) {
40
- this.logger.error('Error in requestHandler for inbound request:', data.request.toString(), error);
41
- throw error; // StreamManager will handle error response
42
- }
43
- });
44
- this.logger.debug('Registered InboundRequest handler for limited connection');
45
- }
46
- try {
47
- // Create dedicated reader and writer streams
48
- await this.createReaderStream();
49
- await this.createWriterStream();
50
- this.isInitialized = true;
51
- this.logger.info('Limited stream manager initialized', {
52
- remotePeer: this.limitedConfig.p2pConnection.remotePeer.toString(),
53
- });
54
- }
55
- catch (error) {
56
- this.logger.error('Failed to initialize limited stream manager:', error);
57
- throw new oError(oErrorCodes.INTERNAL_ERROR, `Failed to initialize limited stream manager: ${error.message}`);
58
- }
59
- }
60
- /**
61
- * Creates and initializes the dedicated reader stream
62
- */
63
- async createReaderStream() {
64
- try {
65
- // Create reader stream using parent's createStream method
66
- this.readerStream = await this.createStream(this.limitedConfig.protocol, this.limitedConfig.remoteAddress, {});
67
- // Send self-identification message
68
- await this.sendStreamInitMessage(this.readerStream.p2pStream, 'reader');
69
- // Start background reader loop
70
- await this.startReaderLoop();
71
- this.eventEmitter.emit(StreamManagerEvent.ReaderStarted, {
72
- streamId: this.readerStream.p2pStream.id,
73
- });
74
- this.logger.info('Reader stream created and started', {
75
- streamId: this.readerStream.p2pStream.id,
76
- });
77
- }
78
- catch (error) {
79
- this.logger.error('Failed to create reader stream:', error);
80
- throw error;
81
- }
82
- }
83
- /**
84
- * Creates and initializes the dedicated writer stream
85
- */
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) {
109
- const initMessage = {
110
- type: 'stream-init',
111
- role,
112
- timestamp: Date.now(),
113
- connectionId: this.limitedConfig.p2pConnection.id,
114
- };
115
- const messageBytes = new TextEncoder().encode(JSON.stringify(initMessage));
116
- await this.sendLengthPrefixed(stream, messageBytes, {});
117
- this.logger.debug('Sent stream-init message, waiting for ack', {
118
- streamId: stream.id,
119
- role,
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
- }
158
- }
159
- /**
160
- * Starts the background reader loop
161
- * Continuously listens for incoming requests on the reader stream
162
- */
163
- async startReaderLoop() {
164
- if (!this.readerStream || this.isReaderLoopActive) {
165
- return;
166
- }
167
- this.isReaderLoopActive = true;
168
- this.readerLoopAbortController = new AbortController();
169
- // Start background task (don't await)
170
- this.runReaderLoop().catch((error) => {
171
- this.logger.error('Reader loop failed:', error);
172
- this.isReaderLoopActive = false;
173
- });
174
- }
175
- /**
176
- * Background reader loop implementation
177
- */
178
- async runReaderLoop() {
179
- if (!this.readerStream) {
180
- return;
181
- }
182
- const stream = this.readerStream.p2pStream;
183
- try {
184
- // Continuous read loop using parent's handleIncomingStream
185
- // This will process all incoming requests on the reader stream
186
- await this.handleIncomingStream(stream, this.limitedConfig.p2pConnection);
187
- }
188
- catch (error) {
189
- if (!this.closing) {
190
- this.logger.error('Reader loop error, attempting recovery:', error);
191
- await this.recoverReaderStream();
192
- }
193
- }
194
- finally {
195
- this.isReaderLoopActive = false;
196
- }
197
- }
198
- /**
199
- * Attempts to recover the reader stream (single retry)
200
- */
201
- async recoverReaderStream() {
202
- try {
203
- this.logger.info('Attempting to recover reader stream...');
204
- // Close old stream if it exists
205
- if (this.readerStream) {
206
- try {
207
- await this.readerStream.p2pStream.close();
208
- }
209
- catch (e) {
210
- // Ignore close errors
211
- }
212
- this.readerStream = undefined;
213
- }
214
- // Try to recreate
215
- await this.createReaderStream();
216
- this.eventEmitter.emit(StreamManagerEvent.ReaderRecovered, {
217
- failureCount: 1,
218
- });
219
- this.logger.info('Reader stream recovered successfully');
220
- }
221
- catch (error) {
222
- this.logger.error('Reader stream recovery failed:', error);
223
- this.eventEmitter.emit(StreamManagerEvent.ReaderFailed, {
224
- error: error.message,
225
- failureCount: 1,
226
- });
227
- }
228
- }
229
- /**
230
- * Override getOrCreateStream to use persistent writer stream for outbound requests
231
- * This eliminates ephemeral stream creation for limited connections
232
- * Auto-initializes on first use
233
- */
234
- async getOrCreateStream(protocol, remoteAddress, config = {}) {
235
- // Auto-initialize on first use
236
- if (!this.isInitialized) {
237
- await this.initialize();
238
- }
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,
243
- });
244
- return this.writerStream;
245
- }
246
- // Writer stream should always be available after initialization
247
- throw new oError(oErrorCodes.INTERNAL_ERROR, 'Writer stream not available or not open');
248
- }
249
- /**
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
253
- */
254
- async releaseStream(streamId) {
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);
282
- }
283
- /**
284
- * Close the stream manager and cleanup all resources
285
- */
286
- async close() {
287
- if (this.closing) {
288
- return;
289
- }
290
- this.closing = true;
291
- this.logger.info('Closing limited stream manager');
292
- // Stop reader loop
293
- if (this.readerLoopAbortController) {
294
- this.readerLoopAbortController.abort();
295
- }
296
- this.isReaderLoopActive = false;
297
- // Close reader stream
298
- if (this.readerStream) {
299
- try {
300
- await this.readerStream.p2pStream.close();
301
- }
302
- catch (error) {
303
- this.logger.warn('Error closing reader stream:', error);
304
- }
305
- this.readerStream = undefined;
306
- }
307
- // Close writer stream
308
- if (this.writerStream) {
309
- try {
310
- await this.writerStream.p2pStream.close();
311
- }
312
- catch (error) {
313
- this.logger.warn('Error closing writer stream:', error);
314
- }
315
- this.writerStream = undefined;
316
- }
317
- this.eventEmitter.emit(StreamManagerEvent.ManagerClosed, undefined);
318
- }
319
- }