holosphere 1.1.21 → 1.3.0-alpha3
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/content.js +79 -62
- package/federation.js +201 -305
- package/global.js +119 -49
- package/handshake-shim.js +321 -0
- package/hologram.js +38 -14
- package/holosphere-bundle.esm.js +5689 -9042
- package/holosphere-bundle.js +5686 -9043
- package/holosphere-bundle.min.js +28 -30
- package/holosphere.d.ts +197 -474
- package/holosphere.js +260 -406
- package/nostr-utils-shim.js +125 -0
- package/package.json +12 -3
- package/registry-shim.js +109 -0
- package/subscriptions-shim.js +117 -0
package/federation.js
CHANGED
|
@@ -4,164 +4,115 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import * as h3 from 'h3-js';
|
|
7
|
+
import { attachHologramMeta } from './hologram.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
|
-
* Creates a federation relationship between two spaces
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Creates a directional federation relationship between two spaces.
|
|
11
|
+
*
|
|
12
|
+
* Directions are stated from spaceId1's perspective:
|
|
13
|
+
* - `lensConfig.inbound`: lenses spaceId1 receives FROM spaceId2.
|
|
14
|
+
* - `lensConfig.outbound`: lenses spaceId1 sends TO spaceId2.
|
|
15
|
+
*
|
|
16
|
+
* When `bidirectional` is true (default), spaceId2's record is mirrored with
|
|
17
|
+
* inverted directions so it agrees with the relationship from its own view.
|
|
18
|
+
*
|
|
12
19
|
* @param {object} holosphere - The HoloSphere instance
|
|
13
20
|
* @param {string} spaceId1 - The first space ID
|
|
14
21
|
* @param {string} spaceId2 - The second space ID
|
|
15
22
|
* @param {string} [password1] - Optional password for the first space
|
|
16
23
|
* @param {string} [password2] - Optional password for the second space
|
|
17
|
-
* @param {boolean} [bidirectional=true] -
|
|
18
|
-
* @param {object} [lensConfig] -
|
|
19
|
-
* @param {string[]} [lensConfig.
|
|
20
|
-
* @param {string[]} [lensConfig.
|
|
24
|
+
* @param {boolean} [bidirectional=true] - Mirror the relationship onto spaceId2
|
|
25
|
+
* @param {object} [lensConfig] - Lens-direction config from spaceId1's perspective
|
|
26
|
+
* @param {string[]} [lensConfig.inbound] - Lenses spaceId1 receives from spaceId2
|
|
27
|
+
* @param {string[]} [lensConfig.outbound] - Lenses spaceId1 sends to spaceId2
|
|
21
28
|
* @returns {Promise<boolean>} - True if federation was created successfully
|
|
22
29
|
*/
|
|
23
30
|
export async function federate(holosphere, spaceId1, spaceId2, password1 = null, password2 = null, bidirectional = true, lensConfig = {}) {
|
|
24
31
|
if (!spaceId1 || !spaceId2) {
|
|
25
32
|
throw new Error('federate: Missing required space IDs');
|
|
26
33
|
}
|
|
27
|
-
|
|
28
|
-
// Prevent self-federation
|
|
29
34
|
if (spaceId1 === spaceId2) {
|
|
30
35
|
throw new Error('Cannot federate a space with itself');
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
throw new Error('federate: lensConfig.federate and lensConfig.notify must be arrays');
|
|
38
|
+
const { inbound = [], outbound = [] } = lensConfig;
|
|
39
|
+
if (!Array.isArray(inbound) || !Array.isArray(outbound)) {
|
|
40
|
+
throw new Error('federate: lensConfig.inbound and lensConfig.outbound must be arrays');
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// Get or create federation info for first space (A)
|
|
45
|
-
let fedInfo1 = null;
|
|
43
|
+
const applyDirection = (fedInfo, partnerId, partnerInbound, partnerOutbound) => {
|
|
44
|
+
if (!fedInfo.federated) fedInfo.federated = [];
|
|
45
|
+
if (!fedInfo.inbound) fedInfo.inbound = [];
|
|
46
|
+
if (!fedInfo.outbound) fedInfo.outbound = [];
|
|
47
|
+
if (!fedInfo.lensConfig) fedInfo.lensConfig = {};
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
// `federated` is the canonical list — partner is recorded regardless of
|
|
50
|
+
// whether any lenses flow yet.
|
|
51
|
+
if (!fedInfo.federated.includes(partnerId)) {
|
|
52
|
+
fedInfo.federated.push(partnerId);
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
id: spaceId1,
|
|
55
|
-
name: spaceId1,
|
|
56
|
-
federation: [],
|
|
57
|
-
notify: [],
|
|
58
|
-
lensConfig: {}, // New field for lens-specific settings
|
|
59
|
-
timestamp: Date.now()
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Ensure arrays and lensConfig exist
|
|
64
|
-
if (!fedInfo1.federation) fedInfo1.federation = [];
|
|
65
|
-
if (!fedInfo1.notify) fedInfo1.notify = [];
|
|
66
|
-
if (!fedInfo1.lensConfig) fedInfo1.lensConfig = {};
|
|
55
|
+
const hasInbound = partnerInbound.length > 0;
|
|
56
|
+
const hasOutbound = partnerOutbound.length > 0;
|
|
67
57
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
58
|
+
if (hasInbound && !fedInfo.inbound.includes(partnerId)) {
|
|
59
|
+
fedInfo.inbound.push(partnerId);
|
|
60
|
+
} else if (!hasInbound) {
|
|
61
|
+
fedInfo.inbound = fedInfo.inbound.filter(id => id !== partnerId);
|
|
71
62
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (!
|
|
75
|
-
|
|
63
|
+
if (hasOutbound && !fedInfo.outbound.includes(partnerId)) {
|
|
64
|
+
fedInfo.outbound.push(partnerId);
|
|
65
|
+
} else if (!hasOutbound) {
|
|
66
|
+
fedInfo.outbound = fedInfo.outbound.filter(id => id !== partnerId);
|
|
76
67
|
}
|
|
77
68
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
federate: [...federateLenses], // federateLenses & notifyLenses are from the main lensConfig parameter
|
|
82
|
-
notify: [...notifyLenses],
|
|
69
|
+
fedInfo.lensConfig[partnerId] = {
|
|
70
|
+
inbound: [...partnerInbound],
|
|
71
|
+
outbound: [...partnerOutbound],
|
|
83
72
|
timestamp: Date.now()
|
|
84
73
|
};
|
|
85
|
-
|
|
74
|
+
fedInfo.timestamp = Date.now();
|
|
75
|
+
};
|
|
86
76
|
|
|
87
|
-
|
|
88
|
-
|
|
77
|
+
try {
|
|
78
|
+
// Space 1: directions as given.
|
|
79
|
+
let fedInfo1 = null;
|
|
80
|
+
try { fedInfo1 = await holosphere.getGlobal('federation', spaceId1, password1); } catch {}
|
|
81
|
+
if (fedInfo1 == null) {
|
|
82
|
+
fedInfo1 = {
|
|
83
|
+
id: spaceId1, name: spaceId1,
|
|
84
|
+
federated: [], inbound: [], outbound: [],
|
|
85
|
+
lensConfig: {}, partnerNames: {},
|
|
86
|
+
timestamp: Date.now()
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
applyDirection(fedInfo1, spaceId2, inbound, outbound);
|
|
89
90
|
|
|
90
|
-
// Save updated federation info for space1
|
|
91
91
|
try {
|
|
92
92
|
await holosphere.putGlobal('federation', fedInfo1, password1);
|
|
93
93
|
} catch (error) {
|
|
94
94
|
throw new Error(`Failed to create federation: ${error.message}`);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
//
|
|
98
|
-
{
|
|
97
|
+
// Space 2 (mirrored): directions are inverted from its perspective.
|
|
98
|
+
if (bidirectional) {
|
|
99
99
|
let fedInfo2 = null;
|
|
100
|
-
try {
|
|
101
|
-
fedInfo2 = await holosphere.getGlobal('federation', spaceId2, password2);
|
|
102
|
-
} catch (error) {
|
|
103
|
-
}
|
|
104
|
-
|
|
100
|
+
try { fedInfo2 = await holosphere.getGlobal('federation', spaceId2, password2); } catch {}
|
|
105
101
|
if (fedInfo2 == null) {
|
|
106
102
|
fedInfo2 = {
|
|
107
|
-
id: spaceId2,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
notify: [],
|
|
111
|
-
lensConfig: {}, // New field for lens-specific settings
|
|
103
|
+
id: spaceId2, name: spaceId2,
|
|
104
|
+
federated: [], inbound: [], outbound: [],
|
|
105
|
+
lensConfig: {}, partnerNames: {},
|
|
112
106
|
timestamp: Date.now()
|
|
113
107
|
};
|
|
114
108
|
}
|
|
109
|
+
// From spaceId2's view, spaceId1's outbound lenses arrive as inbound,
|
|
110
|
+
// and spaceId1's inbound lenses go out as outbound.
|
|
111
|
+
applyDirection(fedInfo2, spaceId1, outbound, inbound);
|
|
115
112
|
|
|
116
|
-
// Ensure arrays and lensConfig exist
|
|
117
|
-
if (!fedInfo2.federation) fedInfo2.federation = [];
|
|
118
|
-
if (!fedInfo2.notify) fedInfo2.notify = [];
|
|
119
|
-
if (!fedInfo2.lensConfig) fedInfo2.lensConfig = {};
|
|
120
|
-
|
|
121
|
-
// Add space1 to space2's federation list if bidirectional
|
|
122
|
-
if (bidirectional && !fedInfo2.federation.includes(spaceId1)) {
|
|
123
|
-
fedInfo2.federation.push(spaceId1);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Add space1 to space2's notify list if not already present
|
|
127
|
-
if (!fedInfo2.notify.includes(spaceId1)) {
|
|
128
|
-
fedInfo2.notify.push(spaceId1);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Store lens configuration for space1
|
|
132
|
-
fedInfo2.lensConfig[spaceId1] = {
|
|
133
|
-
federate: bidirectional ? [...federateLenses] : [], // Create a copy of the array
|
|
134
|
-
notify: bidirectional ? [...notifyLenses] : [], // Create a copy of the array
|
|
135
|
-
timestamp: Date.now()
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
// Update timestamp
|
|
139
|
-
fedInfo2.timestamp = Date.now();
|
|
140
|
-
|
|
141
|
-
// Save updated federation info for space2
|
|
142
113
|
try {
|
|
143
114
|
await holosphere.putGlobal('federation', fedInfo2, password2);
|
|
144
|
-
} catch
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Create federation metadata record
|
|
149
|
-
const federationMeta = {
|
|
150
|
-
id: `${spaceId1}_${spaceId2}`,
|
|
151
|
-
space1: spaceId1,
|
|
152
|
-
space2: spaceId2,
|
|
153
|
-
created: Date.now(),
|
|
154
|
-
status: 'active',
|
|
155
|
-
bidirectional: bidirectional,
|
|
156
|
-
lensConfig: {
|
|
157
|
-
federate: [...federateLenses], // Create a copy of the array
|
|
158
|
-
notify: [...notifyLenses] // Create a copy of the array
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
await holosphere.putGlobal('federationMeta', federationMeta);
|
|
164
|
-
} catch (error) {
|
|
115
|
+
} catch {}
|
|
165
116
|
}
|
|
166
117
|
|
|
167
118
|
return true;
|
|
@@ -198,8 +149,8 @@ export async function subscribeFederation(holosphere, spaceId, password = null,
|
|
|
198
149
|
const subscriptions = [];
|
|
199
150
|
let lastNotificationTime = {};
|
|
200
151
|
|
|
201
|
-
if (fedInfo.
|
|
202
|
-
for (const federatedSpace of fedInfo.
|
|
152
|
+
if (fedInfo.inbound && fedInfo.inbound.length > 0) {
|
|
153
|
+
for (const federatedSpace of fedInfo.inbound) {
|
|
203
154
|
// For each lens specified (or all if '*')
|
|
204
155
|
for (const lens of lenses) {
|
|
205
156
|
try {
|
|
@@ -283,7 +234,7 @@ export async function getFederation(holosphere, spaceId, password = null) {
|
|
|
283
234
|
* @param {string} spaceId - The ID of the source space.
|
|
284
235
|
* @param {string} targetSpaceId - The ID of the target space in the federation link.
|
|
285
236
|
* @param {string} [password] - Optional password for the source space.
|
|
286
|
-
* @returns {Promise<object|null>} - An object with '
|
|
237
|
+
* @returns {Promise<object|null>} - An object with 'inbound' and 'outbound' arrays, or null if not found.
|
|
287
238
|
*/
|
|
288
239
|
export async function getFederatedConfig(holosphere, spaceId, targetSpaceId, password = null) {
|
|
289
240
|
if (!holosphere || !spaceId || !targetSpaceId) {
|
|
@@ -295,11 +246,11 @@ export async function getFederatedConfig(holosphere, spaceId, targetSpaceId, pas
|
|
|
295
246
|
|
|
296
247
|
if (fedInfo && fedInfo.lensConfig && fedInfo.lensConfig[targetSpaceId]) {
|
|
297
248
|
return {
|
|
298
|
-
|
|
299
|
-
|
|
249
|
+
inbound: fedInfo.lensConfig[targetSpaceId].inbound || [],
|
|
250
|
+
outbound: fedInfo.lensConfig[targetSpaceId].outbound || []
|
|
300
251
|
};
|
|
301
252
|
}
|
|
302
|
-
return null;
|
|
253
|
+
return null;
|
|
303
254
|
} catch (error) {
|
|
304
255
|
console.error(`Error getting federated config for ${spaceId} -> ${targetSpaceId}: ${error.message}`);
|
|
305
256
|
throw error;
|
|
@@ -332,53 +283,64 @@ export async function unfederate(holosphere, spaceId1, spaceId2, password1 = nul
|
|
|
332
283
|
}
|
|
333
284
|
|
|
334
285
|
if (!fedInfo1) {
|
|
335
|
-
// If fedInfo1 doesn't exist, log and proceed to metadata cleanup.
|
|
336
286
|
console.warn(`No federation info found for ${spaceId1}. Skipping its update.`);
|
|
337
287
|
} else {
|
|
338
|
-
|
|
339
|
-
if (!fedInfo1.
|
|
340
|
-
if (!fedInfo1.
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
fedInfo1.
|
|
288
|
+
if (!fedInfo1.federated) fedInfo1.federated = [];
|
|
289
|
+
if (!fedInfo1.inbound) fedInfo1.inbound = [];
|
|
290
|
+
if (!fedInfo1.outbound) fedInfo1.outbound = [];
|
|
291
|
+
if (!fedInfo1.lensConfig) fedInfo1.lensConfig = {};
|
|
292
|
+
if (!fedInfo1.partnerNames) fedInfo1.partnerNames = {};
|
|
293
|
+
|
|
294
|
+
const beforeIn = fedInfo1.inbound.length;
|
|
295
|
+
const beforeOut = fedInfo1.outbound.length;
|
|
296
|
+
|
|
297
|
+
fedInfo1.federated = fedInfo1.federated.filter(id => id !== spaceId2);
|
|
298
|
+
fedInfo1.inbound = fedInfo1.inbound.filter(id => id !== spaceId2);
|
|
299
|
+
fedInfo1.outbound = fedInfo1.outbound.filter(id => id !== spaceId2);
|
|
300
|
+
delete fedInfo1.lensConfig[spaceId2];
|
|
301
|
+
delete fedInfo1.partnerNames[spaceId2];
|
|
348
302
|
fedInfo1.timestamp = Date.now();
|
|
349
|
-
|
|
350
|
-
console.log(`Unfederate:
|
|
351
|
-
|
|
303
|
+
|
|
304
|
+
console.log(`Unfederate: removed ${spaceId2} from ${spaceId1}: inbound ${beforeIn} -> ${fedInfo1.inbound.length}, outbound ${beforeOut} -> ${fedInfo1.outbound.length}`);
|
|
305
|
+
|
|
352
306
|
try {
|
|
353
307
|
await holosphere.putGlobal('federation', fedInfo1, password1);
|
|
354
308
|
} catch (error) {
|
|
355
309
|
console.error(`Failed to update fedInfo1 for ${spaceId1} during unfederate: ${error.message}`);
|
|
356
|
-
throw error;
|
|
310
|
+
throw error;
|
|
357
311
|
}
|
|
358
312
|
}
|
|
359
313
|
|
|
360
|
-
//
|
|
361
|
-
|
|
362
|
-
// The original code only did this if password2 was provided.
|
|
363
|
-
if (password2) { // Retaining original condition for this block
|
|
314
|
+
// Mirror the removal on space2 if password provided.
|
|
315
|
+
if (password2) {
|
|
364
316
|
let fedInfo2 = null;
|
|
365
317
|
try {
|
|
366
318
|
fedInfo2 = await holosphere.getGlobal('federation', spaceId2, password2);
|
|
367
319
|
} catch (error) {
|
|
368
320
|
console.error(`Error getting fedInfo2 for ${spaceId2} during unfederate: ${error.message}`);
|
|
369
321
|
}
|
|
370
|
-
|
|
371
|
-
if (!fedInfo2
|
|
372
|
-
console.warn(`No
|
|
322
|
+
|
|
323
|
+
if (!fedInfo2) {
|
|
324
|
+
console.warn(`No federation info found for ${spaceId2}. Skipping its update.`);
|
|
373
325
|
} else {
|
|
374
|
-
fedInfo2.
|
|
326
|
+
if (!fedInfo2.federated) fedInfo2.federated = [];
|
|
327
|
+
if (!fedInfo2.inbound) fedInfo2.inbound = [];
|
|
328
|
+
if (!fedInfo2.outbound) fedInfo2.outbound = [];
|
|
329
|
+
if (!fedInfo2.lensConfig) fedInfo2.lensConfig = {};
|
|
330
|
+
if (!fedInfo2.partnerNames) fedInfo2.partnerNames = {};
|
|
331
|
+
|
|
332
|
+
fedInfo2.federated = fedInfo2.federated.filter(id => id !== spaceId1);
|
|
333
|
+
fedInfo2.inbound = fedInfo2.inbound.filter(id => id !== spaceId1);
|
|
334
|
+
fedInfo2.outbound = fedInfo2.outbound.filter(id => id !== spaceId1);
|
|
335
|
+
delete fedInfo2.lensConfig[spaceId1];
|
|
336
|
+
delete fedInfo2.partnerNames[spaceId1];
|
|
375
337
|
fedInfo2.timestamp = Date.now();
|
|
376
|
-
|
|
338
|
+
|
|
377
339
|
try {
|
|
378
340
|
await holosphere.putGlobal('federation', fedInfo2, password2);
|
|
379
341
|
} catch (error) {
|
|
380
342
|
console.error(`Failed to update fedInfo2 for ${spaceId2} during unfederate: ${error.message}`);
|
|
381
|
-
throw error;
|
|
343
|
+
throw error;
|
|
382
344
|
}
|
|
383
345
|
}
|
|
384
346
|
}
|
|
@@ -409,14 +371,15 @@ export async function unfederate(holosphere, spaceId1, spaceId2, password1 = nul
|
|
|
409
371
|
}
|
|
410
372
|
|
|
411
373
|
/**
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
374
|
+
* Stops outbound propagation from spaceId1 to spaceId2.
|
|
375
|
+
* Removes spaceId2 from spaceId1's `outbound` list and clears the outbound
|
|
376
|
+
* lens config for that partner. Inbound subscriptions are untouched.
|
|
377
|
+
*
|
|
415
378
|
* @param {object} holosphere - The HoloSphere instance
|
|
416
|
-
* @param {string} spaceId1 - The space to modify
|
|
417
|
-
* @param {string} spaceId2 - The
|
|
379
|
+
* @param {string} spaceId1 - The space to modify
|
|
380
|
+
* @param {string} spaceId2 - The partner to stop sending to
|
|
418
381
|
* @param {string} [password1] - Optional password for the first space
|
|
419
|
-
* @returns {Promise<boolean>} - True if
|
|
382
|
+
* @returns {Promise<boolean>} - True if outbound was removed
|
|
420
383
|
*/
|
|
421
384
|
export async function removeNotify(holosphere, spaceId1, spaceId2, password1 = null) {
|
|
422
385
|
if (!spaceId1 || !spaceId2) {
|
|
@@ -424,29 +387,22 @@ export async function removeNotify(holosphere, spaceId1, spaceId2, password1 = n
|
|
|
424
387
|
}
|
|
425
388
|
|
|
426
389
|
try {
|
|
427
|
-
|
|
428
|
-
let fedInfo = await holosphere.getGlobal('federation', spaceId1, password1);
|
|
429
|
-
|
|
390
|
+
const fedInfo = await holosphere.getGlobal('federation', spaceId1, password1);
|
|
430
391
|
if (!fedInfo) {
|
|
431
392
|
throw new Error(`No federation info found for ${spaceId1}`);
|
|
432
393
|
}
|
|
394
|
+
if (!fedInfo.outbound) fedInfo.outbound = [];
|
|
433
395
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
fedInfo.notify = fedInfo.notify.filter(id => id !== spaceId2);
|
|
440
|
-
|
|
441
|
-
// Update timestamp
|
|
442
|
-
fedInfo.timestamp = Date.now();
|
|
443
|
-
|
|
444
|
-
// Save updated federation info
|
|
445
|
-
await holosphere.putGlobal('federation', fedInfo, password1);
|
|
446
|
-
return true;
|
|
447
|
-
} else {
|
|
448
|
-
return false;
|
|
396
|
+
if (!fedInfo.outbound.includes(spaceId2)) return false;
|
|
397
|
+
|
|
398
|
+
fedInfo.outbound = fedInfo.outbound.filter(id => id !== spaceId2);
|
|
399
|
+
if (fedInfo.lensConfig?.[spaceId2]) {
|
|
400
|
+
fedInfo.lensConfig[spaceId2].outbound = [];
|
|
449
401
|
}
|
|
402
|
+
fedInfo.timestamp = Date.now();
|
|
403
|
+
|
|
404
|
+
await holosphere.putGlobal('federation', fedInfo, password1);
|
|
405
|
+
return true;
|
|
450
406
|
} catch (error) {
|
|
451
407
|
throw error;
|
|
452
408
|
}
|
|
@@ -515,8 +471,8 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
515
471
|
if (includeLocal) {
|
|
516
472
|
spacesToQuery.push(holon); // Add local holon first
|
|
517
473
|
}
|
|
518
|
-
if (includeFederated && fedInfo && fedInfo.
|
|
519
|
-
const federatedSpaces = maxFederatedSpaces === -1 ? fedInfo.
|
|
474
|
+
if (includeFederated && fedInfo && fedInfo.inbound && fedInfo.inbound.length > 0) {
|
|
475
|
+
const federatedSpaces = maxFederatedSpaces === -1 ? fedInfo.inbound : fedInfo.inbound.slice(0, maxFederatedSpaces);
|
|
520
476
|
spacesToQuery = spacesToQuery.concat(federatedSpaces);
|
|
521
477
|
}
|
|
522
478
|
|
|
@@ -596,91 +552,45 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
596
552
|
console.log(`Original data found via soul path:`, JSON.stringify(originalData));
|
|
597
553
|
|
|
598
554
|
if (originalData) {
|
|
599
|
-
// Replace the reference with the
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
_federation: {
|
|
603
|
-
isReference: true,
|
|
604
|
-
resolved: true,
|
|
605
|
-
soul: item.soul,
|
|
606
|
-
timestamp: Date.now()
|
|
607
|
-
}
|
|
608
|
-
};
|
|
555
|
+
// Replace the reference with the resolved data, attaching
|
|
556
|
+
// the canonical _hologram envelope (single source of truth).
|
|
557
|
+
result[i] = attachHologramMeta(originalData, item.soul);
|
|
609
558
|
} else {
|
|
610
|
-
//
|
|
559
|
+
// Original data not found — keep the id so callers can
|
|
560
|
+
// identify the broken reference, and surface the error.
|
|
611
561
|
result[i] = {
|
|
612
562
|
id: item.id,
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
resolved: false,
|
|
563
|
+
_hologram: {
|
|
564
|
+
isHologram: false,
|
|
616
565
|
soul: item.soul,
|
|
617
566
|
error: 'Referenced data not found',
|
|
618
|
-
|
|
567
|
+
resolvedAt: Date.now()
|
|
619
568
|
}
|
|
620
569
|
};
|
|
621
570
|
}
|
|
622
571
|
} else {
|
|
623
572
|
console.warn(`Soul doesn't match expected format: ${item.soul}`);
|
|
624
|
-
// Instead of leaving the original reference, create an error object
|
|
625
573
|
result[i] = {
|
|
626
574
|
id: item.id,
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
resolved: false,
|
|
575
|
+
_hologram: {
|
|
576
|
+
isHologram: false,
|
|
630
577
|
soul: item.soul,
|
|
631
578
|
error: 'Invalid soul format',
|
|
632
|
-
|
|
579
|
+
resolvedAt: Date.now()
|
|
633
580
|
}
|
|
634
581
|
};
|
|
635
582
|
}
|
|
636
583
|
} catch (refError) {
|
|
637
|
-
// Instead of leaving the original reference, create an error object
|
|
638
584
|
result[i] = {
|
|
639
585
|
id: item.id,
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
resolved: false,
|
|
586
|
+
_hologram: {
|
|
587
|
+
isHologram: false,
|
|
643
588
|
soul: item.soul,
|
|
644
589
|
error: refError.message || 'Error resolving reference',
|
|
645
|
-
|
|
590
|
+
resolvedAt: Date.now()
|
|
646
591
|
}
|
|
647
592
|
};
|
|
648
593
|
}
|
|
649
|
-
}
|
|
650
|
-
// For backward compatibility, check for old-style references
|
|
651
|
-
else if (item._federation && item._federation.isReference) {
|
|
652
|
-
console.log(`Found legacy reference: ${item._federation.origin}/${item._federation.lens}/${item[idField]}`);
|
|
653
|
-
|
|
654
|
-
try {
|
|
655
|
-
const reference = item._federation;
|
|
656
|
-
console.log(`Getting original data from ${reference.origin} / ${reference.lens} / ${item[idField]}`);
|
|
657
|
-
|
|
658
|
-
// Get original data
|
|
659
|
-
const originalData = await holosphere.get(
|
|
660
|
-
reference.origin,
|
|
661
|
-
reference.lens,
|
|
662
|
-
item[idField],
|
|
663
|
-
null,
|
|
664
|
-
{ resolveReferences: false } // Prevent infinite recursion
|
|
665
|
-
);
|
|
666
|
-
|
|
667
|
-
console.log(`Original data found:`, JSON.stringify(originalData));
|
|
668
|
-
|
|
669
|
-
if (originalData) {
|
|
670
|
-
// Add federation information to the resolved data
|
|
671
|
-
result[i] = {
|
|
672
|
-
...originalData,
|
|
673
|
-
_federation: {
|
|
674
|
-
...reference,
|
|
675
|
-
resolved: true,
|
|
676
|
-
timestamp: Date.now()
|
|
677
|
-
}
|
|
678
|
-
};
|
|
679
|
-
} else {
|
|
680
|
-
console.warn(`Could not resolve legacy reference: original data not found`);
|
|
681
|
-
}
|
|
682
|
-
} catch (refError) {
|
|
683
|
-
}
|
|
684
594
|
}
|
|
685
595
|
}
|
|
686
596
|
}
|
|
@@ -790,18 +700,16 @@ export async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
|
790
700
|
// Get federation info for this holon using getFederation
|
|
791
701
|
const fedInfo = await getFederation(holosphere, holon, password);
|
|
792
702
|
|
|
793
|
-
// Only
|
|
794
|
-
if (fedInfo && fedInfo.
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
// Further filter by targetSpaces if provided
|
|
703
|
+
// Only propagate if we have outbound partners configured.
|
|
704
|
+
if (fedInfo && fedInfo.outbound && fedInfo.outbound.length > 0) {
|
|
705
|
+
let spaces = fedInfo.outbound;
|
|
706
|
+
|
|
799
707
|
if (targetSpaces && Array.isArray(targetSpaces) && targetSpaces.length > 0) {
|
|
800
708
|
spaces = spaces.filter(space => targetSpaces.includes(space));
|
|
801
709
|
}
|
|
802
|
-
|
|
710
|
+
|
|
803
711
|
if (spaces.length > 0) {
|
|
804
|
-
//
|
|
712
|
+
// Keep only partners whose outbound lens config includes this lens.
|
|
805
713
|
spaces = spaces.filter(targetSpace => {
|
|
806
714
|
const spaceConfig = fedInfo.lensConfig?.[targetSpace];
|
|
807
715
|
if (!spaceConfig) {
|
|
@@ -810,19 +718,13 @@ export async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
|
810
718
|
return false;
|
|
811
719
|
}
|
|
812
720
|
|
|
813
|
-
|
|
814
|
-
const
|
|
721
|
+
const outboundLenses = Array.isArray(spaceConfig.outbound) ? spaceConfig.outbound : [];
|
|
722
|
+
const shouldPropagate = outboundLenses.includes('*') || outboundLenses.includes(lens);
|
|
815
723
|
|
|
816
|
-
const shouldFederate = federateLenses.includes('*') || federateLenses.includes(lens);
|
|
817
|
-
|
|
818
|
-
// Propagation now only depends on the 'federate' list configuration for the lens
|
|
819
|
-
const shouldPropagate = shouldFederate;
|
|
820
|
-
|
|
821
724
|
if (!shouldPropagate) {
|
|
822
|
-
result.messages.push(`Propagation of lens '${lens}' to target space ${targetSpace} skipped: lens not in '
|
|
725
|
+
result.messages.push(`Propagation of lens '${lens}' to target space ${targetSpace} skipped: lens not in 'outbound' configuration.`);
|
|
823
726
|
result.skipped++;
|
|
824
727
|
}
|
|
825
|
-
|
|
826
728
|
return shouldPropagate;
|
|
827
729
|
});
|
|
828
730
|
|
|
@@ -1128,82 +1030,76 @@ export async function resetFederation(holosphere, spaceId, password = null, opti
|
|
|
1128
1030
|
};
|
|
1129
1031
|
}
|
|
1130
1032
|
|
|
1131
|
-
|
|
1132
|
-
result.federatedCount =
|
|
1133
|
-
result.
|
|
1033
|
+
const allPartners = fedInfo.federated || [];
|
|
1034
|
+
result.federatedCount = allPartners.length;
|
|
1035
|
+
result.inboundCount = fedInfo.inbound?.length || 0;
|
|
1036
|
+
result.outboundCount = fedInfo.outbound?.length || 0;
|
|
1134
1037
|
|
|
1135
|
-
//
|
|
1038
|
+
// Reset federation record to empty.
|
|
1136
1039
|
const emptyFedInfo = {
|
|
1137
1040
|
id: spaceId,
|
|
1138
1041
|
name: spaceName || spaceId,
|
|
1139
|
-
|
|
1140
|
-
|
|
1042
|
+
federated: [],
|
|
1043
|
+
inbound: [],
|
|
1044
|
+
outbound: [],
|
|
1045
|
+
lensConfig: {},
|
|
1046
|
+
partnerNames: {},
|
|
1141
1047
|
timestamp: Date.now()
|
|
1142
1048
|
};
|
|
1143
|
-
|
|
1144
|
-
// Update federation record with empty lists
|
|
1145
1049
|
await holosphere.putGlobal('federation', emptyFedInfo, password);
|
|
1146
1050
|
|
|
1147
|
-
//
|
|
1148
|
-
if (notifyPartners &&
|
|
1149
|
-
const updatePromises =
|
|
1051
|
+
// Tell each partner to drop us from their lists.
|
|
1052
|
+
if (notifyPartners && allPartners.length > 0) {
|
|
1053
|
+
const updatePromises = allPartners.map(async (partnerSpace) => {
|
|
1150
1054
|
try {
|
|
1151
|
-
// Get partner's federation info
|
|
1152
1055
|
const partnerFedInfo = await getFederation(holosphere, partnerSpace);
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
id => id !== spaceId.toString()
|
|
1159
|
-
);
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
// Remove this space from partner's notify list
|
|
1163
|
-
if (partnerFedInfo.notify) {
|
|
1164
|
-
partnerFedInfo.notify = partnerFedInfo.notify.filter(
|
|
1165
|
-
id => id !== spaceId.toString()
|
|
1166
|
-
);
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
partnerFedInfo.timestamp = Date.now();
|
|
1170
|
-
|
|
1171
|
-
// Save partner's updated federation info
|
|
1172
|
-
await holosphere.putGlobal('federation', partnerFedInfo);
|
|
1173
|
-
result.partnersNotified++;
|
|
1174
|
-
return true;
|
|
1056
|
+
if (!partnerFedInfo) return false;
|
|
1057
|
+
|
|
1058
|
+
const sid = spaceId.toString();
|
|
1059
|
+
if (partnerFedInfo.federated) {
|
|
1060
|
+
partnerFedInfo.federated = partnerFedInfo.federated.filter(id => id !== sid);
|
|
1175
1061
|
}
|
|
1176
|
-
|
|
1062
|
+
if (partnerFedInfo.inbound) {
|
|
1063
|
+
partnerFedInfo.inbound = partnerFedInfo.inbound.filter(id => id !== sid);
|
|
1064
|
+
}
|
|
1065
|
+
if (partnerFedInfo.outbound) {
|
|
1066
|
+
partnerFedInfo.outbound = partnerFedInfo.outbound.filter(id => id !== sid);
|
|
1067
|
+
}
|
|
1068
|
+
if (partnerFedInfo.lensConfig) {
|
|
1069
|
+
delete partnerFedInfo.lensConfig[sid];
|
|
1070
|
+
}
|
|
1071
|
+
if (partnerFedInfo.partnerNames) {
|
|
1072
|
+
delete partnerFedInfo.partnerNames[sid];
|
|
1073
|
+
}
|
|
1074
|
+
partnerFedInfo.timestamp = Date.now();
|
|
1075
|
+
|
|
1076
|
+
await holosphere.putGlobal('federation', partnerFedInfo);
|
|
1077
|
+
result.partnersNotified++;
|
|
1078
|
+
return true;
|
|
1177
1079
|
} catch (error) {
|
|
1178
|
-
result.errors.push({
|
|
1179
|
-
partner: partnerSpace,
|
|
1180
|
-
error: error.message
|
|
1181
|
-
});
|
|
1080
|
+
result.errors.push({ partner: partnerSpace, error: error.message });
|
|
1182
1081
|
return false;
|
|
1183
1082
|
}
|
|
1184
1083
|
});
|
|
1185
|
-
|
|
1084
|
+
|
|
1186
1085
|
await Promise.all(updatePromises);
|
|
1187
1086
|
}
|
|
1188
|
-
|
|
1189
|
-
//
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
await holosphere.putGlobal('federationMeta', meta);
|
|
1203
|
-
}
|
|
1204
|
-
} catch (error) {
|
|
1087
|
+
|
|
1088
|
+
// Mark any federationMeta records inactive.
|
|
1089
|
+
for (const partnerSpace of allPartners) {
|
|
1090
|
+
try {
|
|
1091
|
+
const metaId = `${spaceId}_${partnerSpace}`;
|
|
1092
|
+
const altMetaId = `${partnerSpace}_${spaceId}`;
|
|
1093
|
+
|
|
1094
|
+
const meta = await holosphere.getGlobal('federationMeta', metaId) ||
|
|
1095
|
+
await holosphere.getGlobal('federationMeta', altMetaId);
|
|
1096
|
+
|
|
1097
|
+
if (meta) {
|
|
1098
|
+
meta.status = 'inactive';
|
|
1099
|
+
meta.endedAt = Date.now();
|
|
1100
|
+
await holosphere.putGlobal('federationMeta', meta);
|
|
1205
1101
|
}
|
|
1206
|
-
}
|
|
1102
|
+
} catch {}
|
|
1207
1103
|
}
|
|
1208
1104
|
|
|
1209
1105
|
result.success = true;
|