UNPKG

@nuwa-ai/identity-kit-web

Version:

Web extensions for Nuwa Identity Kit

170 lines (169 loc) 5.92 kB
import { DIDAuth, VDRRegistry, IdentityKit, } from '@nuwa-ai/identity-kit'; import { LocalStorageKeyStore } from './keystore/LocalStorageKeyStore'; import { IndexedDBKeyStore } from './keystore/IndexedDBKeyStore'; import { DeepLinkManager } from './deeplink/DeepLinkManager'; /** * IdentityKitWeb – High-level Web SDK for Nuwa Identity Kit * Provides a high-level API for web applications */ export class IdentityKitWeb { constructor(keyManager, deepLinkManager, cadopDomain, appName) { this.keyManager = keyManager; this.deepLinkManager = deepLinkManager; this.cadopDomain = cadopDomain; this.appName = appName; } /** * Initialize the IdentityKitWeb */ static async init(options = {}) { const { appName } = options; const cadopDomain = options.cadopDomain || 'https://test-id.nuwa.dev'; // Resolve Rooch network and RPC URL const network = resolveNetworkFromHost(cadopDomain); const rpcUrl = options.roochRpcUrl || (typeof import.meta !== 'undefined' ? import.meta.env?.VITE_ROOCH_RPC_URL : undefined); // Determine KeyStore based on storage preference (defaults to LocalStorage) let keyStore; switch (options.storage) { case 'indexeddb': keyStore = new IndexedDBKeyStore(); break; case 'memory': keyStore = undefined; // let IdentityKit create an in-memory store break; case 'local': default: keyStore = new LocalStorageKeyStore(); break; } // Bootstrap IdentityEnv which internally registers VDRs and prepares KeyManager const env = await IdentityKit.bootstrap({ method: 'rooch', keyStore, vdrOptions: { network, rpcUrl }, }); // Use provided KeyManager if specified, otherwise take from env const keyManager = options.keyManager || env.keyManager; // Create or use provided DeepLinkManager const deepLinkManager = options.deepLinkManager || new DeepLinkManager({ keyManager, }); return new IdentityKitWeb(keyManager, deepLinkManager, cadopDomain, appName); } /** * Check if the user is connected */ async isConnected() { const keyIds = await this.keyManager.listKeyIds(); return keyIds.length > 0; } /** * Get the current DID */ async getDid() { return await this.keyManager.getDid(); } /** * List all key IDs */ async listKeyIds() { return this.keyManager.listKeyIds(); } /** * Connect to Cadop * This will open a new window with the Cadop add-key page */ async connect() { const idFragment = this.generateIdFragment(); const { url } = await this.deepLinkManager.buildAddKeyUrl({ cadopDomain: this.cadopDomain, idFragment, }); // Open the URL in a new window/tab window.open(url, '_blank'); } /** * Handle the callback from Cadop */ async handleCallback(search) { const result = await this.deepLinkManager.handleCallback(search); if (!result.success) { throw new Error(result.error || 'Unknown error during callback'); } } /** * Sign an operation payload using DIDAuth v1 * @param payload Object containing `operation` and `params` fields (other fields will be added automatically) */ async sign(payload) { const keyIds = await this.keyManager.listKeyIds(); if (keyIds.length === 0) { throw new Error('No keys available for signing'); } const keyId = keyIds[0]; // Delegate signature creation to core DIDAuth util return DIDAuth.v1.createSignature(payload, this.keyManager, keyId); } /** * Verify a signature */ async verify(sig, opts) { const registry = VDRRegistry.getInstance(); return DIDAuth.v1.verifySignature(sig, registry, opts); } /** * Logout (clear all keys) */ async logout() { const store = this.keyManager['store']; // Accessing private field if (store) { await store.clear(); } } /** * Generate a readable idFragment based on the application name. * 1. Slugify the provided appName (keep a-z, 0-9, _ and -) * 2. If slug becomes empty (e.g. non-Latin name), fall back to current hostname * 3. If hostname slug is still empty (edge case), use default 'key' * Always append timestamp to ensure uniqueness. */ generateIdFragment() { const slugify = (input) => input .trim() .toLowerCase() .replace(/\s+/g, '-') .replace(/[^a-z0-9_-]/g, ''); let base = this.appName ? slugify(this.appName) : ''; if (!base) { // Fallback to hostname (without port) const host = typeof window !== 'undefined' ? window.location.hostname : ''; base = slugify(host); } if (!base) { base = 'key'; } return `${base}-${Date.now()}`; } /** * Expose the global VDRRegistry instance */ static get registry() { return VDRRegistry.getInstance(); } } /** * Resolve Rooch network from hostname – mimics logic from cadop-service registry.ts */ function resolveNetworkFromHost(hostname) { let cleanHost = hostname.replace(/^https?:\/\//, ''); if (cleanHost.includes(':')) cleanHost = cleanHost.split(':')[0]; const h = cleanHost.toLowerCase(); if (h.startsWith('test-') || h === 'test-id.nuwa.dev') return 'test'; if (h === 'id.nuwa.dev' || h.endsWith('.id.nuwa.dev')) return 'main'; return 'test'; }