holosphere 1.1.6 → 1.1.8
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/FEDERATION.md +108 -160
- package/README.md +140 -0
- package/examples/federation.js +154 -0
- package/examples/references.js +177 -0
- package/federation.js +637 -372
- package/holosphere.d.ts +445 -20
- package/holosphere.js +343 -126
- package/package.json +1 -4
- package/test/delete.test.js +225 -0
- package/test/federation.test.js +50 -2
- package/config/default.js +0 -1
- package/services/environmentalApi.js +0 -253
- package/services/environmentalApitest.js +0 -164
- package/test/ai.test.js +0 -425
- package/test/sea.html +0 -33
- /package/test/{holonauth.test.js → auth.test.js} +0 -0
package/federation.js
CHANGED
|
@@ -3,16 +3,21 @@
|
|
|
3
3
|
* Provides methods for creating, managing, and using federated spaces
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
/**
|
|
7
8
|
* Creates a federation relationship between two spaces
|
|
9
|
+
* Federation is bidirectional by default, and data propagation uses soul references by default.
|
|
10
|
+
*
|
|
8
11
|
* @param {object} holosphere - The HoloSphere instance
|
|
9
12
|
* @param {string} spaceId1 - The first space ID
|
|
10
13
|
* @param {string} spaceId2 - The second space ID
|
|
11
14
|
* @param {string} [password1] - Optional password for the first space
|
|
12
15
|
* @param {string} [password2] - Optional password for the second space
|
|
16
|
+
* @param {boolean} [bidirectional=true] - Whether to set up bidirectional notifications (default: true)
|
|
13
17
|
* @returns {Promise<boolean>} - True if federation was created successfully
|
|
14
18
|
*/
|
|
15
|
-
export async function federate(holosphere, spaceId1, spaceId2, password1 = null, password2 = null) {
|
|
19
|
+
export async function federate(holosphere, spaceId1, spaceId2, password1 = null, password2 = null, bidirectional = true) {
|
|
20
|
+
console.log('FEDERATING', spaceId1, spaceId2, password1, password2, bidirectional)
|
|
16
21
|
if (!spaceId1 || !spaceId2) {
|
|
17
22
|
throw new Error('federate: Missing required space IDs');
|
|
18
23
|
}
|
|
@@ -22,120 +27,18 @@ export async function federate(holosphere, spaceId1, spaceId2, password1 = null,
|
|
|
22
27
|
throw new Error('Cannot federate a space with itself');
|
|
23
28
|
}
|
|
24
29
|
|
|
25
|
-
// Verify access to both spaces before proceeding
|
|
26
|
-
let canAccessSpace1 = false;
|
|
27
|
-
let canAccessSpace2 = false;
|
|
28
|
-
|
|
29
30
|
try {
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
await new Promise((resolve, reject) => {
|
|
34
|
-
const user = holosphere.gun.user();
|
|
35
|
-
user.auth(holosphere.userName(spaceId1), password1, (ack) => {
|
|
36
|
-
if (ack.err) {
|
|
37
|
-
console.warn(`Authentication test for ${spaceId1} failed: ${ack.err}`);
|
|
38
|
-
reject(new Error(`Authentication failed for ${spaceId1}: ${ack.err}`));
|
|
39
|
-
} else {
|
|
40
|
-
canAccessSpace1 = true;
|
|
41
|
-
resolve();
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
} catch (error) {
|
|
46
|
-
// Try to create the user if authentication fails
|
|
47
|
-
try {
|
|
48
|
-
await new Promise((resolve, reject) => {
|
|
49
|
-
const user = holosphere.gun.user();
|
|
50
|
-
user.create(holosphere.userName(spaceId1), password1, (ack) => {
|
|
51
|
-
if (ack.err && !ack.err.includes('already created')) {
|
|
52
|
-
reject(new Error(`User creation failed for ${spaceId1}: ${ack.err}`));
|
|
53
|
-
} else {
|
|
54
|
-
// Try to authenticate again
|
|
55
|
-
user.auth(holosphere.userName(spaceId1), password1, (authAck) => {
|
|
56
|
-
if (authAck.err) {
|
|
57
|
-
reject(new Error(`Authentication failed after creation for ${spaceId1}: ${authAck.err}`));
|
|
58
|
-
} else {
|
|
59
|
-
canAccessSpace1 = true;
|
|
60
|
-
resolve();
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
} catch (createError) {
|
|
67
|
-
console.warn(`Could not create or authenticate user for ${spaceId1}: ${createError.message}`);
|
|
68
|
-
// Continue with limited functionality
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
} else {
|
|
72
|
-
// No password required, assume we can access
|
|
73
|
-
canAccessSpace1 = true;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Test authentication for second space if password provided
|
|
77
|
-
if (password2) {
|
|
78
|
-
try {
|
|
79
|
-
await new Promise((resolve, reject) => {
|
|
80
|
-
const user = holosphere.gun.user();
|
|
81
|
-
user.auth(holosphere.userName(spaceId2), password2, (ack) => {
|
|
82
|
-
if (ack.err) {
|
|
83
|
-
console.warn(`Authentication test for ${spaceId2} failed: ${ack.err}`);
|
|
84
|
-
reject(new Error(`Authentication failed for ${spaceId2}: ${ack.err}`));
|
|
85
|
-
} else {
|
|
86
|
-
canAccessSpace2 = true;
|
|
87
|
-
resolve();
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
} catch (error) {
|
|
92
|
-
// Try to create the user if authentication fails
|
|
93
|
-
try {
|
|
94
|
-
await new Promise((resolve, reject) => {
|
|
95
|
-
const user = holosphere.gun.user();
|
|
96
|
-
user.create(holosphere.userName(spaceId2), password2, (ack) => {
|
|
97
|
-
if (ack.err && !ack.err.includes('already created')) {
|
|
98
|
-
reject(new Error(`User creation failed for ${spaceId2}: ${ack.err}`));
|
|
99
|
-
} else {
|
|
100
|
-
// Try to authenticate again
|
|
101
|
-
user.auth(holosphere.userName(spaceId2), password2, (authAck) => {
|
|
102
|
-
if (authAck.err) {
|
|
103
|
-
reject(new Error(`Authentication failed after creation for ${spaceId2}: ${authAck.err}`));
|
|
104
|
-
} else {
|
|
105
|
-
canAccessSpace2 = true;
|
|
106
|
-
resolve();
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
} catch (createError) {
|
|
113
|
-
console.warn(`Could not create or authenticate user for ${spaceId2}: ${createError.message}`);
|
|
114
|
-
// Continue with limited functionality
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
} else {
|
|
118
|
-
// No password required, assume we can access
|
|
119
|
-
canAccessSpace2 = true;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Warn if we can't access one or both spaces
|
|
123
|
-
if (!canAccessSpace1) {
|
|
124
|
-
console.warn(`Limited access to space ${spaceId1} - federation may be incomplete`);
|
|
125
|
-
}
|
|
126
|
-
if (password2 && !canAccessSpace2) {
|
|
127
|
-
console.warn(`Limited access to space ${spaceId2} - federation may be incomplete`);
|
|
128
|
-
}
|
|
31
|
+
// Get or create federation info for first space (A)
|
|
32
|
+
let fedInfo1 = null;
|
|
129
33
|
|
|
130
|
-
// Get existing federation info for both spaces
|
|
131
|
-
let fedInfo1 = null;
|
|
132
|
-
let fedInfo2 = null;
|
|
133
|
-
|
|
134
34
|
try {
|
|
135
35
|
fedInfo1 = await holosphere.getGlobal('federation', spaceId1, password1);
|
|
136
36
|
} catch (error) {
|
|
137
37
|
console.warn(`Could not get federation info for ${spaceId1}: ${error.message}`);
|
|
138
|
-
// Create new federation info if
|
|
38
|
+
// Create new federation info if it doesn't exist
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
if (fedInfo1 == null) {
|
|
139
42
|
fedInfo1 = {
|
|
140
43
|
id: spaceId1,
|
|
141
44
|
name: spaceId1,
|
|
@@ -145,47 +48,44 @@ export async function federate(holosphere, spaceId1, spaceId2, password1 = null,
|
|
|
145
48
|
};
|
|
146
49
|
}
|
|
147
50
|
|
|
148
|
-
if (password2) {
|
|
149
|
-
try {
|
|
150
|
-
fedInfo2 = await holosphere.getGlobal('federation', spaceId2, password2);
|
|
151
|
-
} catch (error) {
|
|
152
|
-
console.warn(`Could not get federation info for ${spaceId2}: ${error.message}`);
|
|
153
|
-
// Create new federation info if we couldn't get existing
|
|
154
|
-
fedInfo2 = {
|
|
155
|
-
id: spaceId2,
|
|
156
|
-
name: spaceId2,
|
|
157
|
-
federation: [],
|
|
158
|
-
notify: [],
|
|
159
|
-
timestamp: Date.now()
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
51
|
|
|
164
|
-
//
|
|
165
|
-
if (fedInfo1 && fedInfo1.federation && fedInfo1.federation.includes(spaceId2)) {
|
|
166
|
-
console.log(`Federation already exists between ${spaceId1} and ${spaceId2}`);
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Create or update federation info for first space
|
|
171
|
-
if (!fedInfo1) {
|
|
172
|
-
fedInfo1 = {
|
|
173
|
-
id: spaceId1,
|
|
174
|
-
name: spaceId1,
|
|
175
|
-
federation: [],
|
|
176
|
-
notify: [],
|
|
177
|
-
timestamp: Date.now()
|
|
178
|
-
};
|
|
179
|
-
}
|
|
52
|
+
// Ensure arrays exist
|
|
180
53
|
if (!fedInfo1.federation) fedInfo1.federation = [];
|
|
54
|
+
if (!fedInfo1.notify) fedInfo1.notify = [];
|
|
55
|
+
|
|
56
|
+
// Add space2 to space1's federation and notify lists if not already present
|
|
181
57
|
if (!fedInfo1.federation.includes(spaceId2)) {
|
|
182
58
|
fedInfo1.federation.push(spaceId2);
|
|
183
59
|
}
|
|
60
|
+
// // Always add to notify list for the first space (primary direction)
|
|
61
|
+
// if (!fedInfo1.notify.includes(spaceId2)) {
|
|
62
|
+
// fedInfo1.notify.push(spaceId2);
|
|
63
|
+
// }
|
|
64
|
+
|
|
65
|
+
// Update timestamp
|
|
184
66
|
fedInfo1.timestamp = Date.now();
|
|
185
67
|
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
68
|
+
// Save updated federation info for space1
|
|
69
|
+
try {
|
|
70
|
+
await holosphere.putGlobal('federation', fedInfo1, password1);
|
|
71
|
+
console.log(`Updated federation info for ${spaceId1}`);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.warn(`Could not update federation info for ${spaceId1}: ${error.message}`);
|
|
74
|
+
throw new Error(`Failed to create federation: ${error.message}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// If bidirectional is true, handle space2 (B) as well
|
|
78
|
+
//if (bidirectional && password2) {
|
|
79
|
+
{
|
|
80
|
+
let fedInfo2 = null;
|
|
81
|
+
try {
|
|
82
|
+
fedInfo2 = await holosphere.getGlobal('federation', spaceId2, password2);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.warn(`Could not get federation info for ${spaceId2}: ${error.message}`);
|
|
85
|
+
// Create new federation info if it doesn't exist
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
if (fedInfo2 == null) {
|
|
189
89
|
fedInfo2 = {
|
|
190
90
|
id: spaceId2,
|
|
191
91
|
name: spaceId2,
|
|
@@ -194,37 +94,38 @@ export async function federate(holosphere, spaceId1, spaceId2, password1 = null,
|
|
|
194
94
|
timestamp: Date.now()
|
|
195
95
|
};
|
|
196
96
|
}
|
|
97
|
+
|
|
98
|
+
// Add nEnsure arrays exist
|
|
99
|
+
|
|
197
100
|
if (!fedInfo2.notify) fedInfo2.notify = [];
|
|
101
|
+
|
|
102
|
+
// Add space1 to space2's federation list if not already present
|
|
198
103
|
if (!fedInfo2.notify.includes(spaceId1)) {
|
|
199
104
|
fedInfo2.notify.push(spaceId1);
|
|
200
105
|
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
// Update timestamp
|
|
201
109
|
fedInfo2.timestamp = Date.now();
|
|
202
|
-
|
|
203
|
-
// Save
|
|
110
|
+
|
|
111
|
+
// Save updated federation info for space2
|
|
204
112
|
try {
|
|
205
113
|
await holosphere.putGlobal('federation', fedInfo2, password2);
|
|
206
114
|
console.log(`Updated federation info for ${spaceId2}`);
|
|
207
115
|
} catch (error) {
|
|
208
116
|
console.warn(`Could not update federation info for ${spaceId2}: ${error.message}`);
|
|
117
|
+
// Don't throw here as the main federation was successful
|
|
209
118
|
}
|
|
210
119
|
}
|
|
211
120
|
|
|
212
|
-
// Save first federation record
|
|
213
|
-
try {
|
|
214
|
-
await holosphere.putGlobal('federation', fedInfo1, password1);
|
|
215
|
-
console.log(`Updated federation info for ${spaceId1}`);
|
|
216
|
-
} catch (error) {
|
|
217
|
-
console.warn(`Could not update federation info for ${spaceId1}: ${error.message}`);
|
|
218
|
-
throw new Error(`Failed to create federation: ${error.message}`);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
121
|
// Create federation metadata record
|
|
222
122
|
const federationMeta = {
|
|
223
123
|
id: `${spaceId1}_${spaceId2}`,
|
|
224
124
|
space1: spaceId1,
|
|
225
125
|
space2: spaceId2,
|
|
226
126
|
created: Date.now(),
|
|
227
|
-
status: 'active'
|
|
127
|
+
status: 'active',
|
|
128
|
+
bidirectional: bidirectional
|
|
228
129
|
};
|
|
229
130
|
|
|
230
131
|
try {
|
|
@@ -438,29 +339,76 @@ export async function unfederate(holosphere, spaceId1, spaceId2, password1 = nul
|
|
|
438
339
|
}
|
|
439
340
|
|
|
440
341
|
/**
|
|
441
|
-
*
|
|
342
|
+
* Removes a notification relationship between two spaces
|
|
343
|
+
* This removes spaceId2 from the notify list of spaceId1
|
|
344
|
+
*
|
|
442
345
|
* @param {object} holosphere - The HoloSphere instance
|
|
443
|
-
* @param {string}
|
|
444
|
-
* @param {string}
|
|
445
|
-
* @param {
|
|
446
|
-
* @
|
|
447
|
-
* @param {string} [options.idField='id'] - Field to use as unique identifier
|
|
448
|
-
* @param {string[]} [options.sumFields=[]] - Fields to sum when aggregating
|
|
449
|
-
* @param {string[]} [options.concatArrays=[]] - Array fields to concatenate
|
|
450
|
-
* @param {boolean} [options.removeDuplicates=true] - Whether to remove duplicates
|
|
451
|
-
* @param {function} [options.mergeStrategy=null] - Custom merge function
|
|
452
|
-
* @param {boolean} [options.includeLocal=true] - Whether to include local data
|
|
453
|
-
* @param {boolean} [options.includeFederated=true] - Whether to include federated data
|
|
454
|
-
* @param {string} [password] - Optional password for accessing private data
|
|
455
|
-
* @returns {Promise<Array>} - Combined array of local and federated data
|
|
346
|
+
* @param {string} spaceId1 - The space to modify (remove from its notify list)
|
|
347
|
+
* @param {string} spaceId2 - The space to be removed from notifications
|
|
348
|
+
* @param {string} [password1] - Optional password for the first space
|
|
349
|
+
* @returns {Promise<boolean>} - True if notification was removed successfully
|
|
456
350
|
*/
|
|
457
|
-
export async function
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
throw new Error('getFederated: Missing required parameters');
|
|
351
|
+
export async function removeNotify(holosphere, spaceId1, spaceId2, password1 = null) {
|
|
352
|
+
if (!spaceId1 || !spaceId2) {
|
|
353
|
+
throw new Error('removeNotify: Missing required space IDs');
|
|
461
354
|
}
|
|
462
355
|
|
|
463
|
-
|
|
356
|
+
try {
|
|
357
|
+
// Get federation info for space
|
|
358
|
+
let fedInfo = await holosphere.getGlobal('federation', spaceId1, password1);
|
|
359
|
+
|
|
360
|
+
if (!fedInfo) {
|
|
361
|
+
throw new Error(`No federation info found for ${spaceId1}`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Ensure notify array exists
|
|
365
|
+
if (!fedInfo.notify) fedInfo.notify = [];
|
|
366
|
+
|
|
367
|
+
// Remove space2 from space1's notify list if present
|
|
368
|
+
if (fedInfo.notify.includes(spaceId2)) {
|
|
369
|
+
fedInfo.notify = fedInfo.notify.filter(id => id !== spaceId2);
|
|
370
|
+
|
|
371
|
+
// Update timestamp
|
|
372
|
+
fedInfo.timestamp = Date.now();
|
|
373
|
+
|
|
374
|
+
// Save updated federation info
|
|
375
|
+
await holosphere.putGlobal('federation', fedInfo, password1);
|
|
376
|
+
console.log(`Removed ${spaceId2} from ${spaceId1}'s notify list`);
|
|
377
|
+
return true;
|
|
378
|
+
} else {
|
|
379
|
+
console.log(`${spaceId2} not found in ${spaceId1}'s notify list`);
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.error(`Remove notification failed: ${error.message}`);
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Get and combine data from local and federated sources
|
|
390
|
+
* @param {HoloSphere} holosphere The HoloSphere instance
|
|
391
|
+
* @param {string} holon The local holon name
|
|
392
|
+
* @param {string} lens The lens to query
|
|
393
|
+
* @param {Object} options Options for data retrieval and aggregation
|
|
394
|
+
* @param {boolean} options.aggregate Whether to aggregate results by ID (default: false)
|
|
395
|
+
* @param {string} options.idField The field to use as ID (default: '_id')
|
|
396
|
+
* @param {string[]} options.sumFields Fields to sum during aggregation (default: [])
|
|
397
|
+
* @param {string[]} options.concatArrays Array fields to concatenate during aggregation (default: [])
|
|
398
|
+
* @param {boolean} options.removeDuplicates Whether to remove duplicates in concatenated arrays (default: true)
|
|
399
|
+
* @param {Function} options.mergeStrategy Custom merge function for aggregation (default: null)
|
|
400
|
+
* @param {boolean} options.includeLocal Whether to include local data (default: true)
|
|
401
|
+
* @param {boolean} options.includeFederated Whether to include federated data (default: true)
|
|
402
|
+
* @param {boolean} options.resolveReferences Whether to resolve federation references (default: true)
|
|
403
|
+
* @param {number} options.maxFederatedSpaces Maximum number of federated spaces to query (default: -1 for all)
|
|
404
|
+
* @param {number} options.timeout Timeout in milliseconds for federated queries (default: 10000)
|
|
405
|
+
* @returns {Promise<Array>} Combined array of local and federated data
|
|
406
|
+
*/
|
|
407
|
+
export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
408
|
+
console.log(`getFederated called with options:`, JSON.stringify(options));
|
|
409
|
+
|
|
410
|
+
// Set default options
|
|
411
|
+
const {
|
|
464
412
|
aggregate = false,
|
|
465
413
|
idField = 'id',
|
|
466
414
|
sumFields = [],
|
|
@@ -469,152 +417,239 @@ export async function getFederated(holosphere, holon, lens, options = {}, passwo
|
|
|
469
417
|
mergeStrategy = null,
|
|
470
418
|
includeLocal = true,
|
|
471
419
|
includeFederated = true,
|
|
472
|
-
|
|
473
|
-
|
|
420
|
+
resolveReferences = true, // Default to true
|
|
421
|
+
maxFederatedSpaces = -1,
|
|
422
|
+
timeout = 10000
|
|
474
423
|
} = options;
|
|
424
|
+
|
|
425
|
+
console.log(`resolveReferences option: ${resolveReferences}`);
|
|
426
|
+
|
|
427
|
+
// Validate required parameters
|
|
428
|
+
if (!holosphere || !holon || !lens) {
|
|
429
|
+
throw new Error('Missing required parameters: holosphere, holon, and lens are required');
|
|
430
|
+
}
|
|
475
431
|
|
|
476
432
|
// Get federation info for current space
|
|
477
433
|
// Use holon as the space ID
|
|
478
434
|
const spaceId = holon;
|
|
479
|
-
const fedInfo = await getFederation(holosphere, spaceId
|
|
435
|
+
const fedInfo = await getFederation(holosphere, spaceId);
|
|
480
436
|
|
|
481
|
-
|
|
482
|
-
let allData = [];
|
|
437
|
+
console.log(`Federation info retrieved:`, JSON.stringify(fedInfo));
|
|
483
438
|
|
|
484
|
-
//
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// If federation is disabled or no federation exists, return local data only
|
|
491
|
-
if (!includeFederated || !fedInfo || !fedInfo.federation || fedInfo.federation.length === 0) {
|
|
492
|
-
return allData;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// Limit number of federated spaces to query
|
|
496
|
-
const federatedSpaces = fedInfo.federation.slice(0, maxFederatedSpaces);
|
|
439
|
+
// Initialize result array and track processed IDs to avoid duplicates
|
|
440
|
+
const result = [];
|
|
441
|
+
const processedIds = new Set();
|
|
442
|
+
const references = new Map(); // To keep track of references for resolution
|
|
497
443
|
|
|
498
|
-
//
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
444
|
+
// Process each federated space first to prioritize federation data
|
|
445
|
+
if (includeFederated && fedInfo && fedInfo.federation && fedInfo.federation.length > 0) {
|
|
446
|
+
console.log(`Found ${fedInfo.federation.length} federated spaces`);
|
|
447
|
+
|
|
448
|
+
// Limit number of federated spaces to query
|
|
449
|
+
const federatedSpaces = maxFederatedSpaces === -1 ? fedInfo.federation : fedInfo.federation.slice(0, maxFederatedSpaces);
|
|
450
|
+
console.log(`Will process ${federatedSpaces.length} federated spaces: ${JSON.stringify(federatedSpaces)}`);
|
|
451
|
+
|
|
452
|
+
// Process federated spaces
|
|
453
|
+
for (const federatedSpace of federatedSpaces) {
|
|
454
|
+
try {
|
|
455
|
+
console.log(`=== PROCESSING FEDERATED SPACE: ${federatedSpace} ===`);
|
|
456
|
+
|
|
457
|
+
// Get all data for this lens from the federated space
|
|
458
|
+
const federatedItems = await holosphere.getAll(federatedSpace, lens);
|
|
459
|
+
console.log(`Got ${federatedItems.length} items from federated space ${federatedSpace}`);
|
|
460
|
+
console.log(`Federated items:`, JSON.stringify(federatedItems));
|
|
461
|
+
|
|
462
|
+
// Process each item
|
|
463
|
+
for (const item of federatedItems) {
|
|
464
|
+
if (!item) {
|
|
465
|
+
console.log('Item is null or undefined, skipping');
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
console.log(`Checking item for ID field '${idField}':`, item);
|
|
470
|
+
|
|
471
|
+
if (!item[idField]) {
|
|
472
|
+
console.log(`Item missing ID field '${idField}', available fields:`, Object.keys(item));
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// For now, just add this item to results, we'll resolve references later
|
|
477
|
+
result.push(item);
|
|
478
|
+
processedIds.add(item[idField]);
|
|
519
479
|
}
|
|
520
|
-
})
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
return [];
|
|
480
|
+
} catch (error) {
|
|
481
|
+
console.warn(`Error processing federated space ${federatedSpace}: ${error.message}`);
|
|
482
|
+
}
|
|
524
483
|
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
// Wait for all federated data requests to complete
|
|
528
|
-
const federatedResults = await Promise.allSettled(federatedDataPromises);
|
|
484
|
+
}
|
|
529
485
|
|
|
530
|
-
//
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
486
|
+
// Now get local data if requested
|
|
487
|
+
if (includeLocal) {
|
|
488
|
+
const localData = await holosphere.getAll(holon, lens);
|
|
489
|
+
console.log(`Got ${localData.length} local items from holon ${holon}`);
|
|
490
|
+
|
|
491
|
+
// Add each local item to results, but only if not already processed
|
|
492
|
+
for (const item of localData) {
|
|
493
|
+
if (item && item[idField] && !processedIds.has(item[idField])) {
|
|
494
|
+
result.push(item);
|
|
495
|
+
processedIds.add(item[idField]);
|
|
496
|
+
} else if (item && item[idField]) {
|
|
497
|
+
console.log(`Local item ${item[idField]} already in result from federation, skipping`);
|
|
498
|
+
}
|
|
534
499
|
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
//
|
|
538
|
-
if (
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
for (
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Now resolve references if needed
|
|
503
|
+
if (resolveReferences) {
|
|
504
|
+
console.log(`Resolving references for ${result.length} items`);
|
|
505
|
+
|
|
506
|
+
for (let i = 0; i < result.length; i++) {
|
|
507
|
+
const item = result[i];
|
|
508
|
+
|
|
509
|
+
// Check for simplified reference (item with id and soul)
|
|
510
|
+
if (item.soul && item.id) {
|
|
511
|
+
console.log(`Found simple reference with soul: ${item.soul}`);
|
|
512
|
+
|
|
513
|
+
try {
|
|
514
|
+
// Parse the soul to get the components
|
|
515
|
+
const soulParts = item.soul.split('/');
|
|
516
|
+
if (soulParts.length >= 4) {
|
|
517
|
+
const originHolon = soulParts[1];
|
|
518
|
+
const originLens = soulParts[2];
|
|
519
|
+
const originKey = soulParts[3];
|
|
520
|
+
|
|
521
|
+
console.log(`Extracting from soul - holon: ${originHolon}, lens: ${originLens}, key: ${originKey}`);
|
|
522
|
+
|
|
523
|
+
// Get original data using the extracted path
|
|
524
|
+
const originalData = await holosphere.get(
|
|
525
|
+
originHolon,
|
|
526
|
+
originLens,
|
|
527
|
+
originKey,
|
|
528
|
+
null,
|
|
529
|
+
{ resolveReferences: false } // Prevent infinite recursion
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
console.log(`Original data found via soul path:`, JSON.stringify(originalData));
|
|
533
|
+
|
|
534
|
+
if (originalData) {
|
|
535
|
+
// Replace the reference with the original data
|
|
536
|
+
result[i] = {
|
|
537
|
+
...originalData,
|
|
538
|
+
_federation: {
|
|
539
|
+
isReference: true,
|
|
540
|
+
resolved: true,
|
|
541
|
+
soul: item.soul,
|
|
542
|
+
timestamp: Date.now()
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
console.log(`Reference resolved successfully via soul path, processed item:`, JSON.stringify(result[i]));
|
|
546
|
+
} else {
|
|
547
|
+
console.warn(`Could not resolve reference: original data not found at extracted path`);
|
|
548
|
+
}
|
|
549
|
+
} else {
|
|
550
|
+
console.warn(`Soul doesn't match expected format: ${item.soul}`);
|
|
562
551
|
}
|
|
552
|
+
} catch (refError) {
|
|
553
|
+
console.warn(`Error resolving reference by soul in getFederated: ${refError.message}`);
|
|
563
554
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
555
|
+
}
|
|
556
|
+
// For backward compatibility, check for old-style references
|
|
557
|
+
else if (item._federation && item._federation.isReference) {
|
|
558
|
+
console.log(`Found legacy reference: ${item._federation.origin}/${item._federation.lens}/${item[idField]}`);
|
|
559
|
+
|
|
560
|
+
try {
|
|
561
|
+
const reference = item._federation;
|
|
562
|
+
console.log(`Getting original data from ${reference.origin} / ${reference.lens} / ${item[idField]}`);
|
|
563
|
+
|
|
564
|
+
// Get original data
|
|
565
|
+
const originalData = await holosphere.get(
|
|
566
|
+
reference.origin,
|
|
567
|
+
reference.lens,
|
|
568
|
+
item[idField],
|
|
569
|
+
null,
|
|
570
|
+
{ resolveReferences: false } // Prevent infinite recursion
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
console.log(`Original data found:`, JSON.stringify(originalData));
|
|
574
|
+
|
|
575
|
+
if (originalData) {
|
|
576
|
+
// Add federation information to the resolved data
|
|
577
|
+
result[i] = {
|
|
578
|
+
...originalData,
|
|
579
|
+
_federation: {
|
|
580
|
+
...reference,
|
|
581
|
+
resolved: true,
|
|
582
|
+
timestamp: Date.now()
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
console.log(`Legacy reference resolved successfully, processed item:`, JSON.stringify(result[i]));
|
|
586
|
+
} else {
|
|
587
|
+
console.warn(`Could not resolve legacy reference: original data not found`);
|
|
574
588
|
}
|
|
589
|
+
} catch (refError) {
|
|
590
|
+
console.warn(`Error resolving legacy reference in getFederated: ${refError.message}`);
|
|
575
591
|
}
|
|
576
|
-
|
|
577
|
-
// Update federation metadata
|
|
578
|
-
merged.federation = {
|
|
579
|
-
...merged.federation,
|
|
580
|
-
timestamp: Math.max(
|
|
581
|
-
merged.federation?.timestamp || 0,
|
|
582
|
-
item.federation?.timestamp || 0
|
|
583
|
-
),
|
|
584
|
-
origins: Array.from(new Set([
|
|
585
|
-
...(Array.isArray(merged.federation?.origins) ? merged.federation.origins :
|
|
586
|
-
(merged.federation?.origin ? [merged.federation.origin] : [])),
|
|
587
|
-
...(Array.isArray(item.federation?.origins) ? item.federation.origins :
|
|
588
|
-
(item.federation?.origin ? [item.federation.origin] : []))
|
|
589
|
-
]))
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
// Update the aggregated item
|
|
593
|
-
aggregated.set(itemId, merged);
|
|
594
592
|
}
|
|
595
593
|
}
|
|
596
|
-
|
|
597
|
-
return Array.from(aggregated.values());
|
|
598
594
|
}
|
|
599
|
-
|
|
600
|
-
//
|
|
601
|
-
if (
|
|
602
|
-
|
|
595
|
+
|
|
596
|
+
// Apply aggregation if requested
|
|
597
|
+
if (aggregate && result.length > 0) {
|
|
598
|
+
// Group items by ID
|
|
599
|
+
const groupedById = result.reduce((acc, item) => {
|
|
600
|
+
const id = item[idField];
|
|
601
|
+
if (!acc[id]) {
|
|
602
|
+
acc[id] = [];
|
|
603
|
+
}
|
|
604
|
+
acc[id].push(item);
|
|
605
|
+
return acc;
|
|
606
|
+
}, {});
|
|
607
|
+
|
|
608
|
+
// Aggregate each group
|
|
609
|
+
const aggregatedData = Object.values(groupedById).map(group => {
|
|
610
|
+
// If only one item in group, no aggregation needed
|
|
611
|
+
if (group.length === 1) return group[0];
|
|
612
|
+
|
|
613
|
+
// Use custom merge strategy if provided
|
|
614
|
+
if (mergeStrategy && typeof mergeStrategy === 'function') {
|
|
615
|
+
return mergeStrategy(group);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Default aggregation strategy
|
|
619
|
+
const base = { ...group[0] };
|
|
620
|
+
|
|
621
|
+
// Sum numeric fields
|
|
622
|
+
for (const field of sumFields) {
|
|
623
|
+
if (typeof base[field] === 'number') {
|
|
624
|
+
base[field] = group.reduce((sum, item) => sum + (Number(item[field]) || 0), 0);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Concatenate array fields
|
|
629
|
+
for (const field of concatArrays) {
|
|
630
|
+
if (Array.isArray(base[field])) {
|
|
631
|
+
const allValues = group.reduce((all, item) => {
|
|
632
|
+
return Array.isArray(item[field]) ? [...all, ...item[field]] : all;
|
|
633
|
+
}, []);
|
|
634
|
+
|
|
635
|
+
// Remove duplicates if requested
|
|
636
|
+
base[field] = removeDuplicates ? Array.from(new Set(allValues)) : allValues;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Add aggregation metadata
|
|
641
|
+
base._aggregated = {
|
|
642
|
+
count: group.length,
|
|
643
|
+
timestamp: Date.now()
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
return base;
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
return aggregatedData;
|
|
603
650
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
const uniqueMap = new Map();
|
|
607
|
-
allData.forEach(item => {
|
|
608
|
-
const id = item[idField];
|
|
609
|
-
if (!id) return;
|
|
610
|
-
|
|
611
|
-
const existing = uniqueMap.get(id);
|
|
612
|
-
if (!existing ||
|
|
613
|
-
(item.federation?.timestamp > (existing.federation?.timestamp || 0))) {
|
|
614
|
-
uniqueMap.set(id, item);
|
|
615
|
-
}
|
|
616
|
-
});
|
|
617
|
-
return Array.from(uniqueMap.values());
|
|
651
|
+
|
|
652
|
+
return result;
|
|
618
653
|
}
|
|
619
654
|
|
|
620
655
|
/**
|
|
@@ -624,100 +659,330 @@ export async function getFederated(holosphere, holon, lens, options = {}, passwo
|
|
|
624
659
|
* @param {string} lens - The lens identifier
|
|
625
660
|
* @param {object} data - The data to propagate
|
|
626
661
|
* @param {object} [options] - Propagation options
|
|
627
|
-
* @param {
|
|
628
|
-
* @param {
|
|
662
|
+
* @param {boolean} [options.useReferences=true] - Whether to use references instead of duplicating data
|
|
663
|
+
* @param {string[]} [options.targetSpaces] - Specific target spaces to propagate to (defaults to all federated spaces)
|
|
664
|
+
* @param {string} [options.password] - Password for accessing the source holon (if needed)
|
|
629
665
|
* @returns {Promise<object>} - Result with success count and errors
|
|
630
666
|
*/
|
|
631
|
-
export async function
|
|
632
|
-
if (!holon || !lens || !data) {
|
|
633
|
-
throw new Error('
|
|
667
|
+
export async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
668
|
+
if (!holosphere || !holon || !lens || !data) {
|
|
669
|
+
throw new Error('propagate: Missing required parameters');
|
|
634
670
|
}
|
|
635
|
-
|
|
636
|
-
if (!data.id) {
|
|
637
|
-
data.id = holosphere.generateId();
|
|
638
|
-
}
|
|
639
|
-
|
|
671
|
+
// Default propagation options
|
|
640
672
|
const {
|
|
673
|
+
useReferences = true,
|
|
641
674
|
targetSpaces = null,
|
|
642
|
-
|
|
675
|
+
password = null
|
|
643
676
|
} = options;
|
|
644
|
-
|
|
677
|
+
|
|
678
|
+
const result = {
|
|
679
|
+
success: 0,
|
|
680
|
+
errors: 0,
|
|
681
|
+
errorDetails: [],
|
|
682
|
+
propagated: false,
|
|
683
|
+
referencesUsed: useReferences
|
|
684
|
+
};
|
|
685
|
+
|
|
645
686
|
try {
|
|
646
|
-
// Get federation info for
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
if (!fedInfo || !fedInfo.
|
|
651
|
-
return {
|
|
687
|
+
// Get federation info for this holon using getFederation
|
|
688
|
+
const fedInfo = await getFederation(holosphere, holon, password);
|
|
689
|
+
|
|
690
|
+
// If no federation info or no federation list, return with message
|
|
691
|
+
if (!fedInfo || !fedInfo.federation || fedInfo.federation.length === 0) {
|
|
692
|
+
return {
|
|
693
|
+
...result,
|
|
694
|
+
message: `No federation found for ${holon}`
|
|
695
|
+
};
|
|
652
696
|
}
|
|
653
697
|
|
|
654
|
-
//
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
return { success: 0, errors: 0, message: 'No matching spaces to propagate to' };
|
|
698
|
+
// If no notification list or it's empty, return with message
|
|
699
|
+
if (!fedInfo.notify || fedInfo.notify.length === 0) {
|
|
700
|
+
return {
|
|
701
|
+
...result,
|
|
702
|
+
message: `No notification targets found for ${holon}`
|
|
703
|
+
};
|
|
661
704
|
}
|
|
662
705
|
|
|
663
|
-
//
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
706
|
+
// Filter federation spaces to those in notify list
|
|
707
|
+
let spaces = fedInfo.notify;
|
|
708
|
+
|
|
709
|
+
// Further filter by targetSpaces if provided
|
|
710
|
+
if (targetSpaces && Array.isArray(targetSpaces) && targetSpaces.length > 0) {
|
|
711
|
+
spaces = spaces.filter(space => targetSpaces.includes(space));
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (spaces.length === 0) {
|
|
715
|
+
return {
|
|
716
|
+
...result,
|
|
717
|
+
message: 'No valid target spaces found after filtering'
|
|
670
718
|
};
|
|
671
719
|
}
|
|
672
720
|
|
|
673
|
-
//
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
721
|
+
// For each target space, propagate the data
|
|
722
|
+
const propagatePromises = spaces.map(async (targetSpace) => {
|
|
723
|
+
try {
|
|
724
|
+
// Get federation info for target space using getFederation
|
|
725
|
+
const targetFedInfo = await getFederation(holosphere, targetSpace);
|
|
726
|
+
|
|
727
|
+
// If using references, create a soul reference instead of duplicating the data
|
|
728
|
+
if (useReferences) {
|
|
729
|
+
// Create a soul path that points to the original data
|
|
730
|
+
const soul = `${holosphere.appname}/${holon}/${lens}/${data.id}`;
|
|
731
|
+
|
|
732
|
+
// Create a minimal reference object with just id and soul
|
|
733
|
+
const reference = {
|
|
734
|
+
id: data.id,
|
|
735
|
+
soul: soul,
|
|
736
|
+
_federation: {
|
|
737
|
+
origin: holon,
|
|
738
|
+
lens: lens,
|
|
739
|
+
timestamp: Date.now()
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
console.log(`Using soul reference: ${soul} for data: ${data.id}`);
|
|
744
|
+
|
|
745
|
+
// Store the reference in the target space without propagation
|
|
746
|
+
await holosphere.put(targetSpace, lens, reference, null, { autoPropagate: false });
|
|
747
|
+
|
|
748
|
+
result.success++;
|
|
749
|
+
return true;
|
|
750
|
+
} else {
|
|
751
|
+
// If not using references, store a full copy without propagation
|
|
752
|
+
const dataToStore = {
|
|
753
|
+
...data,
|
|
754
|
+
_federation: {
|
|
755
|
+
origin: holon,
|
|
756
|
+
lens: lens,
|
|
757
|
+
timestamp: Date.now()
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
await holosphere.put(targetSpace, lens, dataToStore, null, { autoPropagate: false });
|
|
761
|
+
result.success++;
|
|
762
|
+
return true;
|
|
763
|
+
}
|
|
764
|
+
} catch (error) {
|
|
765
|
+
result.errors++;
|
|
766
|
+
result.errorDetails.push({
|
|
767
|
+
space: targetSpace,
|
|
768
|
+
error: error.message
|
|
769
|
+
});
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
await Promise.all(propagatePromises);
|
|
775
|
+
|
|
776
|
+
result.propagated = result.success > 0;
|
|
777
|
+
return result;
|
|
778
|
+
} catch (error) {
|
|
779
|
+
console.error('Error in propagate:', error);
|
|
780
|
+
return {
|
|
781
|
+
...result,
|
|
782
|
+
error: error.message
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Tracks a federated message across different chats
|
|
789
|
+
* @param {object} holosphere - The HoloSphere instance
|
|
790
|
+
* @param {string} originalChatId - The ID of the original chat
|
|
791
|
+
* @param {string} messageId - The ID of the original message
|
|
792
|
+
* @param {string} federatedChatId - The ID of the federated chat
|
|
793
|
+
* @param {string} federatedMessageId - The ID of the message in the federated chat
|
|
794
|
+
* @param {string} type - The type of message (e.g., 'quest', 'announcement')
|
|
795
|
+
* @returns {Promise<void>}
|
|
796
|
+
*/
|
|
797
|
+
export async function federateMessage(holosphere, originalChatId, messageId, federatedChatId, federatedMessageId, type = 'generic') {
|
|
798
|
+
const trackingKey = `${originalChatId}_${messageId}_fedmsgs`;
|
|
799
|
+
const tracking = await holosphere.getGlobal('federation_messages', trackingKey) || {
|
|
800
|
+
id: trackingKey,
|
|
801
|
+
originalChatId,
|
|
802
|
+
originalMessageId: messageId,
|
|
803
|
+
type,
|
|
804
|
+
messages: []
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
// Update or add the federated message info
|
|
808
|
+
const existingMsg = tracking.messages.find(m => m.chatId === federatedChatId);
|
|
809
|
+
if (existingMsg) {
|
|
810
|
+
existingMsg.messageId = federatedMessageId;
|
|
811
|
+
existingMsg.timestamp = Date.now();
|
|
812
|
+
} else {
|
|
813
|
+
tracking.messages.push({
|
|
814
|
+
chatId: federatedChatId,
|
|
815
|
+
messageId: federatedMessageId,
|
|
816
|
+
timestamp: Date.now()
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
await holosphere.putGlobal('federation_messages', tracking);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Gets all federated messages for a given original message
|
|
825
|
+
* @param {object} holosphere - The HoloSphere instance
|
|
826
|
+
* @param {string} originalChatId - The ID of the original chat
|
|
827
|
+
* @param {string} messageId - The ID of the original message
|
|
828
|
+
* @returns {Promise<Object|null>} The tracking information for the message
|
|
829
|
+
*/
|
|
830
|
+
export async function getFederatedMessages(holosphere, originalChatId, messageId) {
|
|
831
|
+
const trackingKey = `${originalChatId}_${messageId}_fedmsgs`;
|
|
832
|
+
return await holosphere.getGlobal('federation_messages', trackingKey);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Updates a federated message across all federated chats
|
|
837
|
+
* @param {object} holosphere - The HoloSphere instance
|
|
838
|
+
* @param {string} originalChatId - The ID of the original chat
|
|
839
|
+
* @param {string} messageId - The ID of the original message
|
|
840
|
+
* @param {Function} updateCallback - Function to update the message in each chat
|
|
841
|
+
* @returns {Promise<void>}
|
|
842
|
+
*/
|
|
843
|
+
export async function updateFederatedMessages(holosphere, originalChatId, messageId, updateCallback) {
|
|
844
|
+
const tracking = await getFederatedMessages(holosphere, originalChatId, messageId);
|
|
845
|
+
if (!tracking?.messages) return;
|
|
846
|
+
|
|
847
|
+
for (const msg of tracking.messages) {
|
|
848
|
+
try {
|
|
849
|
+
await updateCallback(msg.chatId, msg.messageId);
|
|
850
|
+
} catch (error) {
|
|
851
|
+
console.warn(`Failed to update federated message in chat ${msg.chatId}:`, error);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Resets all federation relationships for a space
|
|
858
|
+
* @param {object} holosphere - The HoloSphere instance
|
|
859
|
+
* @param {string} spaceId - The ID of the space to reset federation for
|
|
860
|
+
* @param {string} [password] - Optional password for the space
|
|
861
|
+
* @param {object} [options] - Reset options
|
|
862
|
+
* @param {boolean} [options.notifyPartners=true] - Whether to notify federation partners about the reset
|
|
863
|
+
* @param {string} [options.spaceName] - Optional name for the space (defaults to spaceId if not provided)
|
|
864
|
+
* @returns {Promise<object>} - Result object with success/error info
|
|
865
|
+
*/
|
|
866
|
+
export async function resetFederation(holosphere, spaceId, password = null, options = {}) {
|
|
867
|
+
if (!spaceId) {
|
|
868
|
+
throw new Error('resetFederation: Missing required space ID');
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const {
|
|
872
|
+
notifyPartners = true,
|
|
873
|
+
spaceName = null
|
|
874
|
+
} = options;
|
|
875
|
+
|
|
876
|
+
const result = {
|
|
877
|
+
success: false,
|
|
878
|
+
federatedCount: 0,
|
|
879
|
+
notifyCount: 0,
|
|
880
|
+
partnersNotified: 0,
|
|
881
|
+
errors: []
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
try {
|
|
885
|
+
// Get current federation info to know what we're clearing
|
|
886
|
+
const fedInfo = await getFederation(holosphere, spaceId, password);
|
|
887
|
+
|
|
888
|
+
if (!fedInfo) {
|
|
889
|
+
return {
|
|
890
|
+
...result,
|
|
891
|
+
success: true,
|
|
892
|
+
message: 'No federation configuration found for this space'
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Store counts for reporting
|
|
897
|
+
result.federatedCount = fedInfo.federation?.length || 0;
|
|
898
|
+
result.notifyCount = fedInfo.notify?.length || 0;
|
|
899
|
+
|
|
900
|
+
// Create empty federation record
|
|
901
|
+
const emptyFedInfo = {
|
|
902
|
+
id: spaceId,
|
|
903
|
+
name: spaceName || spaceId,
|
|
904
|
+
federation: [],
|
|
905
|
+
notify: [],
|
|
906
|
+
timestamp: Date.now()
|
|
678
907
|
};
|
|
679
908
|
|
|
680
|
-
//
|
|
681
|
-
|
|
682
|
-
|
|
909
|
+
// Update federation record with empty lists
|
|
910
|
+
await holosphere.putGlobal('federation', emptyFedInfo, password);
|
|
911
|
+
|
|
912
|
+
// Notify federation partners if requested
|
|
913
|
+
if (notifyPartners && fedInfo.federation && fedInfo.federation.length > 0) {
|
|
914
|
+
const updatePromises = fedInfo.federation.map(async (partnerSpace) => {
|
|
683
915
|
try {
|
|
684
|
-
//
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
}
|
|
916
|
+
// Get partner's federation info
|
|
917
|
+
const partnerFedInfo = await getFederation(holosphere, partnerSpace);
|
|
918
|
+
|
|
919
|
+
if (partnerFedInfo) {
|
|
920
|
+
// Remove this space from partner's federation list
|
|
921
|
+
if (partnerFedInfo.federation) {
|
|
922
|
+
partnerFedInfo.federation = partnerFedInfo.federation.filter(
|
|
923
|
+
id => id !== spaceId.toString()
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// Remove this space from partner's notify list
|
|
928
|
+
if (partnerFedInfo.notify) {
|
|
929
|
+
partnerFedInfo.notify = partnerFedInfo.notify.filter(
|
|
930
|
+
id => id !== spaceId.toString()
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
partnerFedInfo.timestamp = Date.now();
|
|
935
|
+
|
|
936
|
+
// Save partner's updated federation info
|
|
937
|
+
await holosphere.putGlobal('federation', partnerFedInfo);
|
|
938
|
+
console.log(`Updated federation info for partner ${partnerSpace}`);
|
|
939
|
+
result.partnersNotified++;
|
|
940
|
+
return true;
|
|
941
|
+
}
|
|
942
|
+
return false;
|
|
701
943
|
} catch (error) {
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
944
|
+
console.warn(`Could not update federation info for partner ${partnerSpace}: ${error.message}`);
|
|
945
|
+
result.errors.push({
|
|
946
|
+
partner: partnerSpace,
|
|
705
947
|
error: error.message
|
|
706
948
|
});
|
|
707
|
-
|
|
949
|
+
return false;
|
|
708
950
|
}
|
|
709
|
-
})
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
await Promise.all(updatePromises);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// Update federation metadata records if they exist
|
|
957
|
+
if (fedInfo.federation && fedInfo.federation.length > 0) {
|
|
958
|
+
for (const partnerSpace of fedInfo.federation) {
|
|
959
|
+
try {
|
|
960
|
+
const metaId = `${spaceId}_${partnerSpace}`;
|
|
961
|
+
const altMetaId = `${partnerSpace}_${spaceId}`;
|
|
962
|
+
|
|
963
|
+
const meta = await holosphere.getGlobal('federationMeta', metaId) ||
|
|
964
|
+
await holosphere.getGlobal('federationMeta', altMetaId);
|
|
965
|
+
|
|
966
|
+
if (meta) {
|
|
967
|
+
meta.status = 'inactive';
|
|
968
|
+
meta.endedAt = Date.now();
|
|
969
|
+
await holosphere.putGlobal('federationMeta', meta);
|
|
970
|
+
console.log(`Updated federation metadata for ${spaceId} and ${partnerSpace}`);
|
|
971
|
+
}
|
|
972
|
+
} catch (error) {
|
|
973
|
+
console.warn(`Could not update federation metadata for ${partnerSpace}: ${error.message}`);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
result.success = true;
|
|
979
|
+
return result;
|
|
714
980
|
} catch (error) {
|
|
715
|
-
console.
|
|
716
|
-
return {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
errorDetails: [{ error: error.message }]
|
|
981
|
+
console.error(`Federation reset failed: ${error.message}`);
|
|
982
|
+
return {
|
|
983
|
+
...result,
|
|
984
|
+
success: false,
|
|
985
|
+
error: error.message
|
|
721
986
|
};
|
|
722
987
|
}
|
|
723
988
|
}
|