// Smoldot
// Copyright (C) 2019-2022  Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
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 _InnerChecker_healthCallback, _InnerChecker_currentHealthCheckId, _InnerChecker_currentHealthTimeout, _InnerChecker_currentSubunsubRequestId, _InnerChecker_currentSubscriptionId, _InnerChecker_requestToSmoldot, _InnerChecker_isSyncing, _InnerChecker_nextRequestId;
/*
 * Creates a new health checker.
 *
 * The role of the health checker is to report to the user the health of a smoldot chain.
 *
 * In order to use it, start by creating a health checker, and call `setSendJsonRpc` to set the
 * way to send a JSON-RPC request to a chain. The health checker is disabled by default. Use
 * `start()` in order to start the health checks. The `start()` function must be passed a callback called
 * when an update to the health of the node is available.
 *
 * In order to send a JSON-RPC request to the chain, you **must** use the `sendJsonRpc` function
 * of the health checker. The health checker rewrites the `id` of the requests it receives.
 *
 * When the chain send a JSON-RPC response, it must be passed to `responsePassThrough()`. This
 * function intercepts the responses destined to the requests that have been emitted by the health
 * checker and returns `null`. If the response doesn't concern the health checker, the response is
 * simply returned by the function.
 *
 * # How it works
 *
 * The health checker periodically calls the `system_health` JSON-RPC call in order to determine
 * the health of the chain.
 *
 * In addition to this, as long as the health check reports that `isSyncing` is `true`, the
 * health checker also maintains a subscription to new best blocks using `chain_subscribeNewHeads`.
 * Whenever a new block is notified, a health check is performed immediately in order to determine
 * whether `isSyncing` has changed to `false`.
 *
 * Thanks to this subscription, the latency of the report of the switch from `isSyncing: true` to
 * `isSyncing: false` is very low.
 *
 */
