@olane/o-node 0.7.53 → 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/interfaces/stream-init-message.d.ts +39 -4
- package/dist/src/connection/interfaces/stream-init-message.d.ts.map +1 -1
- package/dist/src/connection/interfaces/stream-init-message.js +11 -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 +120 -2
- 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 +34 -5
- package/dist/src/connection/o-node-stream.manager.d.ts.map +1 -1
- package/dist/src/connection/o-node-stream.manager.js +190 -20
- package/dist/src/connection/stream-manager.events.d.ts +13 -1
- package/dist/src/connection/stream-manager.events.d.ts.map +1 -1
- package/dist/src/connection/stream-manager.events.js +2 -0
- package/dist/src/o-node.tool.d.ts.map +1 -1
- package/dist/src/o-node.tool.js +14 -19
- 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
|
@@ -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)
|
|
@@ -100,7 +107,7 @@ export class oNodeStreamManager extends oObject {
|
|
|
100
107
|
const stream = await this.p2pConnection.newStream(protocol, {
|
|
101
108
|
signal: config.signal,
|
|
102
109
|
maxOutboundStreams: config.maxOutboundStreams ?? 1000,
|
|
103
|
-
runOnLimitedConnection: config.runOnLimitedConnection ??
|
|
110
|
+
runOnLimitedConnection: config.runOnLimitedConnection ?? true,
|
|
104
111
|
});
|
|
105
112
|
// Wrap in oNodeStream with metadata
|
|
106
113
|
const wrappedStream = new oNodeStream(stream, {
|
|
@@ -157,6 +164,72 @@ export class oNodeStreamManager extends oObject {
|
|
|
157
164
|
getAllStreams() {
|
|
158
165
|
return Array.from(this.streams.values());
|
|
159
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Gets a stream by its ID
|
|
169
|
+
* Checks persistent caller streams (reader/writer) and tracked streams
|
|
170
|
+
*
|
|
171
|
+
* @param streamId - The ID of the stream to retrieve
|
|
172
|
+
* @returns The libp2p Stream or undefined if not found
|
|
173
|
+
*/
|
|
174
|
+
getStreamById(streamId) {
|
|
175
|
+
// Check caller writer stream
|
|
176
|
+
if (this.callerWriterStream?.id === streamId) {
|
|
177
|
+
return this.callerWriterStream;
|
|
178
|
+
}
|
|
179
|
+
// Check caller reader stream
|
|
180
|
+
if (this.callerReaderStream?.id === streamId) {
|
|
181
|
+
return this.callerReaderStream;
|
|
182
|
+
}
|
|
183
|
+
// Check tracked streams
|
|
184
|
+
const wrappedStream = this.streams.get(streamId);
|
|
185
|
+
return wrappedStream?.p2pStream;
|
|
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
|
+
}
|
|
160
233
|
/**
|
|
161
234
|
* Emits an async event and waits for the first listener to return a result
|
|
162
235
|
* This enables event-based request handling with async responses
|
|
@@ -194,23 +267,76 @@ export class oNodeStreamManager extends oObject {
|
|
|
194
267
|
/**
|
|
195
268
|
* Handles a stream initialization message
|
|
196
269
|
* Stores reference to caller's reader stream for bidirectional communication
|
|
270
|
+
* Sends acknowledgment back to confirm stream registration
|
|
197
271
|
*
|
|
198
272
|
* @param message - The decoded stream init message
|
|
199
273
|
* @param stream - The stream that sent the message
|
|
200
274
|
*/
|
|
201
|
-
handleStreamInitMessage(message, stream) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
275
|
+
async handleStreamInitMessage(message, stream) {
|
|
276
|
+
try {
|
|
277
|
+
if (message.role === 'reader') {
|
|
278
|
+
this.callerReaderStream = stream;
|
|
279
|
+
this.logger.info('Identified caller reader stream', {
|
|
280
|
+
streamId: stream.id,
|
|
281
|
+
connectionId: message.connectionId,
|
|
282
|
+
});
|
|
283
|
+
this.emit(StreamManagerEvent.StreamIdentified, {
|
|
284
|
+
streamId: stream.id,
|
|
285
|
+
role: message.role,
|
|
286
|
+
connectionId: message.connectionId,
|
|
287
|
+
});
|
|
288
|
+
// Set up monitoring for reader stream closure
|
|
289
|
+
this.setupStreamCloseMonitoring(stream, 'reader');
|
|
290
|
+
}
|
|
291
|
+
else if (message.role === 'writer') {
|
|
292
|
+
this.callerWriterStream = stream;
|
|
293
|
+
this.logger.info('Identified caller writer stream', {
|
|
294
|
+
streamId: stream.id,
|
|
295
|
+
connectionId: message.connectionId,
|
|
296
|
+
});
|
|
297
|
+
this.emit(StreamManagerEvent.StreamIdentified, {
|
|
298
|
+
streamId: stream.id,
|
|
299
|
+
role: message.role,
|
|
300
|
+
connectionId: message.connectionId,
|
|
301
|
+
});
|
|
302
|
+
// Set up monitoring for writer stream closure
|
|
303
|
+
this.setupStreamCloseMonitoring(stream, 'writer');
|
|
304
|
+
}
|
|
305
|
+
// Send acknowledgment back to caller
|
|
306
|
+
const ackMessage = {
|
|
307
|
+
type: 'stream-init-ack',
|
|
308
|
+
status: 'success',
|
|
205
309
|
streamId: stream.id,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
310
|
+
role: message.role,
|
|
311
|
+
timestamp: Date.now(),
|
|
312
|
+
};
|
|
313
|
+
const ackBytes = new TextEncoder().encode(JSON.stringify(ackMessage));
|
|
314
|
+
await this.sendLengthPrefixed(stream, ackBytes, {});
|
|
315
|
+
this.logger.debug('Sent stream-init-ack', {
|
|
209
316
|
streamId: stream.id,
|
|
210
317
|
role: message.role,
|
|
211
|
-
connectionId: message.connectionId,
|
|
212
318
|
});
|
|
213
319
|
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
this.logger.error('Failed to process stream-init message', error);
|
|
322
|
+
// Try to send error acknowledgment
|
|
323
|
+
try {
|
|
324
|
+
const errorAck = {
|
|
325
|
+
type: 'stream-init-ack',
|
|
326
|
+
status: 'error',
|
|
327
|
+
streamId: stream.id,
|
|
328
|
+
role: message.role,
|
|
329
|
+
error: error.message,
|
|
330
|
+
timestamp: Date.now(),
|
|
331
|
+
};
|
|
332
|
+
const errorAckBytes = new TextEncoder().encode(JSON.stringify(errorAck));
|
|
333
|
+
await this.sendLengthPrefixed(stream, errorAckBytes, {});
|
|
334
|
+
}
|
|
335
|
+
catch (ackError) {
|
|
336
|
+
this.logger.error('Failed to send error acknowledgment', ackError);
|
|
337
|
+
}
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
214
340
|
}
|
|
215
341
|
/**
|
|
216
342
|
* Extracts and parses JSON from various formats including:
|
|
@@ -301,7 +427,7 @@ export class oNodeStreamManager extends oObject {
|
|
|
301
427
|
// Parse JSON (handles markdown blocks, mixed content, and JSON5)
|
|
302
428
|
const message = this.extractAndParseJSON(decoded);
|
|
303
429
|
if (this.isStreamInit(message)) {
|
|
304
|
-
this.handleStreamInitMessage(message, stream);
|
|
430
|
+
await this.handleStreamInitMessage(message, stream);
|
|
305
431
|
// Continue reading for subsequent messages on this stream
|
|
306
432
|
}
|
|
307
433
|
else if (this.isRequest(message)) {
|
|
@@ -318,7 +444,6 @@ export class oNodeStreamManager extends oObject {
|
|
|
318
444
|
catch (error) {
|
|
319
445
|
// Stream closed or error occurred
|
|
320
446
|
if (stream.status === 'open') {
|
|
321
|
-
this.logger.error('Error in length-prefixed stream handler:', error);
|
|
322
447
|
this.emit(StreamManagerEvent.StreamError, {
|
|
323
448
|
streamId: stream.id,
|
|
324
449
|
error,
|
|
@@ -330,6 +455,42 @@ export class oNodeStreamManager extends oObject {
|
|
|
330
455
|
this.untrackStreamHandler(stream.id);
|
|
331
456
|
}
|
|
332
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* Determines which stream to use for sending the response
|
|
460
|
+
* Checks for _streamId in request params and routes accordingly
|
|
461
|
+
*
|
|
462
|
+
* @param request - The incoming request
|
|
463
|
+
* @param defaultStream - The stream the request came on (fallback)
|
|
464
|
+
* @returns The stream to use for the response
|
|
465
|
+
*/
|
|
466
|
+
getResponseStream(request, defaultStream) {
|
|
467
|
+
const streamId = request.params._streamId;
|
|
468
|
+
// If no explicit response stream specified, use the request stream (backward compatibility)
|
|
469
|
+
if (!streamId) {
|
|
470
|
+
return defaultStream;
|
|
471
|
+
}
|
|
472
|
+
// Check if the response stream is the identified caller writer stream
|
|
473
|
+
if (this.callerWriterStream && this.callerWriterStream.id === streamId) {
|
|
474
|
+
this.logger.debug('Routing response to caller writer stream', {
|
|
475
|
+
requestId: request.id,
|
|
476
|
+
streamId,
|
|
477
|
+
});
|
|
478
|
+
return this.callerWriterStream;
|
|
479
|
+
}
|
|
480
|
+
if (this.callerReaderStream && this.callerReaderStream.id === streamId) {
|
|
481
|
+
this.logger.debug('Routing response to caller reader stream', {
|
|
482
|
+
requestId: request.id,
|
|
483
|
+
streamId,
|
|
484
|
+
});
|
|
485
|
+
return this.callerReaderStream;
|
|
486
|
+
}
|
|
487
|
+
// If specified stream not found, warn and fall back to request stream
|
|
488
|
+
this.logger.warn('Specified response stream not found, using request stream', {
|
|
489
|
+
requestId: request.id,
|
|
490
|
+
streamId,
|
|
491
|
+
});
|
|
492
|
+
return defaultStream;
|
|
493
|
+
}
|
|
333
494
|
/**
|
|
334
495
|
* Handles a request message by emitting an event and sending response
|
|
335
496
|
*
|
|
@@ -340,6 +501,8 @@ export class oNodeStreamManager extends oObject {
|
|
|
340
501
|
async handleRequestMessage(message, stream, connection) {
|
|
341
502
|
const request = new oRequest(message);
|
|
342
503
|
const responseBuilder = ResponseBuilder.create();
|
|
504
|
+
// Determine which stream to use for the response
|
|
505
|
+
const responseStream = this.getResponseStream(request, stream);
|
|
343
506
|
try {
|
|
344
507
|
// Emit InboundRequest event and wait for handler to process
|
|
345
508
|
const result = await this.emitAsync(StreamManagerEvent.InboundRequest, {
|
|
@@ -348,13 +511,13 @@ export class oNodeStreamManager extends oObject {
|
|
|
348
511
|
connection,
|
|
349
512
|
});
|
|
350
513
|
const response = await responseBuilder.build(request, result, null);
|
|
351
|
-
await CoreUtils.sendResponse(response,
|
|
514
|
+
await CoreUtils.sendResponse(response, responseStream);
|
|
352
515
|
this.logger.debug(`Successfully processed request: method=${request.method}, id=${request.id}`);
|
|
353
516
|
}
|
|
354
517
|
catch (error) {
|
|
355
518
|
this.logger.error(`Error processing request: method=${request.method}, id=${request.id}`, error);
|
|
356
519
|
const errorResponse = await responseBuilder.buildError(request, error);
|
|
357
|
-
await CoreUtils.sendResponse(errorResponse,
|
|
520
|
+
await CoreUtils.sendResponse(errorResponse, responseStream);
|
|
358
521
|
this.emit(StreamManagerEvent.StreamError, {
|
|
359
522
|
streamId: stream.id,
|
|
360
523
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
@@ -478,7 +641,14 @@ export class oNodeStreamManager extends oObject {
|
|
|
478
641
|
this.logger.info('Closing stream manager', {
|
|
479
642
|
activeStreams: this.streams.size,
|
|
480
643
|
activeHandlers: this.activeStreamHandlers.size,
|
|
644
|
+
monitoringIntervals: this.streamMonitoringIntervals.size,
|
|
481
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();
|
|
482
652
|
// Abort all active stream handlers
|
|
483
653
|
for (const [streamId, { abortController },] of this.activeStreamHandlers.entries()) {
|
|
484
654
|
abortController.abort();
|
|
@@ -5,12 +5,14 @@ export declare enum StreamManagerEvent {
|
|
|
5
5
|
ManagerInitialized = "manager-initialized",
|
|
6
6
|
ManagerClosed = "manager-closed",
|
|
7
7
|
ReaderStarted = "reader-started",
|
|
8
|
+
WriterStarted = "writer-started",
|
|
8
9
|
ReaderFailed = "reader-failed",
|
|
9
10
|
ReaderRecovered = "reader-recovered",
|
|
10
11
|
RecoveryFailed = "recovery-failed",
|
|
11
12
|
StreamReplaced = "stream-replaced",
|
|
12
13
|
StreamFailed = "stream-failed",
|
|
13
14
|
StreamIdentified = "stream-identified",
|
|
15
|
+
StreamClosed = "stream-closed",
|
|
14
16
|
InboundRequest = "inbound-request",
|
|
15
17
|
InboundResponse = "inbound-response",
|
|
16
18
|
StreamError = "stream-error"
|
|
@@ -23,6 +25,9 @@ export interface InitializedData {
|
|
|
23
25
|
export interface ReaderStartedData {
|
|
24
26
|
streamId: string;
|
|
25
27
|
}
|
|
28
|
+
export interface WriterStartedData {
|
|
29
|
+
streamId: string;
|
|
30
|
+
}
|
|
26
31
|
export interface ReaderFailedData {
|
|
27
32
|
error?: string;
|
|
28
33
|
failureCount: number;
|
|
@@ -46,9 +51,14 @@ export interface StreamFailedData {
|
|
|
46
51
|
}
|
|
47
52
|
export interface StreamIdentifiedData {
|
|
48
53
|
streamId: string;
|
|
49
|
-
role: 'reader' | 'standard';
|
|
54
|
+
role: 'reader' | 'writer' | 'standard';
|
|
50
55
|
connectionId?: string;
|
|
51
56
|
}
|
|
57
|
+
export interface StreamClosedData {
|
|
58
|
+
streamId: string;
|
|
59
|
+
role: 'reader' | 'writer';
|
|
60
|
+
status: string;
|
|
61
|
+
}
|
|
52
62
|
export interface InboundRequestData {
|
|
53
63
|
request: any;
|
|
54
64
|
stream: any;
|
|
@@ -70,12 +80,14 @@ export type StreamManagerEventData = {
|
|
|
70
80
|
[StreamManagerEvent.ManagerInitialized]: InitializedData;
|
|
71
81
|
[StreamManagerEvent.ManagerClosed]: void;
|
|
72
82
|
[StreamManagerEvent.ReaderStarted]: ReaderStartedData;
|
|
83
|
+
[StreamManagerEvent.WriterStarted]: WriterStartedData;
|
|
73
84
|
[StreamManagerEvent.ReaderFailed]: ReaderFailedData;
|
|
74
85
|
[StreamManagerEvent.ReaderRecovered]: ReaderRecoveredData;
|
|
75
86
|
[StreamManagerEvent.RecoveryFailed]: RecoveryFailedData;
|
|
76
87
|
[StreamManagerEvent.StreamReplaced]: StreamReplacedData;
|
|
77
88
|
[StreamManagerEvent.StreamFailed]: StreamFailedData;
|
|
78
89
|
[StreamManagerEvent.StreamIdentified]: StreamIdentifiedData;
|
|
90
|
+
[StreamManagerEvent.StreamClosed]: StreamClosedData;
|
|
79
91
|
[StreamManagerEvent.InboundRequest]: InboundRequestData;
|
|
80
92
|
[StreamManagerEvent.InboundResponse]: InboundResponseData;
|
|
81
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,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,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,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"stream-manager.events.d.ts","sourceRoot":"","sources":["../../../src/connection/stream-manager.events.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,12 +6,14 @@ export var StreamManagerEvent;
|
|
|
6
6
|
StreamManagerEvent["ManagerInitialized"] = "manager-initialized";
|
|
7
7
|
StreamManagerEvent["ManagerClosed"] = "manager-closed";
|
|
8
8
|
StreamManagerEvent["ReaderStarted"] = "reader-started";
|
|
9
|
+
StreamManagerEvent["WriterStarted"] = "writer-started";
|
|
9
10
|
StreamManagerEvent["ReaderFailed"] = "reader-failed";
|
|
10
11
|
StreamManagerEvent["ReaderRecovered"] = "reader-recovered";
|
|
11
12
|
StreamManagerEvent["RecoveryFailed"] = "recovery-failed";
|
|
12
13
|
StreamManagerEvent["StreamReplaced"] = "stream-replaced";
|
|
13
14
|
StreamManagerEvent["StreamFailed"] = "stream-failed";
|
|
14
15
|
StreamManagerEvent["StreamIdentified"] = "stream-identified";
|
|
16
|
+
StreamManagerEvent["StreamClosed"] = "stream-closed";
|
|
15
17
|
StreamManagerEvent["InboundRequest"] = "inbound-request";
|
|
16
18
|
StreamManagerEvent["InboundResponse"] = "inbound-response";
|
|
17
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
|
|
@@ -25,13 +24,16 @@ export class oNodeTool extends oTool(oServerNode) {
|
|
|
25
24
|
await this.p2pNode.handle(reuseProtocol, this.handleStreamReuse.bind(this), {
|
|
26
25
|
maxInboundStreams: 10000,
|
|
27
26
|
maxOutboundStreams: maxOutboundsStreams,
|
|
28
|
-
runOnLimitedConnection:
|
|
27
|
+
runOnLimitedConnection: true, // reuse is always on limited connections
|
|
29
28
|
});
|
|
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
|
}
|