"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));

var _fromMetadata = _interopRequireDefault(require("@polkadot/api-metadata/consts/fromMetadata"));

var _fromMetadata2 = _interopRequireDefault(require("@polkadot/api-metadata/extrinsics/fromMetadata"));

var _fromMetadata3 = _interopRequireDefault(require("@polkadot/api-metadata/storage/fromMetadata"));

var _types = require("@polkadot/types");

var _Extrinsic = require("@polkadot/types/primitive/Extrinsic/Extrinsic");

var _util = require("@polkadot/util");

var _utilCrypto = require("@polkadot/util-crypto");

var _defaults = _interopRequireDefault(require("@polkadot/util-crypto/address/defaults"));

var _Decorate = _interopRequireDefault(require("./Decorate"));

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }

const KEEPALIVE_INTERVAL = 15000;
const DEFAULT_SS58 = new _types.u32(_defaults.default.prefix); // these are override types for polkadot chains
// NOTE The SessionKeys definition for Polkadot and Substrate (OpaqueKeys
// implementation) are different. Detect Polkadot and inject the `Keys`
// definition as applicable. (3 keys in substrate vs 4 in Polkadot).

const TYPES_FOR_POLKADOT = {
  Keys: 'SessionKeysPolkadot'
}; // NOTE this is for support of old, e.g. Alex, old metadata and BlockNumber/Index
// This is detected based on metadata version, since this is what we have up-front

const TYPES_SUBSTRATE_1 = {
  BlockNumber: 'u64',
  Index: 'u64',
  EventRecord: 'EventRecord0to76',
  ValidatorPrefs: 'ValidatorPrefs0to145'
}; // Type overrides based on specific nodes

const TYPES_CHAIN = {
  // TODO Remove this once it is not needed, i.e. upgraded
  'Kusama CC1': {
    RawBabePreDigest: 'RawBabePreDigest0to159'
  }
}; // Type overrides for specific spec types as given in runtimeVersion

const TYPES_SPEC = {
  kusama: TYPES_FOR_POLKADOT,
  polkadot: TYPES_FOR_POLKADOT
};
const l = (0, _util.logger)('api/decorator');

class Init extends _Decorate.default {
  constructor(options, type, decorateMethod) {
    super(options, type, decorateMethod);
    this._healthTimer = null;

    this._onProviderConnect = async () => {
      this.emit('connected');

      this._isConnected.next(true);

      try {
        const [hasMeta, cryptoReady] = await Promise.all([this.loadMeta(), (0, _utilCrypto.cryptoWaitReady)()]);

        if (hasMeta && !this._isReady && cryptoReady) {
          this._isReady = true;
          this.emit('ready', this);
        }

        this._healthTimer = setInterval(() => {
          this._rpcCore.system.health().toPromise().catch(() => {// ignore
          });
        }, KEEPALIVE_INTERVAL);
      } catch (_error) {
        const error = new Error("FATAL: Unable to initialize the API: ".concat(_error.message));
        l.error(error);
        this.emit('error', error);
      }
    };

    this._onProviderDisconnect = () => {
      this.emit('disconnected');

      this._isConnected.next(false);

      if (this._healthTimer) {
        clearInterval(this._healthTimer);
        this._healthTimer = null;
      }
    };

    this._onProviderError = error => {
      this.emit('error', error);
    };

    if (!this.hasSubscriptions) {
      console.warn('Api will be available in a limited mode since the provider does not support subscriptions');
    } // We only register the types (global) if this is not a cloned instance.
    // Do right up-front, so we get in the user types before we are actually
    // doing anything on-chain, this ensures we have the overrides in-place


    if (!options.source && options.types) {
      this.registerTypes(options.types);
    }

    this._rpc = this.decorateRpc(this._rpcCore, this.decorateMethod);
    this._rx.rpc = this.decorateRpc(this._rpcCore, this.rxDecorateMethod);
    this._queryMulti = this.decorateMulti(this.decorateMethod);
    this._rx.queryMulti = this.decorateMulti(this.rxDecorateMethod);
    this._rx.signer = options.signer;

    this._rpcCore.provider.on('disconnected', this._onProviderDisconnect);

    this._rpcCore.provider.on('error', this._onProviderError);

    this._rpcCore.provider.on('connected', this._onProviderConnect);
  }

