shogun-core
Version:
SHOGUN CORE - Core library for Shogun Ecosystem
993 lines • 58 kB
JavaScript
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