"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Polymesh = void 0;
const api_1 = require("@polkadot/api");
const apollo_cache_inmemory_1 = require("apollo-cache-inmemory");
const apollo_client_1 = require("apollo-client");
const apollo_link_1 = require("apollo-link");
const apollo_link_context_1 = require("apollo-link-context");
const apollo_link_http_1 = require("apollo-link-http");
const cross_fetch_1 = __importDefault(require("cross-fetch"));
const schema_1 = __importDefault(require("./polkadot/schema"));
const semver_1 = require("semver");
const websocket_1 = require("websocket");
const websocket_as_promised_1 = __importDefault(require("websocket-as-promised"));
const AccountManagement_1 = require("./AccountManagement");
const Assets_1 = require("./Assets");
const Identities_1 = require("./Identities");
const internal_1 = require("./internal");
const queries_1 = require("./middleware/queries");
const Settlements_1 = require("./Settlements");
const types_1 = require("./types");
const constants_1 = require("./utils/constants");
const conversion_1 = require("./utils/conversion");
const Claims_1 = require("./Claims");
const Network_1 = require("./Network");
/**
 * Main entry point of the Polymesh SDK
 */
class Polymesh {
    /**
     * @hidden
     */
    constructor(context) {
        this.context = {};
        this.context = context;
        this.claims = new Claims_1.Claims(context);
        this.network = new Network_1.Network(context);
        this.settlements = new Settlements_1.Settlements(context);
        this.accountManagement = new AccountManagement_1.AccountManagement(context);
        this.identities = new Identities_1.Identities(context);
        this.assets = new Assets_1.Assets(context);
    }
    // eslint-disable-next-line require-jsdoc
    static connect(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const { nodeUrl, accountSeed, keyring, accountUri, accountMnemonic, signer, middleware } = params;
            let context;
            /* istanbul ignore next: part of configuration, doesn't need to be tested */
            const wsp = new websocket_as_promised_1.default(nodeUrl, {
                createWebSocket: url => new websocket_1.w3cwebsocket(url),
                packMessage: data => JSON.stringify(data),
                unpackMessage: data => JSON.parse(data.toString()),
                attachRequestId: (data, requestId) => Object.assign({ id: requestId }, data),
                extractRequestId: data => data && data.id,
            });
            yield wsp.open();
            const { result: version } = yield wsp.sendRequest(constants_1.SYSTEM_VERSION_RPC_CALL);
            if (!semver_1.satisfies(version, constants_1.SUPPORTED_VERSION_RANGE)) {
                throw new internal_1.PolymeshError({
                    code: types_1.ErrorCode.FatalError,
                    message: 'Unsupported Polymesh version. Please upgrade the SDK',
                    data: {
                        polymeshVersion: version,
                        supportedVersionRange: constants_1.SUPPORTED_VERSION_RANGE,
                    },
                });
            }
            yield wsp.close();
            try {
                const { types, rpc } = schema_1.default;
                const polymeshApi = yield api_1.ApiPromise.create({
                    provider: new api_1.WsProvider(nodeUrl),
                    types,
                    rpc,
                });
                let middlewareApi = null;
                if (middleware) {
                    middlewareApi = new apollo_client_1.ApolloClient({
                        link: apollo_link_context_1.setContext((_, { headers }) => {
                            return {
                                headers: Object.assign(Object.assign({}, headers), { 'x-api-key': middleware.key }),
                            };
                        }).concat(apollo_link_1.ApolloLink.from([
                            new apollo_link_http_1.HttpLink({
                                uri: middleware.link,
                                fetch: cross_fetch_1.default,
                            }),
                        ])),
                        cache: new apollo_cache_inmemory_1.InMemoryCache(),
                        defaultOptions: {
                            watchQuery: {
                                fetchPolicy: 'no-cache',
                            },
                            query: {
                                fetchPolicy: 'no-cache',
                            },
                        },
                    });
                }
                if (signer) {
                    polymeshApi.setSigner(signer);
                }
                context = yield internal_1.Context.create({
                    polymeshApi,
                    middlewareApi,
                    accountSeed,
                    accountUri,
                    accountMnemonic,
                    keyring,
                });
            }
            catch (err) {
                const { message, code } = err;
                throw new internal_1.PolymeshError({
                    code,
                    message: `Error while connecting to "${nodeUrl}": "${message || 'The node couldn’t be reached'}"`,
                });
            }
            if (middleware) {
                try {
                    yield context.queryMiddleware(queries_1.heartbeat());
                }
                catch (err) {
                    if (err.message.indexOf('Forbidden') > -1 ||
                        err.message.indexOf('Missing Authentication Token') > -1) {
                        throw new internal_1.PolymeshError({
                            code: types_1.ErrorCode.FatalError,
                            message: 'Incorrect middleware URL or API key',
                        });
                    }
                }
            }
            return new Polymesh(context);
        });
    }
    /**
     * Retrieve the Identity associated to the current Account (null if there is none)
     */
    getCurrentIdentity() {
        return this.context.getCurrentAccount().getIdentity();
    }
    /**
     * Handle connection errors
     *
     * @returns an unsubscribe callback
     */
    onConnectionError(callback) {
        const { context: { polymeshApi }, } = this;
        polymeshApi.on('error', callback);
        return () => {
            polymeshApi.off('error', callback);
        };
    }
    /**
     * Handle disconnection
     *
     * @returns an unsubscribe callback
     */
    onDisconnect(callback) {
        const { context: { polymeshApi }, } = this;
        polymeshApi.on('disconnected', callback);
        return () => {
            polymeshApi.off('disconnected', callback);
        };
    }
    /**
     * Disconnect the client and close all open connections and subscriptions
     *
     * @note the SDK will become unusable after this operation. It will throw an error when attempting to
     *   access any chain or middleware data. If you wish to continue using the SDK, you must
     *   create a new instance by calling [[connect]]
     */
    disconnect() {
        return this.context.disconnect();
    }
    // eslint-disable-next-line require-jsdoc
    addSigner(params) {
        const { context } = this;
        const { address } = this.context.addPair(params);
        return new internal_1.Account({ address }, context);
    }
    /**
     * Set the SDK's current signing key to the provided address
     *
     * @note the key must have been previously added via [[addSigner]]
     */
    setSigner(signer) {
        this.context.setPair(conversion_1.signerToString(signer));
    }
    // TODO @monitz87: remove when the dApp team no longer needs it
    /* eslint-disable @typescript-eslint/naming-convention */
    /* istanbul ignore next: only for testing purposes */
    /**
     * Polkadot client
     */
    get _polkadotApi() {
        return this.context.polymeshApi;
    }
    /* istanbul ignore next: only for testing purposes */
    /**
     * Middleware client
     */
    get _middlewareApi() {
        return this.context.middlewareApi;
    }
}
exports.Polymesh = Polymesh;
//# sourceMappingURL=Polymesh.js.map