export function healthChecker() {
    // `null` if health checker is not started.
    let checker = null;
    let sendJsonRpc = null;
    return {
        setSendJsonRpc: (cb) => {
            sendJsonRpc = cb;
        },
        start: (healthCallback) => {
            if (checker !== null)
                throw new Error("Can't start the health checker multiple times in parallel");
            if (!sendJsonRpc)
                throw new Error("setSendJsonRpc must be called before starting the health checks");
            checker = new InnerChecker(healthCallback, sendJsonRpc);
            checker.startHealthCheck();
        },
        stop: () => {
            if (checker === null)
                return; // Already stopped.
            checker.destroy();
            checker = null;
        },
        sendJsonRpc: (request) => {
            if (!sendJsonRpc)
                throw new Error("setSendJsonRpc must be called before sending requests");
            if (checker === null)
                sendJsonRpc(request);
            else
                checker.sendJsonRpc(request);
        },
        responsePassThrough: (jsonRpcResponse) => {
            if (checker === null)
                return jsonRpcResponse;
            return checker.responsePassThrough(jsonRpcResponse);
        },
    };
}
class InnerChecker {
    constructor(healthCallback, requestToSmoldot) {
        _InnerChecker_healthCallback.set(this, void 0);
        _InnerChecker_currentHealthCheckId.set(this, null);
        _InnerChecker_currentHealthTimeout.set(this, null);
        _InnerChecker_currentSubunsubRequestId.set(this, null);
        _InnerChecker_currentSubscriptionId.set(this, null);
        _InnerChecker_requestToSmoldot.set(this, void 0);
        _InnerChecker_isSyncing.set(this, false);
        _InnerChecker_nextRequestId.set(this, 0);
        __classPrivateFieldSet(this, _InnerChecker_healthCallback, healthCallback, "f");
        __classPrivateFieldSet(this, _InnerChecker_requestToSmoldot, requestToSmoldot, "f");
    }
    sendJsonRpc(request) {
        // Replace the `id` in the request to prefix the request ID with `extern:`.
        let parsedRequest;
        try {
            parsedRequest = JSON.parse(request);
        }
        catch (err) {
            return;
        }
        ;
        if (parsedRequest.id) {
            const newId = 'extern:' + JSON.stringify(parsedRequest.id);
            parsedRequest.id = newId;
        }
        __classPrivateFieldGet(this, _InnerChecker_requestToSmoldot, "f").call(this, JSON.stringify(parsedRequest));
    }
    responsePassThrough(jsonRpcResponse) {
        let parsedResponse;
        try {
            parsedResponse = JSON.parse(jsonRpcResponse);
        }
        catch (err) {
            return jsonRpcResponse;
        }
        ;
        // Check whether response is a response to `system_health`.
        if (parsedResponse.id && __classPrivateFieldGet(this, _InnerChecker_currentHealthCheckId, "f") == parsedResponse.id) {
            __classPrivateFieldSet(this, _InnerChecker_currentHealthCheckId, null, "f");
            // Check whether query was successful. It is possible for queries to fail for
            // various reasons, such as the client being overloaded.
            if (!parsedResponse.result) {
                this.update();
                return null;
            }
            __classPrivateFieldGet(this, _InnerChecker_healthCallback, "f").call(this, parsedResponse.result);
            __classPrivateFieldSet(this, _InnerChecker_isSyncing, parsedResponse.result.isSyncing, "f");
            this.update();
            return null;
        }
        // Check whether response is a response to the subscription or unsubscription.
        if (parsedResponse.id && __classPrivateFieldGet(this, _InnerChecker_currentSubunsubRequestId, "f") == parsedResponse.id) {
            __classPrivateFieldSet(this, _InnerChecker_currentSubunsubRequestId, null, "f");
            // Check whether query was successful. It is possible for queries to fail for
            // various reasons, such as the client being overloaded.
            if (!parsedResponse.result) {
                this.update();
                return null;
            }
            if (__classPrivateFieldGet(this, _InnerChecker_currentSubscriptionId, "f"))
                __classPrivateFieldSet(this, _InnerChecker_currentSubscriptionId, null, "f");
            else
                __classPrivateFieldSet(this, _InnerChecker_currentSubscriptionId, parsedResponse.result, "f");
            this.update();
            return null;
        }
        // Check whether response is a notification to a subscription.
        if (parsedResponse.params && __classPrivateFieldGet(this, _InnerChecker_currentSubscriptionId, "f") &&
            parsedResponse.params.subscription == __classPrivateFieldGet(this, _InnerChecker_currentSubscriptionId, "f")) {
            // Note that after a successful subscription, a notification containing
            // the current best block is always returned. Considering that a
            // subscription is performed in response to a health check, calling
            // `startHealthCheck()` here will lead to a second health check.
            // It might seem redundant to perform two health checks in a quick
            // succession, but doing so doesn't lead to any problem, and it is
            // actually possible for the health to have changed in between as the
            // current best block might have been updated during the subscription
            // request.
            this.startHealthCheck();
            this.update();
            return null;
        }
        // Response doesn't concern us.
        if (parsedResponse.id) {
            // Need to remove the `extern:` prefix.
            if (!parsedResponse.id.startsWith('extern:'))
                throw new Error('State inconsistency in health checker');
            const newId = JSON.parse(parsedResponse.id.slice('extern:'.length));
            parsedResponse.id = newId;
        }
        return JSON.stringify(parsedResponse);
    }
    update() {
        if (!__classPrivateFieldGet(this, _InnerChecker_currentHealthTimeout, "f")) {
            __classPrivateFieldSet(this, _InnerChecker_currentHealthTimeout, setTimeout(() => {
                __classPrivateFieldSet(this, _InnerChecker_currentHealthTimeout, null, "f");
                this.startHealthCheck();
            }, 10000), "f");
        }
        if (__classPrivateFieldGet(this, _InnerChecker_isSyncing, "f") && !__classPrivateFieldGet(this, _InnerChecker_currentSubscriptionId, "f") && !__classPrivateFieldGet(this, _InnerChecker_currentSubunsubRequestId, "f"))
            this.startSubscription();
        if (!__classPrivateFieldGet(this, _InnerChecker_isSyncing, "f") && __classPrivateFieldGet(this, _InnerChecker_currentSubscriptionId, "f") && !__classPrivateFieldGet(this, _InnerChecker_currentSubunsubRequestId, "f"))
            this.endSubscription();
    }
    startHealthCheck() {
        if (__classPrivateFieldGet(this, _InnerChecker_currentHealthCheckId, "f"))
            throw new Error('Internal error in health checker');
        if (__classPrivateFieldGet(this, _InnerChecker_currentHealthTimeout, "f")) {
            clearTimeout(__classPrivateFieldGet(this, _InnerChecker_currentHealthTimeout, "f"));
            __classPrivateFieldSet(this, _InnerChecker_currentHealthTimeout, null, "f");
        }
        __classPrivateFieldSet(this, _InnerChecker_currentHealthCheckId, "health-checker:" + __classPrivateFieldGet(this, _InnerChecker_nextRequestId, "f"), "f");
        __classPrivateFieldSet(this, _InnerChecker_nextRequestId, __classPrivateFieldGet(this, _InnerChecker_nextRequestId, "f") + 1, "f");
        __classPrivateFieldGet(this, _InnerChecker_requestToSmoldot, "f").call(this, JSON.stringify({
            jsonrpc: "2.0",
            id: __classPrivateFieldGet(this, _InnerChecker_currentHealthCheckId, "f"),
            method: 'system_health',
            params: [],
        }));
    }
    startSubscription() {
        if (__classPrivateFieldGet(this, _InnerChecker_currentSubunsubRequestId, "f") || __classPrivateFieldGet(this, _InnerChecker_currentSubscriptionId, "f"))
            throw new Error('Internal error in health checker');
        __classPrivateFieldSet(this, _InnerChecker_currentSubunsubRequestId, "health-checker:" + __classPrivateFieldGet(this, _InnerChecker_nextRequestId, "f"), "f");
        __classPrivateFieldSet(this, _InnerChecker_nextRequestId, __classPrivateFieldGet(this, _InnerChecker_nextRequestId, "f") + 1, "f");
        __classPrivateFieldGet(this, _InnerChecker_requestToSmoldot, "f").call(this, JSON.stringify({
            jsonrpc: "2.0",
            id: __classPrivateFieldGet(this, _InnerChecker_currentSubunsubRequestId, "f"),
            method: 'chain_subscribeNewHeads',
            params: [],
        }));
    }
    endSubscription() {
        if (__classPrivateFieldGet(this, _InnerChecker_currentSubunsubRequestId, "f") || !__classPrivateFieldGet(this, _InnerChecker_currentSubscriptionId, "f"))
            throw new Error('Internal error in health checker');
        __classPrivateFieldSet(this, _InnerChecker_currentSubunsubRequestId, "health-checker:" + __classPrivateFieldGet(this, _InnerChecker_nextRequestId, "f"), "f");
        __classPrivateFieldSet(this, _InnerChecker_nextRequestId, __classPrivateFieldGet(this, _InnerChecker_nextRequestId, "f") + 1, "f");
        __classPrivateFieldGet(this, _InnerChecker_requestToSmoldot, "f").call(this, JSON.stringify({
            jsonrpc: "2.0",
            id: __classPrivateFieldGet(this, _InnerChecker_currentSubunsubRequestId, "f"),
            method: 'chain_unsubscribeNewHeads',
            params: [__classPrivateFieldGet(this, _InnerChecker_currentSubscriptionId, "f")],
        }));
    }
    destroy() {
        if (__classPrivateFieldGet(this, _InnerChecker_currentHealthTimeout, "f")) {
            clearTimeout(__classPrivateFieldGet(this, _InnerChecker_currentHealthTimeout, "f"));
            __classPrivateFieldSet(this, _InnerChecker_currentHealthTimeout, null, "f");
        }
    }
}
_InnerChecker_healthCallback = new WeakMap(), _InnerChecker_currentHealthCheckId = new WeakMap(), _InnerChecker_currentHealthTimeout = new WeakMap(), _InnerChecker_currentSubunsubRequestId = new WeakMap(), _InnerChecker_currentSubscriptionId = new WeakMap(), _InnerChecker_requestToSmoldot = new WeakMap(), _InnerChecker_isSyncing = new WeakMap(), _InnerChecker_nextRequestId = new WeakMap();
