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