@bigmi/client
Version:
Reactive primitives for Bitcoin apps.
274 lines (273 loc) • 8.62 kB
JavaScript
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