shogun-core
Version:
SHOGUN SDK - Core library for Shogun SDK
571 lines (570 loc) • 23.1 kB
JavaScript
"use strict";
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ShogunCore = exports.ShogunEventEmitter = exports.ShogunStorage = exports.Gun = exports.SEA = void 0;
const gundb_1 = require("./gundb");
const rxjs_integration_1 = require("./gundb/rxjs-integration");
const eventEmitter_1 = require("./utils/eventEmitter");
const logger_1 = require("./utils/logger");
const errorHandler_1 = require("./utils/errorHandler");
const storage_1 = require("./storage/storage");
const shogun_1 = require("./types/shogun");
const webauthnPlugin_1 = require("./plugins/webauthn/webauthnPlugin");
const web3ConnectorPlugin_1 = require("./plugins/web3/web3ConnectorPlugin");
const nostrConnectorPlugin_1 = require("./plugins/nostr/nostrConnectorPlugin");
const oauthPlugin_1 = require("./plugins/oauth/oauthPlugin");
const gun_es_1 = require("./gundb/gun-es/gun-es");
Object.defineProperty(exports, "Gun", { enumerable: true, get: function () { return gun_es_1.Gun; } });
Object.defineProperty(exports, "SEA", { enumerable: true, get: function () { return gun_es_1.SEA; } });
__exportStar(require("./utils/errorHandler"), exports);
__exportStar(require("./gundb"), exports);
__exportStar(require("./gundb/rxjs-integration"), exports);
__exportStar(require("./plugins"), exports);
__exportStar(require("./types/shogun"), exports);
var storage_2 = require("./storage/storage");
Object.defineProperty(exports, "ShogunStorage", { enumerable: true, get: function () { return storage_2.ShogunStorage; } });
var events_1 = require("./types/events");
Object.defineProperty(exports, "ShogunEventEmitter", { enumerable: true, get: function () { return events_1.ShogunEventEmitter; } });
/**
* Main ShogunCore class - implements the IShogunCore interface
*
* This is the primary entry point for the Shogun SDK, providing access to:
* - Decentralized database (GunInstance)
* - Authentication methods (traditional, WebAuthn, MetaMask)
* - Plugin system for extensibility
* - RxJS integration for reactive programming
*
* @since 2.0.0
*/
class ShogunCore {
static API_VERSION = "2.0.0";
_gun;
_user = null;
gundb;
storage;
eventEmitter;
provider;
config;
rx;
plugins = new Map();
currentAuthMethod;
/**
* Initialize the Shogun SDK
* @param config - SDK Configuration object
* @description Creates a new instance of ShogunCore with the provided configuration.
* Initializes all required components including storage, event emitter, GunInstance connection,
* and plugin system.
*/
constructor(config) {
(0, logger_1.log)("Initializing Shogun");
this.config = config;
this.storage = new storage_1.ShogunStorage();
this.eventEmitter = new eventEmitter_1.EventEmitter();
if (config.logging) {
(0, logger_1.configureLogging)(config.logging);
}
errorHandler_1.ErrorHandler.addListener((error) => {
this.eventEmitter.emit("error", {
action: error.code,
message: error.message,
type: error.type,
});
});
try {
if (config.gunInstance) {
this._gun = config.gunInstance;
}
else {
this._gun = (0, gun_es_1.Gun)(config.peers || []);
}
}
catch (error) {
(0, logger_1.logError)("Error creating Gun instance:", error);
throw new Error(`Failed to create Gun instance: ${error}`);
}
try {
this.gundb = new gundb_1.GunInstance(this._gun, config.scope || "");
this._gun = this.gundb.gun;
}
catch (error) {
(0, logger_1.logError)("Error initializing GunInstance:", error);
throw new Error(`Failed to initialize GunInstance: ${error}`);
}
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.rx = new rxjs_integration_1.GunRxJS(this._gun);
this.registerBuiltinPlugins(config);
if (config.plugins?.autoRegister &&
config.plugins.autoRegister.length > 0) {
for (const plugin of config.plugins.autoRegister) {
try {
this.register(plugin);
}
catch (error) {
(0, logger_1.logError)(`Failed to auto-register plugin ${plugin.name}:`, error);
}
}
}
(0, logger_1.log)("ShogunSDK initialized! 🚀");
}
/**
* Access to the Gun instance
* @returns The Gun instance
*/
get gun() {
return this._gun;
}
/**
* Access to the current user
* @returns The current Gun user instance
*/
get user() {
return this._user;
}
/**
* Register built-in plugins based on configuration
* @private
*/
registerBuiltinPlugins(config) {
try {
// Authentication plugins group
if (config.webauthn?.enabled) {
const webauthnPlugin = new webauthnPlugin_1.WebauthnPlugin();
webauthnPlugin._category = shogun_1.PluginCategory.Authentication;
this.register(webauthnPlugin);
(0, logger_1.log)("Webauthn plugin registered");
}
if (config.web3?.enabled) {
const web3ConnectorPlugin = new web3ConnectorPlugin_1.Web3ConnectorPlugin();
web3ConnectorPlugin._category = shogun_1.PluginCategory.Authentication;
this.register(web3ConnectorPlugin);
(0, logger_1.log)("Web3Connector plugin registered");
}
if (config.nostr?.enabled) {
const nostrConnectorPlugin = new nostrConnectorPlugin_1.NostrConnectorPlugin();
nostrConnectorPlugin._category = shogun_1.PluginCategory.Authentication;
this.register(nostrConnectorPlugin);
(0, logger_1.log)("NostrConnector plugin registered");
}
// Register OAuth plugin if enabled
if (config.oauth?.enabled) {
const oauthPlugin = new oauthPlugin_1.OAuthPlugin();
oauthPlugin._category = shogun_1.PluginCategory.Authentication;
// Configure the plugin with provider settings from config
if (config.oauth.providers) {
oauthPlugin.configure({
providers: config.oauth.providers,
});
}
this.register(oauthPlugin);
(0, logger_1.log)("OAuth plugin registered with providers:", config.oauth.providers);
}
}
catch (error) {
(0, logger_1.logError)("Error registering builtin plugins:", error);
}
}
// 🔌 PLUGIN MANAGER 🔌
/**
* Register a new plugin with the SDK
* @param plugin The plugin to register
* @throws Error if a plugin with the same name is already registered
*/
register(plugin) {
if (this.plugins.has(plugin.name)) {
throw new Error(`Plugin with name "${plugin.name}" already registered`);
}
plugin.initialize(this);
this.plugins.set(plugin.name, plugin);
(0, logger_1.log)(`Registered plugin: ${plugin.name}`);
}
/**
* Unregister a plugin from the SDK
* @param pluginName Name of the plugin to unregister
*/
unregister(pluginName) {
const plugin = this.plugins.get(pluginName);
if (!plugin) {
(0, logger_1.log)(`Plugin "${pluginName}" not found, nothing to unregister`);
return;
}
if (plugin.destroy) {
plugin.destroy();
}
this.plugins.delete(pluginName);
(0, logger_1.log)(`Unregistered plugin: ${pluginName}`);
}
/**
* Retrieve a registered plugin by name
* @param name Name of the plugin
* @returns The requested plugin or undefined if not found
* @template T Type of the plugin or its public interface
*/
getPlugin(name) {
return this.plugins.get(name);
}
/**
* Check if a plugin is registered
* @param name Name of the plugin to check
* @returns true if the plugin is registered, false otherwise
*/
hasPlugin(name) {
return this.plugins.has(name);
}
/**
* Get all plugins of a specific category
* @param category Category of plugins to filter
* @returns Array of plugins in the specified category
*/
getPluginsByCategory(category) {
const result = [];
this.plugins.forEach((plugin) => {
if (plugin._category === category) {
result.push(plugin);
}
});
return result;
}
/**
* Get an authentication method plugin by type
* @param type The type of authentication method
* @returns The authentication plugin or undefined if not available
* This is a more modern approach to accessing authentication methods
*/
getAuthenticationMethod(type) {
switch (type) {
case "webauthn":
return this.getPlugin(shogun_1.CorePlugins.WebAuthn);
case "web3":
return this.getPlugin(shogun_1.CorePlugins.Web3);
case "nostr":
return this.getPlugin(shogun_1.CorePlugins.Nostr);
case "password":
default:
return {
login: (username, password) => {
this.login(username, password);
},
signUp: (username, password, confirm) => {
this.signUp(username, password, confirm);
},
};
}
}
// *********************************************************************************************************
// 🔐 ERROR HANDLER 🔐
// *********************************************************************************************************
/**
* Retrieve recent errors logged by the system
* @param count - Number of errors to retrieve (default: 10)
* @returns List of most recent errors
*/
getRecentErrors(count = 10) {
return errorHandler_1.ErrorHandler.getRecentErrors(count);
}
// *********************************************************************************************************
// 🔐 LOGGING 🔐
// *********************************************************************************************************
/**
* Configure logging behavior for the Shogun SDK
* @param {LoggingConfig} config - Logging configuration object containing:
* - level: The minimum log level to display (error, warn, info, debug, trace)
* - logToConsole: Whether to output logs to the console (default: true)
* - customLogger: Optional custom logger implementation
* - logTimestamps: Whether to include timestamps in logs (default: true)
* @returns {void}
* @description Updates the logging configuration for the SDK. Changes take effect immediately
* for all subsequent log operations.
*/
configureLogging(config) {
(0, logger_1.configureLogging)(config);
(0, logger_1.log)("Logging reconfigured with new settings");
}
// *********************************************************************************************************
// 🔐 AUTHENTICATION
// *********************************************************************************************************
/**
* Check if user is logged in
* @returns {boolean} True if user is logged in, false otherwise
* @description Verifies authentication status by checking GunInstance login state
* and presence of authentication credentials in storage
*/
isLoggedIn() {
return this.gundb.isLoggedIn();
}
/**
* Perform user logout
* @description Logs out the current user from GunInstance and emits logout event.
* If user is not authenticated, the logout operation is ignored.
*/
logout() {
try {
if (!this.isLoggedIn()) {
(0, logger_1.log)("Logout ignored: user not authenticated");
return;
}
this.gundb.logout();
this.eventEmitter.emit("auth:logout", {});
(0, logger_1.log)("Logout completed successfully");
}
catch (error) {
errorHandler_1.ErrorHandler.handle(errorHandler_1.ErrorType.AUTHENTICATION, "LOGOUT_FAILED", error instanceof Error ? error.message : "Error during logout", error);
}
}
/**
* Authenticate user with username and password
* @param username - Username
* @param password - User password
* @returns {Promise<AuthResult>} Promise with authentication result
* @description Attempts to log in user with provided credentials.
* Emits login event on success.
*/
async login(username, password) {
(0, logger_1.log)("Login");
try {
(0, logger_1.log)(`Login attempt for user: ${username}`);
// Verify parameters
if (!username || !password) {
return {
success: false,
error: "Username and password are required",
};
}
if (!this.currentAuthMethod) {
this.currentAuthMethod = "password";
(0, logger_1.log)("Authentication method set to default: password");
}
const timeoutDuration = this.config?.timeouts?.login ?? 15000;
const loginPromiseWithTimeout = new Promise(async (resolve) => {
const timeoutId = setTimeout(() => {
resolve({
success: false,
error: "Login timeout",
});
}, timeoutDuration);
try {
const loginResult = await this.gundb.login(username, password);
clearTimeout(timeoutId);
if (!loginResult.success) {
resolve({
success: false,
error: loginResult.error || "Wrong user or password",
});
}
else {
resolve({
success: true,
userPub: loginResult.userPub,
username: loginResult.username,
});
}
}
catch (error) {
clearTimeout(timeoutId);
resolve({
success: false,
error: error.message || "Login error",
});
}
});
const result = await loginPromiseWithTimeout;
if (result.success) {
this.eventEmitter.emit("auth:login", {
userPub: result.userPub ?? "",
});
(0, logger_1.log)(`Current auth method before wallet check: ${this.currentAuthMethod}`);
}
return result;
}
catch (error) {
errorHandler_1.ErrorHandler.handle(errorHandler_1.ErrorType.AUTHENTICATION, "LOGIN_FAILED", error.message ?? "Unknown error during login", error);
return {
success: false,
error: error.message ?? "Unknown error during login",
};
}
}
/**
* Register a new user with provided credentials
* @param username - Username
* @param password - Password
* @param passwordConfirmation - Password confirmation
* @returns {Promise<SignUpResult>} Registration result
* @description Creates a new user account with the provided credentials.
* Validates password requirements and emits signup event on success.
*/
async signUp(username, password, passwordConfirmation) {
(0, logger_1.log)("Sign up");
try {
if (!username || !password) {
return {
success: false,
error: "Username and password are required",
};
}
if (passwordConfirmation !== undefined &&
password !== passwordConfirmation) {
return {
success: false,
error: "Passwords do not match",
};
}
if (password.length < 6) {
return {
success: false,
error: "Password must be at least 6 characters long",
};
}
// Emit a debug event to monitor the flow
this.eventEmitter.emit("debug", {
action: "signup_start",
username,
timestamp: Date.now(),
});
(0, logger_1.log)(`Attempting user registration: ${username}`);
const timeoutDuration = this.config?.timeouts?.signup ?? 30000; // Default timeout of 30 seconds
const signupPromiseWithTimeout = new Promise(async (resolve) => {
const timeoutId = setTimeout(() => {
resolve({
success: false,
error: "Registration timeout",
});
}, timeoutDuration);
try {
const result = await this.gundb.signUp(username, password);
clearTimeout(timeoutId);
if (result.success) {
this.eventEmitter.emit("debug", {
action: "signup_complete",
username,
userPub: result.userPub,
timestamp: Date.now(),
});
this.eventEmitter.emit("auth:signup", {
userPub: result.userPub ?? "",
username,
});
}
else {
this.eventEmitter.emit("debug", {
action: "signup_failed",
username,
error: result.error,
timestamp: Date.now(),
});
}
resolve(result);
}
catch (error) {
clearTimeout(timeoutId);
resolve({
success: false,
error: error.message || "Registration error",
});
}
});
return await signupPromiseWithTimeout;
}
catch (error) {
(0, logger_1.logError)(`Error during registration for user ${username}:`, error);
// Emit a debug event to monitor the flow in case of exception
this.eventEmitter.emit("debug", {
action: "signup_exception",
username,
error: error.message || "Unknown error",
timestamp: Date.now(),
});
return {
success: false,
error: error.message ?? "Unknown error during registration",
};
}
}
// 📢 EVENT EMITTER 📢
/**
* Emits an event through the core's event emitter.
* Plugins should use this method to emit events instead of accessing the private eventEmitter directly.
* @param eventName The name of the event to emit.
* @param data The data to pass with the event.
* @returns {boolean} Indicates if the event had listeners.
*/
emit(eventName, data) {
return this.eventEmitter.emit(eventName, data);
}
/**
* Add an event listener
* @param eventName The name of the event to listen for
* @param listener The callback function to execute when the event is emitted
* @returns {this} Returns this instance for method chaining
*/
on(eventName, listener) {
this.eventEmitter.on(eventName, listener);
return this;
}
/**
* Add a one-time event listener
* @param eventName The name of the event to listen for
* @param listener The callback function to execute when the event is emitted
* @returns {this} Returns this instance for method chaining
*/
once(eventName, listener) {
this.eventEmitter.once(eventName, listener);
return this;
}
/**
* Remove an event listener
* @param eventName The name of the event to stop listening for
* @param listener The callback function to remove
* @returns {this} Returns this instance for method chaining
*/
off(eventName, listener) {
this.eventEmitter.off(eventName, listener);
return this;
}
/**
* Remove all listeners for a specific event or all events
* @param eventName Optional. The name of the event to remove listeners for.
* If not provided, all listeners for all events are removed.
* @returns {this} Returns this instance for method chaining
*/
removeAllListeners(eventName) {
this.eventEmitter.removeAllListeners(eventName);
return this;
}
/**
* Set the current authentication method
* This is used by plugins to indicate which authentication method was used
* @param method The authentication method used
*/
setAuthMethod(method) {
(0, logger_1.log)(`Setting authentication method from '${this.currentAuthMethod}' to '${method}'`);
this.currentAuthMethod = method;
(0, logger_1.log)(`Authentication method successfully set to: ${method}`);
}
/**
* Get the current authentication method
* @returns The current authentication method or undefined if not set
*/
getAuthMethod() {
return this.currentAuthMethod;
}
}
exports.ShogunCore = ShogunCore;