@soulcraft/brainy 1.5.0 → 2.0.1
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/CHANGELOG.md +188 -0
- package/LICENSE +2 -2
- package/README.md +200 -595
- package/bin/brainy-interactive.js +564 -0
- package/bin/brainy-ts.js +18 -0
- package/bin/brainy.js +672 -81
- package/dist/augmentationPipeline.d.ts +48 -220
- package/dist/augmentationPipeline.js +60 -508
- package/dist/augmentationRegistry.d.ts +22 -31
- package/dist/augmentationRegistry.js +28 -79
- package/dist/augmentations/apiServerAugmentation.d.ts +108 -0
- package/dist/augmentations/apiServerAugmentation.js +502 -0
- package/dist/augmentations/batchProcessingAugmentation.d.ts +95 -0
- package/dist/augmentations/batchProcessingAugmentation.js +567 -0
- package/dist/augmentations/brainyAugmentation.d.ts +153 -0
- package/dist/augmentations/brainyAugmentation.js +145 -0
- package/dist/augmentations/cacheAugmentation.d.ts +105 -0
- package/dist/augmentations/cacheAugmentation.js +238 -0
- package/dist/augmentations/conduitAugmentations.d.ts +54 -156
- package/dist/augmentations/conduitAugmentations.js +156 -1082
- package/dist/augmentations/connectionPoolAugmentation.d.ts +62 -0
- package/dist/augmentations/connectionPoolAugmentation.js +316 -0
- package/dist/augmentations/defaultAugmentations.d.ts +53 -0
- package/dist/augmentations/defaultAugmentations.js +88 -0
- package/dist/augmentations/entityRegistryAugmentation.d.ts +126 -0
- package/dist/augmentations/entityRegistryAugmentation.js +386 -0
- package/dist/augmentations/indexAugmentation.d.ts +117 -0
- package/dist/augmentations/indexAugmentation.js +284 -0
- package/dist/augmentations/intelligentVerbScoringAugmentation.d.ts +152 -0
- package/dist/augmentations/intelligentVerbScoringAugmentation.js +554 -0
- package/dist/augmentations/metricsAugmentation.d.ts +202 -0
- package/dist/augmentations/metricsAugmentation.js +291 -0
- package/dist/augmentations/monitoringAugmentation.d.ts +94 -0
- package/dist/augmentations/monitoringAugmentation.js +227 -0
- package/dist/augmentations/neuralImport.d.ts +50 -117
- package/dist/augmentations/neuralImport.js +255 -629
- package/dist/augmentations/requestDeduplicatorAugmentation.d.ts +52 -0
- package/dist/augmentations/requestDeduplicatorAugmentation.js +162 -0
- package/dist/augmentations/serverSearchAugmentations.d.ts +43 -22
- package/dist/augmentations/serverSearchAugmentations.js +125 -72
- package/dist/augmentations/storageAugmentation.d.ts +54 -0
- package/dist/augmentations/storageAugmentation.js +93 -0
- package/dist/augmentations/storageAugmentations.d.ts +96 -0
- package/dist/augmentations/storageAugmentations.js +182 -0
- package/dist/augmentations/synapseAugmentation.d.ts +156 -0
- package/dist/augmentations/synapseAugmentation.js +312 -0
- package/dist/augmentations/walAugmentation.d.ts +108 -0
- package/dist/augmentations/walAugmentation.js +515 -0
- package/dist/brainyData.d.ts +404 -130
- package/dist/brainyData.js +1331 -853
- package/dist/chat/BrainyChat.d.ts +16 -8
- package/dist/chat/BrainyChat.js +60 -32
- package/dist/chat/ChatCLI.d.ts +1 -1
- package/dist/chat/ChatCLI.js +6 -6
- package/dist/cli/catalog.d.ts +3 -3
- package/dist/cli/catalog.js +116 -70
- package/dist/cli/commands/core.d.ts +61 -0
- package/dist/cli/commands/core.js +348 -0
- package/dist/cli/commands/neural.d.ts +25 -0
- package/dist/cli/commands/neural.js +508 -0
- package/dist/cli/commands/utility.d.ts +37 -0
- package/dist/cli/commands/utility.js +276 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.js +167 -0
- package/dist/cli/interactive.d.ts +164 -0
- package/dist/cli/interactive.js +542 -0
- package/dist/cortex/neuralImport.js +5 -5
- package/dist/critical/model-guardian.js +11 -4
- package/dist/embeddings/lightweight-embedder.d.ts +23 -0
- package/dist/embeddings/lightweight-embedder.js +136 -0
- package/dist/embeddings/universal-memory-manager.d.ts +38 -0
- package/dist/embeddings/universal-memory-manager.js +206 -0
- package/dist/embeddings/worker-embedding.d.ts +7 -0
- package/dist/embeddings/worker-embedding.js +77 -0
- package/dist/embeddings/worker-manager.d.ts +28 -0
- package/dist/embeddings/worker-manager.js +162 -0
- package/dist/examples/basicUsage.js +7 -7
- package/dist/graph/pathfinding.d.ts +78 -0
- package/dist/graph/pathfinding.js +393 -0
- package/dist/hnsw/hnswIndex.d.ts +13 -0
- package/dist/hnsw/hnswIndex.js +35 -0
- package/dist/hnsw/hnswIndexOptimized.d.ts +1 -0
- package/dist/hnsw/hnswIndexOptimized.js +3 -0
- package/dist/index.d.ts +9 -11
- package/dist/index.js +21 -11
- package/dist/indices/fieldIndex.d.ts +76 -0
- package/dist/indices/fieldIndex.js +357 -0
- package/dist/mcp/brainyMCPAdapter.js +3 -2
- package/dist/mcp/mcpAugmentationToolset.js +11 -17
- package/dist/neural/embeddedPatterns.d.ts +41 -0
- package/dist/neural/embeddedPatterns.js +4044 -0
- package/dist/neural/naturalLanguageProcessor.d.ts +94 -0
- package/dist/neural/naturalLanguageProcessor.js +317 -0
- package/dist/neural/naturalLanguageProcessorStatic.d.ts +64 -0
- package/dist/neural/naturalLanguageProcessorStatic.js +151 -0
- package/dist/neural/neuralAPI.d.ts +255 -0
- package/dist/neural/neuralAPI.js +612 -0
- package/dist/neural/patternLibrary.d.ts +101 -0
- package/dist/neural/patternLibrary.js +313 -0
- package/dist/neural/patterns.d.ts +27 -0
- package/dist/neural/patterns.js +68 -0
- package/dist/neural/staticPatternMatcher.d.ts +35 -0
- package/dist/neural/staticPatternMatcher.js +153 -0
- package/dist/scripts/precomputePatternEmbeddings.d.ts +19 -0
- package/dist/scripts/precomputePatternEmbeddings.js +100 -0
- package/dist/storage/adapters/fileSystemStorage.d.ts +5 -0
- package/dist/storage/adapters/fileSystemStorage.js +20 -0
- package/dist/storage/adapters/s3CompatibleStorage.d.ts +5 -0
- package/dist/storage/adapters/s3CompatibleStorage.js +16 -0
- package/dist/storage/enhancedClearOperations.d.ts +83 -0
- package/dist/storage/enhancedClearOperations.js +345 -0
- package/dist/storage/storageFactory.js +31 -27
- package/dist/triple/TripleIntelligence.d.ts +134 -0
- package/dist/triple/TripleIntelligence.js +548 -0
- package/dist/types/augmentations.d.ts +45 -344
- package/dist/types/augmentations.js +5 -2
- package/dist/types/brainyDataInterface.d.ts +20 -10
- package/dist/types/graphTypes.d.ts +46 -0
- package/dist/types/graphTypes.js +16 -2
- package/dist/utils/BoundedRegistry.d.ts +29 -0
- package/dist/utils/BoundedRegistry.js +54 -0
- package/dist/utils/embedding.js +20 -3
- package/dist/utils/hybridModelManager.js +10 -5
- package/dist/utils/metadataFilter.d.ts +33 -19
- package/dist/utils/metadataFilter.js +58 -23
- package/dist/utils/metadataIndex.d.ts +37 -6
- package/dist/utils/metadataIndex.js +427 -64
- package/dist/utils/requestDeduplicator.d.ts +10 -0
- package/dist/utils/requestDeduplicator.js +24 -0
- package/dist/utils/unifiedCache.d.ts +103 -0
- package/dist/utils/unifiedCache.js +311 -0
- package/package.json +40 -125
- package/scripts/ensure-models.js +108 -0
- package/scripts/prepare-models.js +387 -0
- package/OFFLINE_MODELS.md +0 -56
- package/dist/intelligence/neuralEngine.d.ts +0 -207
- package/dist/intelligence/neuralEngine.js +0 -706
- package/dist/utils/modelLoader.d.ts +0 -32
- package/dist/utils/modelLoader.js +0 -219
- package/dist/utils/modelManager.d.ts +0 -77
- package/dist/utils/modelManager.js +0 -219
|
@@ -1,29 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Conduit Augmentations - Data Synchronization Bridges
|
|
3
|
+
*
|
|
4
|
+
* These augmentations connect and synchronize data between multiple Brainy instances.
|
|
5
|
+
* Now using the unified BrainyAugmentation interface.
|
|
6
|
+
*/
|
|
7
|
+
import { BaseAugmentation } from './brainyAugmentation.js';
|
|
2
8
|
import { v4 as uuidv4 } from '../universal/uuid.js';
|
|
3
9
|
/**
|
|
4
|
-
* Base class for conduit augmentations that
|
|
10
|
+
* Base class for conduit augmentations that sync between Brainy instances
|
|
11
|
+
* Converted to use the unified BrainyAugmentation interface
|
|
5
12
|
*/
|
|
6
|
-
class BaseConduitAugmentation {
|
|
7
|
-
constructor(
|
|
8
|
-
|
|
9
|
-
this.
|
|
10
|
-
this.
|
|
13
|
+
class BaseConduitAugmentation extends BaseAugmentation {
|
|
14
|
+
constructor() {
|
|
15
|
+
super(...arguments);
|
|
16
|
+
this.timing = 'after'; // Conduits run after operations to sync
|
|
17
|
+
this.operations = ['addNoun', 'delete', 'addVerb'];
|
|
18
|
+
this.priority = 20; // Medium-low priority
|
|
11
19
|
this.connections = new Map();
|
|
12
|
-
this.name = name;
|
|
13
20
|
}
|
|
14
|
-
async
|
|
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() {
|
|
21
|
+
async onShutdown() {
|
|
27
22
|
// Close all connections
|
|
28
23
|
for (const [connectionId, connection] of this.connections.entries()) {
|
|
29
24
|
try {
|
|
@@ -32,1127 +27,206 @@ class BaseConduitAugmentation {
|
|
|
32
27
|
}
|
|
33
28
|
}
|
|
34
29
|
catch (error) {
|
|
35
|
-
|
|
30
|
+
this.log(`Failed to close connection ${connectionId}: ${error}`, 'error');
|
|
36
31
|
}
|
|
37
32
|
}
|
|
38
33
|
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
34
|
}
|
|
49
35
|
}
|
|
50
36
|
/**
|
|
51
|
-
* WebSocket
|
|
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.
|
|
37
|
+
* WebSocket Conduit Augmentation
|
|
38
|
+
* Syncs data between Brainy instances using WebSockets
|
|
55
39
|
*/
|
|
56
40
|
export class WebSocketConduitAugmentation extends BaseConduitAugmentation {
|
|
57
|
-
constructor(
|
|
58
|
-
super(
|
|
59
|
-
this.
|
|
41
|
+
constructor() {
|
|
42
|
+
super(...arguments);
|
|
43
|
+
this.name = 'websocket-conduit';
|
|
60
44
|
this.webSocketConnections = new Map();
|
|
61
45
|
this.messageCallbacks = new Map();
|
|
62
46
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
};
|
|
47
|
+
async execute(operation, params, next) {
|
|
48
|
+
// Execute the operation first
|
|
49
|
+
const result = await next();
|
|
50
|
+
// Then sync to connected instances
|
|
51
|
+
if (this.shouldSync(operation)) {
|
|
52
|
+
await this.syncOperation(operation, params, result);
|
|
93
53
|
}
|
|
54
|
+
return result;
|
|
94
55
|
}
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
}
|
|
56
|
+
shouldSync(operation) {
|
|
57
|
+
return ['addNoun', 'deleteNoun', 'addVerb'].includes(operation);
|
|
157
58
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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'
|
|
59
|
+
async syncOperation(operation, params, result) {
|
|
60
|
+
// Broadcast to all connected WebSocket instances
|
|
61
|
+
for (const [id, connection] of this.webSocketConnections) {
|
|
62
|
+
if (connection.socket && connection.readyState === 1) { // OPEN state
|
|
63
|
+
try {
|
|
64
|
+
const message = JSON.stringify({
|
|
65
|
+
type: 'sync',
|
|
66
|
+
operation,
|
|
67
|
+
params,
|
|
68
|
+
timestamp: Date.now()
|
|
208
69
|
});
|
|
209
|
-
|
|
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}`));
|
|
70
|
+
if (typeof connection.socket.send === 'function') {
|
|
71
|
+
connection.socket.send(message);
|
|
282
72
|
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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');
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
this.log(`Failed to sync to ${id}: ${error}`, 'error');
|
|
76
|
+
}
|
|
450
77
|
}
|
|
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
78
|
}
|
|
457
79
|
}
|
|
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();
|
|
80
|
+
async establishConnection(url, config) {
|
|
465
81
|
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
82
|
const connectionId = uuidv4();
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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) => {
|
|
83
|
+
const protocols = config?.protocols;
|
|
84
|
+
// Create WebSocket based on environment
|
|
85
|
+
let socket;
|
|
86
|
+
if (typeof WebSocket !== 'undefined') {
|
|
87
|
+
// Browser environment
|
|
88
|
+
socket = new WebSocket(url, protocols);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Node.js environment - dynamic import
|
|
553
92
|
try {
|
|
554
|
-
|
|
555
|
-
|
|
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);
|
|
93
|
+
const ws = await import('ws');
|
|
94
|
+
socket = new ws.WebSocket(url, protocols);
|
|
566
95
|
}
|
|
567
|
-
catch
|
|
568
|
-
|
|
96
|
+
catch {
|
|
97
|
+
this.log('WebSocket not available in this environment', 'error');
|
|
98
|
+
return null;
|
|
569
99
|
}
|
|
100
|
+
}
|
|
101
|
+
// Setup event handlers
|
|
102
|
+
socket.onopen = () => {
|
|
103
|
+
this.log(`Connected to ${url}`);
|
|
570
104
|
};
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
targetPeerId: targetSystemId,
|
|
581
|
-
candidate: event.candidate
|
|
582
|
-
});
|
|
583
|
-
}
|
|
105
|
+
socket.onmessage = (event) => {
|
|
106
|
+
this.handleMessage(connectionId, event.data);
|
|
107
|
+
};
|
|
108
|
+
socket.onerror = (error) => {
|
|
109
|
+
this.log(`WebSocket error: ${error}`, 'error');
|
|
110
|
+
};
|
|
111
|
+
socket.onclose = () => {
|
|
112
|
+
this.log(`Disconnected from ${url}`);
|
|
113
|
+
this.webSocketConnections.delete(connectionId);
|
|
584
114
|
};
|
|
585
|
-
//
|
|
115
|
+
// Wait for connection to open
|
|
116
|
+
await new Promise((resolve, reject) => {
|
|
117
|
+
const timeout = setTimeout(() => {
|
|
118
|
+
reject(new Error('Connection timeout'));
|
|
119
|
+
}, 5000);
|
|
120
|
+
socket.onopen = () => {
|
|
121
|
+
clearTimeout(timeout);
|
|
122
|
+
resolve();
|
|
123
|
+
};
|
|
124
|
+
socket.onerror = (error) => {
|
|
125
|
+
clearTimeout(timeout);
|
|
126
|
+
reject(error);
|
|
127
|
+
};
|
|
128
|
+
});
|
|
586
129
|
const connection = {
|
|
587
130
|
connectionId,
|
|
588
|
-
url
|
|
589
|
-
|
|
590
|
-
|
|
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
|
|
131
|
+
url,
|
|
132
|
+
readyState: socket.readyState,
|
|
133
|
+
socket
|
|
629
134
|
};
|
|
630
|
-
// Store the connection
|
|
631
135
|
this.webSocketConnections.set(connectionId, connection);
|
|
632
|
-
|
|
633
|
-
|
|
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
|
-
};
|
|
136
|
+
this.connections.set(connectionId, connection);
|
|
137
|
+
return connection;
|
|
651
138
|
}
|
|
652
139
|
catch (error) {
|
|
653
|
-
|
|
654
|
-
return
|
|
655
|
-
success: false,
|
|
656
|
-
data: null,
|
|
657
|
-
error: `Failed to establish WebRTC connection: ${error}`
|
|
658
|
-
};
|
|
140
|
+
this.log(`Failed to establish connection to ${url}: ${error}`, 'error');
|
|
141
|
+
return null;
|
|
659
142
|
}
|
|
660
143
|
}
|
|
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) {
|
|
144
|
+
handleMessage(connectionId, data) {
|
|
668
145
|
try {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
if (
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
]
|
|
146
|
+
const message = typeof data === 'string' ? JSON.parse(data) : data;
|
|
147
|
+
// Handle sync messages from remote instances
|
|
148
|
+
if (message.type === 'sync') {
|
|
149
|
+
// Apply the operation to our local instance
|
|
150
|
+
this.applySyncOperation(message).catch(error => {
|
|
151
|
+
this.log(`Failed to apply sync operation: ${error}`, 'error');
|
|
676
152
|
});
|
|
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
153
|
}
|
|
793
|
-
//
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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
|
-
});
|
|
154
|
+
// Notify any registered callbacks
|
|
155
|
+
const callbacks = this.messageCallbacks.get(connectionId);
|
|
156
|
+
if (callbacks) {
|
|
157
|
+
callbacks.forEach(callback => callback(message));
|
|
806
158
|
}
|
|
807
159
|
}
|
|
808
160
|
catch (error) {
|
|
809
|
-
|
|
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
|
-
};
|
|
161
|
+
this.log(`Failed to handle message: ${error}`, 'error');
|
|
874
162
|
}
|
|
875
163
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
* @param options Additional options
|
|
880
|
-
*/
|
|
881
|
-
async writeData(data, options) {
|
|
882
|
-
await this.ensureInitialized();
|
|
164
|
+
async applySyncOperation(message) {
|
|
165
|
+
// Apply the synced operation to our local Brainy instance
|
|
166
|
+
const { operation, params } = message;
|
|
883
167
|
try {
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
168
|
+
switch (operation) {
|
|
169
|
+
case 'addNoun':
|
|
170
|
+
await this.context?.brain.addNoun(params.content, params.metadata);
|
|
171
|
+
break;
|
|
172
|
+
case 'deleteNoun':
|
|
173
|
+
await this.context?.brain.deleteNoun(params.id);
|
|
174
|
+
break;
|
|
175
|
+
case 'addVerb':
|
|
176
|
+
await this.context?.brain.addVerb(params.source, params.target, params.verb, params.metadata);
|
|
177
|
+
break;
|
|
891
178
|
}
|
|
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
179
|
}
|
|
930
180
|
catch (error) {
|
|
931
|
-
|
|
932
|
-
return {
|
|
933
|
-
success: false,
|
|
934
|
-
data: null,
|
|
935
|
-
error: `Failed to write data: ${error}`
|
|
936
|
-
};
|
|
181
|
+
this.log(`Failed to apply ${operation}: ${error}`, 'error');
|
|
937
182
|
}
|
|
938
183
|
}
|
|
939
184
|
/**
|
|
940
|
-
*
|
|
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
|
|
185
|
+
* Subscribe to messages from a specific connection
|
|
943
186
|
*/
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
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}`);
|
|
187
|
+
onMessage(connectionId, callback) {
|
|
188
|
+
if (!this.messageCallbacks.has(connectionId)) {
|
|
189
|
+
this.messageCallbacks.set(connectionId, new Set());
|
|
957
190
|
}
|
|
191
|
+
this.messageCallbacks.get(connectionId).add(callback);
|
|
958
192
|
}
|
|
959
193
|
/**
|
|
960
|
-
*
|
|
961
|
-
* @param url The WebSocket server URL to connect to
|
|
962
|
-
* @param protocols Optional subprotocols
|
|
194
|
+
* Send a message to a specific connection
|
|
963
195
|
*/
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
196
|
+
sendMessage(connectionId, data) {
|
|
197
|
+
const connection = this.webSocketConnections.get(connectionId);
|
|
198
|
+
if (connection?.socket && connection.readyState === 1) {
|
|
967
199
|
try {
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
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());
|
|
200
|
+
const message = typeof data === 'string' ? data : JSON.stringify(data);
|
|
201
|
+
connection.socket.send(message);
|
|
202
|
+
return true;
|
|
1051
203
|
}
|
|
1052
204
|
catch (error) {
|
|
1053
|
-
|
|
205
|
+
this.log(`Failed to send message: ${error}`, 'error');
|
|
1054
206
|
}
|
|
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
207
|
}
|
|
1071
|
-
|
|
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);
|
|
208
|
+
return false;
|
|
1139
209
|
}
|
|
1140
210
|
}
|
|
1141
211
|
/**
|
|
1142
|
-
*
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
212
|
+
* Example usage:
|
|
213
|
+
*
|
|
214
|
+
* // Server instance
|
|
215
|
+
* const serverBrain = new BrainyData()
|
|
216
|
+
* serverBrain.augmentations.register(new APIServerAugmentation())
|
|
217
|
+
* await serverBrain.init()
|
|
218
|
+
*
|
|
219
|
+
* // Client instance
|
|
220
|
+
* const clientBrain = new BrainyData()
|
|
221
|
+
* const conduit = new WebSocketConduitAugmentation()
|
|
222
|
+
* clientBrain.augmentations.register(conduit)
|
|
223
|
+
* await clientBrain.init()
|
|
224
|
+
*
|
|
225
|
+
* // Connect client to server
|
|
226
|
+
* await conduit.establishConnection('ws://localhost:3000/ws')
|
|
227
|
+
*
|
|
228
|
+
* // Now operations sync automatically!
|
|
229
|
+
* await clientBrain.addNoun('synced data', { source: 'client' })
|
|
230
|
+
* // This will automatically sync to the server
|
|
231
|
+
*/
|
|
1158
232
|
//# sourceMappingURL=conduitAugmentations.js.map
|