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/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
+ }