UNPKG

@bigmi/client

Version:

Reactive primitives for Bitcoin apps.

274 lines (273 loc) 8.62 kB
import { ChainNotConfiguredError } from "../errors/config.js"; import { createEmitter } from "./createEmitter.js"; import { createStorage, getDefaultStorage } from "./createStorage.js"; import { createClient, uid, version } from "@bigmi/core"; import { persist, subscribeWithSelector } from "zustand/middleware"; import { createStore } from "zustand/vanilla"; //#region src/factories/createConfig.ts function createConfig(parameters) { const { multiInjectedProviderDiscovery = true, storage = createStorage({ key: "bigmi", storage: getDefaultStorage() }), syncConnectedChain = true, ssr = false, ...rest } = parameters; const chains = createStore(() => rest.chains); const connectors = createStore(() => { const collection = []; const rdnsSet = /* @__PURE__ */ new Set(); for (const connectorFns of rest.connectors ?? []) { const connector = setup(connectorFns); collection.push(connector); if (!ssr && connector.rdns) { const rdnsValues = typeof connector.rdns === "string" ? [connector.rdns] : connector.rdns; for (const rdns of rdnsValues) rdnsSet.add(rdns); } } return collection; }); function setup(connectorFn) { const emitter = createEmitter(uid()); const connector = { ...connectorFn({ emitter, chains: chains.getState(), storage, transports: rest.transports }), emitter, uid: emitter.uid }; emitter.on("connect", connect); connector.setup?.(); return connector; } const clients = /* @__PURE__ */ new Map(); function getClient(config = {}) { const chainId = config.chainId ?? store.getState().chainId; const chain = chains.getState().find((x) => x.id === chainId); if (config.chainId && !chain) throw new ChainNotConfiguredError(); { const client = clients.get(store.getState().chainId); if (client && !chain) return client; if (!chain) throw new ChainNotConfiguredError(); } { const client = clients.get(chainId); if (client) return client; } let client; if (rest.client) client = rest.client({ chain }); else { const chainId = chain.id; const chainIds = chains.getState().map((x) => x.id); const properties = {}; const entries = Object.entries(rest); for (const [key, value] of entries) { if (key === "chains" || key === "client" || key === "connectors" || key === "transports") continue; if (typeof value === "object") if (chainId in value) properties[key] = value[chainId]; else { if (chainIds.some((x) => x in value)) continue; properties[key] = value; } else properties[key] = value; } client = createClient({ ...properties, chain, transport: (parameters) => rest.transports[chainId]({ ...parameters, connectors }) }); } clients.set(chainId, client); return client; } function getInitialState() { return { chainId: chains.getState()[0].id, connections: /* @__PURE__ */ new Map(), current: null, status: "disconnected" }; } let currentVersion; const prefix = "0.0.0-canary-"; if (version.startsWith(prefix)) currentVersion = Number.parseInt(version.replace(prefix, ""), 10); else currentVersion = Number.parseInt(version.split(".")[0] ?? "0", 10); const store = createStore(subscribeWithSelector(storage ? persist(getInitialState, { migrate(persistedState, version) { if (version === currentVersion) return persistedState; const initialState = getInitialState(); const chainId = validatePersistedChainId(persistedState, initialState.chainId); return { ...initialState, chainId }; }, name: "store", partialize(state) { return { connections: { __type: "Map", value: Array.from(state.connections.entries()).map(([key, connection]) => { const { id, name, type, uid } = connection.connector; const connector = { id, name, type, uid }; return [key, { ...connection, connector }]; }) }, chainId: state.chainId, current: state.current }; }, merge(persistedState, currentState) { if (typeof persistedState === "object" && persistedState && "status" in persistedState) persistedState.status = void 0; const chainId = validatePersistedChainId(persistedState, currentState.chainId); return { ...currentState, ...persistedState, chainId }; }, skipHydration: ssr, storage, version: currentVersion }) : getInitialState)); store.setState(getInitialState()); function validatePersistedChainId(persistedState, defaultChainId) { return persistedState && typeof persistedState === "object" && "chainId" in persistedState && typeof persistedState.chainId === "string" && chains.getState().some((x) => x.id === persistedState.chainId) ? persistedState.chainId : defaultChainId; } if (syncConnectedChain) store.subscribe(({ connections, current }) => current ? connections.get(current)?.chainId : void 0, (chainId) => { if (!chains.getState().some((x) => x.id === chainId)) return; return store.setState((x) => ({ ...x, chainId: chainId ?? x.chainId })); }); function change(data) { store.setState((x) => { const connection = x.connections.get(data.uid); if (!connection) return x; return { ...x, connections: new Map(x.connections).set(data.uid, { accounts: data.accounts ?? connection.accounts, chainId: data.chainId ?? connection.chainId, connector: connection.connector }) }; }); } function connect(data) { if (store.getState().status === "connecting" || store.getState().status === "reconnecting") return; store.setState((x) => { const connector = connectors.getState().find((x) => x.uid === data.uid); if (!connector) return x; if (connector.emitter.listenerCount("connect")) connector.emitter.off("connect", change); if (!connector.emitter.listenerCount("change")) connector.emitter.on("change", change); if (!connector.emitter.listenerCount("disconnect")) connector.emitter.on("disconnect", disconnect); return { ...x, connections: new Map(x.connections).set(data.uid, { accounts: data.accounts, chainId: data.chainId, connector }), current: data.uid, status: "connected" }; }); } function disconnect(data) { store.setState((x) => { const connection = x.connections.get(data.uid); if (connection) { const connector = connection.connector; if (connector.emitter.listenerCount("change")) connection.connector.emitter.off("change", change); if (connector.emitter.listenerCount("disconnect")) connection.connector.emitter.off("disconnect", disconnect); if (!connector.emitter.listenerCount("connect")) connection.connector.emitter.on("connect", connect); } x.connections.delete(data.uid); if (x.connections.size === 0) return { ...x, connections: /* @__PURE__ */ new Map(), current: null, status: "disconnected" }; const nextConnection = x.connections.values().next().value; return { ...x, connections: new Map(x.connections), current: nextConnection.connector.uid }; }); } return { get chains() { return chains.getState(); }, get connectors() { return connectors.getState(); }, storage, getClient, get state() { return store.getState(); }, setState(value) { let newState; if (typeof value === "function") newState = value(store.getState()); else newState = value; const initialState = getInitialState(); if (typeof newState !== "object") newState = initialState; if (Object.keys(initialState).some((x) => !(x in newState))) newState = initialState; store.setState(newState, true); }, subscribe(selector, listener, options) { return store.subscribe(selector, listener, options ? { ...options, fireImmediately: options.emitImmediately } : void 0); }, _internal: { store, ssr: Boolean(ssr), syncConnectedChain, transports: rest.transports, chains: { setState(value) { const nextChains = typeof value === "function" ? value(chains.getState()) : value; if (nextChains.length === 0) return; return chains.setState(nextChains, true); }, subscribe(listener) { return chains.subscribe(listener); } }, connectors: { setup, setState(value) { return connectors.setState(typeof value === "function" ? value(connectors.getState()) : value, true); }, subscribe(listener) { return connectors.subscribe(listener); } }, events: { change, connect, disconnect } } }; } //#endregion export { createConfig }; //# sourceMappingURL=createConfig.js.map