UNPKG

@nfid/identitykit

Version:

A React library for adding wallet connections to dApps.

1,105 lines (1,060 loc) 77.9 kB
'use client'; import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import { createContext, useState, useCallback, useMemo, useEffect, useContext, useRef } from 'react'; import { createContext as createContext$1, useContextSelector, useContext as useContext$1 } from 'use-context-selector'; import { SignerAgent } from '@slide-computer/signer-agent'; import { Principal } from '@dfinity/principal'; import { AnonymousIdentity, HttpAgent } from '@dfinity/agent'; import { Ed25519KeyIdentity, ECDSAKeyIdentity, isDelegationValid, PartialIdentity, PartialDelegationIdentity, DelegationIdentity, DelegationChain, Delegation } from '@dfinity/identity'; import { IdbStorage, getIdentity, getDelegationChain, setIdentity, setDelegationChain, removeIdentity, removeDelegationChain } from '@slide-computer/signer-storage'; import { SubAccount, LedgerCanister, AccountIdentifier } from '@dfinity/ledger-icp'; import { toBase64, fromBase64, Signer } from '@slide-computer/signer'; import { PostMessageTransport } from '@slide-computer/signer-web'; import { BrowserExtensionTransport } from '@slide-computer/signer-extension'; import { AuthClientTransport } from '@slide-computer/signer-transport-auth-client'; import { StoicTransport } from '@slide-computer/signer-transport-stoic'; import { useAsyncMemo } from 'use-async-memo'; import clsx from 'clsx'; import { MenuItems, Menu as Menu$1, MenuButton } from '@headlessui/react'; import colors from 'tailwindcss/colors'; import * as RadixTooltip from '@radix-ui/react-tooltip'; import { TooltipProvider } from '@radix-ui/react-tooltip'; import * as Dialog from '@radix-ui/react-dialog'; var IdentityKitTheme; (function (IdentityKitTheme) { IdentityKitTheme["LIGHT"] = "light"; IdentityKitTheme["DARK"] = "dark"; IdentityKitTheme["SYSTEM"] = "system"; })(IdentityKitTheme || (IdentityKitTheme = {})); const DEFAULT_SIZES = { width: 450, height: 640, }; const ThemeContext = createContext(IdentityKitTheme.SYSTEM); const Context = createContext$1(null); class Agent { signerAgentStrategy; agentStrategy; delegation; constructor(signerAgentStrategy, agentStrategy, delegation) { this.signerAgentStrategy = signerAgentStrategy; this.agentStrategy = agentStrategy; this.delegation = delegation; } static async create({ delegation, signerAgent, agent }) { return new Agent(signerAgent, agent, delegation); } async call(...params) { const delegationTargets = this.delegation?.targets; const strategy = this.delegation && (!delegationTargets?.length || delegationTargets?.find((t) => t.compareTo(Principal.from(params[0])) === "eq")) ? this.agentStrategy : this.signerAgentStrategy; return strategy.call(...params); } async query(...params) { const delegationTargets = this.delegation?.targets; const strategy = this.delegation && (!delegationTargets?.length || delegationTargets?.find((t) => t.compareTo(Principal.from(params[0])) === "eq")) ? this.agentStrategy : this.signerAgentStrategy; return strategy.query(...params); } get rootKey() { return this.agentStrategy.rootKey; } async fetchRootKey() { return this.agentStrategy.fetchRootKey(); } async getPrincipal() { return this.agentStrategy.getPrincipal(); } async status() { return this.agentStrategy.status(); } async readState(...params) { const delegationTargets = this.delegation?.targets; const strategy = this.delegation && (!delegationTargets?.length || delegationTargets?.find((t) => t.compareTo(Principal.from(params[0])) === "eq")) ? this.agentStrategy : this.signerAgentStrategy; return strategy.readState(...params); } async createReadStateRequest(...params) { return this.agentStrategy.createReadStateRequest?.(...params); } } const DEFAULT_MAX_TIME_TO_LIVE = BigInt(1.8e12); // 30 min const DEFAULT_IDLE_TIMEOUT = 14_400_000; // 4 hours const TIMEOUT_MAX_DELAY = 2147483647; /** * Detects if the `timeout` ms is over, and calls `onTimeout` and registered callbacks. * To override these defaults, you can pass an `onTimeout` callback, or configure a custom `timeout` in milliseconds */ class TimeoutManager { callbacks = []; timeout; timeoutID = undefined; /** * @param options {@link IdleManagerOptions} */ constructor(options) { const { onTimeout, timeout } = options || {}; this.callbacks = onTimeout ? [onTimeout] : []; this.timeout = timeout > TIMEOUT_MAX_DELAY ? TIMEOUT_MAX_DELAY : timeout; const _resetTimer = this._resetTimer.bind(this); window.addEventListener("load", _resetTimer, true); _resetTimer(); } /** * @param {TimeoutCB} callback function to be called on timeout */ registerCallback(callback) { this.callbacks.push(callback); } /** * Cleans up the timeout manager and its listeners */ exit() { clearTimeout(this.timeoutID); window.removeEventListener("load", this._resetTimer, true); } /** * Resets the timeouts during cleanup */ _resetTimer() { const exit = this.exit.bind(this); window.clearTimeout(this.timeoutID); this.timeoutID = window.setTimeout(() => { exit(); this.callbacks.forEach((cb) => cb()); }, this.timeout); } } const events = ["mousedown", "mousemove", "keydown", "touchstart", "wheel"]; /** * Detects if the user has been idle for a duration of `idleTimeout` ms, and calls `onIdle` and registered callbacks. * By default, the IdleManager will log a user out after 10 minutes of inactivity. * To override these defaults, you can pass an `onIdle` callback, or configure a custom `idleTimeout` in milliseconds */ class IdleManager extends TimeoutManager { constructor(options = {}) { super({ timeout: options.idleTimeout || DEFAULT_IDLE_TIMEOUT, onTimeout: options.onIdle }); const _resetTimer = this._resetTimer.bind(this); events.forEach(function (name) { document.addEventListener(name, _resetTimer, true); }); const debounce = (func, wait) => { let timeout; return (...args) => { // eslint-disable-next-line @typescript-eslint/no-this-alias const context = this; const later = function () { timeout = undefined; func.apply(context, args); }; clearTimeout(timeout); timeout = window.setTimeout(later, wait); }; }; if (options?.captureScroll) { // debounce scroll events const scroll = debounce(_resetTimer, options?.scrollDebounce ?? 100); window.addEventListener("scroll", scroll, true); } } } const STORAGE_KEY = "client"; const STORAGE_CONNECTED_OWNER_KEY = "connected-owner"; const STORAGE_CONNECTED_SUBACCOUNT_KEY = "connected-subaccount"; class SignerClient { options; idleManager; storage = new IdbStorage(); connectedUser; constructor(options) { this.options = options; if (!options?.idleOptions?.disableIdle) { this.idleManager = new IdleManager(options.idleOptions); this.registerDefaultIdleCallback(); } if (options.storage) this.storage = options.storage; } registerDefaultIdleCallback() { /** * Default behavior is to clear stored identity and reload the page. * By either setting the disableDefaultIdleCallback flag or passing in a custom idle callback, we will ignore this config */ if (!this.options?.idleOptions?.disableDefaultIdleCallback) { this.idleManager?.registerCallback(async () => { await this.logout(); }); } } async logout(options) { await this.setConnectedUserToStorage(undefined); this.idleManager?.exit(); this.idleManager = undefined; await this.options.onLogout?.(); if (options?.returnTo) { try { window.history.pushState({}, "", options.returnTo); } catch (e) { window.location.href = options.returnTo; } } } async setConnectedUser(user) { if (!user) this.connectedUser = undefined; else { let subAccount; if (user.subAccount) { const subAccountOrError = SubAccount.fromBytes(new Uint8Array(user.subAccount)); if (typeof subAccountOrError === typeof Error) { throw subAccount; } subAccount = subAccountOrError; } this.connectedUser = { principal: Principal.from(user.owner), subAccount, }; } } async setConnectedUserToStorage(user) { if (!user) { await this.storage.remove(STORAGE_CONNECTED_OWNER_KEY); await this.storage.remove(STORAGE_CONNECTED_SUBACCOUNT_KEY); localStorage.removeItem("connected"); this.setConnectedUser(undefined); return; } await this.storage.set(STORAGE_CONNECTED_OWNER_KEY, user.owner); localStorage.setItem("connected", "1"); if (user.subAccount) await this.storage.set(STORAGE_CONNECTED_SUBACCOUNT_KEY, new TextDecoder().decode(user.subAccount)); this.setConnectedUser({ owner: user.owner, subAccount: user.subAccount, }); } // sync method to check if it's needed to check authorization reading from async storage static shouldCheckIsUserConnected() { return !!localStorage.getItem("connected"); } async getConnectedUserFromStorage() { const owner = await this.storage.get(STORAGE_CONNECTED_OWNER_KEY); if (!owner) return; const subAccount = await this.storage.get(STORAGE_CONNECTED_SUBACCOUNT_KEY); return { owner: owner.toString(), subAccount: subAccount ? new TextEncoder().encode(subAccount.toString()).buffer : undefined, }; } get crypto() { return this.options.crypto ?? globalThis.crypto; } } const ED25519_KEY_LABEL = "Ed25519"; var DelegationType; (function (DelegationType) { DelegationType["ACCOUNT"] = "ACCOUNT"; DelegationType["RELYING_PARTY"] = "RELYING_PARTY"; })(DelegationType || (DelegationType = {})); const NANOS_IN_MILLIS = BigInt(1000000); class DelegationSignerClient extends SignerClient { identity; baseIdentity; targets; maxTimeToLive; expirationManager; constructor(options, identity, baseIdentity, targets, maxTimeToLive = BigInt(DEFAULT_MAX_TIME_TO_LIVE)) { // TODO for delegation use delegation expiration as idle timeout super(options); this.identity = identity; this.baseIdentity = baseIdentity; this.targets = targets; this.maxTimeToLive = maxTimeToLive; } static async create(options) { const storage = options.storage ?? new IdbStorage(); let baseIdentity = options.identity; let identity = new AnonymousIdentity(); if (this.shouldCheckIsUserConnected() && !baseIdentity) { baseIdentity = await getIdentity(STORAGE_KEY, storage); } if (!baseIdentity) { const createdBaseIdentity = await (!options?.keyType || options?.keyType === ED25519_KEY_LABEL ? Ed25519KeyIdentity.generate(crypto.getRandomValues(new Uint8Array(32))) : ECDSAKeyIdentity.generate()); baseIdentity = createdBaseIdentity; } if (this.shouldCheckIsUserConnected()) { const delegationChain = await getDelegationChain(STORAGE_KEY, storage); const delegationValid = baseIdentity && delegationChain && isDelegationValid(delegationChain); identity = delegationValid ? DelegationSignerClient.createIdentity(baseIdentity, delegationChain) : new AnonymousIdentity(); const signerClient = new DelegationSignerClient(options, identity, baseIdentity, options.targets, options.maxTimeToLive); if (delegationValid) { signerClient.initExpirationManager(delegationChain); } const storageConnectedUser = await signerClient.getConnectedUserFromStorage(); await signerClient.setConnectedUser(storageConnectedUser); return signerClient; } const signerClient = new DelegationSignerClient(options, identity, baseIdentity, options.targets, options.maxTimeToLive); return signerClient; } static createIdentity(baseIdentity, delegationChain) { if (baseIdentity instanceof PartialIdentity) { return PartialDelegationIdentity.fromDelegation(baseIdentity, delegationChain); } return DelegationIdentity.fromDelegation(baseIdentity, delegationChain); } async login() { const params = this.options.derivationOrigin ? { icrc95DerivationOrigin: this.options.derivationOrigin, } : {}; const delegationChainResponse = await this.options.signer.sendRequest({ id: this.crypto.randomUUID(), jsonrpc: "2.0", method: "icrc34_delegation", params: { ...params, publicKey: toBase64(this.baseIdentity.getPublicKey().toDer()), targets: this.targets, maxTimeToLive: this.maxTimeToLive === undefined ? undefined : String(this.maxTimeToLive), }, }); if ("error" in delegationChainResponse) { throw Error(delegationChainResponse.error.message); } const delegationChain = DelegationChain.fromDelegations(delegationChainResponse.result.signerDelegation.map((delegation) => { return { delegation: new Delegation(fromBase64(delegation.delegation.pubkey), BigInt(delegation.delegation.expiration), delegation.delegation.targets?.map((principal) => Principal.fromText(principal))), signature: fromBase64(delegation.signature), }; }), fromBase64(delegationChainResponse.result.publicKey)); if (this.baseIdentity instanceof Ed25519KeyIdentity || this.baseIdentity instanceof ECDSAKeyIdentity) { await setIdentity(STORAGE_KEY, this.baseIdentity, this.storage); } await setDelegationChain(STORAGE_KEY, delegationChain, this.storage); this.identity = DelegationSignerClient.createIdentity(this.baseIdentity, delegationChain); await this.setConnectedUserToStorage({ owner: this.identity.getPrincipal().toString() }); if (!this.options?.idleOptions?.disableIdle && !this.idleManager) { this.idleManager = new IdleManager(this.options.idleOptions); this.registerDefaultIdleCallback(); } return this.initExpirationManager(delegationChain); } initExpirationManager(delegationChain) { if (!this.expirationManager) { const delegationExpirationInMillis = Number(delegationChain.delegations.reduce((acc, value) => { const bigIntValue = BigInt(value.delegation.expiration) / NANOS_IN_MILLIS; return bigIntValue > acc ? bigIntValue : acc; }, BigInt(delegationChain.delegations[0].delegation.expiration) / NANOS_IN_MILLIS)) - Date.now(); this.expirationManager = new TimeoutManager({ timeout: delegationExpirationInMillis }); this.expirationManager?.registerCallback(async () => { await this.logout(); }); } } async logout(options) { await Promise.all([ removeIdentity(STORAGE_KEY, this.storage), removeDelegationChain(STORAGE_KEY, this.storage), ]); this.identity = new AnonymousIdentity(); return super.logout(options); } getIdentity() { return this.identity; } async getDelegationType() { if (!this.connectedUser) throw new Error("Not authorized"); const delegationChain = await getDelegationChain(STORAGE_KEY, this.storage); if (!delegationChain) throw new Error("Not authorized"); return delegationChain.delegations[0].delegation.targets?.length ? DelegationType.ACCOUNT : DelegationType.RELYING_PARTY; } async getDelegation() { const chain = await getDelegationChain(STORAGE_KEY, this.storage); return chain?.delegations[0]; } } class AccountsSignerClient extends SignerClient { static async create(options) { const signerClient = new AccountsSignerClient(options); if (SignerClient.shouldCheckIsUserConnected()) { const storageConnectedUser = await signerClient.getConnectedUserFromStorage(); await signerClient.setConnectedUser(storageConnectedUser); } return signerClient; } async login() { // get and transform accounts from signer const params = this.options.derivationOrigin ? { params: { icrc95DerivationOrigin: this.options.derivationOrigin, }, } : {}; const accountsResponse = await this.options.signer.sendRequest({ method: "icrc27_accounts", id: this.crypto.randomUUID(), jsonrpc: "2.0", ...params, }); if ("error" in accountsResponse) { throw Error(accountsResponse.error.message); } const accounts = accountsResponse.result.accounts.map(({ owner, subaccount }) => { return { owner: Principal.fromText(owner), subaccount: subaccount === undefined ? undefined : fromBase64(subaccount), }; }); await this.setAccounts(accounts); const account = accounts[0]; if (!account.subaccount) { if (!this.options?.idleOptions?.disableIdle && !this.idleManager) { this.idleManager = new IdleManager(this.options.idleOptions); this.registerDefaultIdleCallback(); } await this.setConnectedUserToStorage({ owner: account.owner.toString() }); return; } await this.setConnectedUserToStorage({ owner: account.owner.toString(), subAccount: account.subaccount, }); if (!this.options?.idleOptions?.disableIdle && !this.idleManager) { this.idleManager = new IdleManager(this.options.idleOptions); this.registerDefaultIdleCallback(); } } async logout(options) { await this.storage.remove(`accounts-${STORAGE_KEY}`); return super.logout(options); } async setAccounts(accounts) { return this.storage.set(`accounts-${STORAGE_KEY}`, JSON.stringify(accounts.map((acc) => ({ owner: acc.owner.toString(), subaccount: acc.subaccount && new TextDecoder().decode(acc.subaccount), })))); } async getAccounts() { const storageData = await this.storage.get(`accounts-${STORAGE_KEY}`); if (!storageData || typeof storageData !== "string") return; return JSON.parse(storageData).map(({ owner, subaccount }) => { let subAccount; if (subaccount) { const subAccountOrError = SubAccount.fromBytes(new Uint8Array(new TextEncoder().encode(subaccount))); if (typeof subAccountOrError === typeof Error) { throw subAccount; } subAccount = subAccountOrError; } return { principal: Principal.from(owner), subAccount, }; }); } } const IdentityKitAuthType = { DELEGATION: "DELEGATION", ACCOUNTS: "ACCOUNTS", }; class IdentityKit { signerClient; constructor(signerClient) { this.signerClient = signerClient; } async getIcpBalance() { const connectedUser = await this.signerClient.connectedUser; if (!connectedUser) throw new Error("Not authenticated"); const balance = (await LedgerCanister.create().accountBalance({ accountIdentifier: AccountIdentifier.fromPrincipal({ principal: connectedUser.principal, subAccount: connectedUser.subAccount, }), certified: false, })).toString(); return Number(balance) / 10 ** 8; } static async create({ signerClientOptions, authType }) { if (authType === IdentityKitAuthType.DELEGATION) { const signerClient = await DelegationSignerClient.create(signerClientOptions); return new this(signerClient); } else { const signerClient = await AccountsSignerClient.create(signerClientOptions); return new this(signerClient); } } } var TransportType; (function (TransportType) { TransportType[TransportType["NEW_TAB"] = 0] = "NEW_TAB"; TransportType[TransportType["EXTENSION"] = 1] = "EXTENSION"; TransportType[TransportType["INTERNET_IDENTITY"] = 2] = "INTERNET_IDENTITY"; TransportType[TransportType["STOIC"] = 3] = "STOIC"; })(TransportType || (TransportType = {})); const NFIDW = { id: "NFIDW", description: "Quickly sign in or create an anonymous, self-sovereign wallet with your email address or passkey.", providerUrl: "https://nfid.one/rpc", transportType: TransportType.NEW_TAB, label: "NFID Wallet", icon: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS45MDIyIDMuMTU2MjlDMTYuNzcxNCAzLjA2MzYzIDE3LjQwMDggMi4yODM5OCAxNy4zMDgxIDEuNDE0ODlDMTcuMjE1NSAwLjU0NTgwOSAxNi40MzU4IC0wLjA4MzYwOTkgMTUuNTY2NyAwLjAwOTA0OTQ2TDEyLjMzNDUgMC4zNTM2MzNDMTAuNjc4NiAwLjUzMDE2NSA5LjM1MDQ2IDAuNjcxNzUxIDguMjcxOCAwLjg2NzMwNkM3LjE2MDYgMS4wNjg3NiA2LjIwODI3IDEuMzQ0MTQgNS4zMjM3OCAxLjgzOTgxQzMuODg3NzMgMi42NDQ1NyAyLjY5NzE5IDMuODIzODUgMS44Nzk0NCA1LjI1MTk2QzEuMzc1NjggNi4xMzE3MyAxLjA5MjA0IDcuMDgxMTIgMC44ODExODUgOC4xODk2MUMwLjY3NjUzNSA5LjI2NTQ5IDAuNTIzODkzIDEwLjU5MSAwLjMzMzYyMSAxMi4yNDMyTDAuMzI1NDI0IDEyLjMxNDRMMC4wMDgwNjAwNiAxNS40NzA0Qy0wLjA3OTM4NzUgMTYuMzQgMC41NTQ3MjYgMTcuMTE1OSAxLjQyNDM5IDE3LjIwMzRDMi4yOTQwNiAxNy4yOTA4IDMuMDY5OTUgMTYuNjU2NyAzLjE1NzQgMTUuNzg3MUwzLjQ3MjQ4IDEyLjY1MzhDMy42NzA1NCAxMC45MzQgMy44MTA2NiA5LjcyNzMgMy45OTA2NiA4Ljc4MTAzQzQuMTY3MzEgNy44NTIzNSA0LjM2NDQ5IDcuMjgxODEgNC42MjYyNiA2LjgyNDY3QzUuMTU5MDEgNS44OTQyNyA1LjkzNDg3IDUuMTI1NTkgNi44NzEyMSA0LjYwMDg2QzcuMzMxNTEgNC4zNDI5MSA3LjkwNDg2IDQuMTUwNTEgOC44MzY0NiAzLjk4MTYxQzkuNzg3NjYgMy44MDkxNiAxMS4wMDA1IDMuNjc4ODggMTIuNzI5OSAzLjQ5NDVMMTUuOTAyMiAzLjE1NjI5Wk00Ni42OTE5IDEuNDE0ODlDNDYuNTk5MiAyLjI4Mzk4IDQ3LjIyODYgMy4wNjM2MyA0OC4wOTc4IDMuMTU2MjlMNTEuMjcwMSAzLjQ5NDVDNTIuOTk5NSAzLjY3ODg4IDU0LjIxMjMgMy44MDkxNiA1NS4xNjM1IDMuOTgxNjFDNTYuMDk1MSA0LjE1MDUxIDU2LjY2ODUgNC4zNDI5MSA1Ny4xMjg4IDQuNjAwODZDNTguMDY1MSA1LjEyNTU5IDU4Ljg0MSA1Ljg5NDI3IDU5LjM3MzcgNi44MjQ2N0M1OS42MzU1IDcuMjgxODEgNTkuODMyNyA3Ljg1MjM1IDYwLjAwOTMgOC43ODEwM0M2MC4xODkzIDkuNzI3MyA2MC4zMjk1IDEwLjkzNCA2MC41Mjc1IDEyLjY1MzhMNjAuODQyNiAxNS43ODcxQzYwLjkzIDE2LjY1NjcgNjEuNzA1OSAxNy4yOTA4IDYyLjU3NTYgMTcuMjAzNEM2My40NDUzIDE3LjExNTkgNjQuMDc5NCAxNi4zNCA2My45OTE5IDE1LjQ3MDRMNjMuNjc0NiAxMi4zMTQ0TDYzLjY2NjQgMTIuMjQzMkM2My40NzYxIDEwLjU5MSA2My4zMjM1IDkuMjY1NDkgNjMuMTE4OCA4LjE4OTYxQzYyLjkwOCA3LjA4MTEyIDYyLjYyNDMgNi4xMzE3MyA2Mi4xMjA2IDUuMjUxOTZDNjEuMzAyOCAzLjgyMzg1IDYwLjExMjMgMi42NDQ1NyA1OC42NzYyIDEuODM5ODFDNTcuNzkxNyAxLjM0NDE0IDU2LjgzOTQgMS4wNjg3NiA1NS43MjgyIDAuODY3MzA2QzU0LjY0OTUgMC42NzE3NTEgNTMuMzIxNCAwLjUzMDE2NSA1MS42NjU1IDAuMzUzNjMzTDQ4LjQzMzMgMC4wMDkwNDk0NkM0Ny41NjQyIC0wLjA4MzYwOTkgNDYuNzg0NSAwLjU0NTgwOSA0Ni42OTE5IDEuNDE0ODlaTTQ2LjY5MTkgNjIuNTg1MUM0Ni41OTkyIDYxLjcxNiA0Ny4yMjg2IDYwLjkzNjQgNDguMDk3OCA2MC44NDM3TDUxLjI3MDEgNjAuNTA1NUM1Mi45OTk1IDYwLjMyMTEgNTQuMjEyMyA2MC4xOTA4IDU1LjE2MzUgNjAuMDE4NEM1Ni4wOTUxIDU5Ljg0OTUgNTYuNjY4NSA1OS42NTcxIDU3LjEyODggNTkuMzk5MUM1OC4wNjUxIDU4Ljg3NDQgNTguODQxIDU4LjEwNTcgNTkuMzczNyA1Ny4xNzUzQzU5LjYzNTUgNTYuNzE4MiA1OS44MzI3IDU2LjE0NzYgNjAuMDA5MyA1NS4yMTlDNjAuMTg5MyA1NC4yNzI3IDYwLjMyOTUgNTMuMDY2IDYwLjUyNzUgNTEuMzQ2Mkw2MC44NDI2IDQ4LjIxMjlDNjAuOTMgNDcuMzQzMyA2MS43MDU5IDQ2LjcwOTIgNjIuNTc1NiA0Ni43OTY2QzYzLjQ0NTMgNDYuODg0MSA2NC4wNzk0IDQ3LjY2IDYzLjk5MTkgNDguNTI5Nkw2My42NzQ2IDUxLjY4NTZMNjMuNjY2NCA1MS43NTY4QzYzLjQ3NjEgNTMuNDA5IDYzLjMyMzUgNTQuNzM0NSA2My4xMTg4IDU1LjgxMDRDNjIuOTA4IDU2LjkxODkgNjIuNjI0MyA1Ny44NjgzIDYyLjEyMDYgNTguNzQ4QzYxLjMwMjggNjAuMTc2MiA2MC4xMTIzIDYxLjM1NTQgNTguNjc2MiA2Mi4xNjAyQzU3Ljc5MTcgNjIuNjU1OSA1Ni44Mzk0IDYyLjkzMTIgNTUuNzI4MiA2My4xMzI3QzU0LjY0OTYgNjMuMzI4MiA1My4zMjE2IDYzLjQ2OTggNTEuNjY1OCA2My42NDYzTDUxLjY2NTYgNjMuNjQ2M0w1MS42NjU1IDYzLjY0NjRMNTEuNjY1NSA2My42NDY0TDQ4LjQzMzMgNjMuOTkxQzQ3LjU2NDIgNjQuMDgzNiA0Ni43ODQ1IDYzLjQ1NDIgNDYuNjkxOSA2Mi41ODUxWk0xNy4zMDgxIDYyLjU4NTZDMTcuNDAwOCA2MS43MTY1IDE2Ljc3MTQgNjAuOTM2OSAxNS45MDIyIDYwLjg0NDJMMTIuNzI5OSA2MC41MDZDMTEuMDAwNSA2MC4zMjE2IDkuNzg3NjYgNjAuMTkxMyA4LjgzNjQ2IDYwLjAxODlDNy45MDQ4NiA1OS44NSA3LjMzMTUxIDU5LjY1NzYgNi44NzEyMSA1OS4zOTk2QzUuOTM0ODcgNTguODc0OSA1LjE1OTAxIDU4LjEwNjIgNC42MjYyNiA1Ny4xNzU4QzQuMzY0NDkgNTYuNzE4NyA0LjE2NzMxIDU2LjE0ODEgMy45OTA2NiA1NS4yMTk1QzMuODEwNjYgNTQuMjczMiAzLjY3MDU0IDUzLjA2NjUgMy40NzI0OCA1MS4zNDY3TDMuMTU3NCA0OC4yMTM0QzMuMDY5OTUgNDcuMzQzOCAyLjI5NDA2IDQ2LjcwOTcgMS40MjQzOSA0Ni43OTcxQzAuNTU0NzI2IDQ2Ljg4NDYgLTAuMDc5Mzg3NSA0Ny42NjA0IDAuMDA4MDYwMDYgNDguNTMwMUwwLjMyNTQyNCA1MS42ODYxTDAuMzMzNjIxIDUxLjc1NzNDMC41MjM4OTMgNTMuNDA5NSAwLjY3NjUzNSA1NC43MzUgMC44ODExODUgNTUuODEwOUMxLjA5MjA0IDU2LjkxOTQgMS4zNzU2OCA1Ny44Njg4IDEuODc5NDQgNTguNzQ4NUMyLjY5NzE5IDYwLjE3NjYgMy44ODc3MyA2MS4zNTU5IDUuMzIzNzggNjIuMTYwN0M2LjIwODI3IDYyLjY1NjMgNy4xNjA2IDYyLjkzMTcgOC4yNzE4IDYzLjEzMzJDOS4zNTA0NCA2My4zMjg3IDEwLjY3ODYgNjMuNDcwMyAxMi4zMzQ1IDYzLjY0NjhMMTIuMzM0NSA2My42NDY5TDE1LjU2NjcgNjMuOTkxNEMxNi40MzU4IDY0LjA4NDEgMTcuMjE1NSA2My40NTQ3IDE3LjMwODEgNjIuNTg1NlpNMTYuNDUxMiAxOS43OTU0QzE1LjY5NTUgMTkuNzk1NCAxNS4wODI4IDIwLjQwODEgMTUuMDgyOCAyMS4xNjM4VjQzLjI5NzJDMTUuMDgyOCA0NC4wNTMgMTUuNjk1NSA0NC42NjU2IDE2LjQ1MTIgNDQuNjY1NkgyMC43NDk0QzIxLjUwNTIgNDQuNjY1NiAyMi4xMTc4IDQ0LjA1MyAyMi4xMTc4IDQzLjI5NzJWMjEuMTYzOEMyMi4xMTc4IDIwLjQwODEgMjEuNTA1MiAxOS43OTU0IDIwLjc0OTQgMTkuNzk1NEgxNi40NTEyWk0yOC4xMDgyIDE5Ljc5NTRDMjcuMzUyNSAxOS43OTU0IDI2LjczOTggMjAuNDA4MSAyNi43Mzk4IDIxLjE2MzhWNDMuMjk3MkMyNi43Mzk4IDQ0LjA1MyAyNy4zNTI1IDQ0LjY2NTYgMjguMTA4MiA0NC42NjU2SDM4LjUwMDRDNDEuMjI0NCA0NC42NjU2IDQzLjY0MDUgNDQuMTU2NCA0NS43NDg3IDQzLjEzNzlDNDcuODU2OCA0Mi4xMTk0IDQ5LjQ5MTIgNDAuNjc0NiA1MC42NTE5IDM4LjgwMzRDNTEuODEyNSAzNi45MzIyIDUyLjM5MjkgMzQuNzQxMiA1Mi4zOTI5IDMyLjIzMDVDNTIuMzkyOSAyOS43MTk4IDUxLjgxMjUgMjcuNTI4OSA1MC42NTE5IDI1LjY1NzdDNDkuNDkxMiAyMy43ODY1IDQ3Ljg1NjggMjIuMzQxNyA0NS43NDg3IDIxLjMyMzJDNDMuNjQwNSAyMC4zMDQ3IDQxLjIyNDQgMTkuNzk1NCAzOC41MDA0IDE5Ljc5NTRIMjguMTA4MlpNNDMuMzMyNiAzNy4yNDAxQzQyLjA1MzUgMzguNDQ4MSA0MC4zNDggMzkuMDUyMSAzOC4yMTYyIDM5LjA1MjFIMzQuMTg1NEMzMy45NTg3IDM5LjA1MjEgMzMuNzc0OSAzOC44NjgzIDMzLjc3NDkgMzguNjQxNlYyNS44MTk1QzMzLjc3NDkgMjUuNTkyOCAzMy45NTg3IDI1LjQwOSAzNC4xODU0IDI1LjQwOUgzOC4yMTYyQzQwLjM0OCAyNS40MDkgNDIuMDUzNSAyNi4wMTMgNDMuMzMyNiAyNy4yMjFDNDQuNjM1NCAyOC40Mjg5IDQ1LjI4NjggMzAuMDk4OCA0NS4yODY4IDMyLjIzMDVDNDUuMjg2OCAzNC4zNjIzIDQ0LjYzNTQgMzYuMDMyMSA0My4zMzI2IDM3LjI0MDFaIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfODAzODBfNTQpIi8+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfODAzODBfNTQiIHgxPSIwLjc2NTgxMiIgeTE9Ii0yLjcwNDk2ZS0wNyIgeDI9IjYzLjU0MTciIHkyPSI2NC4yNzkzIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiMwMEE2OTUiLz4KPHN0b3Agb2Zmc2V0PSIwLjUiIHN0b3AtY29sb3I9IiMwMTg0NzciLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMDA2RjY0Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==", }; ({ transportType: TransportType.NEW_TAB}); const InternetIdentity = { id: "InternetIdentity", providerUrl: "", transportType: TransportType.INTERNET_IDENTITY, label: "Internet Identity", icon: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI0LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAzNTguOCAxNzkuOCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzU4LjggMTc5Ljg7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbDp1cmwoI1NWR0lEXzFfKTt9Cgkuc3Qxe2ZpbGw6dXJsKCNTVkdJRF8yXyk7fQoJLnN0MntmaWxsLXJ1bGU6ZXZlbm9kZDtjbGlwLXJ1bGU6ZXZlbm9kZDtmaWxsOiMyOUFCRTI7fQo8L3N0eWxlPgo8bGluZWFyR3JhZGllbnQgaWQ9IlNWR0lEXzFfIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjIyNC43ODUzIiB5MT0iMjU3Ljc1MzYiIHgyPSIzNDguMDY2MyIgeTI9IjEzMy40NTgxIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEgMCAwIC0xIDAgMjcyKSI+Cgk8c3RvcCAgb2Zmc2V0PSIwLjIxIiBzdHlsZT0ic3RvcC1jb2xvcjojRjE1QTI0Ii8+Cgk8c3RvcCAgb2Zmc2V0PSIwLjY4NDEiIHN0eWxlPSJzdG9wLWNvbG9yOiNGQkIwM0IiLz4KPC9saW5lYXJHcmFkaWVudD4KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI3MS42LDBjLTIwLDAtNDEuOSwxMC45LTY1LDMyLjRjLTEwLjksMTAuMS0yMC41LDIxLjEtMjcuNSwyOS44YzAsMCwxMS4yLDEyLjksMjMuNSwyNi44CgljNi43LTguNCwxNi4yLTE5LjgsMjcuMy0zMC4xYzIwLjUtMTkuMiwzMy45LTIzLjEsNDEuNi0yMy4xYzI4LjgsMCw1Mi4yLDI0LjIsNTIuMiw1NC4xYzAsMjkuNi0yMy40LDUzLjgtNTIuMiw1NC4xCgljLTEuNCwwLTMtMC4yLTUtMC42YzguNCwzLjksMTcuNSw2LjcsMjYsNi43YzUyLjgsMCw2My4yLTM2LjUsNjMuOC0zOS4xYzEuNS02LjcsMi40LTEzLjcsMi40LTIwLjlDMzU4LjYsNDAuNCwzMTkuNiwwLDI3MS42LDB6Ii8+CjxsaW5lYXJHcmFkaWVudCBpZD0iU1ZHSURfMl8iIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMTMzLjk0NjEiIHkxPSIxMDYuNDI2MiIgeDI9IjEwLjY2NTMiIHkyPSIyMzAuNzIxNSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxIDAgMCAtMSAwIDI3MikiPgoJPHN0b3AgIG9mZnNldD0iMC4yMSIgc3R5bGU9InN0b3AtY29sb3I6I0VEMUU3OSIvPgoJPHN0b3AgIG9mZnNldD0iMC44OTI5IiBzdHlsZT0ic3RvcC1jb2xvcjojNTIyNzg1Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik04Ny4xLDE3OS44YzIwLDAsNDEuOS0xMC45LDY1LTMyLjRjMTAuOS0xMC4xLDIwLjUtMjEuMSwyNy41LTI5LjhjMCwwLTExLjItMTIuOS0yMy41LTI2LjgKCWMtNi43LDguNC0xNi4yLDE5LjgtMjcuMywzMC4xYy0yMC41LDE5LTM0LDIzLjEtNDEuNiwyMy4xYy0yOC44LDAtNTIuMi0yNC4yLTUyLjItNTQuMWMwLTI5LjYsMjMuNC01My44LDUyLjItNTQuMQoJYzEuNCwwLDMsMC4yLDUsMC42Yy04LjQtMy45LTE3LjUtNi43LTI2LTYuN0MxMy40LDI5LjYsMyw2Ni4xLDIuNCw2OC44QzAuOSw3NS41LDAsODIuNSwwLDg5LjdDMCwxMzkuNCwzOSwxNzkuOCw4Ny4xLDE3OS44eiIvPgo8cGF0aCBjbGFzcz0ic3QyIiBkPSJNMTI3LjMsNTkuN2MtNS44LTUuNi0zNC0yOC41LTYxLTI5LjNDMTguMSwyOS4yLDQsNjQuMiwyLjcsNjguN0MxMiwyOS41LDQ2LjQsMC4yLDg3LjIsMAoJYzMzLjMsMCw2NywzMi43LDkxLjksNjIuMmMwLDAsMC4xLTAuMSwwLjEtMC4xYzAsMCwxMS4yLDEyLjksMjMuNSwyNi44YzAsMCwxNCwxNi41LDI4LjgsMzFjNS44LDUuNiwzMy45LDI4LjIsNjAuOSwyOQoJYzQ5LjUsMS40LDYzLjItMzUuNiw2My45LTM4LjRjLTkuMSwzOS41LTQzLjYsNjguOS04NC42LDY5LjFjLTMzLjMsMC02Ny0zMi43LTkyLTYyLjJjMCwwLjEtMC4xLDAuMS0wLjEsMC4yCgljMCwwLTExLjItMTIuOS0yMy41LTI2LjhDMTU2LjIsOTAuOCwxNDIuMiw3NC4yLDEyNy4zLDU5Ljd6IE0yLjcsNjkuMWMwLTAuMSwwLTAuMiwwLjEtMC4zQzIuNyw2OC45LDIuNyw2OSwyLjcsNjkuMXoiLz4KPC9zdmc+Cg==", }; ({ transportType: TransportType.STOIC}); ({ transportType: TransportType.NEW_TAB}); function useCreateIdentityKit({ selectedSigner, clearSigner, signerClientOptions = {}, authType, onConnectFailure, onConnectSuccess, realConnectDisabled, ...props }) { const [ik, setIk] = useState(null); const [user, setUser] = useState(); const [icpBalance, setIcpBalance] = useState(); const onDisconnect = useCallback(async () => { setIk(null); setUser(undefined); setIcpBalance(undefined); await selectedSigner?.value.transport.connection?.disconnect(); await clearSigner(); props.onDisconnect?.(); if (selectedSigner?.id === InternetIdentity.id) window.location.reload(); }, [ik?.signerClient, clearSigner, props.onDisconnect, selectedSigner]); // create disconnect func const disconnect = useCallback(async () => { return await ik?.signerClient?.logout(); }, [ik?.signerClient]); // create fetchBalance func const fetchIcpBalance = useMemo(() => { if (!user || !ik) return; return () => ik.getIcpBalance().then(setIcpBalance); }, [ik, user, setIcpBalance]); useEffect(() => { setIk(null); // when signer is selected, but user is not connected, create indetity kit and trigger login if (selectedSigner && !ik?.signerClient) { IdentityKit.create({ authType, signerClientOptions: { ...signerClientOptions, crypto, signer: selectedSigner.value, onLogout: onDisconnect, }, }).then(async (instance) => { if (!realConnectDisabled) { if (!instance.signerClient.connectedUser) { try { await instance.signerClient.login(); setUser(instance.signerClient.connectedUser); onConnectSuccess?.(); } catch (e) { await selectedSigner.value.transport.connection?.disconnect(); await clearSigner(); onConnectFailure?.(e); } } else { if (instance.signerClient.getIdentity?.() instanceof AnonymousIdentity) { await instance.signerClient.logout(); await disconnect(); return; } setUser(instance.signerClient.connectedUser); } } else { onConnectSuccess?.(); } setIk(instance); }); } }, [selectedSigner, realConnectDisabled]); // fetch balance when user connected useEffect(() => { if (icpBalance === undefined) { fetchIcpBalance?.(); } }, [icpBalance, user, fetchIcpBalance]); return { user, disconnect, icpBalance, signerClient: ik?.signerClient, fetchIcpBalance, }; } function useProceedTheme(theme = IdentityKitTheme.SYSTEM) { const [finalTheme, setFinalTheme] = useState(theme); useEffect(() => { if (!theme || theme === IdentityKitTheme.SYSTEM) { setFinalTheme(window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? IdentityKitTheme.DARK : IdentityKitTheme.LIGHT); } else { setFinalTheme(theme); } }, [theme]); return finalTheme; } async function getPopupTransportBuilder(options) { return new PostMessageTransport({ ...options, detectNonClickEstablishment: false, }); } async function getExtensionTransportBuilder({ uuid }) { return BrowserExtensionTransport.findTransport({ uuid }); } async function getAuthClientTransportBuilder(options) { return await AuthClientTransport.create({ ...options, authClientCreateOptions: { ...options.authClientCreateOptions, idleOptions: { disableIdle: true, }, }, }); } async function getStoicTransportBuilder({ maxTimeToLive, }) { return await StoicTransport.create({ maxTimeToLive }); } class TransportBuilder { static builders = { [TransportType.NEW_TAB]: ({ url, crypto, window, windowOpenerFeatures }) => getPopupTransportBuilder({ url, crypto, window, windowOpenerFeatures, }), [TransportType.EXTENSION]: ({ id }) => { if (!id) { throw Error("Id is required to find the ICRC-94 specific wallet"); } return getExtensionTransportBuilder({ uuid: id }); }, [TransportType.INTERNET_IDENTITY]: ({ maxTimeToLive, derivationOrigin, identity, keyType, storage, allowInternetIdentityPinAuthentication, url, }) => getAuthClientTransportBuilder({ authClientCreateOptions: { identity, keyType, storage, }, authClientLoginOptions: { maxTimeToLive, derivationOrigin, allowPinAuthentication: allowInternetIdentityPinAuthentication, identityProvider: url, }, }), [TransportType.STOIC]: ({ maxTimeToLive }) => getStoicTransportBuilder({ maxTimeToLive }), }; static async build(request) { return await TransportBuilder.builders[request.transportType]({ ...request, maxTimeToLive: request.maxTimeToLive || DEFAULT_MAX_TIME_TO_LIVE, }); } } function useProceedSigner({ signers, transports, closeModal, crypto, window, windowOpenerFeatures, onConnectFailure, }) { // saved to local storage for next js (localStorage is not defined during server render) const [localStorageSigner, setLocalStorageSigner] = useState((typeof window !== "undefined" && localStorage.getItem("signerId")) || ""); const [selectedSigner, setSelectedSigner] = useState(undefined); const [isSignerBeingSelected, setIsSignerBeingSelected] = useState(false); const selectSigner = useCallback(async (signerId) => { if (!signerId) { localStorage.removeItem("signerId"); setLocalStorageSigner(undefined); return setSelectedSigner(undefined); } try { setIsSignerBeingSelected(true); closeModal(); const signerConfig = signers.find((s) => s.id === signerId); if (!signerConfig) throw new Error(`Signer with id ${signerId} not found`); const transport = transports?.find((t) => t.signerId === signerId)?.value; if (!transport) throw new Error("Transport was not found"); if (!transport.connection?.connected && !localStorageSigner) { await transport.connection?.connect(); } const createdSigner = new Signer({ crypto, transport, }); setSelectedSigner({ value: createdSigner, id: signerId }); setIsSignerBeingSelected(false); } catch (e) { setIsSignerBeingSelected(false); onConnectFailure?.(e); return; } }, [signers, crypto, closeModal, transports, localStorageSigner]); const selectCustomSigner = useCallback(async (url) => { const transport = await TransportBuilder.build({ transportType: TransportType.NEW_TAB, url, crypto, window, windowOpenerFeatures, }); const signer = new Signer({ crypto, transport }); setSelectedSigner({ value: signer, id: url }); closeModal(); }, [crypto, window, closeModal, windowOpenerFeatures]); // default selected signer from local storage useEffect(() => { // for next.js, where localStorage is not available during ssr const lsSigner = localStorage.getItem("signerId"); if (!selectedSigner && lsSigner && transports) { selectSigner(lsSigner); } }, [selectedSigner, selectSigner, transports]); const setSelectedSignerToLocalStorage = useCallback(() => { if (selectedSigner && selectedSigner?.id) { localStorage.setItem("signerId", selectedSigner?.id); } }, [selectedSigner]); return { selectSigner, setSelectedSignerToLocalStorage, // clears both local state and local storage clearSigner: () => selectSigner(), selectCustomSigner, // selected signer is local storage signer by default (in case authenticated user) selectedSigner, // signer id in localStorage (used on connected user page reload) localStorageSigner, isSignerBeingSelected, }; } class ContextNotInitializedError extends Error { constructor() { super("IdentityKit context not initialized"); } } function useBalance() { return useContextSelector(Context, (ctx) => { if (!ctx) throw new ContextNotInitializedError(); return { balance: ctx.icpBalance, fetchBalance: ctx.fetchIcpBalance, }; }); } function useSigner() { return useContextSelector(Context, (ctx) => { if (!ctx) throw new ContextNotInitializedError(); return ctx.selectedSigner?.value; }); } function useModal() { return useContextSelector(Context, (ctx) => { if (!ctx) throw new ContextNotInitializedError(); return { isModalOpen: ctx.isModalOpen, toggleModal: ctx.toggleModal, featuredSigner: ctx.featuredSigner, signers: ctx.signers, selectCustomSigner: ctx.selectCustomSigner, selectSigner: ctx.selectSigner, }; }); } function useTheme() { return useContext(ThemeContext); } function useAuthType() { return useContextSelector(Context, (ctx) => { if (!ctx) throw new ContextNotInitializedError(); return ctx.authType; }); } function useUser() { return useContextSelector(Context, (ctx) => { if (!ctx) throw new ContextNotInitializedError(); return ctx.user; }); } function useSignerClient() { return useContextSelector(Context, (ctx) => { if (!ctx) throw new ContextNotInitializedError(); return ctx.signerClient; }); } function useIsInitializing() { return useContextSelector(Context, (ctx) => { if (!ctx) throw new ContextNotInitializedError(); return ctx.isInitializing; }); } function useIsUserConnecting() { return useContextSelector(Context, (ctx) => { if (!ctx) throw new ContextNotInitializedError(); return ctx.isUserConnecting; }); } function useConnect() { return useContextSelector(Context, (ctx) => { if (!ctx) throw new ContextNotInitializedError(); return ctx.connect; }); } function useDisconnect() { return useContextSelector(Context, (ctx) => { if (!ctx) throw new ContextNotInitializedError(); return ctx.disconnect; }); } function useSignerConfig() { return useContextSelector(Context, (ctx) => { if (!ctx) throw new ContextNotInitializedError(); return ctx.selectedSigner?.id ? (ctx.signers.find((s) => s.id === ctx.selectedSigner?.id) ?? { id: ctx.selectedSigner?.id, label: ctx.selectedSigner?.id, transportType: TransportType.NEW_TAB, providerUrl: ctx.selectedSigner?.id, }) : undefined; }); } function useIdentity() { const signerClient = useSignerClient(); const authType = useAuthType(); const identity = useMemo(() => authType === IdentityKitAuthType.ACCOUNTS || signerClient instanceof AccountsSignerClient ? undefined : signerClient?.getIdentity(), [authType, signerClient]); return identity; } function useAccounts() { const authType = useAuthType(); const signerClient = useSignerClient(); const user = useUser(); const [accounts, setAccounts] = useState(); useEffect(() => { if (!user) { setAccounts(undefined); } else { if (authType === IdentityKitAuthType.ACCOUNTS && !accounts && signerClient instanceof AccountsSignerClient) { signerClient?.getAccounts().then(setAccounts); } } }, [user, authType, signerClient]); return accounts; } function useDelegationType() { const signerClient = useSignerClient(); const authType = useAuthType(); const user = useUser(); const delegationType = useAsyncMemo(() => { if (!user || !signerClient || authType !== IdentityKitAuthType.DELEGATION || signerClient instanceof AccountsSignerClient) return undefined; return signerClient.getDelegationType(); }, [user, authType, signerClient]); return delegationType; } function useAgent(agentOptions = {}) { const identity = useIdentity(); const selectedSigner = useSigner(); const user = useUser(); const signerClient = useSignerClient(); const authType = useAuthType(); const ikAgent = useAsyncMemo(async () => { if (!selectedSigner || !user || !signerClient) return undefined; const isAccountsAuth = authType === IdentityKitAuthType.ACCOUNTS; if (!isAccountsAuth && !identity) return undefined; const delegation = isAccountsAuth ? undefined : await signerClient.getDelegation(); const defaultAgent = await HttpAgent.create({ identity, ...agentOptions, }); const signerAgent = await SignerAgent.create({ signer: selectedSigner, account: user.principal, agent: defaultAgent, }); return Agent.create({ signerAgent, agent: defaultAgent, identity, delegation: delegation?.delegation, }); }, [selectedSigner, signerClient, authType, identity]); return ikAgent; } function useClickOutside(handler) { const ref = useRef(null); useEffect(() => { const listener = (event) => { const el = ref?.current; // Do nothing if clicking ref's element or descendent elements if (!el || el.contains(event.target)) return; handler(event); }; document.addEventListener(`mousedown`, listener); document.addEventListener(`touchstart`, listener); return () => { document.removeEventListener(`mousedown`, listener); document.removeEventListener(`touchstart`, listener); }; }, [ref, handler]); return ref; } function useAuth() { const user = useUser(); const isConnecting = useIsUserConnecting(); const connect = useConnect(); const disconnect = useDisconnect(); return { user, isConnecting, connect, disconnect, }; } function useCreatePromise() { // Internal state to force re-renders when the promise resolves const [, setResolvedValue] = useState(); // Store the promise and its resolve/reject handlers in a ref const promiseRef = useRef({ promise: null, resolve: null, reject: null, }); // Create the promise const createPromise = useCallback(() => { promiseRef.current.promise = new Promise((resolve, reject) => { promiseRef.current.resolve = resolve; promiseRef.current.reject = reject; }); return promiseRef.current.promise; }, []); // Resolve the promise const resolve = useCallback((value) => { if (promiseRef.current.resolve) { promiseRef.current.resolve(value); setResolvedValue(value); // Trigger re-render } }, []); // Reject the promise const reject = useCallback((error) => { if (promiseRef.current.reject) { promiseRef.current.reject(error); setResolvedValue(undefined); // Trigger re-render (optional) } }, []); return { createPromise, resolve, reject, promise: promiseRef.current.promise }; } /** * @deprecated This function is deprecated. Please use separate hooks instead (useUser, useBalance etc). */ function useIdentityKit() { const ctx = useContext$1(Context); if (!ctx) { throw new ContextNotInitializedError(); } const { selectedSigner, user, icpBalance, authType, isInitializing, isUserConnecting, connect, disconnect, fetchIcpBalance, } = ctx; const identity = useIdentity(); const delegationType = useDelegationType(); const accounts = useAccounts(); return { signer: selectedSigner?.value, user, icpBalance, authType, accounts, delegationType, identity, isInitializing, isUserConnecting, connect, disconnect, fetchIcpBalance, }; } function ThemeProvider({ children, ...props }) { const theme = useProceedTheme(props.theme); return jsx(ThemeContext.Provider, { value: theme, children: children }); } const validateUrl = (url) => { const urlPattern = new RegExp("^(https?:\\/\\/)?" + "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + "((\\d{1,3}\\.){3}\\d{1,3}))" + "(\\:\\d+)?" + "(\\/[-a-z\\d%_.~+]*)*" + "(\\?[;&a-z\\d%_.~+=-]*)?" + "(\\#[-a-z\\d_]*)?$", "i"); return urlPattern.test(url); }; const ICP_DECIMALS = 8; function countDecimals(value) { if (Math.floor(value) === value) return 0; const str = value.toString(); if (str.indexOf(".") !== -1 && str.indexOf("-") !== -1) { return Number(str.split("-")[1]); } else if (