var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _ExtensionProvider_instances, _ExtensionProvider_coder, _ExtensionProvider_eventemitter, _ExtensionProvider_handlers, _ExtensionProvider_subscriptions, _ExtensionProvider_waitingForId, _ExtensionProvider_chainId, _ExtensionProvider_connectionStatePingerId, _ExtensionProvider_isConnected, _ExtensionProvider_chainSpecs, _ExtensionProvider_parachainSpecs, _ExtensionProvider_handleMessage, _ExtensionProvider_onMessageResult, _ExtensionProvider_onMessageSubscribe, _ExtensionProvider_simulateLifecycle, _ExtensionProvider_checkClientPeercount, _ExtensionProvider_addChain;
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { RpcCoder } from "@polkadot/rpc-provider/coder";
import { logger } from "@polkadot/util";
import EventEmitter from "eventemitter3";
import { isUndefined, eraseRecord } from "../utils/index.js";
import { HealthCheckError } from "../errors.js";
import { SupportedChains } from "../specs/index.js";
import { getRandomChainId } from "./getRandomChainId.js";
const EXTENSION_ORIGIN = "substrate-connect-extension";
const CLIENT_ORIGIN = "substrate-connect-client";
const l = logger(CLIENT_ORIGIN);
const ANGLICISMS = {
    chain_finalisedHead: "chain_finalizedHead",
    chain_subscribeFinalisedHeads: "chain_subscribeFinalizedHeads",
    chain_unsubscribeFinalisedHeads: "chain_unsubscribeFinalizedHeads",
};
/*
 * Number of milliseconds to wait between checks to see if we have any
 * connected peers in the smoldot client
 */
const CONNECTION_STATE_PINGER_INTERVAL = 2000;
const sendMessage = (msg) => {
    window.postMessage(msg, "*");
};
const createChain = (specMsg) => new Promise((res, rej) => {
    const waitForChainCb = ({ data }) => {
        if (data.origin !== EXTENSION_ORIGIN ||
            data.chainId !== specMsg.chainId) {
            return;
        }
        window.removeEventListener("message", waitForChainCb);
        if (data.type === "chain-ready")
            return res();
        rej(new Error(data.type === "error"
            ? data.errorMessage
            : "Unexpected message received from the extension while waiting for 'chain-ready' message"));
    };
    window.addEventListener("message", waitForChainCb);
    sendMessage(specMsg);
});
/**
 * The ExtensionProvider allows interacting with a smoldot-based WASM light
 * client running in a browser extension.  It is not designed to be used
 * directly.  You should use the `\@substrate/connect` package.
 */
