UNPKG

shogun-core

Version:

SHOGUN CORE - Core library for Shogun Ecosystem

993 lines 58 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; import { RxJSHolster } from './rxjs-holster.js'; import { EventEmitter } from '../utils/eventEmitter.js'; import * as crypto from './crypto.js'; /** * Holster configuration constants. * @internal */ var CONFIG = { PASSWORD: { MIN_LENGTH: 8, }, }; /** * DataBaseHolster * * Manages Holster user authentication and various utility helpers for * session, alias/username, SEA cryptography, event handling, and reactive streams. * This is a native Holster implementation that doesn't require Gun compatibility layer. */ var DataBaseHolster = /** @class */ (function () { /** * Constructs a new DataBaseHolster instance connected to a Holster instance. * @param holster The main Holster instance. * @param sea Optional cryptography (Holster SEA) instance; will be auto-discovered if not provided. * @throws If holster or holster.user() is not provided. */ function DataBaseHolster(holster, core, sea) { var _a; /** Cached user instance or `null` if not logged in */ this.user = null; /** Registered callbacks for auth state changes */ this.onAuthCallbacks = []; /** Whether the database instance has been destroyed */ this._isDestroyed = false; /** Polling interval for auth state changes */ this.authPollInterval = null; /** Last known user state */ this.lastUserState = null; this.eventEmitter = new EventEmitter(); this.core = core; if (!holster) { throw new Error('Holster instance is required but was not provided'); } if (typeof holster.user !== 'function') { throw new Error('Holster instance is invalid: holster.user is not a function'); } this.holster = holster; // Holster's recall() doesn't take options, but it already checks sessionStorage first var userInstance = this.holster.user(); userInstance.recall(); this.user = userInstance.is ? userInstance : null; this.subscribeToAuthEvents(); this.crypto = crypto; this.sea = sea || null; if (!this.sea) { if (this.holster.SEA) { this.sea = this.holster.SEA; } else if ((_a = globalThis.Holster) === null || _a === void 0 ? void 0 : _a.SEA) { this.sea = globalThis.Holster.SEA; } else if (globalThis.SEA) { this.sea = globalThis.SEA; } } this._rxjs = new RxJSHolster(this.holster); // Create usernames node using Holster's API this.usernamesNode = this.holster.get('usernames'); console.log('[DB] DataBaseHolster initialization completed'); } /** * Initialize the database instance. */ DataBaseHolster.prototype.initialize = function () { // Database is already initialized in constructor }; /** * Internal: subscribe to Holster auth state changes and notify listeners. * Since Holster doesn't have native auth events, we poll for changes. * @internal */ DataBaseHolster.prototype.subscribeToAuthEvents = function () { var _this = this; // Poll for user state changes every 100ms this.authPollInterval = setInterval(function () { var user = _this.holster.user(); var currentState = user.is; if (currentState !== _this.lastUserState) { var previousState = _this.lastUserState; _this.lastUserState = currentState; if (currentState) { // User logged in _this.notifyAuthListeners(currentState.pub || ''); // Emit auth:login event if core is available and user just logged in if (_this.core && typeof _this.core.emit === 'function' && !previousState) { _this.core.emit('auth:login', { userPub: currentState.pub || '', username: currentState.username || '', method: 'password', }); } } else { // User logged out _this.notifyAuthListeners(''); // Emit auth:logout event if core is available and user just logged out if (_this.core && typeof _this.core.emit === 'function' && previousState) { _this.core.emit('auth:logout', undefined); } } } }, 100); }; /** * Internal: notify all onAuth callbacks with current user. * @param pub User's public key (pub). * @internal */ DataBaseHolster.prototype.notifyAuthListeners = function (pub) { var user = this.holster.user(); this.onAuthCallbacks.forEach(function (cb) { return cb(user); }); }; /** * Listen for authentication/sign-in events (login, logout, etc). * @param callback Function to call with new user instance. * @returns Function to remove the registered callback. */ DataBaseHolster.prototype.onAuth = function (callback) { var _this = this; this.onAuthCallbacks.push(callback); var user = this.holster.user(); if (user && user.is) callback(user); return function () { var i = _this.onAuthCallbacks.indexOf(callback); if (i !== -1) _this.onAuthCallbacks.splice(i, 1); }; }; /** * Check if a user is currently logged in (there is a valid session). * @returns `true` if logged in; otherwise `false`. */ DataBaseHolster.prototype.isLoggedIn = function () { try { var user = this.holster.user(); return !!(user && user.is && user.is.pub); } catch (error) { return false; } }; /** * Attempt to restore a previously saved session from sessionStorage. * @returns Object indicating success, error, and userPub if restored. */ DataBaseHolster.prototype.restoreSession = function () { try { if (typeof sessionStorage === 'undefined') { return { success: false, error: 'sessionStorage not available' }; } var sessionData = sessionStorage.getItem('gunSessionData'); if (!sessionData) { return { success: false, error: 'No saved session' }; } var session = JSON.parse(sessionData); if (!session.userPub) { return { success: false, error: 'Invalid session data' }; } // Check if session is expired if (session.expiresAt && Date.now() > session.expiresAt) { sessionStorage.removeItem('gunSessionData'); return { success: false, error: 'Session expired' }; } // Verify session restoration var user = this.holster.user(); if (user.is && user.is.pub === session.userPub) { this.user = user; return { success: true, userPub: session.userPub }; } return { success: false, error: 'Session verification failed' }; } catch (error) { return { success: false, error: String(error) }; } }; /** * Log out the current user, clear local state and remove session from storage. */ DataBaseHolster.prototype.logout = function () { try { var wasLoggedIn = !!this.user; var currentUser = this.holster.user(); if (currentUser && currentUser.is) { currentUser.leave(); } this.user = null; if (typeof sessionStorage !== 'undefined') { sessionStorage.removeItem('gunSessionData'); } // Emit auth:logout event if core is available and user was logged in if (wasLoggedIn && this.core && typeof this.core.emit === 'function') { this.core.emit('auth:logout', undefined); } } catch (error) { console.error('[DB] Error during logout:', error); } }; /** * Validate that a provided password meets minimum length requirements. */ DataBaseHolster.prototype.validatePasswordStrength = function (password) { if (password.length < CONFIG.PASSWORD.MIN_LENGTH) { return { valid: false, error: "Password must be at least ".concat(CONFIG.PASSWORD.MIN_LENGTH, " characters long"), }; } return { valid: true }; }; /** * Validate a signup request's username, password, and/or cryptographic pair. */ DataBaseHolster.prototype.validateSignupCredentials = function (username, password, pair) { if (!username || username.length < 1) { return { valid: false, error: 'Username must be more than 0 characters long', }; } if (!/^[a-zA-Z0-9._-]+$/.test(username)) { return { valid: false, error: 'Username can only contain letters, numbers, dots, underscores, and hyphens', }; } if (pair) { if (!pair.pub || !pair.priv || !pair.epub || !pair.epriv) { return { valid: false, error: 'Invalid pair provided' }; } return { valid: true }; } return this.validatePasswordStrength(password); }; /** * Ensures that an alias/username is available in Holster for registration. */ DataBaseHolster.prototype.ensureAliasAvailable = function (alias_1) { return __awaiter(this, arguments, void 0, function (alias, timeout) { var available; if (timeout === void 0) { timeout = 5000; } return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.isAliasAvailable(alias, timeout)]; case 1: available = _a.sent(); if (!available) { throw new Error("Alias \"".concat(alias, "\" is already registered in Holster")); } return [2 /*return*/]; } }); }); }; /** * Checks if a given alias/username is available on Holster. * Uses the same approach as isAliasTaken but returns the inverse. */ DataBaseHolster.prototype.isAliasAvailable = function (alias_1) { return __awaiter(this, arguments, void 0, function (alias, timeout) { var normalizedAlias; var _this = this; if (timeout === void 0) { timeout = 5000; } return __generator(this, function (_a) { if (typeof alias !== 'string' || !alias.trim()) { throw new Error('Alias must be a non-empty string'); } normalizedAlias = alias.trim().toLowerCase(); return [2 /*return*/, new Promise(function (resolve, reject) { var settled = false; var timer = setTimeout(function () { if (settled) return; settled = true; // If timeout, assume alias is available (optimistic approach) // This allows signup to proceed even if the check times out resolve(true); }, timeout); // Holster: check if username exists by looking up ~@username // Use the same approach as isAliasTaken for consistency _this.holster.get("~@".concat(normalizedAlias)).next(null, null, function (user) { if (settled) return; settled = true; clearTimeout(timer); // If user exists, alias is taken (return false) // If user is null/undefined, alias is available (return true) resolve(!user); }); })]; }); }); }; /** * Checks if a given alias/username is taken on Holster. */ DataBaseHolster.prototype.isAliasTaken = function (alias) { return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { return [2 /*return*/, new Promise(function (resolve, reject) { // Holster: use .get().next() for chaining // Check if username exists by looking up ~@username _this.holster.get("~@".concat(alias)).next(null, null, function (user) { // If user exists, alias is taken (return true) // If user is null/undefined, alias is available (return false) resolve(!!user); }); })]; }); }); }; /** * Register a new alias (username) → public key mapping on Holster. */ DataBaseHolster.prototype.registerAlias = function (alias_1, userPub_1) { return __awaiter(this, arguments, void 0, function (alias, userPub, timeout) { var normalizedAlias, available, taken; var _this = this; if (timeout === void 0) { timeout = 5000; } return __generator(this, function (_a) { switch (_a.label) { case 0: if (!alias || !alias.trim()) { throw new Error('Alias must be provided for registration'); } if (!userPub) { throw new Error('userPub must be provided for alias registration'); } normalizedAlias = alias.trim().toLowerCase(); return [4 /*yield*/, this.isAliasAvailable(normalizedAlias, timeout).catch(function (error) { console.error('[DB] Alias availability check failed:', error); throw error; })]; case 1: available = _a.sent(); return [4 /*yield*/, this.isAliasTaken(normalizedAlias)]; case 2: taken = _a.sent(); if (taken) { throw new Error("Alias \"".concat(normalizedAlias, "\" is already taken")); } if (!available) { throw new Error("Alias \"".concat(normalizedAlias, "\" is no longer available for registration")); } return [4 /*yield*/, new Promise(function (resolve, reject) { var settled = false; var timer = setTimeout(function () { if (settled) return; settled = true; reject(new Error('Timeout while registering alias')); }, timeout); // Holster: use .next() for chaining _this.usernamesNode .next(normalizedAlias, null, null) .put(userPub, function (ack) { if (settled) return; settled = true; clearTimeout(timer); if (ack && typeof ack === 'string' && ack.startsWith('error')) { reject(new Error(ack)); return; } resolve(); }); }).catch(function (error) { console.error('[DB] Failed to register alias:', error); throw error; })]; case 3: _a.sent(); return [2 /*return*/]; } }); }); }; /** * Reset holster.user() authentication state and clear cached user. * @internal */ DataBaseHolster.prototype.resetAuthState = function () { try { var user = this.holster.user(); if (user && user.is) { user.leave(); } this.user = null; } catch (e) { // Ignore } }; /** * Assemble a standard AuthResult object after a successful login. */ DataBaseHolster.prototype.buildLoginResult = function (username, userPub) { var user = this.holster.user(); var seaPair = user.is; return { success: true, userPub: userPub, username: username, sea: seaPair ? { pub: seaPair.pub, priv: seaPair.priv, epub: seaPair.epub, epriv: seaPair.epriv, } : undefined, }; }; /** * Save credentials for the current session to sessionStorage, if available. */ DataBaseHolster.prototype.saveCredentials = function (userInfo) { try { if (typeof sessionStorage !== 'undefined') { var sessionInfo = { username: userInfo.alias, pair: userInfo.pair, userPub: userInfo.userPub, timestamp: Date.now(), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7 days }; sessionStorage.setItem('gunSessionData', JSON.stringify(sessionInfo)); } } catch (error) { console.error('[DB] Error saving credentials:', error); } }; /** * Register and authenticate a new user account. */ DataBaseHolster.prototype.signUp = function (username, password, pair) { return __awaiter(this, void 0, void 0, function () { var validation, normalizedUsername, user, aliasError_1, result; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: validation = this.validateSignupCredentials(username, password, pair); if (!validation.valid) { return [2 /*return*/, { success: false, error: validation.error }]; } this.resetAuthState(); normalizedUsername = username.trim().toLowerCase(); user = this.holster.user(); // Note: Holster might not support pair-based auth directly // You may need to implement this or throw an error if (pair) { return [2 /*return*/, { success: false, error: 'Pair-based signup not yet supported with Holster', }]; } _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, this.ensureAliasAvailable(normalizedUsername)]; case 2: _a.sent(); return [3 /*break*/, 4]; case 3: aliasError_1 = _a.sent(); return [2 /*return*/, { success: false, error: aliasError_1 instanceof Error ? aliasError_1.message : String(aliasError_1), }]; case 4: return [4 /*yield*/, new Promise(function (resolve) { var callbackInvoked = false; user.create(normalizedUsername, password, function (createAck) { if (callbackInvoked) { return; } if (createAck && createAck !== null) { callbackInvoked = true; _this.resetAuthState(); resolve({ success: false, error: createAck || 'Signup failed' }); return; } // After create, authenticate user.auth(normalizedUsername, password, function (authAck) { return __awaiter(_this, void 0, void 0, function () { var authenticatedUserPub, alias, registerError_1; var _a, _b; return __generator(this, function (_c) { switch (_c.label) { case 0: if (callbackInvoked) { return [2 /*return*/]; } callbackInvoked = true; if (authAck && authAck !== null) { this.resetAuthState(); resolve({ success: false, error: authAck || 'Authentication after signup failed', }); return [2 /*return*/]; } authenticatedUserPub = (_a = user === null || user === void 0 ? void 0 : user.is) === null || _a === void 0 ? void 0 : _a.pub; if (!authenticatedUserPub) { this.resetAuthState(); resolve({ success: false, error: 'User not authenticated after signup', }); return [2 /*return*/]; } this.user = user; alias = ((_b = user === null || user === void 0 ? void 0 : user.is) === null || _b === void 0 ? void 0 : _b.username) || normalizedUsername; try { this.saveCredentials({ alias: alias || normalizedUsername, pair: { pub: user.is.pub, priv: user.is.priv, epub: user.is.epub, epriv: user.is.epriv, }, userPub: authenticatedUserPub, }); } catch (saveError) { // Ignore save errors } _c.label = 1; case 1: _c.trys.push([1, 3, , 4]); return [4 /*yield*/, this.registerAlias(alias || normalizedUsername, authenticatedUserPub)]; case 2: _c.sent(); return [3 /*break*/, 4]; case 3: registerError_1 = _c.sent(); console.error('[DB] Alias registration failed:', registerError_1); return [3 /*break*/, 4]; case 4: // Emit auth:signup event if core is available if (this.core && typeof this.core.emit === 'function') { this.core.emit('auth:signup', { userPub: authenticatedUserPub, username: normalizedUsername, method: 'password', }); } resolve({ success: true, userPub: authenticatedUserPub, username: normalizedUsername, isNewUser: true, sea: user.is ? { pub: user.is.pub, priv: user.is.priv, epub: user.is.epub, epriv: user.is.epriv, } : undefined, }); return [2 /*return*/]; } }); }); }); }); })]; case 5: result = _a.sent(); return [2 /*return*/, result]; } }); }); }; /** * Sign in (authenticate) as an existing user by username/password or SEA pair. */ DataBaseHolster.prototype.login = function (username, password, pair) { return __awaiter(this, void 0, void 0, function () { var normalizedUsername, user; var _this = this; return __generator(this, function (_a) { this.resetAuthState(); normalizedUsername = username.trim().toLowerCase(); user = this.holster.user(); return [2 /*return*/, new Promise(function (resolve) { // Holster doesn't support pair-based auth directly if (pair) { resolve({ success: false, error: 'Pair-based login not yet supported with Holster', }); return; } user.auth(normalizedUsername, password, function (ack) { var _a, _b; if (ack && ack !== null) { _this.resetAuthState(); resolve({ success: false, error: ack }); return; } var userPub = (_a = user === null || user === void 0 ? void 0 : user.is) === null || _a === void 0 ? void 0 : _a.pub; if (!userPub) { _this.resetAuthState(); resolve({ success: false, error: 'No userPub available' }); return; } _this.user = user; var alias = ((_b = user === null || user === void 0 ? void 0 : user.is) === null || _b === void 0 ? void 0 : _b.username) || normalizedUsername; try { _this.saveCredentials({ alias: alias || normalizedUsername, pair: { pub: user.is.pub, priv: user.is.priv, epub: user.is.epub, epriv: user.is.epriv, }, userPub: userPub, }); } catch (saveError) { // Ignore save errors } // Don't emit auth:login here - let subscribeToAuthEvents() handle it via polling // This prevents duplicate events. The polling will detect the state change from // null/undefined to user.is and emit the event once. resolve(_this.buildLoginResult(alias || normalizedUsername, userPub)); }); })]; }); }); }; /** * Returns the currently authenticated user's public key and Holster user instance. */ DataBaseHolster.prototype.getCurrentUser = function () { try { var user = this.holster.user(); if (user && user.is && user.is.pub) { return { pub: user.is.pub, user: user, }; } return null; } catch (error) { return null; } }; /** * Get current user's public key. */ DataBaseHolster.prototype.getUserPub = function () { var _a; try { var user = this.holster.user(); return ((_a = user === null || user === void 0 ? void 0 : user.is) === null || _a === void 0 ? void 0 : _a.pub) || null; } catch (error) { return null; } }; /** * Authenticate using a SEA pair directly. * If username doesn't exist, creates a new user with the provided pair. */ DataBaseHolster.prototype.loginWithPair = function (username, pair) { return __awaiter(this, void 0, void 0, function () { var normalizedUsername, user; var _this = this; return __generator(this, function (_a) { // Validate pair structure if (!pair || !pair.pub || !pair.priv || !pair.epub || !pair.epriv) { return [2 /*return*/, { success: false, error: 'Invalid pair structure - missing required keys', }]; } this.resetAuthState(); normalizedUsername = username.trim().toLowerCase(); user = this.holster.user(); // Validate username if (!normalizedUsername || normalizedUsername.length < 1) { return [2 /*return*/, { success: false, error: 'Username must be more than 0 characters long', }]; } if (!/^[a-zA-Z0-9._-]+$/.test(normalizedUsername)) { return [2 /*return*/, { success: false, error: 'Username can only contain letters, numbers, dots, underscores, and hyphens', }]; } // Ensure SEA is available if (!this.sea) { return [2 /*return*/, { success: false, error: 'SEA cryptography not available', }]; } // Ensure wire is available if (!this.holster.wire) { return [2 /*return*/, { success: false, error: 'Holster wire not available', }]; } return [2 /*return*/, new Promise(function (resolve) { return __awaiter(_this, void 0, void 0, function () { var userPub, soul, aliasAvailable, existingPub, error_1; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: userPub = '~' + pair.pub; soul = '~@' + normalizedUsername; _a.label = 1; case 1: _a.trys.push([1, 5, , 6]); return [4 /*yield*/, this.isAliasAvailable(normalizedUsername)]; case 2: aliasAvailable = _a.sent(); if (!!aliasAvailable) return [3 /*break*/, 4]; return [4 /*yield*/, new Promise(function (resolve) { _this.usernamesNode.next(normalizedUsername, null, function (pub) { resolve(pub || null); }); })]; case 3: existingPub = _a.sent(); if (existingPub && existingPub !== pair.pub) { resolve({ success: false, error: "Username exists but public key doesn't match", }); return [2 /*return*/]; } // Pub matches or username exists but no pub registered - verify user data exists this.holster.wire.get({ '#': userPub, '.': ['username', 'pub', 'epub'] }, function (userMsg) { if (userMsg.err || !userMsg.put || !userMsg.put[userPub]) { // User data doesn't exist, create it _this.createUserWithPair(normalizedUsername, pair, userPub, soul, resolve); return; } // User data exists - set user.is and authenticate user.is = { username: normalizedUsername, pub: pair.pub, epub: pair.epub, priv: pair.priv, epriv: pair.epriv, }; _this.user = user; try { _this.saveCredentials({ alias: normalizedUsername, pair: pair, userPub: pair.pub, }); } catch (saveError) { // Ignore save errors } // Don't emit auth:login here - let subscribeToAuthEvents() handle it via polling // This prevents duplicate events. The polling will detect the state change from // null/undefined to user.is and emit the event once. resolve(_this.buildLoginResult(normalizedUsername, pair.pub)); }, { wait: 5000 }); return [2 /*return*/]; case 4: // Username doesn't exist - create new user with the provided pair this.createUserWithPair(normalizedUsername, pair, userPub, soul, resolve); return [3 /*break*/, 6]; case 5: error_1 = _a.sent(); resolve({ success: false, error: "Error checking username: ".concat(error_1.message || error_1), }); return [3 /*break*/, 6]; case 6: return [2 /*return*/]; } }); }); })]; }); }); }; /** * Helper to create a user with pair and register alias. * @internal */ DataBaseHolster.prototype.createUserWithPair = function (normalizedUsername, pair, userPub, soul, resolve) { return __awaiter(this, void 0, void 0, function () { var user, timestamp, sig, data, graph, error_2; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: user = this.holster.user(); _a.label = 1; case 1: _a.trys.push([1, 4, , 5]); // Ensure alias is available before creating return [4 /*yield*/, this.ensureAliasAvailable(normalizedUsername)]; case 2: // Ensure alias is available before creating _a.sent(); timestamp = Date.now(); return [4 /*yield*/, this.sea.signTimestamp(timestamp, pair)]; case 3: sig = _a.sent(); data = { username: normalizedUsername, pub: pair.pub, epub: pair.epub, // For pair-based users, we don't store encrypted auth // The pair itself is the authentication mechanism }; graph = this.createGraph(userPub, data, sig, pair.pub, timestamp); // Put user data this.holster.wire.put(graph, function (err) { var _a; if (err) { resolve({ success: false, error: "Error creating user: ".concat(err), }); return; } // Create username -> pub mapping (required by Holster) var rel = (_a = {}, _a[userPub] = { '#': userPub }, _a); var relGraph = _this.createGraph(soul, rel, null, null, null); _this.holster.wire.put(relGraph, function (relErr) { return __awaiter(_this, void 0, void 0, function () { var registerError_2; return __generator(this, function (_a) { switch (_a.label) { case 0: if (relErr) { resolve({ success: false, error: "Error creating username mapping: ".concat(relErr), }); return [2 /*return*/]; } _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, this.registerAlias(normalizedUsername, pair.pub)]; case 2: _a.sent(); return [3 /*break*/, 4]; case 3: registerError_2 = _a.sent(); console.error('[DB] Alias registration failed:', registerError_2); return [3 /*break*/, 4]; case 4: // Set user.is with the pair user.is = { username: normalizedUsername, pub: pair.pub, epub: pair.epub, priv: pair.priv, epriv: pair.epriv, }; this.user = user; try { this.saveCredentials({ alias: normalizedUsername, pair: pair, userPub: pair.pub, }); } catch (saveError) { // Ignore save errors } // Emit auth:signup event (new user created) if core is available // The auth:login event will be emitted by subscribeToAuthEvents() when it detects the state change if (this.core && typeof this.core.emit === 'function') { this.core.emit('auth:signup', { userPub: pair.pub, username: normalizedUsername, method: 'pair', }); } // Don't emit auth:login here - let subscribeToAuthEvents() handle it via polling // This prevents duplicate events. The polling will detect the state change from // null/undefined to user.is and emit the event once. resolve(this.buildLoginResult(normalizedUsername, pair.pub)); return [2 /*return*/]; } }); }); }); }); return [3 /*break*/, 5]; case 4: error_2 = _a.sent(); resolve({ success: false, error: "Error during user creation: ".concat(error_2.message || error_2), }); return [3 /*break*/, 5]; case 5: return [2 /*return*/]; } }); }); }; /** * Helper to create a graph structure compatible with Holster. * @internal */ DataBaseHolster.prototype.createGraph = function (soul, data, sig, pub, timesta