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/global.js
CHANGED
|
@@ -65,11 +65,15 @@ export async function putGlobal(holoInstance, tableName, data, password = null)
|
|
|
65
65
|
|
|
66
66
|
return new Promise((resolve, reject) => {
|
|
67
67
|
try {
|
|
68
|
-
// Create a copy of data
|
|
68
|
+
// Create a copy of data, stripping read-side envelopes that
|
|
69
|
+
// must never be persisted (they're attached at resolution time).
|
|
69
70
|
let dataToStore = { ...data };
|
|
70
71
|
if (dataToStore._meta !== undefined) {
|
|
71
72
|
delete dataToStore._meta;
|
|
72
73
|
}
|
|
74
|
+
if (dataToStore._hologram !== undefined) {
|
|
75
|
+
delete dataToStore._hologram;
|
|
76
|
+
}
|
|
73
77
|
const payload = JSON.stringify(dataToStore);
|
|
74
78
|
|
|
75
79
|
// Check if the data being stored is a hologram
|
|
@@ -327,8 +331,12 @@ export async function getAllGlobal(holoInstance, tableName, password = null) {
|
|
|
327
331
|
user.get('private').get(tableName) :
|
|
328
332
|
holoInstance.gun.get(holoInstance.appname).get(tableName);
|
|
329
333
|
|
|
330
|
-
// PASS 1: Get shallow node to determine expected item count
|
|
331
|
-
|
|
334
|
+
// PASS 1: Get shallow node to determine expected item count.
|
|
335
|
+
// Retry once if empty — Gun's .once() reads from local cache, which
|
|
336
|
+
// may be cold immediately after startup before peers have synced.
|
|
337
|
+
const shallowOnce = () => new Promise((res) => dataPath.once((d) => res(d)));
|
|
338
|
+
|
|
339
|
+
const processShallow = (data) => {
|
|
332
340
|
if (!data) {
|
|
333
341
|
resolve([]);
|
|
334
342
|
return;
|
|
@@ -350,60 +358,63 @@ export async function getAllGlobal(holoInstance, tableName, password = null) {
|
|
|
350
358
|
return;
|
|
351
359
|
}
|
|
352
360
|
|
|
353
|
-
// PASS 2:
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (!itemData
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
resolve(output);
|
|
362
|
-
}
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const processingPromise = (async () => {
|
|
367
|
-
try {
|
|
368
|
-
const parsed = await holoInstance.parse(itemData);
|
|
369
|
-
if (!parsed) return;
|
|
361
|
+
// PASS 2: iterate explicitly over the filtered keys.
|
|
362
|
+
// Avoid dataPath.map().once() — it fires for null tombstone
|
|
363
|
+
// siblings and can resolve before real items are processed.
|
|
364
|
+
const processItem = async (itemData, key) => {
|
|
365
|
+
if (!itemData) return;
|
|
366
|
+
try {
|
|
367
|
+
const parsed = await holoInstance.parse(itemData);
|
|
368
|
+
if (!parsed) return;
|
|
370
369
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
370
|
+
if (holoInstance.isHologram(parsed)) {
|
|
371
|
+
const resolved = await holoInstance.resolveHologram(parsed, {
|
|
372
|
+
followHolograms: true
|
|
373
|
+
});
|
|
375
374
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
return;
|
|
375
|
+
if (resolved === null) {
|
|
376
|
+
try {
|
|
377
|
+
await holoInstance.deleteGlobal(tableName, key, password);
|
|
378
|
+
} catch (deleteError) {
|
|
379
|
+
console.error(`Failed to delete invalid global hologram at ${tableName}/${key}:`, deleteError);
|
|
383
380
|
}
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
384
383
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
} else {
|
|
388
|
-
output.push(parsed);
|
|
389
|
-
}
|
|
384
|
+
if (resolved !== parsed) {
|
|
385
|
+
output.push(resolved);
|
|
390
386
|
} else {
|
|
391
387
|
output.push(parsed);
|
|
392
388
|
}
|
|
393
|
-
}
|
|
394
|
-
|
|
389
|
+
} else {
|
|
390
|
+
output.push(parsed);
|
|
395
391
|
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
392
|
+
} catch (error) {
|
|
393
|
+
console.error('Error parsing data:', error);
|
|
394
|
+
}
|
|
395
|
+
};
|
|
400
396
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
397
|
+
Promise.all(keys.map((key) => {
|
|
398
|
+
const inline = data[key];
|
|
399
|
+
if (typeof inline !== 'object' || inline === null) {
|
|
400
|
+
return processItem(inline, key);
|
|
404
401
|
}
|
|
405
|
-
|
|
406
|
-
|
|
402
|
+
return new Promise((resolveItem) => {
|
|
403
|
+
dataPath.get(key).once((itemData) => {
|
|
404
|
+
processItem(itemData, key).then(resolveItem, resolveItem);
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
})).then(() => resolve(output));
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
(async () => {
|
|
411
|
+
let data = await shallowOnce();
|
|
412
|
+
if (!data) {
|
|
413
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
414
|
+
data = await shallowOnce();
|
|
415
|
+
}
|
|
416
|
+
processShallow(data);
|
|
417
|
+
})();
|
|
407
418
|
});
|
|
408
419
|
} catch (error) {
|
|
409
420
|
console.error('Error in getAllGlobal:', error);
|
|
@@ -730,11 +741,70 @@ export async function deleteAllGlobal(holoInstance, tableName, password = null)
|
|
|
730
741
|
}
|
|
731
742
|
}
|
|
732
743
|
|
|
744
|
+
/**
|
|
745
|
+
* Subscribe to real-time changes in a global table.
|
|
746
|
+
* @param {HoloSphere} holoInstance - The HoloSphere instance.
|
|
747
|
+
* @param {string} tableName - The table name to subscribe to.
|
|
748
|
+
* @param {string|null} key - Specific key to subscribe to, or null for all keys.
|
|
749
|
+
* @param {function} callback - Callback for data changes.
|
|
750
|
+
* @param {object} [options] - Subscription options.
|
|
751
|
+
* @param {boolean} [options.realtimeOnly] - Only fire for new changes.
|
|
752
|
+
* @returns {Promise<{ unsubscribe: () => void }>}
|
|
753
|
+
*/
|
|
754
|
+
export async function subscribeGlobal(holoInstance, tableName, key, callback, options = {}) {
|
|
755
|
+
const dataPath = holoInstance.gun.get(holoInstance.appname).get(tableName);
|
|
756
|
+
let active = true;
|
|
757
|
+
|
|
758
|
+
if (key) {
|
|
759
|
+
// Subscribe to a specific key
|
|
760
|
+
dataPath.get(key).on(async (data) => {
|
|
761
|
+
if (!active || !data) return;
|
|
762
|
+
try {
|
|
763
|
+
const parsed = await holoInstance.parse(data);
|
|
764
|
+
if (parsed) callback(parsed, key);
|
|
765
|
+
} catch (e) {
|
|
766
|
+
console.warn('[subscribeGlobal] Error parsing data:', e);
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
} else {
|
|
770
|
+
// Subscribe to all keys in the table
|
|
771
|
+
dataPath.map().on(async (data, k) => {
|
|
772
|
+
if (!active || !data || k === '_') return;
|
|
773
|
+
try {
|
|
774
|
+
const parsed = await holoInstance.parse(data);
|
|
775
|
+
if (parsed) callback(parsed, k);
|
|
776
|
+
} catch (e) {
|
|
777
|
+
console.warn('[subscribeGlobal] Error parsing data:', e);
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return {
|
|
783
|
+
unsubscribe: () => {
|
|
784
|
+
active = false;
|
|
785
|
+
if (key) {
|
|
786
|
+
dataPath.get(key).off();
|
|
787
|
+
} else {
|
|
788
|
+
dataPath.off();
|
|
789
|
+
}
|
|
790
|
+
},
|
|
791
|
+
stop: () => {
|
|
792
|
+
active = false;
|
|
793
|
+
if (key) {
|
|
794
|
+
dataPath.get(key).off();
|
|
795
|
+
} else {
|
|
796
|
+
dataPath.off();
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
|
|
733
802
|
// Export all global operations as default
|
|
734
803
|
export default {
|
|
735
804
|
putGlobal,
|
|
736
805
|
getGlobal,
|
|
737
806
|
getAllGlobal,
|
|
738
807
|
deleteGlobal,
|
|
739
|
-
deleteAllGlobal
|
|
740
|
-
|
|
808
|
+
deleteAllGlobal,
|
|
809
|
+
subscribeGlobal
|
|
810
|
+
};
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GunDB-based federation handshake protocol for HoloSphere v1.3
|
|
3
|
+
* Replaces Nostr NIP-44 encrypted DMs with GunDB DM channels.
|
|
4
|
+
* Same protocol payloads (JSON with type: federation_request/response/update),
|
|
5
|
+
* different transport layer.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generate a unique message ID
|
|
10
|
+
*/
|
|
11
|
+
function generateMessageId() {
|
|
12
|
+
return Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 10);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get the DM path for a recipient in GunDB
|
|
17
|
+
*/
|
|
18
|
+
function getDMPath(holosphere, recipientPubKey) {
|
|
19
|
+
return holosphere.gun.get(holosphere.appname).get('_dm').get(recipientPubKey);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Write a DM to a recipient's channel
|
|
24
|
+
*/
|
|
25
|
+
async function sendDM(holosphere, recipientPubKey, message) {
|
|
26
|
+
const msgId = generateMessageId();
|
|
27
|
+
const payload = JSON.stringify({
|
|
28
|
+
...message,
|
|
29
|
+
id: msgId,
|
|
30
|
+
timestamp: Date.now()
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
getDMPath(holosphere, recipientPubKey).get(msgId).put(payload, (ack) => {
|
|
35
|
+
if (ack.err) {
|
|
36
|
+
console.warn('[handshake] Failed to send DM:', ack.err);
|
|
37
|
+
resolve({ success: false, error: ack.err });
|
|
38
|
+
} else {
|
|
39
|
+
resolve({ success: true, id: msgId });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Subscribe to federation DMs for a public key.
|
|
47
|
+
* Listens for incoming federation requests, responses, and updates.
|
|
48
|
+
*
|
|
49
|
+
* @param {object} holosphere - The HoloSphere instance
|
|
50
|
+
* @param {string|Uint8Array} privateKey - The user's private key (for identity)
|
|
51
|
+
* @param {string} publicKey - The user's public key
|
|
52
|
+
* @param {object} handlers - Event handlers
|
|
53
|
+
* @param {function} handlers.onRequest - Called when a federation request is received
|
|
54
|
+
* @param {function} handlers.onResponse - Called when a federation response is received
|
|
55
|
+
* @param {function} handlers.onUpdate - Called when a federation update is received
|
|
56
|
+
* @param {function} handlers.onUpdateResponse - Called when an update response is received
|
|
57
|
+
* @returns {function} Unsubscribe function
|
|
58
|
+
*/
|
|
59
|
+
export function subscribeToFederationDMs(holosphere, privateKey, publicKey, handlers) {
|
|
60
|
+
const dmPath = getDMPath(holosphere, publicKey);
|
|
61
|
+
let active = true;
|
|
62
|
+
const processedMessages = new Set();
|
|
63
|
+
|
|
64
|
+
dmPath.map().on((data, key) => {
|
|
65
|
+
if (!active || !data || key === '_') return;
|
|
66
|
+
|
|
67
|
+
// Avoid processing the same message twice
|
|
68
|
+
if (processedMessages.has(key)) return;
|
|
69
|
+
processedMessages.add(key);
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const message = typeof data === 'string' ? JSON.parse(data) : data;
|
|
73
|
+
const senderPubKey = message.senderPubKey || message.sender || '';
|
|
74
|
+
|
|
75
|
+
switch (message.type) {
|
|
76
|
+
case 'federation_request':
|
|
77
|
+
if (handlers.onRequest) {
|
|
78
|
+
handlers.onRequest(message, senderPubKey);
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
case 'federation_response':
|
|
82
|
+
if (handlers.onResponse) {
|
|
83
|
+
handlers.onResponse(message, senderPubKey);
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
case 'federation_update':
|
|
87
|
+
if (handlers.onUpdate) {
|
|
88
|
+
handlers.onUpdate(message, senderPubKey);
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
case 'federation_update_response':
|
|
92
|
+
if (handlers.onUpdateResponse) {
|
|
93
|
+
handlers.onUpdateResponse(message, senderPubKey);
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
default:
|
|
97
|
+
console.log('[handshake] Unknown DM type:', message.type);
|
|
98
|
+
}
|
|
99
|
+
} catch (e) {
|
|
100
|
+
// Skip unparseable messages
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Return unsubscribe function
|
|
105
|
+
return () => {
|
|
106
|
+
active = false;
|
|
107
|
+
dmPath.off();
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Initiate a federation handshake with a partner.
|
|
113
|
+
*
|
|
114
|
+
* @param {object} holosphere - The HoloSphere instance
|
|
115
|
+
* @param {string|Uint8Array} privateKey - The initiator's private key
|
|
116
|
+
* @param {object} params - Handshake parameters
|
|
117
|
+
* @param {string} params.partnerPubKey - The partner's public key
|
|
118
|
+
* @param {string} params.holonId - The initiator's holon ID
|
|
119
|
+
* @param {string} params.holonName - The initiator's holon name
|
|
120
|
+
* @param {object} [params.lensConfig] - Lens configuration to share
|
|
121
|
+
* @param {string} [params.message] - Optional message
|
|
122
|
+
* @returns {Promise<{ success: boolean, requestId?: string }>}
|
|
123
|
+
*/
|
|
124
|
+
export async function initiateFederationHandshake(holosphere, privateKey, params) {
|
|
125
|
+
const {
|
|
126
|
+
partnerPubKey,
|
|
127
|
+
holonId,
|
|
128
|
+
holonName,
|
|
129
|
+
lensConfig = {},
|
|
130
|
+
message = ''
|
|
131
|
+
} = params;
|
|
132
|
+
|
|
133
|
+
const senderPubKey = holosphere.client?.publicKey || '';
|
|
134
|
+
|
|
135
|
+
const request = {
|
|
136
|
+
type: 'federation_request',
|
|
137
|
+
senderPubKey,
|
|
138
|
+
senderHolonId: holonId,
|
|
139
|
+
senderHolonName: holonName,
|
|
140
|
+
lensConfig,
|
|
141
|
+
message,
|
|
142
|
+
status: 'pending'
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const result = await sendDM(holosphere, partnerPubKey, request);
|
|
146
|
+
if (result.success) {
|
|
147
|
+
console.log('[handshake] Federation request sent to:', partnerPubKey?.slice(0, 8));
|
|
148
|
+
}
|
|
149
|
+
return { success: result.success, requestId: result.id };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Accept a federation request.
|
|
154
|
+
*
|
|
155
|
+
* @param {object} holosphere - The HoloSphere instance
|
|
156
|
+
* @param {string|Uint8Array} privateKey - The responder's private key
|
|
157
|
+
* @param {object} params - Accept parameters
|
|
158
|
+
* @param {string} params.requesterPubKey - The requester's public key
|
|
159
|
+
* @param {string} params.holonId - The responder's holon ID
|
|
160
|
+
* @param {string} params.holonName - The responder's holon name
|
|
161
|
+
* @param {object} [params.lensConfig] - Lens configuration
|
|
162
|
+
* @param {string} [params.requestId] - Original request ID
|
|
163
|
+
* @returns {Promise<{ success: boolean }>}
|
|
164
|
+
*/
|
|
165
|
+
export async function acceptFederationRequest(holosphere, privateKey, params) {
|
|
166
|
+
const {
|
|
167
|
+
requesterPubKey,
|
|
168
|
+
holonId,
|
|
169
|
+
holonName,
|
|
170
|
+
lensConfig = {},
|
|
171
|
+
requestId
|
|
172
|
+
} = params;
|
|
173
|
+
|
|
174
|
+
const senderPubKey = holosphere.client?.publicKey || '';
|
|
175
|
+
|
|
176
|
+
const response = {
|
|
177
|
+
type: 'federation_response',
|
|
178
|
+
senderPubKey,
|
|
179
|
+
responderHolonId: holonId,
|
|
180
|
+
responderHolonName: holonName,
|
|
181
|
+
lensConfig,
|
|
182
|
+
status: 'accepted',
|
|
183
|
+
requestId
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Add the requester as an allowed author
|
|
187
|
+
if (requesterPubKey) {
|
|
188
|
+
holosphere.addAllowedAuthor(requesterPubKey);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return sendDM(holosphere, requesterPubKey, response);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Reject a federation request.
|
|
196
|
+
*/
|
|
197
|
+
export async function rejectFederationRequest(holosphere, privateKey, params) {
|
|
198
|
+
const { requesterPubKey, holonId, reason = '', requestId } = params;
|
|
199
|
+
const senderPubKey = holosphere.client?.publicKey || '';
|
|
200
|
+
|
|
201
|
+
const response = {
|
|
202
|
+
type: 'federation_response',
|
|
203
|
+
senderPubKey,
|
|
204
|
+
responderHolonId: holonId,
|
|
205
|
+
status: 'rejected',
|
|
206
|
+
reason,
|
|
207
|
+
requestId
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
return sendDM(holosphere, requesterPubKey, response);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Process a received federation response.
|
|
215
|
+
* Creates the federation relationship on the initiator's side.
|
|
216
|
+
*
|
|
217
|
+
* @param {object} holosphere - The HoloSphere instance
|
|
218
|
+
* @param {object} response - The response object
|
|
219
|
+
* @param {string} senderPubKey - The responder's public key
|
|
220
|
+
* @param {object} options - Processing options
|
|
221
|
+
* @param {string} options.holonId - The initiator's holon ID
|
|
222
|
+
* @param {string[]} [options.inboundLenses] - Lenses to accept inbound data for
|
|
223
|
+
* @returns {Promise<{ success: boolean }>}
|
|
224
|
+
*/
|
|
225
|
+
export async function processFederationResponse(holosphere, response, senderPubKey, options = {}) {
|
|
226
|
+
const { holonId, inboundLenses = [] } = options;
|
|
227
|
+
|
|
228
|
+
if (response.status !== 'accepted') {
|
|
229
|
+
return { success: false, reason: 'not_accepted' };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
// Add the responder as an allowed author
|
|
234
|
+
if (senderPubKey) {
|
|
235
|
+
holosphere.addAllowedAuthor(senderPubKey);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Store the responder's holon ID for federation lookup
|
|
239
|
+
const responderHolonId = response.responderHolonId || senderPubKey;
|
|
240
|
+
|
|
241
|
+
console.log('[handshake] Processing accepted federation from:', responderHolonId?.slice(0, 8));
|
|
242
|
+
return { success: true, responderHolonId };
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.error('[handshake] Error processing federation response:', error);
|
|
245
|
+
return { success: false, error: error.message };
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Request a federation update (e.g., change shared lenses).
|
|
251
|
+
*
|
|
252
|
+
* @param {object} holosphere - The HoloSphere instance
|
|
253
|
+
* @param {string|Uint8Array} privateKey - The requester's private key
|
|
254
|
+
* @param {object} params - Update parameters
|
|
255
|
+
* @param {string} params.partnerPubKey - The partner's public key
|
|
256
|
+
* @param {string} params.holonId - The requester's holon ID
|
|
257
|
+
* @param {string} params.holonName - The requester's holon name
|
|
258
|
+
* @param {object} params.newLensConfig - The new lens configuration
|
|
259
|
+
* @param {string} [params.message] - Optional message
|
|
260
|
+
* @returns {Promise<{ success: boolean }>}
|
|
261
|
+
*/
|
|
262
|
+
export async function requestFederationUpdate(holosphere, privateKey, params) {
|
|
263
|
+
const {
|
|
264
|
+
partnerPubKey,
|
|
265
|
+
holonId,
|
|
266
|
+
holonName,
|
|
267
|
+
newLensConfig = {},
|
|
268
|
+
message = ''
|
|
269
|
+
} = params;
|
|
270
|
+
|
|
271
|
+
const senderPubKey = holosphere.client?.publicKey || '';
|
|
272
|
+
|
|
273
|
+
const update = {
|
|
274
|
+
type: 'federation_update',
|
|
275
|
+
senderPubKey,
|
|
276
|
+
senderHolonId: holonId,
|
|
277
|
+
senderHolonName: holonName,
|
|
278
|
+
newLensConfig,
|
|
279
|
+
message
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
return sendDM(holosphere, partnerPubKey, update);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Accept a federation update request.
|
|
287
|
+
*/
|
|
288
|
+
export async function acceptFederationUpdate(holosphere, privateKey, params) {
|
|
289
|
+
const { requesterPubKey, holonId, newLensConfig = {}, updateId } = params;
|
|
290
|
+
const senderPubKey = holosphere.client?.publicKey || '';
|
|
291
|
+
|
|
292
|
+
const response = {
|
|
293
|
+
type: 'federation_update_response',
|
|
294
|
+
senderPubKey,
|
|
295
|
+
responderHolonId: holonId,
|
|
296
|
+
status: 'accepted',
|
|
297
|
+
newLensConfig,
|
|
298
|
+
updateId
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
return sendDM(holosphere, requesterPubKey, response);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Reject a federation update request.
|
|
306
|
+
*/
|
|
307
|
+
export async function rejectFederationUpdate(holosphere, privateKey, params) {
|
|
308
|
+
const { requesterPubKey, holonId, reason = '', updateId } = params;
|
|
309
|
+
const senderPubKey = holosphere.client?.publicKey || '';
|
|
310
|
+
|
|
311
|
+
const response = {
|
|
312
|
+
type: 'federation_update_response',
|
|
313
|
+
senderPubKey,
|
|
314
|
+
responderHolonId: holonId,
|
|
315
|
+
status: 'rejected',
|
|
316
|
+
reason,
|
|
317
|
+
updateId
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
return sendDM(holosphere, requesterPubKey, response);
|
|
321
|
+
}
|
package/hologram.js
CHANGED
|
@@ -78,7 +78,37 @@ export function isHologram(data) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
*
|
|
81
|
+
* Attaches the canonical `_hologram` envelope to data resolved from a soul reference.
|
|
82
|
+
*
|
|
83
|
+
* This is the SINGLE source of truth for resolved-hologram metadata in HoloSphere.
|
|
84
|
+
* Every code path that returns "data resolved from a hologram reference" must go
|
|
85
|
+
* through this helper so consumers can rely on one shape.
|
|
86
|
+
*
|
|
87
|
+
* @param {object} originalData - The data fetched from the source holon/lens/key.
|
|
88
|
+
* @param {string} hologramSoul - The soul path the hologram pointed at.
|
|
89
|
+
* @returns {object} - originalData merged with `_hologram: { isHologram, soul, sourceHolon, sourceLens, sourceKey, resolvedAt }`.
|
|
90
|
+
*/
|
|
91
|
+
export function attachHologramMeta(originalData, hologramSoul) {
|
|
92
|
+
const parts = parseSoulPath(hologramSoul);
|
|
93
|
+
return {
|
|
94
|
+
...originalData,
|
|
95
|
+
_hologram: {
|
|
96
|
+
isHologram: true,
|
|
97
|
+
soul: hologramSoul,
|
|
98
|
+
sourceHolon: parts?.holon ?? null,
|
|
99
|
+
sourceLens: parts?.lens ?? null,
|
|
100
|
+
sourceKey: parts?.key ?? null,
|
|
101
|
+
resolvedAt: Date.now(),
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Resolves a hologram to its actual data.
|
|
108
|
+
*
|
|
109
|
+
* On success, the returned object spreads `originalData` and attaches the
|
|
110
|
+
* canonical `_hologram` envelope (see {@link attachHologramMeta}).
|
|
111
|
+
*
|
|
82
112
|
* @param {HoloSphere} holoInstance - The HoloSphere instance.
|
|
83
113
|
* @param {object} hologram - The hologram to resolve
|
|
84
114
|
* @param {object} [options] - Optional parameters
|
|
@@ -86,7 +116,7 @@ export function isHologram(data) {
|
|
|
86
116
|
* @param {Set<string>} [options.visited] - Internal use: Tracks visited souls to prevent loops
|
|
87
117
|
* @param {number} [options.maxDepth=10] - Maximum resolution depth to prevent infinite loops
|
|
88
118
|
* @param {number} [options.currentDepth=0] - Current resolution depth
|
|
89
|
-
* @returns {Promise<object|null>} - The resolved data, null if resolution failed due to target not found, or the original hologram for circular/invalid cases.
|
|
119
|
+
* @returns {Promise<object|null>} - The resolved data with `_hologram` attached, null if resolution failed due to target not found, or the original hologram for circular/invalid cases.
|
|
90
120
|
*/
|
|
91
121
|
export async function resolveHologram(holoInstance, hologram, options = {}) {
|
|
92
122
|
if (!isHologram(hologram)) {
|
|
@@ -143,16 +173,9 @@ export async function resolveHologram(holoInstance, hologram, options = {}) {
|
|
|
143
173
|
);
|
|
144
174
|
|
|
145
175
|
if (originalData && !originalData._invalidHologram) {
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
_meta: {
|
|
150
|
-
...(originalData._meta || {}), // Preserve original _meta
|
|
151
|
-
resolvedFromHologram: true, // This is now the primary indicator
|
|
152
|
-
hologramSoul: hologram.soul, // Clarified meta field
|
|
153
|
-
resolutionDepth: currentDepth
|
|
154
|
-
}
|
|
155
|
-
};
|
|
176
|
+
// Attach the canonical `_hologram` envelope. This is the only
|
|
177
|
+
// resolved-hologram indicator HoloSphere emits.
|
|
178
|
+
return attachHologramMeta(originalData, hologram.soul);
|
|
156
179
|
} else {
|
|
157
180
|
console.warn(`!!! Original data NOT FOUND for soul: ${hologram.soul}. Removing broken hologram.`);
|
|
158
181
|
|
|
@@ -179,5 +202,6 @@ export default {
|
|
|
179
202
|
createHologram,
|
|
180
203
|
parseSoulPath,
|
|
181
204
|
isHologram,
|
|
182
|
-
resolveHologram
|
|
183
|
-
|
|
205
|
+
resolveHologram,
|
|
206
|
+
attachHologramMeta
|
|
207
|
+
};
|