export class ExtensionProvider {
    constructor(relayChain, parachain) {
        _ExtensionProvider_instances.add(this);
        _ExtensionProvider_coder.set(this, new RpcCoder());
        _ExtensionProvider_eventemitter.set(this, new EventEmitter());
        _ExtensionProvider_handlers.set(this, {});
        _ExtensionProvider_subscriptions.set(this, {});
        _ExtensionProvider_waitingForId.set(this, {});
        _ExtensionProvider_chainId.set(this, void 0);
        _ExtensionProvider_connectionStatePingerId.set(this, void 0);
        _ExtensionProvider_isConnected.set(this, false);
        _ExtensionProvider_chainSpecs.set(this, void 0);
        _ExtensionProvider_parachainSpecs.set(this, void 0);
        /*
         * How frequently to see if we have any peers
         */
        this.healthPingerInterval = CONNECTION_STATE_PINGER_INTERVAL;
        _ExtensionProvider_handleMessage.set(this, (data) => {
            const { type } = data;
            if (type === "error") {
                __classPrivateFieldSet(this, _ExtensionProvider_isConnected, false, "f");
                const error = new Error(data.errorMessage);
                this.emit("error", error);
                // reject all hanging requests
                eraseRecord(__classPrivateFieldGet(this, _ExtensionProvider_handlers, "f"), (h) => h.callback(error, undefined));
                eraseRecord(__classPrivateFieldGet(this, _ExtensionProvider_waitingForId, "f"));
                return;
            }
            if (type === "rpc" && data.jsonRpcMessage) {
                l.debug(() => ["received", data.jsonRpcMessage]);
                const response = JSON.parse(data.jsonRpcMessage);
                return isUndefined(response.method)
                    ? __classPrivateFieldGet(this, _ExtensionProvider_onMessageResult, "f").call(this, response)
                    : __classPrivateFieldGet(this, _ExtensionProvider_onMessageSubscribe, "f").call(this, response);
            }
            const errorMessage = `Unrecognised message type from extension ${type}`;
            return this.emit("error", new Error(errorMessage));
        });
        _ExtensionProvider_onMessageResult.set(this, (response) => {
            const handler = __classPrivateFieldGet(this, _ExtensionProvider_handlers, "f")[response.id];
            if (!handler) {
                l.debug(() => `Unable to find handler for id=${response.id}`);
                return;
            }
            try {
                const { method, subscription } = handler;
                const result = __classPrivateFieldGet(this, _ExtensionProvider_coder, "f").decodeResponse(response);
                // first send the result - in case of subs, we may have an update
                // immediately if we have some queued results already
                handler.callback(null, result);
                if (subscription) {
                    const subId = `${subscription.type}::${result}`;
                    __classPrivateFieldGet(this, _ExtensionProvider_subscriptions, "f")[subId] = {
                        ...subscription,
                        method,
                    };
                    // if we have a result waiting for this subscription already
                    if (__classPrivateFieldGet(this, _ExtensionProvider_waitingForId, "f")[subId]) {
                        __classPrivateFieldGet(this, _ExtensionProvider_onMessageSubscribe, "f").call(this, __classPrivateFieldGet(this, _ExtensionProvider_waitingForId, "f")[subId]);
                    }
                }
            }
            catch (error) {
                handler.callback(error, undefined);
            }
            delete __classPrivateFieldGet(this, _ExtensionProvider_handlers, "f")[response.id];
        });
        _ExtensionProvider_onMessageSubscribe.set(this, (response) => {
            const method = ANGLICISMS[response.method] || response.method || "invalid";
            const subId = `${method}::${response.params.subscription}`;
            const handler = __classPrivateFieldGet(this, _ExtensionProvider_subscriptions, "f")[subId];
            if (!handler) {
                // store the response, we could have out-of-order subid coming in
                __classPrivateFieldGet(this, _ExtensionProvider_waitingForId, "f")[subId] = response;
                l.debug(() => `Unable to find handler for subscription=${subId} responseId=${response.id}`);
                return;
            }
            // housekeeping
            delete __classPrivateFieldGet(this, _ExtensionProvider_waitingForId, "f")[subId];
            try {
                const result = __classPrivateFieldGet(this, _ExtensionProvider_coder, "f").decodeResponse(response);
                handler.callback(null, result);
            }
            catch (error) {
                handler.callback(error, undefined);
            }
        });
        _ExtensionProvider_simulateLifecycle.set(this, (health) => {
            // development chains should not have peers so we only emit connected
            // once and never disconnect
            if (health.shouldHavePeers == false) {
                if (!__classPrivateFieldGet(this, _ExtensionProvider_isConnected, "f")) {
                    __classPrivateFieldSet(this, _ExtensionProvider_isConnected, true, "f");
                    this.emit("connected");
                    l.debug(`emitted CONNECTED`);
                    return;
                }
                return;
            }
            const peerCount = health.peers;
            const peerChecks = (peerCount > 0 || !health.shouldHavePeers) && !health.isSyncing;
            l.debug(`Simulating lifecylce events from system_health`);
            l.debug(`isConnected: ${__classPrivateFieldGet(this, _ExtensionProvider_isConnected, "f").toString()}, new peerCount: ${peerCount}`);
            if (__classPrivateFieldGet(this, _ExtensionProvider_isConnected, "f") && peerChecks) {
                // still connected
                return;
            }
            if (__classPrivateFieldGet(this, _ExtensionProvider_isConnected, "f") && peerCount === 0) {
                __classPrivateFieldSet(this, _ExtensionProvider_isConnected, false, "f");
                this.emit("disconnected");
                l.debug(`emitted DISCONNECTED`);
                return;
            }
            if (!__classPrivateFieldGet(this, _ExtensionProvider_isConnected, "f") && peerChecks) {
                __classPrivateFieldSet(this, _ExtensionProvider_isConnected, true, "f");
                this.emit("connected");
                l.debug(`emitted CONNECTED`);
                return;
            }
            // still not connected
        });
        _ExtensionProvider_checkClientPeercount.set(this, () => {
            this.send("system_health", [])
                .then(__classPrivateFieldGet(this, _ExtensionProvider_simulateLifecycle, "f"))
                .catch((error) => this.emit("error", new HealthCheckError(error)));
        });
        __classPrivateFieldSet(this, _ExtensionProvider_chainSpecs, relayChain, "f");
        __classPrivateFieldSet(this, _ExtensionProvider_connectionStatePingerId, null, "f");
        __classPrivateFieldSet(this, _ExtensionProvider_parachainSpecs, "", "f");
        if (parachain) {
            __classPrivateFieldSet(this, _ExtensionProvider_parachainSpecs, parachain, "f");
        }
        __classPrivateFieldSet(this, _ExtensionProvider_chainId, getRandomChainId(), "f");
    }
    /**
     * Lets polkadot-js know we support subscriptions
     *
     * @remarks Always returns `true` - this provider supports subscriptions.
     * PolkadotJS uses this internally.
     */
    get hasSubscriptions() {
        return true;
    }
    /**
     * clone
     *
     * @remarks This method is not supported
     * @throws {@link Error}
     */
    clone() {
        throw new Error("clone() is not supported.");
    }
    /**
     * "Connect" to the extension - sends a message to the `ExtensionMessageRouter`
     * asking it to connect to the extension background.
     *
     * @returns a resolved Promise
     * @remarks this is async to fulfill the interface with PolkadotJS
     */
    async connect() {
        try {
            await __classPrivateFieldGet(this, _ExtensionProvider_instances, "m", _ExtensionProvider_addChain).call(this);
        }
        catch (e) {
            const error = e instanceof Error
                ? e
                : new Error(`An unnexpected error happened while trying to connect. ${e}`);
            this.emit("error", error);
            eraseRecord(__classPrivateFieldGet(this, _ExtensionProvider_handlers, "f"), (h) => h.callback(error, undefined));
            return;
        }
        window.addEventListener("message", ({ data }) => {
            if (data.origin === EXTENSION_ORIGIN &&
                data.chainId === __classPrivateFieldGet(this, _ExtensionProvider_chainId, "f")) {
                __classPrivateFieldGet(this, _ExtensionProvider_handleMessage, "f").call(this, data);
            }
        });
        __classPrivateFieldSet(this, _ExtensionProvider_connectionStatePingerId, setInterval(__classPrivateFieldGet(this, _ExtensionProvider_checkClientPeercount, "f"), this.healthPingerInterval), "f");
    }
    /**
     * Manually "disconnect" - sends a message to the `ExtensionMessageRouter`
     * telling it to disconnect the port with the background manager.
     */
    disconnect() {
        if (__classPrivateFieldGet(this, _ExtensionProvider_connectionStatePingerId, "f") !== null) {
            clearInterval(__classPrivateFieldGet(this, _ExtensionProvider_connectionStatePingerId, "f"));
        }
        sendMessage({
            origin: CLIENT_ORIGIN,
            chainId: __classPrivateFieldGet(this, _ExtensionProvider_chainId, "f"),
            type: "remove-chain",
        });
        __classPrivateFieldSet(this, _ExtensionProvider_isConnected, false, "f");
        this.emit("disconnected");
        return Promise.resolve();
    }
    /**
     * Whether the node is connected or not.
     *
     * @returns true - if connected otherwise false
     */
    get isConnected() {
        return __classPrivateFieldGet(this, _ExtensionProvider_isConnected, "f");
    }
    /**
     * Listen to provider events - in practice the smoldot provider only
     * emits a `connected` event after successfully starting the smoldot client
     * and `disconnected` after `disconnect` is called.
     * @param type - Event
     * @param sub - Callback
     */
    on(type, sub) {
        __classPrivateFieldGet(this, _ExtensionProvider_eventemitter, "f").on(type, sub);
        return () => {
            __classPrivateFieldGet(this, _ExtensionProvider_eventemitter, "f").removeListener(type, sub);
        };
    }
    /**
     * Send an RPC request  the wasm client
     *
     * @param method - The RPC methods to execute
     * @param params - Encoded paramaters as applicable for the method
     * @param subscription - Subscription details (internally used by `subscribe`)
     */
    async send(method, params, isCacheable, subscription) {
        return new Promise((resolve, reject) => {
            const json = __classPrivateFieldGet(this, _ExtensionProvider_coder, "f").encodeJson(method, params);
            const id = __classPrivateFieldGet(this, _ExtensionProvider_coder, "f").getId();
            const callback = (error, result) => {
                error ? reject(error) : resolve(result);
            };
            l.debug(() => ["calling", method, json]);
            __classPrivateFieldGet(this, _ExtensionProvider_handlers, "f")[id] = {
                callback,
                method,
                subscription,
            };
            const rpcMsg = {
                origin: CLIENT_ORIGIN,
                chainId: __classPrivateFieldGet(this, _ExtensionProvider_chainId, "f"),
                type: "rpc",
                jsonRpcMessage: json,
            };
            sendMessage(rpcMsg);
        });
    }
    /**
     * Allows subscribing to a specific event.
     *
     * @param type     - Subscription type
     * @param method   - Subscription method
     * @param params   - Parameters
     * @param callback - Callback
     * @returns Promise  - resolves to the id of the subscription you can use with [[unsubscribe]].
     *
     * @example
     * <BR>
     *
     * ```javascript
     * const provider = new ExtensionProvider(client);
     * const rpc = new Rpc(provider);
     *
     * rpc.state.subscribeStorage([[storage.balances.freeBalance, <Address>]], (_, values) => {
     *   console.log(values)
     * }).then((subscriptionId) => {
     *   console.log('balance changes subscription id: ', subscriptionId)
     * })
     * ```
     */
    async subscribe(
    // the "method" property of the JSON response to this subscription
    type, 
    // the "method" property of the JSON request to register the subscription
    method, params, callback) {
        return (await this.send(method, params, false, {
            callback,
            type,
        }));
    }
    /**
     * Allows unsubscribing to subscriptions made with [[subscribe]].
     *
     * @param type   - Subscription type
     * @param method - Subscription method
     * @param id     - Id passed for send parameter
     * @returns Promise resolving to whether the unsunscribe request was successful.
     */
    async unsubscribe(type, method, id) {
        const subscription = `${type}::${id}`;
        if (isUndefined(__classPrivateFieldGet(this, _ExtensionProvider_subscriptions, "f")[subscription])) {
            l.debug(() => `Unable to find active subscription=${subscription}`);
            return false;
        }
        delete __classPrivateFieldGet(this, _ExtensionProvider_subscriptions, "f")[subscription];
        return (await this.send(method, [id]));
    }
    emit(type, ...args) {
        __classPrivateFieldGet(this, _ExtensionProvider_eventemitter, "f").emit(type, ...args);
    }
}
_ExtensionProvider_coder = new WeakMap(), _ExtensionProvider_eventemitter = new WeakMap(), _ExtensionProvider_handlers = new WeakMap(), _ExtensionProvider_subscriptions = new WeakMap(), _ExtensionProvider_waitingForId = new WeakMap(), _ExtensionProvider_chainId = new WeakMap(), _ExtensionProvider_connectionStatePingerId = new WeakMap(), _ExtensionProvider_isConnected = new WeakMap(), _ExtensionProvider_chainSpecs = new WeakMap(), _ExtensionProvider_parachainSpecs = new WeakMap(), _ExtensionProvider_handleMessage = new WeakMap(), _ExtensionProvider_onMessageResult = new WeakMap(), _ExtensionProvider_onMessageSubscribe = new WeakMap(), _ExtensionProvider_simulateLifecycle = new WeakMap(), _ExtensionProvider_checkClientPeercount = new WeakMap(), _ExtensionProvider_instances = new WeakSet(), _ExtensionProvider_addChain = async function _ExtensionProvider_addChain() {
    const specMsg = {
        origin: CLIENT_ORIGIN,
        chainId: __classPrivateFieldGet(this, _ExtensionProvider_parachainSpecs, "f") ? getRandomChainId() : __classPrivateFieldGet(this, _ExtensionProvider_chainId, "f"),
        ...(SupportedChains[__classPrivateFieldGet(this, _ExtensionProvider_chainSpecs, "f")]
            ? {
                type: "add-well-known-chain",
                chainName: __classPrivateFieldGet(this, _ExtensionProvider_chainSpecs, "f"),
            }
            : {
                type: "add-chain",
                chainSpec: __classPrivateFieldGet(this, _ExtensionProvider_chainSpecs, "f"),
                potentialRelayChainIds: [],
            }),
    };
    await createChain(specMsg);
    if (!__classPrivateFieldGet(this, _ExtensionProvider_parachainSpecs, "f"))
        return;
    await createChain({
        origin: CLIENT_ORIGIN,
        chainId: __classPrivateFieldGet(this, _ExtensionProvider_chainId, "f"),
        type: "add-chain",
        chainSpec: __classPrivateFieldGet(this, _ExtensionProvider_parachainSpecs, "f"),
        potentialRelayChainIds: [specMsg.chainId],
    });
};
//# sourceMappingURL=ExtensionProvider.js.map