(function() {
  window.textsecure = window.textsecure || {};

  window.textsecure.createTaskWithTimeout = function(task, id, options) {
    options = options || {};
    options.timeout = options.timeout || 1000 * 60 * 2; // two minutes

    var errorForStack = new Error('for stack');
    return function() {
      return new Promise(function(resolve, reject) {
        var complete = false;
        var timer = setTimeout(
          function() {
            if (!complete) {
              var message =
                (id || '') +
                ' task did not complete in time. Calling stack: ' +
                errorForStack.stack;

              console.log(message);
              return reject(new Error(message));
            }
          }.bind(this),
          options.timeout
        );
        var clearTimer = function() {
          try {
            var localTimer = timer;
            if (localTimer) {
              timer = null;
              clearTimeout(localTimer);
            }
          } catch (error) {
            console.log(
              id || '',
              'task ran into problem canceling timer. Calling stack:',
              errorForStack.stack
            );
          }
        };

        var success = function(result) {
          clearTimer();
          complete = true;
          return resolve(result);
        };
        var failure = function(error) {
          clearTimer();
          complete = true;
          return reject(error);
        };

        var promise;
        try {
          promise = task();
        } catch (error) {
          clearTimer();
          throw error;
        }
        if (!promise || !promise.then) {
          clearTimer();
          complete = true;
          return resolve(promise);
        }

        return promise.then(success, failure);
      });
    };
  };
})();