holosphere 1.1.9 → 1.1.11
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/.cursor/rules/futura.mdc +55 -0
- package/FEDERATION.md +17 -17
- package/compute.js +289 -0
- package/content.js +797 -0
- package/examples/federation.js +98 -90
- package/examples/{references.js → holograms.js} +49 -51
- package/federation.js +307 -197
- package/global.js +560 -0
- package/hologram.js +156 -0
- package/holosphere.d.ts +94 -7
- package/holosphere.js +211 -1464
- package/node.js +155 -0
- package/package.json +2 -5
- package/schema.js +132 -0
- package/test/auth.test.js +85 -51
- package/test/delete.test.js +15 -11
- package/test/federation.test.js +179 -0
- package/test/hologram.test.js +316 -0
- package/test/holosphere.test.js +189 -5
- package/test/subscription.test.js +364 -0
- package/utils.js +290 -0
package/utils.js
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// holo_utils.js
|
|
2
|
+
import * as h3 from 'h3-js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Converts latitude and longitude to a holon identifier.
|
|
6
|
+
* @param {number} lat - The latitude.
|
|
7
|
+
* @param {number} lng - The longitude.
|
|
8
|
+
* @param {number} resolution - The resolution level.
|
|
9
|
+
* @returns {Promise<string>} - The resulting holon identifier.
|
|
10
|
+
*/
|
|
11
|
+
export async function getHolon(lat, lng, resolution) { // Doesn't need holoInstance
|
|
12
|
+
return h3.latLngToCell(lat, lng, resolution);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Retrieves all containing holonagons at all scales for given coordinates.
|
|
17
|
+
* @param {number} lat - The latitude.
|
|
18
|
+
* @param {number} lng - The longitude.
|
|
19
|
+
* @returns {Array<string>} - List of holon identifiers.
|
|
20
|
+
*/
|
|
21
|
+
export function getScalespace(lat, lng) { // Doesn't need holoInstance
|
|
22
|
+
let list = []
|
|
23
|
+
let cell = h3.latLngToCell(lat, lng, 14);
|
|
24
|
+
list.push(cell)
|
|
25
|
+
for (let i = 13; i >= 0; i--) {
|
|
26
|
+
list.push(h3.cellToParent(cell, i))
|
|
27
|
+
}
|
|
28
|
+
return list
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Retrieves all containing holonagons at all scales for a given holon.
|
|
33
|
+
* @param {string} holon - The holon identifier.
|
|
34
|
+
* @returns {Array<string>} - List of holon identifiers.
|
|
35
|
+
*/
|
|
36
|
+
export function getHolonScalespace(holon) { // Doesn't need holoInstance
|
|
37
|
+
let list = []
|
|
38
|
+
let res = h3.getResolution(holon)
|
|
39
|
+
for (let i = res; i >= 0; i--) {
|
|
40
|
+
list.push(h3.cellToParent(holon, i))
|
|
41
|
+
}
|
|
42
|
+
return list
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Subscribes to changes in a specific holon and lens.
|
|
47
|
+
* @param {HoloSphere} holoInstance - The HoloSphere instance.
|
|
48
|
+
* @param {string} holon - The holon identifier.
|
|
49
|
+
* @param {string} lens - The lens to subscribe to.
|
|
50
|
+
* @param {function} callback - The callback to execute on changes.
|
|
51
|
+
* @returns {Promise<object>} - Subscription object with unsubscribe method
|
|
52
|
+
*/
|
|
53
|
+
export async function subscribe(holoInstance, holon, lens, callback) {
|
|
54
|
+
if (!holon || !lens) {
|
|
55
|
+
throw new Error('subscribe: Missing holon or lens parameters:', holon, lens);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!callback || typeof callback !== 'function') {
|
|
59
|
+
throw new Error('subscribe: Callback must be a function');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const subscriptionId = holoInstance.generateId(); // Use instance's generateId
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// Get the Gun chain up to the map()
|
|
66
|
+
const mapChain = holoInstance.gun.get(holoInstance.appname).get(holon).get(lens).map();
|
|
67
|
+
|
|
68
|
+
// Create the subscription by calling .on() on the map chain
|
|
69
|
+
const gunListener = mapChain.on(async (data, key) => { // Renamed variable
|
|
70
|
+
// Check if subscription ID still exists (might have been unsubscribed)
|
|
71
|
+
if (!holoInstance.subscriptions[subscriptionId]) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (data) {
|
|
76
|
+
try {
|
|
77
|
+
let parsed = await holoInstance.parse(data);
|
|
78
|
+
if (parsed && holoInstance.isHologram(parsed)) {
|
|
79
|
+
const resolved = await holoInstance.resolveHologram(parsed, { followHolograms: true });
|
|
80
|
+
if (resolved !== parsed) {
|
|
81
|
+
parsed = resolved;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check again if subscription ID still exists before calling callback
|
|
86
|
+
if (holoInstance.subscriptions[subscriptionId]) {
|
|
87
|
+
callback(parsed, key);
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error('Error processing subscribed data:', error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Store the subscription with its ID on the instance
|
|
96
|
+
holoInstance.subscriptions[subscriptionId] = {
|
|
97
|
+
id: subscriptionId,
|
|
98
|
+
holon,
|
|
99
|
+
lens,
|
|
100
|
+
callback,
|
|
101
|
+
mapChain: mapChain, // Store the map chain
|
|
102
|
+
gunListener: gunListener // Store the listener too (optional, maybe needed for close?)
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Return an object with unsubscribe method
|
|
106
|
+
return {
|
|
107
|
+
unsubscribe: async () => {
|
|
108
|
+
const sub = holoInstance.subscriptions[subscriptionId];
|
|
109
|
+
if (!sub) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
// Turn off the Gun subscription using the stored mapChain reference
|
|
115
|
+
if (sub.mapChain) { // Check if mapChain exists
|
|
116
|
+
sub.mapChain.off(); // Call off() on the chain where .on() was attached
|
|
117
|
+
// Optional: Add delay back? Let's omit for now.
|
|
118
|
+
// await new Promise(res => setTimeout(res, 50));
|
|
119
|
+
} // We might not need to call off() on gunListener explicitly
|
|
120
|
+
|
|
121
|
+
// Remove from subscriptions object AFTER turning off listener
|
|
122
|
+
delete holoInstance.subscriptions[subscriptionId];
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error(`Error during unsubscribe logic for ${subscriptionId}:`, error);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('Error creating subscription:', error);
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Notifies subscribers about data changes
|
|
136
|
+
* @param {HoloSphere} holoInstance - The HoloSphere instance.
|
|
137
|
+
* @param {object} data - The data to notify about
|
|
138
|
+
* @private
|
|
139
|
+
*/
|
|
140
|
+
export function notifySubscribers(holoInstance, data) {
|
|
141
|
+
if (!data || !data.holon || !data.lens) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
Object.values(holoInstance.subscriptions).forEach(subscription => {
|
|
147
|
+
if (subscription.holon === data.holon &&
|
|
148
|
+
subscription.lens === data.lens) {
|
|
149
|
+
try {
|
|
150
|
+
if (subscription.callback && typeof subscription.callback === 'function') {
|
|
151
|
+
subscription.callback(data);
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.warn('Error in subscription callback:', error);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.warn('Error notifying subscribers:', error);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Add ID generation method
|
|
164
|
+
export function generateId() { // Doesn't need holoInstance
|
|
165
|
+
return Date.now().toString(10) + Math.random().toString(2);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Closes the HoloSphere instance and cleans up resources.
|
|
170
|
+
* @param {HoloSphere} holoInstance - The HoloSphere instance.
|
|
171
|
+
* @returns {Promise<void>}
|
|
172
|
+
*/
|
|
173
|
+
export async function close(holoInstance) {
|
|
174
|
+
try {
|
|
175
|
+
if (holoInstance.gun) {
|
|
176
|
+
// Unsubscribe from all subscriptions
|
|
177
|
+
const subscriptionIds = Object.keys(holoInstance.subscriptions);
|
|
178
|
+
for (const id of subscriptionIds) {
|
|
179
|
+
try {
|
|
180
|
+
const subscription = holoInstance.subscriptions[id];
|
|
181
|
+
if (subscription) {
|
|
182
|
+
// Turn off the Gun subscription using the stored mapChain reference
|
|
183
|
+
if (subscription.mapChain) {
|
|
184
|
+
subscription.mapChain.off();
|
|
185
|
+
} // Also turn off listener directly? Might be redundant.
|
|
186
|
+
// if (subscription.gunListener) {
|
|
187
|
+
// subscription.gunListener.off();
|
|
188
|
+
// }
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.warn(`Error cleaning up subscription ${id}:`, error);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Clear subscriptions
|
|
196
|
+
holoInstance.subscriptions = {};
|
|
197
|
+
|
|
198
|
+
// Clear schema cache using instance method
|
|
199
|
+
holoInstance.clearSchemaCache();
|
|
200
|
+
|
|
201
|
+
// Close Gun connections
|
|
202
|
+
if (holoInstance.gun.back) {
|
|
203
|
+
try {
|
|
204
|
+
// Clean up mesh connections
|
|
205
|
+
const mesh = holoInstance.gun.back('opt.mesh');
|
|
206
|
+
if (mesh) {
|
|
207
|
+
// Clean up mesh.hear
|
|
208
|
+
if (mesh.hear) {
|
|
209
|
+
try {
|
|
210
|
+
// Safely clear mesh.hear without modifying function properties
|
|
211
|
+
const hearKeys = Object.keys(mesh.hear);
|
|
212
|
+
for (const key of hearKeys) {
|
|
213
|
+
// Check if it's an array before trying to clear it
|
|
214
|
+
if (Array.isArray(mesh.hear[key])) {
|
|
215
|
+
mesh.hear[key] = [];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Create a new empty object for mesh.hear
|
|
220
|
+
// Only if mesh.hear is not a function
|
|
221
|
+
if (typeof mesh.hear !== 'function') {
|
|
222
|
+
mesh.hear = {};
|
|
223
|
+
}
|
|
224
|
+
} catch (meshError) {
|
|
225
|
+
console.warn('Error cleaning up Gun mesh hear:', meshError);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Close any open sockets in the mesh
|
|
230
|
+
if (mesh.way) {
|
|
231
|
+
try {
|
|
232
|
+
Object.values(mesh.way).forEach(connection => {
|
|
233
|
+
if (connection && connection.wire && connection.wire.close) {
|
|
234
|
+
connection.wire.close();
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
} catch (sockError) {
|
|
238
|
+
console.warn('Error closing mesh sockets:', sockError);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Clear the peers list
|
|
243
|
+
if (mesh.opt && mesh.opt.peers) {
|
|
244
|
+
mesh.opt.peers = {};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Attempt to clean up any TCP connections
|
|
249
|
+
if (holoInstance.gun.back('opt.web')) {
|
|
250
|
+
try {
|
|
251
|
+
const server = holoInstance.gun.back('opt.web');
|
|
252
|
+
if (server && server.close) {
|
|
253
|
+
server.close();
|
|
254
|
+
}
|
|
255
|
+
} catch (webError) {
|
|
256
|
+
console.warn('Error closing web server:', webError);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
} catch (error) {
|
|
260
|
+
console.warn('Error accessing Gun mesh:', error);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Clear all Gun instance listeners
|
|
265
|
+
try {
|
|
266
|
+
holoInstance.gun.off();
|
|
267
|
+
} catch (error) {
|
|
268
|
+
console.warn('Error turning off Gun listeners:', error);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Wait a moment for cleanup to complete
|
|
272
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
console.log('HoloSphere instance closed successfully');
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.error('Error closing HoloSphere instance:', error);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Creates a namespaced username for Gun authentication
|
|
283
|
+
* @param {HoloSphere} holoInstance - The HoloSphere instance.
|
|
284
|
+
* @param {string} holonId - The holon ID
|
|
285
|
+
* @returns {string} - Namespaced username
|
|
286
|
+
*/
|
|
287
|
+
export function userName(holoInstance, holonId) {
|
|
288
|
+
if (!holonId) return null;
|
|
289
|
+
return `${holoInstance.appname}:${holonId}`;
|
|
290
|
+
}
|