holosphere 1.1.10 → 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 +272 -208
- package/global.js +560 -0
- package/hologram.js +156 -0
- package/holosphere.d.ts +94 -7
- package/holosphere.js +172 -1565
- package/node.js +155 -0
- package/package.json +2 -5
- package/schema.js +132 -0
- package/test/auth.test.js +55 -37
- package/test/delete.test.js +15 -12
- package/test/federation.test.js +179 -0
- package/test/hologram.test.js +316 -0
- package/test/subscription.test.js +105 -70
- package/utils.js +290 -0
- package/test/reference.test.js +0 -211
|
@@ -6,22 +6,39 @@ jest.setTimeout(30000);
|
|
|
6
6
|
|
|
7
7
|
describe('Subscription Tests', () => {
|
|
8
8
|
let holosphere;
|
|
9
|
-
|
|
9
|
+
let testAppName; // Make app name dynamic
|
|
10
|
+
const testHolonBase = 'test_subscription_holon';
|
|
10
11
|
const testLens = 'items';
|
|
12
|
+
let testHolon; // Make holon dynamic
|
|
11
13
|
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
// Create a fresh HoloSphere instance for each test
|
|
14
|
-
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
// Create a fresh HoloSphere instance with unique names for each test
|
|
16
|
+
testAppName = `testApp_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
17
|
+
testHolon = `${testHolonBase}_${Date.now()}`;
|
|
18
|
+
holosphere = new HoloSphere(testAppName, false);
|
|
19
|
+
// Add a small delay after initialization
|
|
20
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
15
21
|
});
|
|
16
22
|
|
|
17
23
|
afterEach(async () => {
|
|
18
|
-
// Clean up resources
|
|
24
|
+
// Clean up resources and potentially test data
|
|
19
25
|
try {
|
|
20
|
-
|
|
26
|
+
if (holosphere) {
|
|
27
|
+
// Attempt to delete data created in the test holon/lenses
|
|
28
|
+
try {
|
|
29
|
+
await holosphere.deleteAll(testHolon, testLens);
|
|
30
|
+
await holosphere.deleteAll(testHolon, 'differentLens');
|
|
31
|
+
// Wait a bit for deletes to process
|
|
32
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
33
|
+
} catch (deleteError) {
|
|
34
|
+
console.warn(`Error during test cleanup deleteAll:`, deleteError);
|
|
35
|
+
}
|
|
36
|
+
await holosphere.close();
|
|
37
|
+
}
|
|
21
38
|
} catch (error) {
|
|
22
|
-
console.warn('Error
|
|
39
|
+
console.warn('Error during afterEach cleanup:', error);
|
|
23
40
|
}
|
|
24
|
-
jest.clearAllMocks();
|
|
41
|
+
// jest.clearAllMocks(); // Not needed if not using jest.fn()
|
|
25
42
|
});
|
|
26
43
|
|
|
27
44
|
test('should properly clean up subscription when unsubscribing', async () => {
|
|
@@ -32,7 +49,7 @@ describe('Subscription Tests', () => {
|
|
|
32
49
|
};
|
|
33
50
|
|
|
34
51
|
// Create a mock callback function
|
|
35
|
-
|
|
52
|
+
function mockCallback (data) {
|
|
36
53
|
console.log('Callback received:', data);
|
|
37
54
|
}
|
|
38
55
|
|
|
@@ -47,11 +64,10 @@ describe('Subscription Tests', () => {
|
|
|
47
64
|
|
|
48
65
|
// Verify the subscription object has the expected structure
|
|
49
66
|
expect(holosphere.subscriptions[subscriptionId]).toBeDefined();
|
|
50
|
-
expect(holosphere.subscriptions[subscriptionId].active).toBe(true);
|
|
51
67
|
expect(holosphere.subscriptions[subscriptionId].holon).toBe(testHolon);
|
|
52
68
|
expect(holosphere.subscriptions[subscriptionId].lens).toBe(testLens);
|
|
53
69
|
expect(holosphere.subscriptions[subscriptionId].callback).toBe(mockCallback);
|
|
54
|
-
expect(holosphere.subscriptions[subscriptionId].
|
|
70
|
+
expect(holosphere.subscriptions[subscriptionId].mapChain).toBeDefined();
|
|
55
71
|
|
|
56
72
|
// Now unsubscribe
|
|
57
73
|
await subscription.unsubscribe();
|
|
@@ -64,104 +80,123 @@ describe('Subscription Tests', () => {
|
|
|
64
80
|
test('should not receive updates after unsubscribing', async () => {
|
|
65
81
|
// Create test data
|
|
66
82
|
const testData = {
|
|
67
|
-
id: 'test-item',
|
|
68
|
-
value: '
|
|
83
|
+
id: 'test-item-unsub',
|
|
84
|
+
value: 'Initial value for unsub test'
|
|
69
85
|
};
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
86
|
+
|
|
87
|
+
// Use a flag to track callback execution
|
|
88
|
+
let callbackFired = false;
|
|
89
|
+
let receivedData = null;
|
|
90
|
+
const dataCallback = (data) => {
|
|
91
|
+
console.log('Callback received (unsub test):', data);
|
|
92
|
+
callbackFired = true;
|
|
93
|
+
receivedData = data;
|
|
94
|
+
};
|
|
95
|
+
|
|
76
96
|
// Set up subscription
|
|
77
|
-
const subscription = await holosphere.subscribe(testHolon, testLens,
|
|
78
|
-
|
|
97
|
+
const subscription = await holosphere.subscribe(testHolon, testLens, dataCallback);
|
|
98
|
+
|
|
79
99
|
// Store data to trigger subscription
|
|
80
100
|
await holosphere.put(testHolon, testLens, testData);
|
|
81
|
-
|
|
101
|
+
|
|
82
102
|
// Wait a bit for the subscription to trigger
|
|
83
103
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
84
|
-
|
|
104
|
+
|
|
85
105
|
// Verify callback was called
|
|
86
|
-
expect(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
106
|
+
expect(callbackFired).toBe(true);
|
|
107
|
+
expect(receivedData).toEqual(expect.objectContaining(testData));
|
|
108
|
+
|
|
109
|
+
// Reset the flag
|
|
110
|
+
callbackFired = false;
|
|
111
|
+
receivedData = null;
|
|
112
|
+
|
|
91
113
|
// Unsubscribe
|
|
92
114
|
await subscription.unsubscribe();
|
|
93
|
-
|
|
115
|
+
|
|
94
116
|
// Store more data
|
|
95
|
-
const newData = { ...testData, value: 'Updated value' };
|
|
117
|
+
const newData = { ...testData, value: 'Updated value after unsub' };
|
|
96
118
|
await holosphere.put(testHolon, testLens, newData);
|
|
97
|
-
|
|
119
|
+
|
|
98
120
|
// Wait a bit to ensure no callbacks are triggered
|
|
99
121
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
100
|
-
|
|
122
|
+
|
|
101
123
|
// Verify callback was NOT called after unsubscribing
|
|
102
|
-
expect(
|
|
124
|
+
expect(callbackFired).toBe(false);
|
|
125
|
+
expect(receivedData).toBeNull();
|
|
103
126
|
});
|
|
104
127
|
|
|
105
128
|
test('should handle multiple subscriptions and unsubscriptions correctly', async () => {
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
129
|
+
// Use flags to track callback execution
|
|
130
|
+
let callback1Fired = false;
|
|
131
|
+
let callback2Fired = false;
|
|
132
|
+
let callback3Fired = false;
|
|
133
|
+
let dataForCb1 = null;
|
|
134
|
+
|
|
135
|
+
const cb1 = (data) => {
|
|
136
|
+
console.log('Callback 1 received (multi test):', data);
|
|
137
|
+
callback1Fired = true;
|
|
138
|
+
dataForCb1 = data;
|
|
139
|
+
};
|
|
140
|
+
const cb2 = (data) => {
|
|
141
|
+
console.log('Callback 2 received (multi test):', data);
|
|
142
|
+
callback2Fired = true;
|
|
143
|
+
};
|
|
144
|
+
const cb3 = (data) => {
|
|
145
|
+
console.log('Callback 3 received (multi test):', data);
|
|
146
|
+
callback3Fired = true;
|
|
147
|
+
};
|
|
148
|
+
|
|
117
149
|
// Set up multiple subscriptions
|
|
118
|
-
const subscription1 = await holosphere.subscribe(testHolon, testLens,
|
|
119
|
-
const subscription2 = await holosphere.subscribe(testHolon, testLens,
|
|
120
|
-
const subscription3 = await holosphere.subscribe(testHolon, 'differentLens',
|
|
121
|
-
|
|
150
|
+
const subscription1 = await holosphere.subscribe(testHolon, testLens, cb1);
|
|
151
|
+
const subscription2 = await holosphere.subscribe(testHolon, testLens, cb2);
|
|
152
|
+
const subscription3 = await holosphere.subscribe(testHolon, 'differentLens', cb3);
|
|
153
|
+
|
|
122
154
|
// Verify subscriptions were created
|
|
123
155
|
expect(Object.keys(holosphere.subscriptions).length).toBe(3);
|
|
124
|
-
|
|
125
|
-
// Unsubscribe from one subscription
|
|
156
|
+
|
|
157
|
+
// Unsubscribe from one subscription (subscription2)
|
|
126
158
|
await subscription2.unsubscribe();
|
|
127
|
-
|
|
159
|
+
|
|
128
160
|
// Verify only the correct subscription was removed
|
|
129
161
|
expect(Object.keys(holosphere.subscriptions).length).toBe(2);
|
|
130
|
-
|
|
162
|
+
|
|
131
163
|
// Create test data
|
|
132
164
|
const testData = {
|
|
133
|
-
id: 'test-item',
|
|
134
|
-
value: 'Test value'
|
|
165
|
+
id: 'test-item-multi',
|
|
166
|
+
value: 'Test value for multi sub'
|
|
135
167
|
};
|
|
136
|
-
|
|
168
|
+
|
|
137
169
|
// Store data to trigger remaining subscriptions
|
|
138
170
|
await holosphere.put(testHolon, testLens, testData);
|
|
139
|
-
|
|
171
|
+
|
|
140
172
|
// Wait a bit for the subscription to trigger
|
|
141
173
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
142
|
-
|
|
174
|
+
|
|
143
175
|
// Verify only active callbacks were called
|
|
144
|
-
expect(
|
|
145
|
-
expect(
|
|
146
|
-
expect(
|
|
147
|
-
|
|
176
|
+
expect(callback1Fired).toBe(true);
|
|
177
|
+
expect(dataForCb1).toEqual(expect.objectContaining(testData));
|
|
178
|
+
expect(callback2Fired).toBe(false); // This callback should not have fired
|
|
179
|
+
expect(callback3Fired).toBe(false); // Different lens
|
|
180
|
+
|
|
148
181
|
// Unsubscribe from all remaining subscriptions
|
|
149
182
|
await subscription1.unsubscribe();
|
|
150
183
|
await subscription3.unsubscribe();
|
|
151
|
-
|
|
184
|
+
|
|
152
185
|
// Verify all subscriptions were removed
|
|
153
186
|
expect(Object.keys(holosphere.subscriptions).length).toBe(0);
|
|
154
187
|
});
|
|
155
188
|
|
|
156
189
|
test('should clean up subscriptions when closing HoloSphere instance', async () => {
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
190
|
+
// Use a flag - not strictly needed for assertion but good practice
|
|
191
|
+
let callbackFired = false;
|
|
192
|
+
const dataCallback = (data) => {
|
|
193
|
+
console.log('Callback received (close test):', data);
|
|
194
|
+
callbackFired = true;
|
|
195
|
+
};
|
|
196
|
+
|
|
162
197
|
// Set up subscription
|
|
163
|
-
await holosphere.subscribe(testHolon, testLens,
|
|
164
|
-
|
|
198
|
+
await holosphere.subscribe(testHolon, testLens, dataCallback);
|
|
199
|
+
|
|
165
200
|
// Verify subscription was created
|
|
166
201
|
expect(Object.keys(holosphere.subscriptions).length).toBe(1);
|
|
167
202
|
|
|
@@ -303,7 +338,7 @@ describe('Subscription Tests', () => {
|
|
|
303
338
|
}
|
|
304
339
|
|
|
305
340
|
// Try to resolve the reference to verify it works correctly
|
|
306
|
-
const resolvedData = await holosphere.get(referenceHolon, testLens, originalData.id, null, {
|
|
341
|
+
const resolvedData = await holosphere.get(referenceHolon, testLens, originalData.id, null, { resolveHolograms: true });
|
|
307
342
|
console.log('Resolved data:', resolvedData);
|
|
308
343
|
|
|
309
344
|
// The resolved data should not be null
|
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
|
+
}
|