UNPKG

shogun-core

Version:

SHOGUN SDK - Core library for Shogun SDK

1,110 lines (1,109 loc) 44.4 kB
"use strict"; /** * GunDB class with enhanced features: * - Dynamic peer linking * - Support for remove/unset operations * - Direct authentication through Gun.user() */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.GunInstance = void 0; const logger_1 = require("../utils/logger"); const errorHandler_1 = require("../utils/errorHandler"); const rxjs_integration_1 = require("./rxjs-integration"); const GunErrors = __importStar(require("./errors")); const crypto = __importStar(require("./crypto")); const utils = __importStar(require("./utils")); const derive_1 = __importDefault(require("./derive")); class GunInstance { gun; user = null; crypto; utils; node; onAuthCallbacks = []; // Integrated modules _rxjs; constructor(gun, appScope = "shogun") { (0, logger_1.log)("Initializing GunDB"); // Validate Gun instance if (!gun) { throw new Error("Gun instance is required but was not provided"); } if (typeof gun !== "object") { throw new Error(`Gun instance must be an object, received: ${typeof gun}`); } if (typeof gun.user !== "function") { throw new Error(`Gun instance is invalid: gun.user is not a function. Received gun.user type: ${typeof gun.user}`); } if (typeof gun.get !== "function") { throw new Error(`Gun instance is invalid: gun.get is not a function. Received gun.get type: ${typeof gun.get}`); } if (typeof gun.on !== "function") { throw new Error(`Gun instance is invalid: gun.on is not a function. Received gun.on type: ${typeof gun.on}`); } this.gun = gun; if (typeof window !== "undefined") { window.ShogunDB = this; window.ShogunGun = gun; } else if (typeof global !== "undefined") { global.ShogunDB = this; global.ShogunGun = gun; } try { this.user = this.gun.user().recall({ sessionStorage: true }); } catch (error) { (0, logger_1.logError)("Error initializing Gun user:", error); throw new Error(`Failed to initialize Gun user: ${error}`); } this.subscribeToAuthEvents(); this.crypto = crypto; this.utils = utils; this.node = this.gun.get(appScope); // Attempt to restore session after initialization setTimeout(async () => { try { const sessionResult = await this.restoreSession(); if (sessionResult.success) { (0, logger_1.log)(`Session automatically restored for user: ${sessionResult.userPub}`); } else { (0, logger_1.log)(`No previous session to restore: ${sessionResult.error}`); } } catch (error) { (0, logger_1.logError)("Error during automatic session restoration:", error); } }, 500); // Give Gun time to initialize } subscribeToAuthEvents() { this.gun.on("auth", (ack) => { (0, logger_1.log)("Auth event received:", ack); if (ack.err) { errorHandler_1.ErrorHandler.handle(errorHandler_1.ErrorType.GUN, "AUTH_EVENT_ERROR", ack.err, new Error(ack.err)); } else { this.notifyAuthListeners(ack.sea?.pub || ""); } }); } notifyAuthListeners(pub) { const user = this.gun.user(); this.onAuthCallbacks.forEach((cb) => cb(user)); } /** * Adds a new peer to the network * @param peer URL of the peer to add */ addPeer(peer) { this.gun.opt({ peers: [peer] }); (0, logger_1.log)(`Added new peer: ${peer}`); } /** * Removes a peer from the network * @param peer URL of the peer to remove */ removePeer(peer) { try { // Get current peers from Gun instance const gunOpts = this.gun._.opt; if (gunOpts && gunOpts.peers) { // Remove the peer from the peers object delete gunOpts.peers[peer]; // Also try to close the connection if it exists const peerConnection = gunOpts.peers[peer]; if (peerConnection && typeof peerConnection.close === "function") { peerConnection.close(); } (0, logger_1.log)(`Removed peer: ${peer}`); } else { (0, logger_1.log)(`Peer not found in current connections: ${peer}`); } } catch (error) { (0, logger_1.logError)(`Error removing peer ${peer}:`, error); } } /** * Gets the list of currently connected peers * @returns Array of peer URLs */ getCurrentPeers() { try { const gunOpts = this.gun._.opt; if (gunOpts && gunOpts.peers) { return Object.keys(gunOpts.peers).filter((peer) => { const peerObj = gunOpts.peers[peer]; // Check if peer is actually connected (not just configured) return peerObj && peerObj.wire && peerObj.wire.hied !== "bye"; }); } return []; } catch (error) { (0, logger_1.logError)("Error getting current peers:", error); return []; } } /** * Gets the list of all configured peers (connected and disconnected) * @returns Array of peer URLs */ getAllConfiguredPeers() { try { const gunOpts = this.gun._.opt; if (gunOpts && gunOpts.peers) { return Object.keys(gunOpts.peers); } return []; } catch (error) { (0, logger_1.logError)("Error getting configured peers:", error); return []; } } /** * Gets detailed information about all peers * @returns Object with peer information */ getPeerInfo() { try { const gunOpts = this.gun._.opt; const peerInfo = {}; if (gunOpts && gunOpts.peers) { Object.keys(gunOpts.peers).forEach((peer) => { const peerObj = gunOpts.peers[peer]; const isConnected = peerObj && peerObj.wire && peerObj.wire.hied !== "bye"; const status = isConnected ? "connected" : peerObj && peerObj.wire ? "disconnected" : "not_initialized"; peerInfo[peer] = { connected: isConnected, status: status, }; }); } return peerInfo; } catch (error) { (0, logger_1.logError)("Error getting peer info:", error); return {}; } } /** * Reconnects to a specific peer * @param peer URL of the peer to reconnect */ reconnectToPeer(peer) { try { // First remove the peer this.removePeer(peer); // Wait a moment then add it back setTimeout(() => { this.addPeer(peer); (0, logger_1.log)(`Reconnected to peer: ${peer}`); }, 1000); } catch (error) { (0, logger_1.logError)(`Error reconnecting to peer ${peer}:`, error); } } /** * Clears all peers and optionally adds new ones * @param newPeers Optional array of new peers to add */ resetPeers(newPeers) { try { const gunOpts = this.gun._.opt; if (gunOpts && gunOpts.peers) { // Clear all existing peers Object.keys(gunOpts.peers).forEach((peer) => { this.removePeer(peer); }); // Add new peers if provided if (newPeers && newPeers.length > 0) { newPeers.forEach((peer) => { this.addPeer(peer); }); } (0, logger_1.log)(`Reset peers. New peers: ${newPeers ? newPeers.join(", ") : "none"}`); } } catch (error) { (0, logger_1.logError)("Error resetting peers:", error); } } /** * Registers an authentication callback * @param callback Function to call on auth events * @returns Function to unsubscribe the callback */ onAuth(callback) { this.onAuthCallbacks.push(callback); const user = this.gun.user(); if (user && user.is) callback(user); return () => { const i = this.onAuthCallbacks.indexOf(callback); if (i !== -1) this.onAuthCallbacks.splice(i, 1); }; } /** * Helper method to navigate to a nested path by splitting and chaining .get() calls * @param node Starting Gun node * @param path Path string (e.g., "test/data/marco") * @returns Gun node at the specified path */ navigateToPath(node, path) { if (!path) return node; // Split path by '/' and filter out empty segments const pathSegments = path .split("/") .filter((segment) => segment.length > 0); // Chain .get() calls for each path segment return pathSegments.reduce((currentNode, segment) => { return currentNode.get(segment); }, node); } /** * Gets the Gun instance * @returns Gun instance */ getGun() { return this.gun; } /** * Gets the current user instance * @returns User instance */ getUser() { return this.gun.user(); } /** * Gets a node at the specified path * @param path Path to the node * @returns Gun node */ get(path) { return this.navigateToPath(this.gun, path); } /** * Puts data at the specified path * @param path Path to store data * @param data Data to store * @returns Promise resolving to operation result */ async put(path, data) { return new Promise((resolve) => { this.navigateToPath(this.gun, path).put(data, (ack) => { resolve(ack.err ? { success: false, error: ack.err } : { success: true }); }); }); } /** * Sets data at the specified path * @param path Path to store data * @param data Data to store * @returns Promise resolving to operation result */ async set(path, data) { return new Promise((resolve) => { this.navigateToPath(this.gun, path).set(data, (ack) => { resolve(ack.err ? { success: false, error: ack.err } : { success: true }); }); }); } /** * Removes data at the specified path * @param path Path to remove * @returns Promise resolving to operation result */ async remove(path) { return new Promise((resolve) => { this.navigateToPath(this.gun, path).put(null, (ack) => { resolve(ack.err ? { success: false, error: ack.err } : { success: true }); }); }); } /** * Signs up a new user using direct Gun authentication * @param username Username * @param password Password * @returns Promise resolving to signup result */ async signUp(username, password) { (0, logger_1.log)("Attempting user registration:", username); try { // Validate credentials if (password.length < 8) { const err = "Passwords must be more than 8 characters long!"; (0, logger_1.log)(err); return { success: false, error: err }; } if (username.length < 1) { const err = "Username must be more than 0 characters long!"; (0, logger_1.log)(err); return { success: false, error: err }; } // First, try to authenticate with Gun's native system to check if user exists (0, logger_1.log)(`Checking if user ${username} exists in Gun's native system...`); const authTestResult = await new Promise((resolve) => { this.gun.user().auth(username, password, (ack) => { if (ack.err) { // User doesn't exist or password is wrong - this is expected for new users resolve({ exists: false, error: ack.err }); } else { // User exists and password is correct resolve({ exists: true, userPub: this.gun.user().is?.pub }); } }); }); if (authTestResult.exists) { (0, logger_1.log)(`User ${username} already exists and password is correct, syncing with tracking system...`); // User exists and can authenticate, sync with tracking system const userPub = authTestResult.userPub; // Check if user exists in our tracking system const existingUser = await this.checkUsernameExists(username); if (!existingUser) { (0, logger_1.log)(`User ${username} not in tracking system, adding them...`); // Add user to our tracking system const userMetadata = { username: username, pub: userPub, createdAt: Date.now(), lastLogin: Date.now(), }; // Save user metadata await new Promise((resolve) => { this.gun.get(userPub).put(userMetadata, (ack) => { if (ack.err) { (0, logger_1.logError)(`Warning: Failed to save user metadata: ${ack.err}`); } else { (0, logger_1.log)(`User metadata saved for: ${username}`); } resolve(); }); }); // Add to users collection await new Promise((resolve) => { this.gun.get("users").set(this.gun.get(userPub), (ack) => { if (ack.err) { (0, logger_1.logError)(`Warning: Failed to add user to collection: ${ack.err}`); } else { (0, logger_1.log)(`User added to collection: ${username}`); } resolve(); }); }); // Create username mapping await new Promise((resolve) => { this.gun .get("usernames") .get(username) .put(userPub, (ack) => { if (ack.err) { (0, logger_1.logError)(`Warning: Could not create username mapping: ${ack.err}`); } else { (0, logger_1.log)(`Username mapping created: ${username} -> ${userPub}`); } resolve(); }); }); } return { success: true, userPub: userPub, username: username, message: "User already exists and was synced with tracking system", }; } // User doesn't exist, attempt to create new user (0, logger_1.log)(`Creating new user: ${username}`); const createResult = await new Promise((resolve) => { this.gun.user().create(username, password, (ack) => { if (ack.err) { (0, logger_1.logError)(`User creation error: ${ack.err}`); resolve({ success: false, error: ack.err }); } else { (0, logger_1.log)(`User created successfully: ${username}`); resolve({ success: true, pub: ack.pub }); } }); }); if (!createResult.success) { return createResult; } // User created successfully, now authenticate to get the userPub const authResult = await new Promise((resolve) => { this.gun.user().auth(username, password, (ack) => { if (ack.err) { (0, logger_1.logError)(`Authentication after creation failed: ${ack.err}`); resolve({ success: false, error: ack.err }); } else { resolve({ success: true, userPub: this.gun.user().is?.pub }); } }); }); if (!authResult.success) { return { success: false, error: "User created but authentication failed", }; } const userPub = authResult.userPub; (0, logger_1.log)(`User authentication successful after creation: ${username} (${userPub})`); // Add to tracking system const userMetadata = { username: username, pub: userPub, createdAt: Date.now(), lastLogin: Date.now(), }; // Save user metadata (best effort) try { await new Promise((resolve) => { this.gun.get(userPub).put(userMetadata, (ack) => { if (ack.err) { (0, logger_1.logError)(`Warning: Failed to save user metadata: ${ack.err}`); } else { (0, logger_1.log)(`User metadata saved for: ${username}`); } resolve(); }); }); // Add to users collection await new Promise((resolve) => { this.gun.get("users").set(this.gun.get(userPub), (ack) => { if (ack.err) { (0, logger_1.logError)(`Warning: Failed to add user to collection: ${ack.err}`); } else { (0, logger_1.log)(`User added to collection: ${username}`); } resolve(); }); }); // Create username mapping await new Promise((resolve) => { this.gun .get("usernames") .get(username) .put(userPub, (ack) => { if (ack.err) { (0, logger_1.logError)(`Warning: Could not create username mapping: ${ack.err}`); } else { (0, logger_1.log)(`Username mapping created: ${username} -> ${userPub}`); } resolve(); }); }); } catch (trackingError) { (0, logger_1.logError)(`Warning: Could not update tracking system: ${trackingError}`); // Don't fail signup for tracking errors } this._savePair(); return { success: true, userPub, username, message: "User created successfully", }; } catch (error) { (0, logger_1.logError)(`Exception during signup for ${username}: ${error}`); return { success: false, error: String(error) }; } } /** * Check if a username already exists in the system * @param username Username to check * @returns Promise resolving to user data if exists, null otherwise */ async checkUsernameExists(username) { try { // First check the username mapping (faster) const mappedPub = await new Promise((resolve) => { this.gun .get("usernames") .get(username) .once((pub) => { resolve(pub || null); }); }); if (mappedPub) { // Get user data from the pub const userData = await new Promise((resolve) => { this.gun.get(mappedPub).once((data) => { resolve(data); }); }); return userData; } // Fallback: Search through all users collection (slower but more reliable) const existingUser = await new Promise((resolve) => { let found = false; let timeoutId; const checkComplete = () => { if (timeoutId) clearTimeout(timeoutId); if (!found) { resolve(null); } }; this.gun .get("users") .map() .once((userData, key) => { if (!found && userData && userData.username === username) { found = true; clearTimeout(timeoutId); resolve(userData); } }); // Set a timeout to avoid hanging timeoutId = setTimeout(checkComplete, 2000); }); return existingUser; } catch (error) { (0, logger_1.logError)(`Error checking username existence: ${error}`); return null; } } /** * Logs in a user using direct Gun authentication * @param username Username * @param password Password * @param callback Optional callback for login result * @returns Promise resolving to login result */ async login(username, password, callback) { (0, logger_1.log)(`Attempting login for user: ${username}`); try { // Attempt Gun.js authentication directly first // This allows login even if our custom tracking system is out of sync const authResult = await new Promise((resolve) => { this.gun.user().auth(username, password, (ack) => { if (ack.err) { (0, logger_1.logError)(`Login error for ${username}: ${ack.err}`); resolve({ success: false, error: ack.err }); } else { (0, logger_1.log)(`Login successful for: ${username}`); resolve({ success: true, ack }); } }); }); if (!authResult.success) { // If Gun.js auth fails, the user likely doesn't exist or password is wrong const result = { success: false, error: `User '${username}' not found. Please check your username or register first.`, }; if (callback) callback(result); return result; } const userPub = this.gun.user().is?.pub; if (!userPub) { const result = { success: false, error: "Authentication failed: No user pub returned.", }; if (callback) callback(result); return result; } (0, logger_1.log)(`Gun.js authentication successful for: ${username} (${userPub})`); // Now try to sync with our custom tracking system // This is best-effort and won't fail the login if it doesn't work try { // Check if user exists in our tracking system const existingUser = await this.checkUsernameExists(username); if (!existingUser) { (0, logger_1.log)(`User ${username} not found in tracking system, adding them...`); // Add user to our tracking system const userMetadata = { username: username, pub: userPub, createdAt: Date.now(), lastLogin: Date.now(), }; // Save user metadata await new Promise((resolve, reject) => { this.gun.get(userPub).put(userMetadata, (ack) => { if (ack.err) { (0, logger_1.logError)(`Warning: Failed to save user metadata: ${ack.err}`); resolve(); // Don't fail login for this } else { (0, logger_1.log)(`User metadata saved for: ${username}`); resolve(); } }); }); // Add to users collection await new Promise((resolve, reject) => { this.gun.get("users").set(this.gun.get(userPub), (ack) => { if (ack.err) { (0, logger_1.logError)(`Warning: Failed to add user to collection: ${ack.err}`); resolve(); // Don't fail login for this } else { (0, logger_1.log)(`User added to collection: ${username}`); resolve(); } }); }); // Create username mapping await new Promise((resolve, reject) => { this.gun .get("usernames") .get(username) .put(userPub, (ack) => { if (ack.err) { (0, logger_1.logError)(`Warning: Could not create username mapping: ${ack.err}`); resolve(); // Don't fail login for this } else { (0, logger_1.log)(`Username mapping created: ${username} -> ${userPub}`); resolve(); } }); }); } else { (0, logger_1.log)(`User ${username} found in tracking system, updating last login...`); // Update last login time this.gun.get(userPub).get("lastLogin").put(Date.now()); } } catch (trackingError) { // Log but don't fail the login for tracking system errors (0, logger_1.logError)(`Warning: Could not sync with tracking system: ${trackingError}`); } (0, logger_1.log)(`Login completed successfully for: ${username} (${userPub})`); this._savePair(); const result = { success: true, userPub, username, }; if (callback) callback(result); return result; } catch (error) { (0, logger_1.logError)(`Exception during login for ${username}: ${error}`); const result = { success: false, error: String(error) }; if (callback) callback(result); return result; } } _savePair() { try { const user = this.gun.user(); const pair = user?._?.sea; const userInfo = user?.is; if (pair && userInfo && typeof localStorage !== "undefined") { // Save the crypto pair localStorage.setItem("gun/pair", JSON.stringify(pair)); // Save user session info const sessionInfo = { pub: userInfo.pub, alias: userInfo.alias || "", timestamp: Date.now(), }; localStorage.setItem("gun/session", JSON.stringify(sessionInfo)); (0, logger_1.log)(`Session saved for user: ${userInfo.alias || userInfo.pub}`); } } catch (error) { (0, logger_1.logError)("Error saving auth pair and session:", error); } } /** * Attempts to restore user session from local storage * @returns Promise resolving to session restoration result */ async restoreSession() { try { if (typeof localStorage === "undefined") { return { success: false, error: "localStorage not available" }; } const sessionInfo = localStorage.getItem("gun/session"); const pairInfo = localStorage.getItem("gun/pair"); if (!sessionInfo || !pairInfo) { (0, logger_1.log)("No saved session found"); return { success: false, error: "No saved session" }; } const session = JSON.parse(sessionInfo); const pair = JSON.parse(pairInfo); // Check if session is not too old (optional - you can adjust this) const sessionAge = Date.now() - session.timestamp; const maxSessionAge = 7 * 24 * 60 * 60 * 1000; // 7 days if (sessionAge > maxSessionAge) { (0, logger_1.log)("Session expired, clearing storage"); localStorage.removeItem("gun/session"); localStorage.removeItem("gun/pair"); return { success: false, error: "Session expired" }; } (0, logger_1.log)(`Attempting to restore session for user: ${session.alias || session.pub}`); // Try to restore the session with Gun const user = this.gun.user(); // Set the pair directly user._ = { sea: pair }; // Try to recall the session const recallResult = await new Promise((resolve) => { try { user.recall({ sessionStorage: true }, (ack) => { if (ack.err) { (0, logger_1.logError)(`Session recall error: ${ack.err}`); resolve(false); } else { resolve(true); } }); } catch (error) { (0, logger_1.logError)(`Session recall exception: ${error}`); resolve(false); } // Fallback timeout setTimeout(() => resolve(false), 3000); }); if (recallResult && user.is?.pub === session.pub) { (0, logger_1.log)(`Session restored successfully for: ${session.alias || session.pub}`); return { success: true, userPub: session.pub }; } else { (0, logger_1.log)("Session restoration failed, clearing storage"); localStorage.removeItem("gun/session"); localStorage.removeItem("gun/pair"); return { success: false, error: "Session restoration failed" }; } } catch (error) { (0, logger_1.logError)(`Error restoring session: ${error}`); return { success: false, error: String(error) }; } } /** * Logs out the current user using direct Gun authentication */ logout() { try { // Check if the user is actually logged in before attempting to logout if (!this.isLoggedIn()) { (0, logger_1.log)("No user logged in, skipping logout"); return; } const currentUser = this.getCurrentUser(); (0, logger_1.log)(`Logging out user: ${currentUser?.pub || "unknown"}`); // Direct logout using Gun this.gun.user().leave(); // Clear local storage session data if (typeof localStorage !== "undefined") { localStorage.removeItem("gun/pair"); localStorage.removeItem("gun/session"); // Also clear old format for backward compatibility localStorage.removeItem("pair"); (0, logger_1.log)("Local session data cleared"); } // Clear sessionStorage as well if (typeof sessionStorage !== "undefined") { sessionStorage.removeItem("gun/"); sessionStorage.removeItem("gun/user"); sessionStorage.removeItem("gun/auth"); (0, logger_1.log)("Session storage cleared"); } (0, logger_1.log)("Logout completed successfully"); } catch (error) { (0, logger_1.logError)("Error during logout:", error); } } /** * Checks if a user is currently logged in * @returns True if logged in */ isLoggedIn() { return !!this.gun.user()?.is?.pub; } /** * Gets the current user * @returns Current user object or null */ getCurrentUser() { const pub = this.gun.user()?.is?.pub; return pub ? { pub, user: this.gun.user() } : null; } /** * Accesses the RxJS module for reactive programming * @returns GunRxJS instance */ rx() { if (!this._rxjs) { this._rxjs = new rxjs_integration_1.GunRxJS(this.gun); } return this._rxjs; } /** * Sets up security questions and password hint * @param username Username * @param password Current password * @param hint Password hint * @param securityQuestions Array of security questions * @param securityAnswers Array of answers to security questions * @returns Promise resolving with the operation result */ async setPasswordHint(username, password, hint, securityQuestions, securityAnswers) { (0, logger_1.log)("Setting password hint for:", username); // Verify that the user is authenticated const loginResult = await this.login(username, password); if (!loginResult.success) { return { success: false, error: "Authentication failed" }; } try { // Generate a proof of work from security question answers const proofOfWork = (await this.crypto.hashText(securityAnswers.join("|"))); // Encrypt the password hint with the proof of work // The PoW (a string) is used as the encryption key const encryptedHint = await this.crypto.encrypt(hint, proofOfWork); // Save security questions and encrypted hint await this.saveUserData("security", { questions: securityQuestions, hint: encryptedHint, }); return { success: true }; } catch (error) { (0, logger_1.logError)("Error setting password hint:", error); return { success: false, error: String(error) }; } } /** * Recovers password hint using security question answers * @param username Username * @param securityAnswers Array of answers to security questions * @returns Promise resolving with the password hint */ async forgotPassword(username, securityAnswers) { (0, logger_1.log)("Attempting password recovery for:", username); try { // Verify the user exists const user = this.gun.user().recall({ sessionStorage: true }); if (!user || !user.is) { return { success: false, error: "User not found" }; } // Retrieve security questions and encrypted hint const securityData = await this.getUserData("security"); if (!securityData || !securityData.hint) { return { success: false, error: "No password hint found", }; } // Decrypt the password hint with the proof of work const hint = await this.crypto.decrypt(securityData.hint, (await this.crypto.hashText(securityAnswers.join("|")))); if (hint === undefined) { return { success: false, error: "Incorrect answers to security questions", }; } return { success: true, hint: hint }; } catch (error) { (0, logger_1.logError)("Error recovering password hint:", error); return { success: false, error: String(error) }; } } /** * Hashes text with Gun.SEA * @param text Text to hash * @returns Promise that resolves with the hashed text */ async hashText(text) { return this.crypto.hashText(text); } /** * Encrypts data with Gun.SEA * @param data Data to encrypt * @param key Encryption key * @returns Promise that resolves with the encrypted data */ async encrypt(data, key) { return this.crypto.encrypt(data, key); } /** * Decrypts data with Gun.SEA * @param encryptedData Encrypted data * @param key Decryption key * @returns Promise that resolves with the decrypted data */ async decrypt(encryptedData, key) { return this.crypto.decrypt(encryptedData, key); } /** * Saves user data at the specified path * @param path Path to save the data (supports nested paths like "test/data/marco") * @param data Data to save * @returns Promise that resolves when the data is saved */ async saveUserData(path, data) { return new Promise((resolve, reject) => { const user = this.gun.user(); if (!user.is) { reject(new Error("User not authenticated")); return; } this.navigateToPath(user, path).put(data, (ack) => { if (ack.err) { reject(new Error(ack.err)); } else { resolve(); } }); }); } /** * Gets user data from the specified path * @param path Path to get the data from (supports nested paths like "test/data/marco") * @returns Promise that resolves with the data */ async getUserData(path) { return new Promise((resolve) => { const user = this.gun.user(); if (!user.is) { resolve(null); return; } this.navigateToPath(user, path).once((data) => { resolve(data); }); }); } /** * Derive cryptographic keys from password and optional extras * Supports multiple key derivation algorithms: P-256, secp256k1 (Bitcoin), secp256k1 (Ethereum) * @param password - Password or seed for key derivation * @param extra - Additional entropy (string or array of strings) * @param options - Derivation options to specify which key types to generate * @returns Promise resolving to derived keys object */ async derive(password, extra, options) { try { (0, logger_1.log)("Deriving cryptographic keys with options:", options); // Call the derive function with the provided parameters const derivedKeys = await (0, derive_1.default)(password, extra, options); (0, logger_1.log)("Key derivation completed successfully"); return derivedKeys; } catch (error) { (0, logger_1.logError)("Error during key derivation:", error); // Use centralized error handler errorHandler_1.ErrorHandler.handle(errorHandler_1.ErrorType.ENCRYPTION, "KEY_DERIVATION_FAILED", error instanceof Error ? error.message : "Failed to derive cryptographic keys", error); throw error; } } /** * Derive P-256 keys (default Gun.SEA behavior) * @param password - Password for key derivation * @param extra - Additional entropy * @returns Promise resolving to P-256 keys */ async deriveP256(password, extra) { return this.derive(password, extra, { includeP256: true }); } /** * Derive Bitcoin secp256k1 keys with P2PKH address * @param password - Password for key derivation * @param extra - Additional entropy * @returns Promise resolving to Bitcoin keys and address */ async deriveBitcoin(password, extra) { return this.derive(password, extra, { includeSecp256k1Bitcoin: true }); } /** * Derive Ethereum secp256k1 keys with Keccak256 address * @param password - Password for key derivation * @param extra - Additional entropy * @returns Promise resolving to Ethereum keys and address */ async deriveEthereum(password, extra) { return this.derive(password, extra, { includeSecp256k1Ethereum: true }); } /** * Derive all supported key types * @param password - Password for key derivation * @param extra - Additional entropy * @returns Promise resolving to all key types */ async deriveAll(password, extra) { return this.derive(password, extra, { includeP256: true, includeSecp256k1Bitcoin: true, includeSecp256k1Ethereum: true, }); } // Errors static Errors = GunErrors; } exports.GunInstance = GunInstance;