UNPKG

holosphere

Version:

Holonic Geospatial Communication Infrastructure

736 lines (674 loc) 38.4 kB
// holo_global.js /** * Stores data in a global (non-holon-specific) table. * @param {HoloSphere} holoInstance - The HoloSphere instance. * @param {string} tableName - The table name to store data in. * @param {object} data - The data to store. If it has an 'id' field, it will be used as the key. * @param {string} [password] - Optional password for private holon. * @returns {Promise<void>} */ export async function putGlobal(holoInstance, tableName, data, password = null) { try { if (!tableName || !data) { throw new Error('Table name and data are required'); } let user = null; if (password) { user = holoInstance.gun.user(); await new Promise((resolve, reject) => { const userNameString = holoInstance.userName(tableName); user.auth(userNameString, password, (authAck) => { if (authAck.err) { // If auth fails, try to create the user console.log(`Initial auth failed for ${userNameString}, attempting to create...`); user.create(userNameString, password, (createAck) => { if (createAck.err) { // Check if error is "User already created" if (createAck.err.includes("already created") || createAck.err.includes("already being created")) { // This means user exists but password might be wrong, or some other issue // Proceed with auth again, it might have been a temporary glitch or race. // Or, it could be that the password is indeed wrong. console.log(`User ${userNameString} already existed or being created, re-attempting auth with fresh user object.`); const freshUser = holoInstance.gun.user(); // Get a new user object freshUser.auth(userNameString, password, (secondAuthAck) => { if (secondAuthAck.err) { console.log(`Auth still failed after user existed check: ${secondAuthAck.err}. Resolving anyway for test operations.`); resolve(); // Resolve anyway to allow test operations } else { resolve(); } }); } else { console.log(`Create user error (resolving anyway for operations): ${createAck.err}`); resolve(); // Resolve anyway to allow test operations } } else { // After successful creation, authenticate again console.log(`User ${userNameString} created successfully, attempting auth...`); user.auth(userNameString, password, (secondAuthAck) => { if (secondAuthAck.err) { reject(new Error(`Failed to auth after create for ${userNameString}: ${secondAuthAck.err}`)); } else { resolve(); } }); } }); } else { resolve(); // Auth successful } }); }); } return new Promise((resolve, reject) => { try { // Create a copy of data without the _meta field if it exists let dataToStore = { ...data }; if (dataToStore._meta !== undefined) { delete dataToStore._meta; } const payload = JSON.stringify(dataToStore); // Check if the data being stored is a hologram const isHologram = holoInstance.isHologram(dataToStore); const dataPath = password ? user.get('private').get(tableName) : holoInstance.gun.get(holoInstance.appname).get(tableName); if (data.id) { const itemPath = dataPath.get(data.id); itemPath.put(payload, ack => { if (ack.err) { reject(new Error(ack.err)); } else { // --- Start: Hologram Tracking Logic (for data *being put*, if it's a hologram) --- if (isHologram) { try { const storedDataSoulInfo = holoInstance.parseSoulPath(dataToStore.soul); if (storedDataSoulInfo) { const targetNodeRef = holoInstance.getNodeRef(dataToStore.soul); // Target of the data *being put* // Soul of the hologram that was *actually stored* at tableName/data.id const storedHologramInstanceSoul = `${holoInstance.appname}/${tableName}/${data.id}`; targetNodeRef.get('_holograms').get(storedHologramInstanceSoul).put(true); } else { console.warn(`Data (ID: ${data.id}) being put is a hologram, but could not parse its soul ${dataToStore.soul} for tracking.`); } } catch (trackingError) { console.warn(`Error updating _holograms set for the target of the data being put (data ID: ${data.id}, soul: ${dataToStore.soul}):`, trackingError); } } // --- End: Hologram Tracking Logic --- resolve(); } }); } else { dataPath.put(payload, ack => { if (ack.err) { reject(new Error(ack.err)); } else { // --- Start: Hologram Tracking Logic (for data *being put*, if it's a hologram) --- if (isHologram) { try { const storedDataSoulInfo = holoInstance.parseSoulPath(dataToStore.soul); if (storedDataSoulInfo) { const targetNodeRef = holoInstance.getNodeRef(dataToStore.soul); // Target of the data *being put* // Soul of the hologram that was *actually stored* at tableName (without specific key) const storedHologramInstanceSoul = `${holoInstance.appname}/${tableName}`; targetNodeRef.get('_holograms').get(storedHologramInstanceSoul).put(true); } else { console.warn(`Data being put is a hologram, but could not parse its soul ${dataToStore.soul} for tracking.`); } } catch (trackingError) { console.warn(`Error updating _holograms set for the target of the data being put (soul: ${dataToStore.soul}):`, trackingError); } } // --- End: Hologram Tracking Logic --- resolve(); } }); } } catch (error) { reject(error); } }); } catch (error) { console.error('Error in putGlobal:', error); throw error; } } /** * Retrieves a specific key from a global table. * @param {HoloSphere} holoInstance - The HoloSphere instance. * @param {string} tableName - The table name to retrieve from. * @param {string} key - The key to retrieve. * @param {string} [password] - Optional password for private holon. * @returns {Promise<object|null>} - The parsed data for the key or null if not found. */ export async function getGlobal(holoInstance, tableName, key, password = null) { try { let user = null; if (password) { user = holoInstance.gun.user(); await new Promise((resolve, reject) => { const userNameString = holoInstance.userName(tableName); user.auth(userNameString, password, (authAck) => { if (authAck.err) { // If auth fails, try to create the user console.log(`Initial auth failed for ${userNameString}, attempting to create...`); user.create(userNameString, password, (createAck) => { if (createAck.err) { // Check if error is "User already created" if (createAck.err.includes("already created") || createAck.err.includes("already being created")) { console.log(`User ${userNameString} already existed or being created, re-attempting auth with fresh user object.`); const freshUser = holoInstance.gun.user(); // Get a new user object freshUser.auth(userNameString, password, (secondAuthAck) => { if (secondAuthAck.err) { console.log(`Auth still failed after user existed check: ${secondAuthAck.err}. Resolving anyway for test operations.`); resolve(); // Resolve anyway to allow test operations } else { resolve(); } }); } else { console.log(`Create user error (resolving anyway for operations): ${createAck.err}`); resolve(); // Resolve anyway to allow test operations } } else { // After successful creation, authenticate again console.log(`User ${userNameString} created successfully, attempting auth...`); user.auth(userNameString, password, (secondAuthAck) => { if (secondAuthAck.err) { reject(new Error(`Failed to auth after create for ${userNameString}: ${secondAuthAck.err}`)); } else { resolve(); } }); } }); } else { resolve(); // Auth successful } }); }); } return new Promise(async (resolve) => { const handleData = async (data) => { if (!data) { resolve(null); return; } try { // The data should be a stringified JSON from putGlobal const parsed = await holoInstance.parse(data); // Use instance's parse if (!parsed) { resolve(null); return; } // Check if this is a hologram that needs to be resolved if (holoInstance.isHologram(parsed)) { // Use instance's isHologram const resolved = await holoInstance.resolveHologram(parsed, { // Use instance's resolveHologram followHolograms: true // Always follow holograms }); if (resolved === null) { try { await holoInstance.deleteGlobal(tableName, key, password); // Use instance's deleteGlobal } catch (deleteError) { console.error(`Failed to delete invalid global hologram at ${tableName}/${key}:`, deleteError); } resolve(null); // Return null as the hologram is invalid return; } if (resolved !== parsed) { // Hologram was resolved successfully resolve(resolved); return; } } resolve(parsed); } catch (e) { console.error('Error parsing data in getGlobal:', e); resolve(null); } }; const dataPath = password ? user.get('private').get(tableName) : holoInstance.gun.get(holoInstance.appname).get(tableName); const itemPath = dataPath.get(key); itemPath.once(handleData); }); } catch (error) { console.error('Error in getGlobal:', error); return null; } } /** * Retrieves all data from a global table. * @param {HoloSphere} holoInstance - The HoloSphere instance. * @param {string} tableName - The table name to retrieve data from. * @param {string} [password] - Optional password for private holon. * @returns {Promise<Array<object>>} - The parsed data from the table as an array. */ export async function getAllGlobal(holoInstance, tableName, password = null) { if (!tableName) { throw new Error('getAllGlobal: Missing table name parameter'); } try { let user = null; if (password) { user = holoInstance.gun.user(); await new Promise((resolve, reject) => { const userNameString = holoInstance.userName(tableName); user.auth(userNameString, password, (authAck) => { if (authAck.err) { // If auth fails, try to create the user console.log(`Initial auth failed for ${userNameString}, attempting to create...`); user.create(userNameString, password, (createAck) => { if (createAck.err) { // Check if error is "User already created" if (createAck.err.includes("already created") || createAck.err.includes("already being created")) { console.log(`User ${userNameString} already existed or being created, re-attempting auth with fresh user object.`); const freshUser = holoInstance.gun.user(); // Get a new user object freshUser.auth(userNameString, password, (secondAuthAck) => { if (secondAuthAck.err) { console.log(`Auth still failed after user existed check: ${secondAuthAck.err}. Resolving anyway for test operations.`); resolve(); // Resolve anyway to allow test operations } else { resolve(); } }); } else { console.log(`Create user error (resolving anyway for operations): ${createAck.err}`); resolve(); // Resolve anyway to allow test operations } } else { // After successful creation, authenticate again console.log(`User ${userNameString} created successfully, attempting auth...`); user.auth(userNameString, password, (secondAuthAck) => { if (secondAuthAck.err) { reject(new Error(`Failed to auth after create for ${userNameString}: ${secondAuthAck.err}`)); } else { resolve(); } }); } }); } else { resolve(); // Auth successful } }); }); } return new Promise((resolve) => { let output = []; let isResolved = false; let timeout = setTimeout(() => { if (!isResolved) { isResolved = true; resolve(output); } }, 5000); const handleData = async (data) => { if (!data) { clearTimeout(timeout); isResolved = true; resolve([]); return; } const keys = Object.keys(data).filter(key => key !== '_'); const promises = keys.map(key => new Promise(async (resolveItem) => { const itemPath = password ? user.get('private').get(tableName).get(key) : holoInstance.gun.get(holoInstance.appname).get(tableName).get(key); const itemData = await new Promise(resolveData => { itemPath.once(resolveData); }); if (itemData) { try { const parsed = await holoInstance.parse(itemData); // Use instance's parse if (parsed) { // Check if this is a hologram that needs to be resolved if (holoInstance.isHologram(parsed)) { // Use instance's isHologram const resolved = await holoInstance.resolveHologram(parsed, { // Use instance's resolveHologram followHolograms: true // Always follow holograms }); if (resolved === null) { try { await holoInstance.deleteGlobal(tableName, key, password); // Use instance's deleteGlobal } catch (deleteError) { console.error(`Failed to delete invalid global hologram at ${tableName}/${key}:`, deleteError); } resolveItem(); return; } if (resolved !== parsed) { // Hologram was resolved successfully output.push(resolved); } else { // If resolution didn't change it (e.g., circular ref guard), push original parsed (which is a hologram) output.push(parsed); } } else { output.push(parsed); } } } catch (error) { console.error('Error parsing data:', error); } } resolveItem(); }) ); await Promise.all(promises); clearTimeout(timeout); if (!isResolved) { isResolved = true; resolve(output); } }; const dataPath = password ? user.get('private').get(tableName) : holoInstance.gun.get(holoInstance.appname).get(tableName); dataPath.once(handleData); }); } catch (error) { console.error('Error in getAllGlobal:', error); return []; } } /** * Deletes a specific key from a global table. * @param {HoloSphere} holoInstance - The HoloSphere instance. * @param {string} tableName - The table name to delete from. * @param {string} key - The key to delete. * @param {string} [password] - Optional password for private holon. * @returns {Promise<boolean>} */ export async function deleteGlobal(holoInstance, tableName, key, password = null) { if (!tableName || !key) { throw new Error('deleteGlobal: Missing required parameters'); } try { // console.log('deleteGlobal - Starting deletion:', { tableName, key, hasPassword: !!password }); // Optional logging let user = null; if (password) { user = holoInstance.gun.user(); await new Promise((resolve, reject) => { const userNameString = holoInstance.userName(tableName); user.auth(userNameString, password, (authAck) => { if (authAck.err) { // If auth fails, try to create the user console.log(`Initial auth failed for ${userNameString}, attempting to create...`); user.create(userNameString, password, (createAck) => { if (createAck.err) { // Check if error is "User already created" if (createAck.err.includes("already created") || createAck.err.includes("already being created")) { console.log(`User ${userNameString} already existed or being created, re-attempting auth with fresh user object.`); const freshUser = holoInstance.gun.user(); // Get a new user object freshUser.auth(userNameString, password, (secondAuthAck) => { if (secondAuthAck.err) { console.log(`Auth still failed after user existed check: ${secondAuthAck.err}. Resolving anyway for test operations.`); resolve(); // Resolve anyway to allow test operations } else { resolve(); } }); } else { console.log(`Create user error (resolving anyway for operations): ${createAck.err}`); resolve(); // Resolve anyway to allow test operations } } else { // After successful creation, authenticate again console.log(`User ${userNameString} created successfully, attempting auth...`); user.auth(userNameString, password, (secondAuthAck) => { if (secondAuthAck.err) { reject(new Error(`Failed to auth after create for ${userNameString}: ${secondAuthAck.err}`)); } else { resolve(); } }); } }); } else { resolve(); // Auth successful } }); }); } const dataPath = password ? user.get('private').get(tableName).get(key) : holoInstance.gun.get(holoInstance.appname).get(tableName).get(key); // --- Start: Hologram Tracking Removal --- let trackingRemovalPromise = Promise.resolve(); // Default to resolved promise // 1. Get the data first to check if it's a hologram const rawDataToDelete = await new Promise((resolve) => dataPath.once(resolve)); let dataToDelete = null; try { if (typeof rawDataToDelete === 'string') { dataToDelete = JSON.parse(rawDataToDelete); } else { // Handle cases where it might already be an object (though likely string) dataToDelete = rawDataToDelete; } } catch(e) { console.warn("[deleteGlobal] Could not JSON parse data for deletion check:", rawDataToDelete, e); dataToDelete = null; // Ensure it's null if parsing fails } // 2. If it is a hologram, try to remove its reference from the target const isDataHologram = dataToDelete && holoInstance.isHologram(dataToDelete); if (isDataHologram) { try { const targetSoul = dataToDelete.soul; const targetSoulInfo = holoInstance.parseSoulPath(targetSoul); if (targetSoulInfo) { const targetNodeRef = holoInstance.getNodeRef(targetSoul); const deletedHologramSoul = `${holoInstance.appname}/${tableName}/${key}`; // Create a promise that resolves when the hologram is removed from the list trackingRemovalPromise = new Promise((resolveTrack) => { // No reject needed, just warn on error targetNodeRef.get('_holograms').get(deletedHologramSoul).put(null, (ack) => { // Remove the hologram entry completely if (ack.err) { console.warn(`[deleteGlobal] Error removing hologram ${deletedHologramSoul} from target ${targetSoul}:`, ack.err); } resolveTrack(); // Resolve regardless of ack error to not block main delete }); }); } else { console.warn(`Could not parse target soul ${targetSoul} for hologram tracking removal during deleteGlobal.`); } } catch (trackingError) { console.warn(`Error initiating hologram reference removal from target ${dataToDelete.soul} during deleteGlobal:`, trackingError); // Ensure trackingRemovalPromise remains resolved if setup fails trackingRemovalPromise = Promise.resolve(); } } // --- End: Hologram Tracking Removal --- // 3. Wait for the tracking removal attempt to be acknowledged await trackingRemovalPromise; // 4. Proceed with the actual deletion of the hologram node itself return new Promise((resolve, reject) => { // Request deletion dataPath.put(null, ack => { // console.log('deleteGlobal - Deletion acknowledgment:', ack); // Optional logging if (ack.err) { console.error('deleteGlobal - Deletion error:', ack.err); reject(new Error(ack.err)); } else { // Resolve directly on success, like deleteFunc resolve(true); } }); }); } catch (error) { console.error('Error in deleteGlobal:', error); throw error; } } /** * Deletes an entire global table. * @param {HoloSphere} holoInstance - The HoloSphere instance. * @param {string} tableName - The table name to delete. * @param {string} [password] - Optional password for private holon. * @returns {Promise<boolean>} */ export async function deleteAllGlobal(holoInstance, tableName, password = null) { if (!tableName) { throw new Error('deleteAllGlobal: Missing table name parameter'); } try { let user = null; if (password) { user = holoInstance.gun.user(); await new Promise((resolve, reject) => { const userNameString = holoInstance.userName(tableName); user.auth(userNameString, password, (authAck) => { if (authAck.err) { // If auth fails, try to create the user console.log(`Initial auth failed for ${userNameString}, attempting to create...`); user.create(userNameString, password, (createAck) => { if (createAck.err) { // Check if error is "User already created" if (createAck.err.includes("already created") || createAck.err.includes("already being created")) { console.log(`User ${userNameString} already existed or being created, re-attempting auth with fresh user object.`); const freshUser = holoInstance.gun.user(); // Get a new user object freshUser.auth(userNameString, password, (secondAuthAck) => { if (secondAuthAck.err) { console.log(`Auth still failed after user existed check: ${secondAuthAck.err}. Resolving anyway for test cleanup.`); resolve(); // Resolve anyway to allow test cleanup } else { resolve(); } }); } else { console.log(`Create user error (resolving anyway for cleanup): ${createAck.err}`); resolve(); // Resolve anyway to allow test cleanup } } else { // After successful creation, authenticate again console.log(`User ${userNameString} created successfully, attempting auth...`); user.auth(userNameString, password, (secondAuthAck) => { if (secondAuthAck.err) { reject(new Error(`Failed to auth after create for ${userNameString}: ${secondAuthAck.err}`)); } else { resolve(); } }); } }); } else { resolve(); // Auth successful } }); }); } return new Promise((resolve, reject) => { try { const deletions = new Set(); let timeout = setTimeout(() => { if (deletions.size === 0) { resolve(true); // No data to delete } }, 5000); const dataPath = password ? user.get('private').get(tableName) : holoInstance.gun.get(holoInstance.appname).get(tableName); dataPath.once(async (data) => { if (!data) { clearTimeout(timeout); resolve(true); return; } const keys = Object.keys(data).filter(key => key !== '_'); // Process each key to handle holograms properly for (const key of keys) { try { // Get the data to check if it's a hologram const itemPath = password ? user.get('private').get(tableName).get(key) : holoInstance.gun.get(holoInstance.appname).get(tableName).get(key); const rawDataToDelete = await new Promise((resolveItem) => itemPath.once(resolveItem)); let dataToDelete = null; try { if (typeof rawDataToDelete === 'string') { dataToDelete = JSON.parse(rawDataToDelete); } else { dataToDelete = rawDataToDelete; } } catch(e) { console.warn("[deleteAllGlobal] Could not JSON parse data for deletion check:", rawDataToDelete, e); dataToDelete = null; } // Check if it's a hologram and handle accordingly const isDataHologram = dataToDelete && holoInstance.isHologram(dataToDelete); if (isDataHologram) { // Handle hologram deletion - remove from target's _holograms list try { const targetSoul = dataToDelete.soul; const targetSoulInfo = holoInstance.parseSoulPath(targetSoul); if (targetSoulInfo) { const targetNodeRef = holoInstance.getNodeRef(targetSoul); const deletedHologramSoul = `${holoInstance.appname}/${tableName}/${key}`; // Remove the hologram from target's _holograms list await new Promise((resolveTrack) => { targetNodeRef.get('_holograms').get(deletedHologramSoul).put(null, (ack) => { if (ack.err) { console.warn(`[deleteAllGlobal] Error removing hologram ${deletedHologramSoul} from target ${targetSoul}:`, ack.err); } resolveTrack(); }); }); } else { console.warn(`Could not parse target soul ${targetSoul} for hologram tracking removal during deleteAllGlobal.`); } } catch (trackingError) { console.warn(`Error removing hologram reference from target ${dataToDelete.soul} during deleteAllGlobal:`, trackingError); } } // Add to deletions set for tracking deletions.add(key); } catch (error) { console.warn(`Error processing key ${key} during deleteAllGlobal:`, error); // Still add to deletions set even if hologram processing failed deletions.add(key); } } // Now delete all the items const promises = keys.map(key => new Promise((resolveDelete, rejectDelete) => { const deletePath = password ? user.get('private').get(tableName).get(key) : holoInstance.gun.get(holoInstance.appname).get(tableName).get(key); deletePath.put(null, ack => { if (ack.err) { console.error(`Failed to delete ${key}:`, ack.err); rejectDelete(new Error(ack.err)); } else { resolveDelete(); } }); }) ); try { await Promise.all(promises); // Finally delete the table itself dataPath.put(null); clearTimeout(timeout); resolve(true); } catch (error) { reject(error); } }); } catch (error) { reject(error); } }); } catch (error) { console.error('Error in deleteAllGlobal:', error); throw error; } } // Export all global operations as default export default { putGlobal, getGlobal, getAllGlobal, deleteGlobal, deleteAllGlobal };