lib/contract_client/utils.js

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.contractErrorPattern = exports.DEFAULT_TIMEOUT = void 0;
exports.implementsToString = implementsToString;
exports.withExponentialBackoff = withExponentialBackoff;
/**
 * The default timeout for waiting for a transaction to be included in a block.
 */
const DEFAULT_TIMEOUT = exports.DEFAULT_TIMEOUT = 10;

/**
 * Keep calling a `fn` for `timeoutInSeconds` seconds, if `keepWaitingIf` is true.
 * Returns an array of all attempts to call the function.
 */
async function withExponentialBackoff(fn, keepWaitingIf, timeoutInSeconds, exponentialFactor = 1.5, verbose = false) {
  const attempts = [];
  let count = 0;
  attempts.push(await fn());
  if (!keepWaitingIf(attempts[attempts.length - 1])) return attempts;
  const waitUntil = new Date(Date.now() + timeoutInSeconds * 1000).valueOf();
  let waitTime = 1000;
  let totalWaitTime = waitTime;
  while (Date.now() < waitUntil && keepWaitingIf(attempts[attempts.length - 1])) {
    count++;
    // Wait a beat
    if (verbose) {
      console.info(`Waiting ${waitTime}ms before trying again (bringing the total wait time to ${totalWaitTime}ms so far, of total ${timeoutInSeconds * 1000}ms)`);
    }
    await new Promise(res => setTimeout(res, waitTime));
    // Exponential backoff
    waitTime = waitTime * exponentialFactor;
    if (new Date(Date.now() + waitTime).valueOf() > waitUntil) {
      waitTime = waitUntil - Date.now();
      if (verbose) {
        console.info(`was gonna wait too long; new waitTime: ${waitTime}ms`);
      }
    }
    totalWaitTime = waitTime + totalWaitTime;
    // Try again
    attempts.push(await fn(attempts[attempts.length - 1]));
    if (verbose && keepWaitingIf(attempts[attempts.length - 1])) {
      console.info(`${count}. Called ${fn}; ${attempts.length} prev attempts. Most recent: ${JSON.stringify(attempts[attempts.length - 1], null, 2)}`);
    }
  }
  return attempts;
}

/**
 * If contracts are implemented using the `#[contracterror]` macro, then the
 * errors get included in the on-chain XDR that also describes your contract's
 * methods. Each error will have a specific number. This Regular Expression
 * matches these "expected error types" that a contract may throw, and helps
 * @{link AssembledTransaction} parse these errors.
 */
const contractErrorPattern = exports.contractErrorPattern = /Error\(Contract, #(\d+)\)/;

/**
 * A TypeScript type guard that checks if an object has a `toString` method.
 */
function implementsToString(obj) {
  return typeof obj === "object" && obj !== null && "toString" in obj;
}