@soulcraft/brainy 0.41.0 → 0.44.0
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/README.md +605 -194
- package/dist/augmentationFactory.d.ts.map +1 -0
- package/dist/augmentationFactory.js +342 -0
- package/dist/augmentationFactory.js.map +1 -0
- package/dist/augmentationPipeline.d.ts.map +1 -0
- package/dist/augmentationPipeline.js +472 -0
- package/dist/augmentationPipeline.js.map +1 -0
- package/dist/augmentationRegistry.d.ts.map +1 -0
- package/dist/augmentationRegistry.js +105 -0
- package/dist/augmentationRegistry.js.map +1 -0
- package/dist/augmentationRegistryLoader.d.ts.map +1 -0
- package/dist/augmentationRegistryLoader.js +213 -0
- package/dist/augmentationRegistryLoader.js.map +1 -0
- package/dist/augmentations/conduitAugmentations.js +1158 -0
- package/dist/augmentations/conduitAugmentations.js.map +1 -0
- package/dist/augmentations/memoryAugmentations.d.ts +2 -0
- package/dist/augmentations/memoryAugmentations.d.ts.map +1 -1
- package/dist/augmentations/memoryAugmentations.js +270 -0
- package/dist/augmentations/memoryAugmentations.js.map +1 -0
- package/dist/augmentations/serverSearchAugmentations.js +531 -0
- package/dist/augmentations/serverSearchAugmentations.js.map +1 -0
- package/dist/brainyData.d.ts.map +1 -0
- package/dist/brainyData.js +3999 -0
- package/dist/brainyData.js.map +1 -0
- package/dist/browserFramework.d.ts +15 -0
- package/dist/browserFramework.d.ts.map +1 -0
- package/dist/browserFramework.js +31 -0
- package/dist/browserFramework.js.map +1 -0
- package/dist/coreTypes.d.ts.map +1 -0
- package/dist/coreTypes.js +5 -0
- package/dist/coreTypes.js.map +1 -0
- package/dist/demo.d.ts +106 -0
- package/dist/demo.d.ts.map +1 -0
- package/dist/demo.js +201 -0
- package/dist/demo.js.map +1 -0
- package/dist/distributed/configManager.d.ts.map +1 -0
- package/dist/distributed/configManager.js +322 -0
- package/dist/distributed/configManager.js.map +1 -0
- package/dist/distributed/domainDetector.d.ts.map +1 -0
- package/dist/distributed/domainDetector.js +307 -0
- package/dist/distributed/domainDetector.js.map +1 -0
- package/dist/distributed/hashPartitioner.d.ts.map +1 -0
- package/dist/distributed/hashPartitioner.js +146 -0
- package/dist/distributed/hashPartitioner.js.map +1 -0
- package/dist/distributed/healthMonitor.d.ts.map +1 -0
- package/dist/distributed/healthMonitor.js +244 -0
- package/dist/distributed/healthMonitor.js.map +1 -0
- package/dist/distributed/index.d.ts.map +1 -0
- package/dist/distributed/index.js +9 -0
- package/dist/distributed/index.js.map +1 -0
- package/dist/distributed/operationalModes.d.ts.map +1 -0
- package/dist/distributed/operationalModes.js +201 -0
- package/dist/distributed/operationalModes.js.map +1 -0
- package/dist/errors/brainyError.d.ts.map +1 -0
- package/dist/errors/brainyError.js +113 -0
- package/dist/errors/brainyError.js.map +1 -0
- package/dist/examples/basicUsage.js +118 -0
- package/dist/examples/basicUsage.js.map +1 -0
- package/dist/hnsw/distributedSearch.js +452 -0
- package/dist/hnsw/distributedSearch.js.map +1 -0
- package/dist/hnsw/hnswIndex.js +602 -0
- package/dist/hnsw/hnswIndex.js.map +1 -0
- package/dist/hnsw/hnswIndexOptimized.js +471 -0
- package/dist/hnsw/hnswIndexOptimized.js.map +1 -0
- package/dist/hnsw/optimizedHNSWIndex.js +313 -0
- package/dist/hnsw/optimizedHNSWIndex.js.map +1 -0
- package/dist/hnsw/partitionedHNSWIndex.js +304 -0
- package/dist/hnsw/partitionedHNSWIndex.js.map +1 -0
- package/dist/hnsw/scaledHNSWSystem.js +559 -0
- package/dist/hnsw/scaledHNSWSystem.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +81 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/brainyMCPAdapter.js +142 -0
- package/dist/mcp/brainyMCPAdapter.js.map +1 -0
- package/dist/mcp/brainyMCPService.js +248 -0
- package/dist/mcp/brainyMCPService.js.map +1 -0
- package/dist/mcp/index.js +17 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/mcpAugmentationToolset.js +180 -0
- package/dist/mcp/mcpAugmentationToolset.js.map +1 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +590 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/sequentialPipeline.d.ts.map +1 -0
- package/dist/sequentialPipeline.js +417 -0
- package/dist/sequentialPipeline.js.map +1 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +46 -0
- package/dist/setup.js.map +1 -0
- package/dist/storage/adapters/baseStorageAdapter.js +349 -0
- package/dist/storage/adapters/baseStorageAdapter.js.map +1 -0
- package/dist/storage/adapters/batchS3Operations.js +287 -0
- package/dist/storage/adapters/batchS3Operations.js.map +1 -0
- package/dist/storage/adapters/fileSystemStorage.js +846 -0
- package/dist/storage/adapters/fileSystemStorage.js.map +1 -0
- package/dist/storage/adapters/memoryStorage.js +532 -0
- package/dist/storage/adapters/memoryStorage.js.map +1 -0
- package/dist/storage/adapters/opfsStorage.d.ts.map +1 -1
- package/dist/storage/adapters/opfsStorage.js +1118 -0
- package/dist/storage/adapters/opfsStorage.js.map +1 -0
- package/dist/storage/adapters/optimizedS3Search.js +248 -0
- package/dist/storage/adapters/optimizedS3Search.js.map +1 -0
- package/dist/storage/adapters/s3CompatibleStorage.js +2026 -0
- package/dist/storage/adapters/s3CompatibleStorage.js.map +1 -0
- package/dist/storage/baseStorage.js +603 -0
- package/dist/storage/baseStorage.js.map +1 -0
- package/dist/storage/cacheManager.js +1306 -0
- package/dist/storage/cacheManager.js.map +1 -0
- package/dist/storage/enhancedCacheManager.js +520 -0
- package/dist/storage/enhancedCacheManager.js.map +1 -0
- package/dist/storage/readOnlyOptimizations.js +425 -0
- package/dist/storage/readOnlyOptimizations.js.map +1 -0
- package/dist/storage/storageFactory.d.ts +0 -1
- package/dist/storage/storageFactory.d.ts.map +1 -1
- package/dist/storage/storageFactory.js +227 -0
- package/dist/storage/storageFactory.js.map +1 -0
- package/dist/types/augmentations.js +16 -0
- package/dist/types/augmentations.js.map +1 -0
- package/dist/types/brainyDataInterface.js +8 -0
- package/dist/types/brainyDataInterface.js.map +1 -0
- package/dist/types/distributedTypes.js +6 -0
- package/dist/types/distributedTypes.js.map +1 -0
- package/dist/types/fileSystemTypes.js +8 -0
- package/dist/types/fileSystemTypes.js.map +1 -0
- package/dist/types/graphTypes.js +247 -0
- package/dist/types/graphTypes.js.map +1 -0
- package/dist/types/mcpTypes.js +22 -0
- package/dist/types/mcpTypes.js.map +1 -0
- package/dist/types/paginationTypes.js +5 -0
- package/dist/types/paginationTypes.js.map +1 -0
- package/dist/types/pipelineTypes.js +7 -0
- package/dist/types/pipelineTypes.js.map +1 -0
- package/dist/types/tensorflowTypes.js +6 -0
- package/dist/types/tensorflowTypes.js.map +1 -0
- package/dist/unified.d.ts.map +1 -0
- package/dist/unified.js +52 -128251
- package/dist/unified.js.map +1 -0
- package/dist/utils/autoConfiguration.js +341 -0
- package/dist/utils/autoConfiguration.js.map +1 -0
- package/dist/utils/cacheAutoConfig.js +261 -0
- package/dist/utils/cacheAutoConfig.js.map +1 -0
- package/dist/utils/crypto.js +45 -0
- package/dist/utils/crypto.js.map +1 -0
- package/dist/utils/distance.js +239 -0
- package/dist/utils/distance.js.map +1 -0
- package/dist/utils/embedding.d.ts.map +1 -1
- package/dist/utils/embedding.js +702 -0
- package/dist/utils/embedding.js.map +1 -0
- package/dist/utils/environment.js +75 -0
- package/dist/utils/environment.js.map +1 -0
- package/dist/utils/fieldNameTracking.js +90 -0
- package/dist/utils/fieldNameTracking.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +8 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/jsonProcessing.js +179 -0
- package/dist/utils/jsonProcessing.js.map +1 -0
- package/dist/utils/logger.js +129 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/operationUtils.js +126 -0
- package/dist/utils/operationUtils.js.map +1 -0
- package/dist/utils/robustModelLoader.d.ts +14 -0
- package/dist/utils/robustModelLoader.d.ts.map +1 -1
- package/dist/utils/robustModelLoader.js +537 -0
- package/dist/utils/robustModelLoader.js.map +1 -0
- package/dist/utils/searchCache.js +248 -0
- package/dist/utils/searchCache.js.map +1 -0
- package/dist/utils/statistics.js +25 -0
- package/dist/utils/statistics.js.map +1 -0
- package/dist/utils/statisticsCollector.js +224 -0
- package/dist/utils/statisticsCollector.js.map +1 -0
- package/dist/utils/textEncoding.js +309 -0
- package/dist/utils/textEncoding.js.map +1 -0
- package/dist/utils/typeUtils.js +40 -0
- package/dist/utils/typeUtils.js.map +1 -0
- package/dist/utils/version.d.ts +15 -3
- package/dist/utils/version.d.ts.map +1 -1
- package/dist/utils/version.js +24 -0
- package/dist/utils/version.js.map +1 -0
- package/dist/utils/workerUtils.js +458 -0
- package/dist/utils/workerUtils.js.map +1 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +54 -0
- package/dist/worker.js.map +1 -0
- package/package.json +30 -29
- package/dist/brainy.js +0 -90220
- package/dist/brainy.min.js +0 -12511
- package/dist/patched-platform-node.d.ts +0 -17
- package/dist/statistics/statisticsManager.d.ts +0 -121
- package/dist/storage/fileSystemStorage.d.ts +0 -73
- package/dist/storage/fileSystemStorage.d.ts.map +0 -1
- package/dist/storage/opfsStorage.d.ts +0 -236
- package/dist/storage/opfsStorage.d.ts.map +0 -1
- package/dist/storage/s3CompatibleStorage.d.ts +0 -157
- package/dist/storage/s3CompatibleStorage.d.ts.map +0 -1
- package/dist/testing/prettyReporter.d.ts +0 -23
- package/dist/testing/prettySummaryReporter.d.ts +0 -22
- package/dist/unified.min.js +0 -16153
- package/dist/utils/environmentDetection.d.ts +0 -47
- package/dist/utils/environmentDetection.d.ts.map +0 -1
- package/dist/utils/tensorflowUtils.d.ts +0 -17
- package/dist/utils/tensorflowUtils.d.ts.map +0 -1
|
@@ -0,0 +1,1158 @@
|
|
|
1
|
+
import { AugmentationType } from '../types/augmentations.js';
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
/**
|
|
4
|
+
* Base class for conduit augmentations that provide data synchronization between Brainy instances
|
|
5
|
+
*/
|
|
6
|
+
class BaseConduitAugmentation {
|
|
7
|
+
constructor(name) {
|
|
8
|
+
this.description = 'Base conduit augmentation';
|
|
9
|
+
this.enabled = true;
|
|
10
|
+
this.isInitialized = false;
|
|
11
|
+
this.connections = new Map();
|
|
12
|
+
this.name = name;
|
|
13
|
+
}
|
|
14
|
+
async initialize() {
|
|
15
|
+
if (this.isInitialized) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
this.isInitialized = true;
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
console.error(`Failed to initialize ${this.name}:`, error);
|
|
23
|
+
throw new Error(`Failed to initialize ${this.name}: ${error}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async shutDown() {
|
|
27
|
+
// Close all connections
|
|
28
|
+
for (const [connectionId, connection] of this.connections.entries()) {
|
|
29
|
+
try {
|
|
30
|
+
if (connection.close) {
|
|
31
|
+
await connection.close();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
console.error(`Failed to close connection ${connectionId}:`, error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
this.connections.clear();
|
|
39
|
+
this.isInitialized = false;
|
|
40
|
+
}
|
|
41
|
+
async getStatus() {
|
|
42
|
+
return this.isInitialized ? 'active' : 'inactive';
|
|
43
|
+
}
|
|
44
|
+
async ensureInitialized() {
|
|
45
|
+
if (!this.isInitialized) {
|
|
46
|
+
await this.initialize();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* WebSocket conduit augmentation for syncing Brainy instances using WebSockets
|
|
52
|
+
*
|
|
53
|
+
* This conduit is for syncing between browsers and servers, or between servers.
|
|
54
|
+
* WebSockets cannot be used for direct browser-to-browser communication without a server in the middle.
|
|
55
|
+
*/
|
|
56
|
+
export class WebSocketConduitAugmentation extends BaseConduitAugmentation {
|
|
57
|
+
constructor(name = 'websocket-conduit') {
|
|
58
|
+
super(name);
|
|
59
|
+
this.description = 'Conduit augmentation that syncs Brainy instances using WebSockets';
|
|
60
|
+
this.webSocketConnections = new Map();
|
|
61
|
+
this.messageCallbacks = new Map();
|
|
62
|
+
}
|
|
63
|
+
getType() {
|
|
64
|
+
return AugmentationType.CONDUIT;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Establishes a connection to another Brainy instance
|
|
68
|
+
* @param targetSystemId The URL or identifier of the target system
|
|
69
|
+
* @param config Configuration options for the connection
|
|
70
|
+
*/
|
|
71
|
+
async establishConnection(targetSystemId, config) {
|
|
72
|
+
await this.ensureInitialized();
|
|
73
|
+
try {
|
|
74
|
+
// For WebSocket connections, targetSystemId should be a WebSocket URL
|
|
75
|
+
const url = targetSystemId;
|
|
76
|
+
const protocols = config.protocols;
|
|
77
|
+
// Create a WebSocket connection
|
|
78
|
+
const connection = await this.connectWebSocket(url, protocols);
|
|
79
|
+
// Store the connection
|
|
80
|
+
this.connections.set(connection.connectionId, connection);
|
|
81
|
+
return {
|
|
82
|
+
success: true,
|
|
83
|
+
data: connection
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
console.error(`Failed to establish connection to ${targetSystemId}:`, error);
|
|
88
|
+
return {
|
|
89
|
+
success: false,
|
|
90
|
+
data: null,
|
|
91
|
+
error: `Failed to establish connection: ${error}`
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Reads data from a connected Brainy instance
|
|
97
|
+
* @param query Query parameters for reading data
|
|
98
|
+
* @param options Additional options
|
|
99
|
+
*/
|
|
100
|
+
async readData(query, options) {
|
|
101
|
+
await this.ensureInitialized();
|
|
102
|
+
try {
|
|
103
|
+
const connectionId = query.connectionId;
|
|
104
|
+
if (!connectionId) {
|
|
105
|
+
throw new Error('connectionId is required for reading data');
|
|
106
|
+
}
|
|
107
|
+
const connection = this.webSocketConnections.get(connectionId);
|
|
108
|
+
if (!connection) {
|
|
109
|
+
throw new Error(`Connection ${connectionId} not found`);
|
|
110
|
+
}
|
|
111
|
+
// Create a request message
|
|
112
|
+
const requestMessage = {
|
|
113
|
+
type: 'read',
|
|
114
|
+
query: query.query || {},
|
|
115
|
+
requestId: uuidv4(),
|
|
116
|
+
options
|
|
117
|
+
};
|
|
118
|
+
// Send the request
|
|
119
|
+
await this.sendWebSocketMessage(connectionId, requestMessage);
|
|
120
|
+
// Return a promise that will be resolved when the response is received
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
const responseHandler = (data) => {
|
|
123
|
+
// Check if this is the response to our request
|
|
124
|
+
const response = data;
|
|
125
|
+
if (response && response.type === 'readResponse' && response.requestId === requestMessage.requestId) {
|
|
126
|
+
// Remove the handler
|
|
127
|
+
this.offWebSocketMessage(connectionId, responseHandler);
|
|
128
|
+
// Resolve with the response data
|
|
129
|
+
resolve({
|
|
130
|
+
success: response.success,
|
|
131
|
+
data: response.data,
|
|
132
|
+
error: response.error
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
// Register the response handler
|
|
137
|
+
this.onWebSocketMessage(connectionId, responseHandler);
|
|
138
|
+
// Set a timeout to prevent hanging
|
|
139
|
+
setTimeout(() => {
|
|
140
|
+
this.offWebSocketMessage(connectionId, responseHandler);
|
|
141
|
+
resolve({
|
|
142
|
+
success: false,
|
|
143
|
+
data: null,
|
|
144
|
+
error: 'Timeout waiting for read response'
|
|
145
|
+
});
|
|
146
|
+
}, 30000); // 30 second timeout
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
console.error(`Failed to read data:`, error);
|
|
151
|
+
return {
|
|
152
|
+
success: false,
|
|
153
|
+
data: null,
|
|
154
|
+
error: `Failed to read data: ${error}`
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Writes data to a connected Brainy instance
|
|
160
|
+
* @param data The data to write
|
|
161
|
+
* @param options Additional options
|
|
162
|
+
*/
|
|
163
|
+
async writeData(data, options) {
|
|
164
|
+
await this.ensureInitialized();
|
|
165
|
+
try {
|
|
166
|
+
const connectionId = data.connectionId;
|
|
167
|
+
if (!connectionId) {
|
|
168
|
+
throw new Error('connectionId is required for writing data');
|
|
169
|
+
}
|
|
170
|
+
const connection = this.webSocketConnections.get(connectionId);
|
|
171
|
+
if (!connection) {
|
|
172
|
+
throw new Error(`Connection ${connectionId} not found`);
|
|
173
|
+
}
|
|
174
|
+
// Create a write message
|
|
175
|
+
const writeMessage = {
|
|
176
|
+
type: 'write',
|
|
177
|
+
data: data.data || {},
|
|
178
|
+
requestId: uuidv4(),
|
|
179
|
+
options
|
|
180
|
+
};
|
|
181
|
+
// Send the write message
|
|
182
|
+
await this.sendWebSocketMessage(connectionId, writeMessage);
|
|
183
|
+
// Return a promise that will be resolved when the response is received
|
|
184
|
+
return new Promise((resolve) => {
|
|
185
|
+
const responseHandler = (data) => {
|
|
186
|
+
// Check if this is the response to our request
|
|
187
|
+
const response = data;
|
|
188
|
+
if (response && response.type === 'writeResponse' && response.requestId === writeMessage.requestId) {
|
|
189
|
+
// Remove the handler
|
|
190
|
+
this.offWebSocketMessage(connectionId, responseHandler);
|
|
191
|
+
// Resolve with the response data
|
|
192
|
+
resolve({
|
|
193
|
+
success: response.success,
|
|
194
|
+
data: response.data,
|
|
195
|
+
error: response.error
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
// Register the response handler
|
|
200
|
+
this.onWebSocketMessage(connectionId, responseHandler);
|
|
201
|
+
// Set a timeout to prevent hanging
|
|
202
|
+
setTimeout(() => {
|
|
203
|
+
this.offWebSocketMessage(connectionId, responseHandler);
|
|
204
|
+
resolve({
|
|
205
|
+
success: false,
|
|
206
|
+
data: null,
|
|
207
|
+
error: 'Timeout waiting for write response'
|
|
208
|
+
});
|
|
209
|
+
}, 30000); // 30 second timeout
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
console.error(`Failed to write data:`, error);
|
|
214
|
+
return {
|
|
215
|
+
success: false,
|
|
216
|
+
data: null,
|
|
217
|
+
error: `Failed to write data: ${error}`
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Monitors a data stream from a connected Brainy instance
|
|
223
|
+
* @param streamId The ID of the stream to monitor (usually a connection ID)
|
|
224
|
+
* @param callback Function to call when new data is received
|
|
225
|
+
*/
|
|
226
|
+
async monitorStream(streamId, callback) {
|
|
227
|
+
await this.ensureInitialized();
|
|
228
|
+
try {
|
|
229
|
+
const connection = this.webSocketConnections.get(streamId);
|
|
230
|
+
if (!connection) {
|
|
231
|
+
throw new Error(`Connection ${streamId} not found`);
|
|
232
|
+
}
|
|
233
|
+
// Register the callback for all messages on this connection
|
|
234
|
+
await this.onWebSocketMessage(streamId, callback);
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
console.error(`Failed to monitor stream ${streamId}:`, error);
|
|
238
|
+
throw new Error(`Failed to monitor stream: ${error}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Establishes a WebSocket connection
|
|
243
|
+
* @param url The WebSocket server URL to connect to
|
|
244
|
+
* @param protocols Optional subprotocols
|
|
245
|
+
*/
|
|
246
|
+
async connectWebSocket(url, protocols) {
|
|
247
|
+
await this.ensureInitialized();
|
|
248
|
+
return new Promise((resolve, reject) => {
|
|
249
|
+
try {
|
|
250
|
+
// Check if WebSocket is available
|
|
251
|
+
if (typeof WebSocket === 'undefined') {
|
|
252
|
+
throw new Error('WebSocket is not available in this environment');
|
|
253
|
+
}
|
|
254
|
+
// Create a new WebSocket connection
|
|
255
|
+
const ws = new WebSocket(url, protocols);
|
|
256
|
+
const connectionId = uuidv4();
|
|
257
|
+
// Create a connection object
|
|
258
|
+
const connection = {
|
|
259
|
+
connectionId,
|
|
260
|
+
url,
|
|
261
|
+
status: 'disconnected',
|
|
262
|
+
send: async (data) => {
|
|
263
|
+
if (ws.readyState !== WebSocket.OPEN) {
|
|
264
|
+
throw new Error('WebSocket is not open');
|
|
265
|
+
}
|
|
266
|
+
ws.send(data);
|
|
267
|
+
},
|
|
268
|
+
close: async () => {
|
|
269
|
+
ws.close();
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
// Set up event handlers
|
|
273
|
+
ws.onopen = () => {
|
|
274
|
+
connection.status = 'connected';
|
|
275
|
+
resolve(connection);
|
|
276
|
+
};
|
|
277
|
+
ws.onerror = (error) => {
|
|
278
|
+
connection.status = 'error';
|
|
279
|
+
console.error(`WebSocket error for ${url}:`, error);
|
|
280
|
+
if (ws.readyState !== WebSocket.OPEN) {
|
|
281
|
+
reject(new Error(`WebSocket connection failed: ${error}`));
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
ws.onclose = () => {
|
|
285
|
+
connection.status = 'disconnected';
|
|
286
|
+
// Remove from connections map
|
|
287
|
+
this.webSocketConnections.delete(connectionId);
|
|
288
|
+
// Remove all callbacks
|
|
289
|
+
this.messageCallbacks.delete(connectionId);
|
|
290
|
+
};
|
|
291
|
+
// Create a message handler wrapper that will call all registered callbacks
|
|
292
|
+
const messageHandlerWrapper = (data) => {
|
|
293
|
+
const callbacks = this.messageCallbacks.get(connectionId);
|
|
294
|
+
if (callbacks) {
|
|
295
|
+
for (const callback of callbacks) {
|
|
296
|
+
try {
|
|
297
|
+
callback(data);
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
console.error(`Error in WebSocket message callback:`, error);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
// Store the message handler wrapper
|
|
306
|
+
connection._messageHandlerWrapper = messageHandlerWrapper;
|
|
307
|
+
// Set up the message handler
|
|
308
|
+
ws.onmessage = (event) => {
|
|
309
|
+
try {
|
|
310
|
+
// Parse the message if it's a string
|
|
311
|
+
let data = event.data;
|
|
312
|
+
if (typeof data === 'string') {
|
|
313
|
+
try {
|
|
314
|
+
data = JSON.parse(data);
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
// If parsing fails, use the raw string
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// Call the message handler wrapper
|
|
321
|
+
messageHandlerWrapper(data);
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
console.error(`Error handling WebSocket message:`, error);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
// Store the stream message handler
|
|
328
|
+
connection._streamMessageHandler = (event) => ws.onmessage && ws.onmessage(event);
|
|
329
|
+
// Store the connection
|
|
330
|
+
this.webSocketConnections.set(connectionId, connection);
|
|
331
|
+
// Initialize the callbacks set
|
|
332
|
+
this.messageCallbacks.set(connectionId, new Set());
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
reject(error);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Sends data through an established WebSocket connection
|
|
341
|
+
* @param connectionId The identifier of the established connection
|
|
342
|
+
* @param data The data to send (will be serialized if not a string)
|
|
343
|
+
*/
|
|
344
|
+
async sendWebSocketMessage(connectionId, data) {
|
|
345
|
+
await this.ensureInitialized();
|
|
346
|
+
const connection = this.webSocketConnections.get(connectionId);
|
|
347
|
+
if (!connection) {
|
|
348
|
+
throw new Error(`WebSocket connection ${connectionId} not found`);
|
|
349
|
+
}
|
|
350
|
+
if (!connection.send) {
|
|
351
|
+
throw new Error(`WebSocket connection ${connectionId} does not support sending messages`);
|
|
352
|
+
}
|
|
353
|
+
// Serialize the data if it's not already a string or binary
|
|
354
|
+
let serializedData;
|
|
355
|
+
if (typeof data === 'string' ||
|
|
356
|
+
data instanceof ArrayBuffer ||
|
|
357
|
+
data instanceof Blob ||
|
|
358
|
+
ArrayBuffer.isView(data)) {
|
|
359
|
+
serializedData = data;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
// Convert to JSON string
|
|
363
|
+
serializedData = JSON.stringify(data);
|
|
364
|
+
}
|
|
365
|
+
// Send the data
|
|
366
|
+
await connection.send(serializedData);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Registers a callback for incoming WebSocket messages
|
|
370
|
+
* @param connectionId The identifier of the established connection
|
|
371
|
+
* @param callback The function to call when a message is received
|
|
372
|
+
*/
|
|
373
|
+
async onWebSocketMessage(connectionId, callback) {
|
|
374
|
+
await this.ensureInitialized();
|
|
375
|
+
const connection = this.webSocketConnections.get(connectionId);
|
|
376
|
+
if (!connection) {
|
|
377
|
+
throw new Error(`WebSocket connection ${connectionId} not found`);
|
|
378
|
+
}
|
|
379
|
+
// Get or create the callbacks set for this connection
|
|
380
|
+
let callbacks = this.messageCallbacks.get(connectionId);
|
|
381
|
+
if (!callbacks) {
|
|
382
|
+
callbacks = new Set();
|
|
383
|
+
this.messageCallbacks.set(connectionId, callbacks);
|
|
384
|
+
}
|
|
385
|
+
// Add the callback
|
|
386
|
+
callbacks.add(callback);
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Removes a callback for incoming WebSocket messages
|
|
390
|
+
* @param connectionId The identifier of the established connection
|
|
391
|
+
* @param callback The function to remove from the callbacks
|
|
392
|
+
*/
|
|
393
|
+
async offWebSocketMessage(connectionId, callback) {
|
|
394
|
+
await this.ensureInitialized();
|
|
395
|
+
const callbacks = this.messageCallbacks.get(connectionId);
|
|
396
|
+
if (callbacks) {
|
|
397
|
+
callbacks.delete(callback);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Closes an established WebSocket connection
|
|
402
|
+
* @param connectionId The identifier of the established connection
|
|
403
|
+
* @param code Optional close code
|
|
404
|
+
* @param reason Optional close reason
|
|
405
|
+
*/
|
|
406
|
+
async closeWebSocket(connectionId, code, reason) {
|
|
407
|
+
await this.ensureInitialized();
|
|
408
|
+
const connection = this.webSocketConnections.get(connectionId);
|
|
409
|
+
if (!connection) {
|
|
410
|
+
throw new Error(`WebSocket connection ${connectionId} not found`);
|
|
411
|
+
}
|
|
412
|
+
if (!connection.close) {
|
|
413
|
+
throw new Error(`WebSocket connection ${connectionId} does not support closing`);
|
|
414
|
+
}
|
|
415
|
+
// Close the connection
|
|
416
|
+
await connection.close();
|
|
417
|
+
// Remove from connections map
|
|
418
|
+
this.webSocketConnections.delete(connectionId);
|
|
419
|
+
// Remove all callbacks
|
|
420
|
+
this.messageCallbacks.delete(connectionId);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* WebRTC conduit augmentation for syncing Brainy instances using WebRTC
|
|
425
|
+
*
|
|
426
|
+
* This conduit is for direct peer-to-peer syncing between browsers.
|
|
427
|
+
* It is the recommended approach for browser-to-browser communication.
|
|
428
|
+
*/
|
|
429
|
+
export class WebRTCConduitAugmentation extends BaseConduitAugmentation {
|
|
430
|
+
constructor(name = 'webrtc-conduit') {
|
|
431
|
+
super(name);
|
|
432
|
+
this.description = 'Conduit augmentation that syncs Brainy instances using WebRTC';
|
|
433
|
+
this.peerConnections = new Map();
|
|
434
|
+
this.dataChannels = new Map();
|
|
435
|
+
this.webSocketConnections = new Map();
|
|
436
|
+
this.messageCallbacks = new Map();
|
|
437
|
+
this.signalServer = null;
|
|
438
|
+
}
|
|
439
|
+
getType() {
|
|
440
|
+
return AugmentationType.CONDUIT;
|
|
441
|
+
}
|
|
442
|
+
async initialize() {
|
|
443
|
+
if (this.isInitialized) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
try {
|
|
447
|
+
// Check if WebRTC is available
|
|
448
|
+
if (typeof RTCPeerConnection === 'undefined') {
|
|
449
|
+
throw new Error('WebRTC is not available in this environment');
|
|
450
|
+
}
|
|
451
|
+
this.isInitialized = true;
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
console.error(`Failed to initialize ${this.name}:`, error);
|
|
455
|
+
throw new Error(`Failed to initialize ${this.name}: ${error}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Establishes a connection to another Brainy instance using WebRTC
|
|
460
|
+
* @param targetSystemId The peer ID or signal server URL
|
|
461
|
+
* @param config Configuration options for the connection
|
|
462
|
+
*/
|
|
463
|
+
async establishConnection(targetSystemId, config) {
|
|
464
|
+
await this.ensureInitialized();
|
|
465
|
+
try {
|
|
466
|
+
// For WebRTC, we need to:
|
|
467
|
+
// 1. Connect to a signaling server (if not already connected)
|
|
468
|
+
// 2. Create a peer connection
|
|
469
|
+
// 3. Create a data channel
|
|
470
|
+
// 4. Exchange ICE candidates and SDP offers/answers
|
|
471
|
+
// Check if we need to connect to a signaling server
|
|
472
|
+
if (!this.signalServer && config.signalServerUrl) {
|
|
473
|
+
// Connect to the signaling server
|
|
474
|
+
this.signalServer = await this.connectWebSocket(config.signalServerUrl);
|
|
475
|
+
// Set up message handling for the signaling server
|
|
476
|
+
await this.onWebSocketMessage(this.signalServer.connectionId, async (data) => {
|
|
477
|
+
// Handle signaling messages
|
|
478
|
+
const message = data;
|
|
479
|
+
if (message.type === 'ice-candidate' && message.targetPeerId === config.localPeerId) {
|
|
480
|
+
// Add ICE candidate to the appropriate peer connection
|
|
481
|
+
const peerConnection = this.peerConnections.get(message.sourcePeerId);
|
|
482
|
+
if (peerConnection) {
|
|
483
|
+
try {
|
|
484
|
+
await peerConnection.addIceCandidate(new RTCIceCandidate(message.candidate));
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
console.error(`Failed to add ICE candidate:`, error);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
else if (message.type === 'offer' && message.targetPeerId === config.localPeerId) {
|
|
492
|
+
// Handle incoming offer
|
|
493
|
+
await this.handleOffer(message.sourcePeerId, message.offer, config);
|
|
494
|
+
}
|
|
495
|
+
else if (message.type === 'answer' && message.targetPeerId === config.localPeerId) {
|
|
496
|
+
// Handle incoming answer
|
|
497
|
+
const peerConnection = this.peerConnections.get(message.sourcePeerId);
|
|
498
|
+
if (peerConnection) {
|
|
499
|
+
try {
|
|
500
|
+
await peerConnection.setRemoteDescription(new RTCSessionDescription(message.answer));
|
|
501
|
+
}
|
|
502
|
+
catch (error) {
|
|
503
|
+
console.error(`Failed to set remote description:`, error);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
// Create a peer connection
|
|
510
|
+
const peerConnection = new RTCPeerConnection({
|
|
511
|
+
iceServers: config.iceServers || [
|
|
512
|
+
{ urls: 'stun:stun.l.google.com:19302' }
|
|
513
|
+
]
|
|
514
|
+
});
|
|
515
|
+
// Generate a connection ID
|
|
516
|
+
const connectionId = uuidv4();
|
|
517
|
+
// Store the peer connection
|
|
518
|
+
this.peerConnections.set(targetSystemId, peerConnection);
|
|
519
|
+
// Create a data channel
|
|
520
|
+
const dataChannel = peerConnection.createDataChannel('brainy-sync', {
|
|
521
|
+
ordered: true
|
|
522
|
+
});
|
|
523
|
+
// Set up data channel event handlers
|
|
524
|
+
dataChannel.onopen = () => {
|
|
525
|
+
console.log(`Data channel to ${targetSystemId} opened`);
|
|
526
|
+
};
|
|
527
|
+
dataChannel.onclose = () => {
|
|
528
|
+
console.log(`Data channel to ${targetSystemId} closed`);
|
|
529
|
+
// Clean up
|
|
530
|
+
this.dataChannels.delete(targetSystemId);
|
|
531
|
+
this.peerConnections.delete(targetSystemId);
|
|
532
|
+
this.webSocketConnections.delete(connectionId);
|
|
533
|
+
this.messageCallbacks.delete(connectionId);
|
|
534
|
+
};
|
|
535
|
+
dataChannel.onerror = (error) => {
|
|
536
|
+
console.error(`Data channel error:`, error);
|
|
537
|
+
};
|
|
538
|
+
// Create a message handler wrapper that will call all registered callbacks
|
|
539
|
+
const messageHandlerWrapper = (data) => {
|
|
540
|
+
const callbacks = this.messageCallbacks.get(connectionId);
|
|
541
|
+
if (callbacks) {
|
|
542
|
+
for (const callback of callbacks) {
|
|
543
|
+
try {
|
|
544
|
+
callback(data);
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
console.error(`Error in WebRTC message callback:`, error);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
dataChannel.onmessage = (event) => {
|
|
553
|
+
try {
|
|
554
|
+
// Parse the message if it's a string
|
|
555
|
+
let data = event.data;
|
|
556
|
+
if (typeof data === 'string') {
|
|
557
|
+
try {
|
|
558
|
+
data = JSON.parse(data);
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
// If parsing fails, use the raw string
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
// Call the message handler wrapper
|
|
565
|
+
messageHandlerWrapper(data);
|
|
566
|
+
}
|
|
567
|
+
catch (error) {
|
|
568
|
+
console.error(`Error handling WebRTC message:`, error);
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
// Store the data channel
|
|
572
|
+
this.dataChannels.set(targetSystemId, dataChannel);
|
|
573
|
+
// Set up ICE candidate handling
|
|
574
|
+
peerConnection.onicecandidate = (event) => {
|
|
575
|
+
if (event.candidate && this.signalServer) {
|
|
576
|
+
// Send the ICE candidate to the peer via the signaling server
|
|
577
|
+
this.sendWebSocketMessage(this.signalServer.connectionId, {
|
|
578
|
+
type: 'ice-candidate',
|
|
579
|
+
sourcePeerId: config.localPeerId,
|
|
580
|
+
targetPeerId: targetSystemId,
|
|
581
|
+
candidate: event.candidate
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
// Create a WebSocket-like connection object for the WebRTC connection
|
|
586
|
+
const connection = {
|
|
587
|
+
connectionId,
|
|
588
|
+
url: `webrtc://${targetSystemId}`,
|
|
589
|
+
status: 'disconnected',
|
|
590
|
+
send: async (data) => {
|
|
591
|
+
const dc = this.dataChannels.get(targetSystemId);
|
|
592
|
+
if (!dc || dc.readyState !== 'open') {
|
|
593
|
+
throw new Error('WebRTC data channel is not open');
|
|
594
|
+
}
|
|
595
|
+
// Send the data
|
|
596
|
+
if (typeof data === 'string') {
|
|
597
|
+
dc.send(data);
|
|
598
|
+
}
|
|
599
|
+
else if (data instanceof Blob) {
|
|
600
|
+
dc.send(data);
|
|
601
|
+
}
|
|
602
|
+
else if (data instanceof ArrayBuffer) {
|
|
603
|
+
dc.send(new Uint8Array(data));
|
|
604
|
+
}
|
|
605
|
+
else if (ArrayBuffer.isView(data)) {
|
|
606
|
+
dc.send(data);
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
// Convert to JSON string
|
|
610
|
+
dc.send(JSON.stringify(data));
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
close: async () => {
|
|
614
|
+
const dc = this.dataChannels.get(targetSystemId);
|
|
615
|
+
if (dc) {
|
|
616
|
+
dc.close();
|
|
617
|
+
}
|
|
618
|
+
const pc = this.peerConnections.get(targetSystemId);
|
|
619
|
+
if (pc) {
|
|
620
|
+
pc.close();
|
|
621
|
+
}
|
|
622
|
+
// Clean up
|
|
623
|
+
this.dataChannels.delete(targetSystemId);
|
|
624
|
+
this.peerConnections.delete(targetSystemId);
|
|
625
|
+
this.webSocketConnections.delete(connectionId);
|
|
626
|
+
this.messageCallbacks.delete(connectionId);
|
|
627
|
+
},
|
|
628
|
+
_messageHandlerWrapper: messageHandlerWrapper
|
|
629
|
+
};
|
|
630
|
+
// Store the connection
|
|
631
|
+
this.webSocketConnections.set(connectionId, connection);
|
|
632
|
+
// Initialize the callbacks set
|
|
633
|
+
this.messageCallbacks.set(connectionId, new Set());
|
|
634
|
+
// Create and send an offer
|
|
635
|
+
const offer = await peerConnection.createOffer();
|
|
636
|
+
await peerConnection.setLocalDescription(offer);
|
|
637
|
+
// Send the offer to the peer via the signaling server
|
|
638
|
+
if (this.signalServer) {
|
|
639
|
+
await this.sendWebSocketMessage(this.signalServer.connectionId, {
|
|
640
|
+
type: 'offer',
|
|
641
|
+
sourcePeerId: config.localPeerId,
|
|
642
|
+
targetPeerId: targetSystemId,
|
|
643
|
+
offer
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
// Return the connection
|
|
647
|
+
return {
|
|
648
|
+
success: true,
|
|
649
|
+
data: connection
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
catch (error) {
|
|
653
|
+
console.error(`Failed to establish WebRTC connection to ${targetSystemId}:`, error);
|
|
654
|
+
return {
|
|
655
|
+
success: false,
|
|
656
|
+
data: null,
|
|
657
|
+
error: `Failed to establish WebRTC connection: ${error}`
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Handles an incoming WebRTC offer
|
|
663
|
+
* @param peerId The ID of the peer sending the offer
|
|
664
|
+
* @param offer The SDP offer
|
|
665
|
+
* @param config Configuration options
|
|
666
|
+
*/
|
|
667
|
+
async handleOffer(peerId, offer, config) {
|
|
668
|
+
try {
|
|
669
|
+
// Create a peer connection if it doesn't exist
|
|
670
|
+
let peerConnection = this.peerConnections.get(peerId);
|
|
671
|
+
if (!peerConnection) {
|
|
672
|
+
peerConnection = new RTCPeerConnection({
|
|
673
|
+
iceServers: config.iceServers || [
|
|
674
|
+
{ urls: 'stun:stun.l.google.com:19302' }
|
|
675
|
+
]
|
|
676
|
+
});
|
|
677
|
+
// Store the peer connection
|
|
678
|
+
this.peerConnections.set(peerId, peerConnection);
|
|
679
|
+
// Set up ICE candidate handling
|
|
680
|
+
peerConnection.onicecandidate = (event) => {
|
|
681
|
+
if (event.candidate && this.signalServer) {
|
|
682
|
+
// Send the ICE candidate to the peer via the signaling server
|
|
683
|
+
this.sendWebSocketMessage(this.signalServer.connectionId, {
|
|
684
|
+
type: 'ice-candidate',
|
|
685
|
+
sourcePeerId: config.localPeerId,
|
|
686
|
+
targetPeerId: peerId,
|
|
687
|
+
candidate: event.candidate
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
// Handle data channel creation by the remote peer
|
|
692
|
+
peerConnection.ondatachannel = (event) => {
|
|
693
|
+
const dataChannel = event.channel;
|
|
694
|
+
// Generate a connection ID
|
|
695
|
+
const connectionId = uuidv4();
|
|
696
|
+
// Store the data channel
|
|
697
|
+
this.dataChannels.set(peerId, dataChannel);
|
|
698
|
+
// Set up data channel event handlers
|
|
699
|
+
dataChannel.onopen = () => {
|
|
700
|
+
console.log(`Data channel from ${peerId} opened`);
|
|
701
|
+
};
|
|
702
|
+
dataChannel.onclose = () => {
|
|
703
|
+
console.log(`Data channel from ${peerId} closed`);
|
|
704
|
+
// Clean up
|
|
705
|
+
this.dataChannels.delete(peerId);
|
|
706
|
+
this.peerConnections.delete(peerId);
|
|
707
|
+
this.webSocketConnections.delete(connectionId);
|
|
708
|
+
this.messageCallbacks.delete(connectionId);
|
|
709
|
+
};
|
|
710
|
+
dataChannel.onerror = (error) => {
|
|
711
|
+
console.error(`Data channel error:`, error);
|
|
712
|
+
};
|
|
713
|
+
// Create a message handler wrapper that will call all registered callbacks
|
|
714
|
+
const messageHandlerWrapper = (data) => {
|
|
715
|
+
const callbacks = this.messageCallbacks.get(connectionId);
|
|
716
|
+
if (callbacks) {
|
|
717
|
+
for (const callback of callbacks) {
|
|
718
|
+
try {
|
|
719
|
+
callback(data);
|
|
720
|
+
}
|
|
721
|
+
catch (error) {
|
|
722
|
+
console.error(`Error in WebRTC message callback:`, error);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
dataChannel.onmessage = (event) => {
|
|
728
|
+
try {
|
|
729
|
+
// Parse the message if it's a string
|
|
730
|
+
let data = event.data;
|
|
731
|
+
if (typeof data === 'string') {
|
|
732
|
+
try {
|
|
733
|
+
data = JSON.parse(data);
|
|
734
|
+
}
|
|
735
|
+
catch {
|
|
736
|
+
// If parsing fails, use the raw string
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
// Call the message handler wrapper
|
|
740
|
+
messageHandlerWrapper(data);
|
|
741
|
+
}
|
|
742
|
+
catch (error) {
|
|
743
|
+
console.error(`Error handling WebRTC message:`, error);
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
// Create a WebSocket-like connection object for the WebRTC connection
|
|
747
|
+
const connection = {
|
|
748
|
+
connectionId,
|
|
749
|
+
url: `webrtc://${peerId}`,
|
|
750
|
+
status: 'disconnected',
|
|
751
|
+
send: async (data) => {
|
|
752
|
+
if (dataChannel.readyState !== 'open') {
|
|
753
|
+
throw new Error('WebRTC data channel is not open');
|
|
754
|
+
}
|
|
755
|
+
// Send the data
|
|
756
|
+
if (typeof data === 'string') {
|
|
757
|
+
dataChannel.send(data);
|
|
758
|
+
}
|
|
759
|
+
else if (data instanceof Blob) {
|
|
760
|
+
dataChannel.send(data);
|
|
761
|
+
}
|
|
762
|
+
else if (data instanceof ArrayBuffer) {
|
|
763
|
+
dataChannel.send(new Uint8Array(data));
|
|
764
|
+
}
|
|
765
|
+
else if (ArrayBuffer.isView(data)) {
|
|
766
|
+
dataChannel.send(data);
|
|
767
|
+
}
|
|
768
|
+
else {
|
|
769
|
+
// Convert to JSON string
|
|
770
|
+
dataChannel.send(JSON.stringify(data));
|
|
771
|
+
}
|
|
772
|
+
},
|
|
773
|
+
close: async () => {
|
|
774
|
+
dataChannel.close();
|
|
775
|
+
const pc = this.peerConnections.get(peerId);
|
|
776
|
+
if (pc) {
|
|
777
|
+
pc.close();
|
|
778
|
+
}
|
|
779
|
+
// Clean up
|
|
780
|
+
this.dataChannels.delete(peerId);
|
|
781
|
+
this.peerConnections.delete(peerId);
|
|
782
|
+
this.webSocketConnections.delete(connectionId);
|
|
783
|
+
this.messageCallbacks.delete(connectionId);
|
|
784
|
+
},
|
|
785
|
+
_messageHandlerWrapper: messageHandlerWrapper
|
|
786
|
+
};
|
|
787
|
+
// Store the connection
|
|
788
|
+
this.webSocketConnections.set(connectionId, connection);
|
|
789
|
+
// Initialize the callbacks set
|
|
790
|
+
this.messageCallbacks.set(connectionId, new Set());
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
// Set the remote description (the offer)
|
|
794
|
+
await peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
|
|
795
|
+
// Create an answer
|
|
796
|
+
const answer = await peerConnection.createAnswer();
|
|
797
|
+
await peerConnection.setLocalDescription(answer);
|
|
798
|
+
// Send the answer to the peer via the signaling server
|
|
799
|
+
if (this.signalServer) {
|
|
800
|
+
await this.sendWebSocketMessage(this.signalServer.connectionId, {
|
|
801
|
+
type: 'answer',
|
|
802
|
+
sourcePeerId: config.localPeerId,
|
|
803
|
+
targetPeerId: peerId,
|
|
804
|
+
answer
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
catch (error) {
|
|
809
|
+
console.error(`Failed to handle WebRTC offer:`, error);
|
|
810
|
+
throw new Error(`Failed to handle WebRTC offer: ${error}`);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Reads data from a connected Brainy instance
|
|
815
|
+
* @param query Query parameters for reading data
|
|
816
|
+
* @param options Additional options
|
|
817
|
+
*/
|
|
818
|
+
async readData(query, options) {
|
|
819
|
+
await this.ensureInitialized();
|
|
820
|
+
try {
|
|
821
|
+
const connectionId = query.connectionId;
|
|
822
|
+
if (!connectionId) {
|
|
823
|
+
throw new Error('connectionId is required for reading data');
|
|
824
|
+
}
|
|
825
|
+
const connection = this.webSocketConnections.get(connectionId);
|
|
826
|
+
if (!connection) {
|
|
827
|
+
throw new Error(`Connection ${connectionId} not found`);
|
|
828
|
+
}
|
|
829
|
+
// Create a request message
|
|
830
|
+
const requestMessage = {
|
|
831
|
+
type: 'read',
|
|
832
|
+
query: query.query || {},
|
|
833
|
+
requestId: uuidv4(),
|
|
834
|
+
options
|
|
835
|
+
};
|
|
836
|
+
// Send the request
|
|
837
|
+
await this.sendWebSocketMessage(connectionId, requestMessage);
|
|
838
|
+
// Return a promise that will be resolved when the response is received
|
|
839
|
+
return new Promise((resolve) => {
|
|
840
|
+
const responseHandler = (data) => {
|
|
841
|
+
// Check if this is the response to our request
|
|
842
|
+
const response = data;
|
|
843
|
+
if (response && response.type === 'readResponse' && response.requestId === requestMessage.requestId) {
|
|
844
|
+
// Remove the handler
|
|
845
|
+
this.offWebSocketMessage(connectionId, responseHandler);
|
|
846
|
+
// Resolve with the response data
|
|
847
|
+
resolve({
|
|
848
|
+
success: response.success,
|
|
849
|
+
data: response.data,
|
|
850
|
+
error: response.error
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
// Register the response handler
|
|
855
|
+
this.onWebSocketMessage(connectionId, responseHandler);
|
|
856
|
+
// Set a timeout to prevent hanging
|
|
857
|
+
setTimeout(() => {
|
|
858
|
+
this.offWebSocketMessage(connectionId, responseHandler);
|
|
859
|
+
resolve({
|
|
860
|
+
success: false,
|
|
861
|
+
data: null,
|
|
862
|
+
error: 'Timeout waiting for read response'
|
|
863
|
+
});
|
|
864
|
+
}, 30000); // 30 second timeout
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
catch (error) {
|
|
868
|
+
console.error(`Failed to read data:`, error);
|
|
869
|
+
return {
|
|
870
|
+
success: false,
|
|
871
|
+
data: null,
|
|
872
|
+
error: `Failed to read data: ${error}`
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Writes data to a connected Brainy instance
|
|
878
|
+
* @param data The data to write
|
|
879
|
+
* @param options Additional options
|
|
880
|
+
*/
|
|
881
|
+
async writeData(data, options) {
|
|
882
|
+
await this.ensureInitialized();
|
|
883
|
+
try {
|
|
884
|
+
const connectionId = data.connectionId;
|
|
885
|
+
if (!connectionId) {
|
|
886
|
+
throw new Error('connectionId is required for writing data');
|
|
887
|
+
}
|
|
888
|
+
const connection = this.webSocketConnections.get(connectionId);
|
|
889
|
+
if (!connection) {
|
|
890
|
+
throw new Error(`Connection ${connectionId} not found`);
|
|
891
|
+
}
|
|
892
|
+
// Create a write message
|
|
893
|
+
const writeMessage = {
|
|
894
|
+
type: 'write',
|
|
895
|
+
data: data.data || {},
|
|
896
|
+
requestId: uuidv4(),
|
|
897
|
+
options
|
|
898
|
+
};
|
|
899
|
+
// Send the write message
|
|
900
|
+
await this.sendWebSocketMessage(connectionId, writeMessage);
|
|
901
|
+
// Return a promise that will be resolved when the response is received
|
|
902
|
+
return new Promise((resolve) => {
|
|
903
|
+
const responseHandler = (data) => {
|
|
904
|
+
// Check if this is the response to our request
|
|
905
|
+
const response = data;
|
|
906
|
+
if (response && response.type === 'writeResponse' && response.requestId === writeMessage.requestId) {
|
|
907
|
+
// Remove the handler
|
|
908
|
+
this.offWebSocketMessage(connectionId, responseHandler);
|
|
909
|
+
// Resolve with the response data
|
|
910
|
+
resolve({
|
|
911
|
+
success: response.success,
|
|
912
|
+
data: response.data,
|
|
913
|
+
error: response.error
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
// Register the response handler
|
|
918
|
+
this.onWebSocketMessage(connectionId, responseHandler);
|
|
919
|
+
// Set a timeout to prevent hanging
|
|
920
|
+
setTimeout(() => {
|
|
921
|
+
this.offWebSocketMessage(connectionId, responseHandler);
|
|
922
|
+
resolve({
|
|
923
|
+
success: false,
|
|
924
|
+
data: null,
|
|
925
|
+
error: 'Timeout waiting for write response'
|
|
926
|
+
});
|
|
927
|
+
}, 30000); // 30 second timeout
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
catch (error) {
|
|
931
|
+
console.error(`Failed to write data:`, error);
|
|
932
|
+
return {
|
|
933
|
+
success: false,
|
|
934
|
+
data: null,
|
|
935
|
+
error: `Failed to write data: ${error}`
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Monitors a data stream from a connected Brainy instance
|
|
941
|
+
* @param streamId The ID of the stream to monitor (usually a connection ID)
|
|
942
|
+
* @param callback Function to call when new data is received
|
|
943
|
+
*/
|
|
944
|
+
async monitorStream(streamId, callback) {
|
|
945
|
+
await this.ensureInitialized();
|
|
946
|
+
try {
|
|
947
|
+
const connection = this.webSocketConnections.get(streamId);
|
|
948
|
+
if (!connection) {
|
|
949
|
+
throw new Error(`Connection ${streamId} not found`);
|
|
950
|
+
}
|
|
951
|
+
// Register the callback for all messages on this connection
|
|
952
|
+
await this.onWebSocketMessage(streamId, callback);
|
|
953
|
+
}
|
|
954
|
+
catch (error) {
|
|
955
|
+
console.error(`Failed to monitor stream ${streamId}:`, error);
|
|
956
|
+
throw new Error(`Failed to monitor stream: ${error}`);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Establishes a WebSocket connection (used for signaling in WebRTC)
|
|
961
|
+
* @param url The WebSocket server URL to connect to
|
|
962
|
+
* @param protocols Optional subprotocols
|
|
963
|
+
*/
|
|
964
|
+
async connectWebSocket(url, protocols) {
|
|
965
|
+
await this.ensureInitialized();
|
|
966
|
+
return new Promise((resolve, reject) => {
|
|
967
|
+
try {
|
|
968
|
+
// Check if WebSocket is available
|
|
969
|
+
if (typeof WebSocket === 'undefined') {
|
|
970
|
+
throw new Error('WebSocket is not available in this environment');
|
|
971
|
+
}
|
|
972
|
+
// Create a new WebSocket connection
|
|
973
|
+
const ws = new WebSocket(url, protocols);
|
|
974
|
+
const connectionId = uuidv4();
|
|
975
|
+
// Create a connection object
|
|
976
|
+
const connection = {
|
|
977
|
+
connectionId,
|
|
978
|
+
url,
|
|
979
|
+
status: 'disconnected',
|
|
980
|
+
send: async (data) => {
|
|
981
|
+
if (ws.readyState !== WebSocket.OPEN) {
|
|
982
|
+
throw new Error('WebSocket is not open');
|
|
983
|
+
}
|
|
984
|
+
ws.send(data);
|
|
985
|
+
},
|
|
986
|
+
close: async () => {
|
|
987
|
+
ws.close();
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
// Set up event handlers
|
|
991
|
+
ws.onopen = () => {
|
|
992
|
+
connection.status = 'connected';
|
|
993
|
+
resolve(connection);
|
|
994
|
+
};
|
|
995
|
+
ws.onerror = (error) => {
|
|
996
|
+
connection.status = 'error';
|
|
997
|
+
console.error(`WebSocket error for ${url}:`, error);
|
|
998
|
+
if (ws.readyState !== WebSocket.OPEN) {
|
|
999
|
+
reject(new Error(`WebSocket connection failed: ${error}`));
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
ws.onclose = () => {
|
|
1003
|
+
connection.status = 'disconnected';
|
|
1004
|
+
// Remove from connections map
|
|
1005
|
+
this.webSocketConnections.delete(connectionId);
|
|
1006
|
+
// Remove all callbacks
|
|
1007
|
+
this.messageCallbacks.delete(connectionId);
|
|
1008
|
+
};
|
|
1009
|
+
// Create a message handler wrapper that will call all registered callbacks
|
|
1010
|
+
const messageHandlerWrapper = (data) => {
|
|
1011
|
+
const callbacks = this.messageCallbacks.get(connectionId);
|
|
1012
|
+
if (callbacks) {
|
|
1013
|
+
for (const callback of callbacks) {
|
|
1014
|
+
try {
|
|
1015
|
+
callback(data);
|
|
1016
|
+
}
|
|
1017
|
+
catch (error) {
|
|
1018
|
+
console.error(`Error in WebSocket message callback:`, error);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
// Store the message handler wrapper
|
|
1024
|
+
connection._messageHandlerWrapper = messageHandlerWrapper;
|
|
1025
|
+
// Set up the message handler
|
|
1026
|
+
ws.onmessage = (event) => {
|
|
1027
|
+
try {
|
|
1028
|
+
// Parse the message if it's a string
|
|
1029
|
+
let data = event.data;
|
|
1030
|
+
if (typeof data === 'string') {
|
|
1031
|
+
try {
|
|
1032
|
+
data = JSON.parse(data);
|
|
1033
|
+
}
|
|
1034
|
+
catch {
|
|
1035
|
+
// If parsing fails, use the raw string
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
// Call the message handler wrapper
|
|
1039
|
+
messageHandlerWrapper(data);
|
|
1040
|
+
}
|
|
1041
|
+
catch (error) {
|
|
1042
|
+
console.error(`Error handling WebSocket message:`, error);
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
// Store the stream message handler
|
|
1046
|
+
connection._streamMessageHandler = (event) => ws.onmessage && ws.onmessage(event);
|
|
1047
|
+
// Store the connection
|
|
1048
|
+
this.webSocketConnections.set(connectionId, connection);
|
|
1049
|
+
// Initialize the callbacks set
|
|
1050
|
+
this.messageCallbacks.set(connectionId, new Set());
|
|
1051
|
+
}
|
|
1052
|
+
catch (error) {
|
|
1053
|
+
reject(error);
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Sends data through an established WebSocket or WebRTC connection
|
|
1059
|
+
* @param connectionId The identifier of the established connection
|
|
1060
|
+
* @param data The data to send (will be serialized if not a string)
|
|
1061
|
+
*/
|
|
1062
|
+
async sendWebSocketMessage(connectionId, data) {
|
|
1063
|
+
await this.ensureInitialized();
|
|
1064
|
+
const connection = this.webSocketConnections.get(connectionId);
|
|
1065
|
+
if (!connection) {
|
|
1066
|
+
throw new Error(`Connection ${connectionId} not found`);
|
|
1067
|
+
}
|
|
1068
|
+
if (!connection.send) {
|
|
1069
|
+
throw new Error(`Connection ${connectionId} does not support sending messages`);
|
|
1070
|
+
}
|
|
1071
|
+
// Serialize the data if it's not already a string or binary
|
|
1072
|
+
let serializedData;
|
|
1073
|
+
if (typeof data === 'string' ||
|
|
1074
|
+
data instanceof ArrayBuffer ||
|
|
1075
|
+
data instanceof Blob ||
|
|
1076
|
+
ArrayBuffer.isView(data)) {
|
|
1077
|
+
serializedData = data;
|
|
1078
|
+
}
|
|
1079
|
+
else {
|
|
1080
|
+
// Convert to JSON string
|
|
1081
|
+
serializedData = JSON.stringify(data);
|
|
1082
|
+
}
|
|
1083
|
+
// Send the data
|
|
1084
|
+
await connection.send(serializedData);
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Registers a callback for incoming WebSocket or WebRTC messages
|
|
1088
|
+
* @param connectionId The identifier of the established connection
|
|
1089
|
+
* @param callback The function to call when a message is received
|
|
1090
|
+
*/
|
|
1091
|
+
async onWebSocketMessage(connectionId, callback) {
|
|
1092
|
+
await this.ensureInitialized();
|
|
1093
|
+
const connection = this.webSocketConnections.get(connectionId);
|
|
1094
|
+
if (!connection) {
|
|
1095
|
+
throw new Error(`Connection ${connectionId} not found`);
|
|
1096
|
+
}
|
|
1097
|
+
// Get or create the callbacks set for this connection
|
|
1098
|
+
let callbacks = this.messageCallbacks.get(connectionId);
|
|
1099
|
+
if (!callbacks) {
|
|
1100
|
+
callbacks = new Set();
|
|
1101
|
+
this.messageCallbacks.set(connectionId, callbacks);
|
|
1102
|
+
}
|
|
1103
|
+
// Add the callback
|
|
1104
|
+
callbacks.add(callback);
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Removes a callback for incoming WebSocket or WebRTC messages
|
|
1108
|
+
* @param connectionId The identifier of the established connection
|
|
1109
|
+
* @param callback The function to remove from the callbacks
|
|
1110
|
+
*/
|
|
1111
|
+
async offWebSocketMessage(connectionId, callback) {
|
|
1112
|
+
await this.ensureInitialized();
|
|
1113
|
+
const callbacks = this.messageCallbacks.get(connectionId);
|
|
1114
|
+
if (callbacks) {
|
|
1115
|
+
callbacks.delete(callback);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Closes an established WebSocket or WebRTC connection
|
|
1120
|
+
* @param connectionId The identifier of the established connection
|
|
1121
|
+
* @param code Optional close code
|
|
1122
|
+
* @param reason Optional close reason
|
|
1123
|
+
*/
|
|
1124
|
+
async closeWebSocket(connectionId, code, reason) {
|
|
1125
|
+
await this.ensureInitialized();
|
|
1126
|
+
const connection = this.webSocketConnections.get(connectionId);
|
|
1127
|
+
if (!connection) {
|
|
1128
|
+
throw new Error(`Connection ${connectionId} not found`);
|
|
1129
|
+
}
|
|
1130
|
+
if (!connection.close) {
|
|
1131
|
+
throw new Error(`Connection ${connectionId} does not support closing`);
|
|
1132
|
+
}
|
|
1133
|
+
// Close the connection
|
|
1134
|
+
await connection.close();
|
|
1135
|
+
// Remove from connections map
|
|
1136
|
+
this.webSocketConnections.delete(connectionId);
|
|
1137
|
+
// Remove all callbacks
|
|
1138
|
+
this.messageCallbacks.delete(connectionId);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Factory function to create the appropriate conduit augmentation based on the type
|
|
1143
|
+
*/
|
|
1144
|
+
export async function createConduitAugmentation(type, name, options = {}) {
|
|
1145
|
+
switch (type) {
|
|
1146
|
+
case 'websocket':
|
|
1147
|
+
const wsAugmentation = new WebSocketConduitAugmentation(name || 'websocket-conduit');
|
|
1148
|
+
await wsAugmentation.initialize();
|
|
1149
|
+
return wsAugmentation;
|
|
1150
|
+
case 'webrtc':
|
|
1151
|
+
const webrtcAugmentation = new WebRTCConduitAugmentation(name || 'webrtc-conduit');
|
|
1152
|
+
await webrtcAugmentation.initialize();
|
|
1153
|
+
return webrtcAugmentation;
|
|
1154
|
+
default:
|
|
1155
|
+
throw new Error(`Unknown conduit augmentation type: ${type}`);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
//# sourceMappingURL=conduitAugmentations.js.map
|