UNPKG

holosphere

Version:

Holonic Geospatial Communication Infrastructure

302 lines (272 loc) 11.4 kB
// holo_utils.js import * as h3 from 'h3-js'; /** * Converts latitude and longitude to a holon identifier. * @param {number} lat - The latitude. * @param {number} lng - The longitude. * @param {number} resolution - The resolution level. * @returns {Promise<string>} - The resulting holon identifier. */ export async function getHolon(lat, lng, resolution) { // Doesn't need holoInstance return h3.latLngToCell(lat, lng, resolution); } /** * Retrieves all containing holonagons at all scales for given coordinates. * @param {number} lat - The latitude. * @param {number} lng - The longitude. * @returns {Array<string>} - List of holon identifiers. */ export function getScalespace(lat, lng) { // Doesn't need holoInstance let list = [] let cell = h3.latLngToCell(lat, lng, 14); list.push(cell) for (let i = 13; i >= 0; i--) { list.push(h3.cellToParent(cell, i)) } return list } /** * Retrieves all containing holonagons at all scales for a given holon. * @param {string} holon - The holon identifier. * @returns {Array<string>} - List of holon identifiers. */ export function getHolonScalespace(holon) { // Doesn't need holoInstance let list = [] let res = h3.getResolution(holon) for (let i = res; i >= 0; i--) { list.push(h3.cellToParent(holon, i)) } return list } /** * Subscribes to changes in a specific holon and lens. * @param {HoloSphere} holoInstance - The HoloSphere instance. * @param {string} holon - The holon identifier. * @param {string} lens - The lens to subscribe to. * @param {function} callback - The callback to execute on changes. * @returns {Promise<object>} - Subscription object with unsubscribe method */ export async function subscribe(holoInstance, holon, lens, callback) { if (!holon || !lens) { throw new Error('subscribe: Missing holon or lens parameters:', holon, lens); } if (!callback || typeof callback !== 'function') { throw new Error('subscribe: Callback must be a function'); } const subscriptionId = holoInstance.generateId(); // Use instance's generateId try { // Get the Gun chain up to the map() const mapChain = holoInstance.gun.get(holoInstance.appname).get(holon).get(lens).map(); // Create the subscription by calling .on() on the map chain const gunListener = mapChain.on(async (data, key) => { // Renamed variable // Check if subscription ID still exists (might have been unsubscribed) if (!holoInstance.subscriptions[subscriptionId]) { return; } if (data) { try { let parsed = await holoInstance.parse(data); if (parsed && holoInstance.isHologram(parsed)) { const resolved = await holoInstance.resolveHologram(parsed, { followHolograms: true }); if (resolved !== parsed) { parsed = resolved; } } // Check again if subscription ID still exists before calling callback if (holoInstance.subscriptions[subscriptionId]) { callback(parsed, key); } } catch (error) { console.error('Error processing subscribed data:', error); } } }); // Store the subscription with its ID on the instance holoInstance.subscriptions[subscriptionId] = { id: subscriptionId, holon, lens, callback, mapChain: mapChain, // Store the map chain gunListener: gunListener // Store the listener too (optional, maybe needed for close?) }; // Return an object with unsubscribe method return { unsubscribe: async () => { const sub = holoInstance.subscriptions[subscriptionId]; if (!sub) { return; } try { // Turn off the Gun subscription using the stored mapChain reference if (sub.mapChain) { // Check if mapChain exists sub.mapChain.off(); // Call off() on the chain where .on() was attached // Optional: Add delay back? Let's omit for now. // await new Promise(res => setTimeout(res, 50)); } // We might not need to call off() on gunListener explicitly // Remove from subscriptions object AFTER turning off listener delete holoInstance.subscriptions[subscriptionId]; } catch (error) { console.error(`Error during unsubscribe logic for ${subscriptionId}:`, error); } } }; } catch (error) { console.error('Error creating subscription:', error); throw error; } } /** * Notifies subscribers about data changes * @param {HoloSphere} holoInstance - The HoloSphere instance. * @param {object} data - The data to notify about * @private */ export function notifySubscribers(holoInstance, data) { if (!data || !data.holon || !data.lens) { return; } try { Object.values(holoInstance.subscriptions).forEach(subscription => { if (subscription.holon === data.holon && subscription.lens === data.lens) { try { if (subscription.callback && typeof subscription.callback === 'function') { subscription.callback(data); } } catch (error) { console.warn('Error in subscription callback:', error); } } }); } catch (error) { console.warn('Error notifying subscribers:', error); } } // Add ID generation method export function generateId() { // Doesn't need holoInstance return Date.now().toString(10) + Math.random().toString(2); } /** * Closes the HoloSphere instance and cleans up resources. * @param {HoloSphere} holoInstance - The HoloSphere instance. * @returns {Promise<void>} */ export async function close(holoInstance) { try { if (holoInstance.gun) { // Unsubscribe from all subscriptions const subscriptionIds = Object.keys(holoInstance.subscriptions); for (const id of subscriptionIds) { try { const subscription = holoInstance.subscriptions[id]; if (subscription) { // Turn off the Gun subscription using the stored mapChain reference if (subscription.mapChain) { subscription.mapChain.off(); } // Also turn off listener directly? Might be redundant. // if (subscription.gunListener) { // subscription.gunListener.off(); // } } } catch (error) { console.warn(`Error cleaning up subscription ${id}:`, error); } } // Clear subscriptions holoInstance.subscriptions = {}; // Clear schema cache using instance method holoInstance.clearSchemaCache(); // Close Gun connections if (holoInstance.gun.back) { try { // Clean up mesh connections const mesh = holoInstance.gun.back('opt.mesh'); if (mesh) { // Clean up mesh.hear if (mesh.hear) { try { // Safely clear mesh.hear without modifying function properties const hearKeys = Object.keys(mesh.hear); for (const key of hearKeys) { // Check if it's an array before trying to clear it if (Array.isArray(mesh.hear[key])) { mesh.hear[key] = []; } } // Create a new empty object for mesh.hear // Only if mesh.hear is not a function if (typeof mesh.hear !== 'function') { mesh.hear = {}; } } catch (meshError) { console.warn('Error cleaning up Gun mesh hear:', meshError); } } // Close any open sockets in the mesh if (mesh.way) { try { Object.values(mesh.way).forEach(connection => { if (connection && connection.wire && connection.wire.close) { connection.wire.close(); } }); } catch (sockError) { console.warn('Error closing mesh sockets:', sockError); } } // Clear the peers list if (mesh.opt && mesh.opt.peers) { mesh.opt.peers = {}; } } // Attempt to clean up any TCP connections if (holoInstance.gun.back('opt.web')) { try { const server = holoInstance.gun.back('opt.web'); if (server && server.close) { server.close(); } } catch (webError) { console.warn('Error closing web server:', webError); } } } catch (error) { console.warn('Error accessing Gun mesh:', error); } } // Clear all Gun instance listeners try { holoInstance.gun.off(); } catch (error) { console.warn('Error turning off Gun listeners:', error); } // Wait a moment for cleanup to complete await new Promise(resolve => setTimeout(resolve, 100)); } console.log('HoloSphere instance closed successfully'); } catch (error) { console.error('Error closing HoloSphere instance:', error); } } /** * Creates a namespaced username for Gun authentication * @param {HoloSphere} holoInstance - The HoloSphere instance. * @param {string} holonId - The holon ID * @returns {string} - Namespaced username */ export function userName(holoInstance, holonId) { if (!holonId) return null; return `${holoInstance.appname}:${holonId}`; } // Export all utility operations as default export default { getHolon, getScalespace, getHolonScalespace, subscribe, notifySubscribers, generateId, close, userName };