  async loadMeta() {
    const {
      metadata = {}
    } = this._options; // only load from on-chain if we are not a clone (default path), alternatively
    // just use the values from the source instance provided

    if (!this._options.source || !this._options.source._isReady) {
      this._runtimeMetadata = await this.metaFromChain(metadata);
    } else {
      this._runtimeMetadata = await this.metaFromSource(this._options.source);
    }

    return this.initFromMeta(this._runtimeMetadata);
  } // eslint-disable-next-line @typescript-eslint/require-await


  async metaFromSource(source) {
    this._extrinsicType = source.extrinsicVersion;
    this._runtimeVersion = source.runtimeVersion;
    this._genesisHash = source.genesisHash;
    const methods = []; // manually build a list of all available methods in this RPC, we are
    // going to filter on it to align the cloned RPC without making a call

    Object.keys(source.rpc).forEach(section => {
      Object.keys(source.rpc[section]).forEach(method => {
        methods.push("".concat(section, "_").concat(method));
      });
    });
    this.filterRpcMethods(methods);
    return source.runtimeMetadata;
  }

  async metaFromChain(optMetadata) {
    const {
      typesChain = {},
      typesSpec = {}
    } = this._options;
    const [genesisHash, runtimeVersion, chain, chainProps] = await Promise.all([this._rpcCore.chain.getBlockHash(0).toPromise(), this._rpcCore.state.getRuntimeVersion().toPromise(), this._rpcCore.system.chain().toPromise(), this._rpcCore.system.properties().toPromise()]);
    const specName = runtimeVersion.specName.toString();
    const chainName = chain.toString(); // based on the node spec & chain, inject specific type overrides

    this.registerTypes(_objectSpread({}, TYPES_SPEC[specName] || {}, {}, TYPES_CHAIN[chainName] || {}, {}, typesSpec[specName] || {}, {}, typesChain[chainName] || {})); // filter the RPC methods (this does an rpc-methods call)

    await this.filterRpc(); // retrieve metadata, either from chain  or as pass-in via options

    const metadataKey = "".concat(genesisHash, "-").concat(runtimeVersion.specVersion);
    const metadata = metadataKey in optMetadata ? new _types.Metadata(optMetadata[metadataKey]) : await this._rpcCore.state.getMetadata().toPromise(); // set our chain version & genesisHash as returned

    this._genesisHash = genesisHash;
    this._runtimeVersion = runtimeVersion; // set the global ss58Format as detected by the chain

    (0, _utilCrypto.setSS58Format)(chainProps.ss58Format.unwrapOr(DEFAULT_SS58).toNumber()); // get unique types & validate

    metadata.getUniqTypes(false);
    return metadata;
  }

  async initFromMeta(metadata) {
    // HACK-ish Old EventRecord, BlockNumber & Indexes for e.g. Alex, based on metadata version
    //   v3 = Alex
    //   v4 = v1.0 branch
    if (metadata.version <= 4) {
      this.registerTypes(TYPES_SUBSTRATE_1);
    }

    const extrinsics = (0, _fromMetadata2.default)(metadata);
    const storage = (0, _fromMetadata3.default)(metadata);
    const constants = (0, _fromMetadata.default)(metadata); // only inject if we are not a clone (global init)

    if (!this._options.source) {
      _types.GenericEvent.injectMetadata(metadata);

      _types.GenericCall.injectMetadata(metadata); // detect the extrinsic version in-use based on the last block


      const {
        block: {
          extrinsics: [firstTx]
        }
      } = await this._rpcCore.chain.getBlock().toPromise(); // If we haven't sync-ed to 1 yes, this won't have any values

      this._extrinsicType = firstTx ? firstTx.type : _Extrinsic.LATEST_EXTRINSIC_VERSION;
    }

    this._extrinsics = this.decorateExtrinsics(extrinsics, this.decorateMethod);
    this._query = this.decorateStorage(storage, this.decorateMethod);
    this._consts = constants;
    this._rx.extrinsicType = this._extrinsicType;
    this._rx.genesisHash = this._genesisHash;
    this._rx.runtimeVersion = this._runtimeVersion;
    this._rx.tx = this.decorateExtrinsics(extrinsics, this.rxDecorateMethod);
    this._rx.query = this.decorateStorage(storage, this.rxDecorateMethod);
    this._rx.consts = constants; // derive is last, since it uses the decorated rx

    this._derive = this.decorateDerive(this._rx, this.decorateMethod);
    return true;
  }

}

exports.default = Init;