@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.
- package/dist/src/connection/index.d.ts +0 -2
- package/dist/src/connection/index.d.ts.map +1 -1
- package/dist/src/connection/index.js +0 -2
- package/dist/src/connection/o-limited-connection-manager.d.ts.map +1 -1
- package/dist/src/o-limited.tool.d.ts +1 -3
- package/dist/src/o-limited.tool.d.ts.map +1 -1
- package/dist/src/o-limited.tool.js +0 -23
- package/dist/test/bidirectional-communication.spec.js +1 -0
- package/package.json +7 -7
- package/dist/src/connection/o-limited-connection.d.ts +0 -43
- package/dist/src/connection/o-limited-connection.d.ts.map +0 -1
- package/dist/src/connection/o-limited-connection.js +0 -95
- package/dist/src/connection/o-limited.stream-manager.d.ts +0 -96
- package/dist/src/connection/o-limited.stream-manager.d.ts.map +0 -1
- package/dist/src/connection/o-limited.stream-manager.js +0 -319
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/connection/index.ts"],"names":[],"mappings":"AAAA,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/connection/index.ts"],"names":[],"mappings":"AAAA,cAAc,mCAAmC,CAAC"}
|
|
@@ -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;
|
|
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 {
|
|
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,
|
|
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.
|
|
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.
|
|
58
|
-
"@olane/o-core": "0.7.
|
|
59
|
-
"@olane/o-node": "0.7.
|
|
60
|
-
"@olane/o-protocol": "0.7.
|
|
61
|
-
"@olane/o-tool": "0.7.
|
|
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": "
|
|
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
|
-
}
|