text: Object.defineProperty(exports, "__esModule", { value: true });
  var tslib_1 = require("tslib");
  var is_1 = require("./is");
  var logger_1 = require("./logger");
  var misc_1 = require("./misc");
  var object_1 = require("./object");
  var supports_1 = require("./supports");
  var global = misc_1.getGlobalObject();
  /**
   * Instrument native APIs to call handlers that can be used to create breadcrumbs, APM spans etc.
   *  - Console API
   *  - Fetch API
   *  - XHR API
   *  - History API
   *  - DOM API (click/typing)
   *  - Error API
   *  - UnhandledRejection API
   */
  var handlers = {};
  var instrumented = {};
  /** Instruments given API */
  function instrument(type) {
      if (instrumented[type]) {
          return;
      }
      instrumented[type] = true;
      switch (type) {
          case 'console':
              instrumentConsole();
              break;
          case 'dom':
              instrumentDOM();
              break;
          case 'xhr':
              instrumentXHR();
              break;
          case 'fetch':
              instrumentFetch();
              break;
          case 'history':
              instrumentHistory();
              break;
          case 'error':
              instrumentError();
              break;
          case 'unhandledrejection':
              instrumentUnhandledRejection();
              break;
          default:
              logger_1.logger.warn('unknown instrumentation type:', type);
      }
  }
  /**
   * Add handler that will be called when given type of instrumentation triggers.
   * Use at your own risk, this might break without changelog notice, only used internally.
   * @hidden
   */
  function addInstrumentationHandler(handler) {
      if (!handler || typeof handler.type !== 'string' || typeof handler.callback !== 'function') {
          return;
      }
      handlers[handler.type] = handlers[handler.type] || [];
      handlers[handler.type].push(handler.callback);
      instrument(handler.type);
  }
  exports.addInstrumentationHandler = addInstrumentationHandler;
  /** JSDoc */
  function triggerHandlers(type, data) {
      var e_1, _a;
      if (!type || !handlers[type]) {
          return;
      }
      try {
          for (var _b = tslib_1.__values(handlers[type] || []), _c = _b.next(); !_c.done; _c = _b.next()) {
              var handler = _c.value;
              try {
                  handler(data);
              }
              catch (e) {
                  logger_1.logger.error("Error while triggering instrumentation handler.\nType: " + type + "\nName: " + misc_1.getFunctionName(handler) + "\nError: " + e);
              }
          }
      }
      catch (e_1_1) { e_1 = { error: e_1_1 }; }
      finally {
          try {
              if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
          }
          finally { if (e_1) throw e_1.error; }
      }
  }
  /** JSDoc */
  function instrumentConsole() {
      if (!('console' in global)) {
          return;
      }
      ['debug', 'info', 'warn', 'error', 'log', 'assert'].forEach(function (level) {
          if (!(level in global.console)) {
              return;
          }
          object_1.fill(global.console, level, function (originalConsoleLevel) {
              return function () {
                  var args = [];
                  for (var _i = 0; _i < arguments.length; _i++) {
                      args[_i] = arguments[_i];
                  }
                  triggerHandlers('console', { args: args, level: level });
                  // this fails for some browsers. :(
                  if (originalConsoleLevel) {
                      Function.prototype.apply.call(originalConsoleLevel, global.console, args);
                  }
              };
          });
      });
  }
  /** JSDoc */
  function instrumentFetch() {
      if (!supports_1.supportsNativeFetch()) {
          return;
      }
      object_1.fill(global, 'fetch', function (originalFetch) {
          return function () {
              var args = [];
              for (var _i = 0; _i < arguments.length; _i++) {
                  args[_i] = arguments[_i];
              }
              var commonHandlerData = {
                  args: args,
                  fetchData: {
                      method: getFetchMethod(args),
                      url: getFetchUrl(args),
                  },
                  startTimestamp: Date.now(),
              };
              triggerHandlers('fetch', tslib_1.__assign({}, commonHandlerData));
              // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
              return originalFetch.apply(global, args).then(function (response) {
                  triggerHandlers('fetch', tslib_1.__assign(tslib_1.__assign({}, commonHandlerData), { endTimestamp: Date.now(), response: response }));
                  return response;
              }, function (error) {
                  triggerHandlers('fetch', tslib_1.__assign(tslib_1.__assign({}, commonHandlerData), { endTimestamp: Date.now(), error: error }));
                  // NOTE: If you are a Sentry user, and you are seeing this stack frame,
                  //       it means the sentry.javascript SDK caught an error invoking your application code.
                  //       This is expected behavior and NOT indicative of a bug with sentry.javascript.
                  throw error;
              });
          };
      });
  }
  /* eslint-disable @typescript-eslint/no-unsafe-member-access */
  /** Extract `method` from fetch call arguments */
  function getFetchMethod(fetchArgs) {
      if (fetchArgs === void 0) { fetchArgs = []; }
      if ('Request' in global && is_1.isInstanceOf(fetchArgs[0], Request) && fetchArgs[0].method) {
          return String(fetchArgs[0].method).toUpperCase();
      }
      if (fetchArgs[1] && fetchArgs[1].method) {
          return String(fetchArgs[1].method).toUpperCase();
      }
      return 'GET';
  }
  /** Extract `url` from fetch call arguments */
  function getFetchUrl(fetchArgs) {
      if (fetchArgs === void 0) { fetchArgs = []; }
      if (typeof fetchArgs[0] === 'string') {
          return fetchArgs[0];
      }
      if ('Request' in global && is_1.isInstanceOf(fetchArgs[0], Request)) {
          return fetchArgs[0].url;
      }
      return String(fetchArgs[0]);
  }
  /* eslint-enable @typescript-eslint/no-unsafe-member-access */
  /** JSDoc */
  function instrumentXHR() {
      if (!('XMLHttpRequest' in global)) {
          return;
      }
      var xhrproto = XMLHttpRequest.prototype;
      object_1.fill(xhrproto, 'open', function (originalOpen) {
          return function () {
              var args = [];
              for (var _i = 0; _i < arguments.length; _i++) {
                  args[_i] = arguments[_i];
              }
              // eslint-disable-next-line @typescript-eslint/no-this-alias
              var xhr = this;
              var url = args[1];
              xhr.__sentry_xhr__ = {
                  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
                  method: is_1.isString(args[0]) ? args[0].toUpperCase() : args[0],
                  url: args[1],
              };
              // if Sentry key appears in URL, don't capture it as a request
              // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
              if (is_1.isString(url) && xhr.__sentry_xhr__.method === 'POST' && url.match(/sentry_key/)) {
                  xhr.__sentry_own_request__ = true;
              }
              var onreadystatechangeHandler = function () {
                  if (xhr.readyState === 4) {
                      try {
                          // touching statusCode in some platforms throws
                          // an exception
                          if (xhr.__sentry_xhr__) {
                              xhr.__sentry_xhr__.status_code = xhr.status;
                          }
                      }
                      catch (e) {
                          /* do nothing */
                      }
                      triggerHandlers('xhr', {
                          args: args,
                          endTimestamp: Date.now(),
                          startTimestamp: Date.now(),
                          xhr: xhr,
                      });
                  }
              };
              if ('onreadystatechange' in xhr && typeof xhr.onreadystatechange === 'function') {
                  object_1.fill(xhr, 'onreadystatechange', function (original) {
                      return function () {
                          var readyStateArgs = [];
                          for (var _i = 0; _i < arguments.length; _i++) {
                              readyStateArgs[_i] = arguments[_i];
                          }
                          onreadystatechangeHandler();
                          return original.apply(xhr, readyStateArgs);
                      };
                  });
              }
              else {
                  xhr.addEventListener('readystatechange', onreadystatechangeHandler);
              }
              return originalOpen.apply(xhr, args);
          };
      });
      object_1.fill(xhrproto, 'send', function (originalSend) {
          return function () {
              var args = [];
              for (var _i = 0; _i < arguments.length; _i++) {
                  args[_i] = arguments[_i];
              }
              triggerHandlers('xhr', {
                  args: args,
                  startTimestamp: Date.now(),
                  xhr: this,
              });
              return originalSend.apply(this, args);
          };
      });
  }
  var lastHref;
  /** JSDoc */
  function instrumentHistory() {
      if (!supports_1.supportsHistory()) {
          return;
      }
      var oldOnPopState = global.onpopstate;
      global.onpopstate = function () {
          var args = [];
          for (var _i = 0; _i < arguments.length; _i++) {
              args[_i] = arguments[_i];
          }
          var to = global.location.href;
          // keep track of the current URL state, as we always receive only the updated state
          var from = lastHref;
          lastHref = to;
          triggerHandlers('history', {
              from: from,
              to: to,
          });
          if (oldOnPopState) {
              return oldOnPopState.apply(this, args);
          }
      };
      /** @hidden */
      function historyReplacementFunction(originalHistoryFunction) {
          return function () {
              var args = [];
              for (var _i = 0; _i < arguments.length; _i++) {
                  args[_i] = arguments[_i];
              }
              var url = args.length > 2 ? args[2] : undefined;
              if (url) {
                  // coerce to string (this is what pushState does)
                  var from = lastHref;
                  var to = String(url);
                  // keep track of the current URL state, as we always receive only the updated state
                  lastHref = to;
                  triggerHandlers('history', {
                      from: from,
                      to: to,
                  });
              }
              return originalHistoryFunction.apply(this, args);
          };
      }
      object_1.fill(global.history, 'pushState', historyReplacementFunction);
      object_1.fill(global.history, 'replaceState', historyReplacementFunction);
  }
  /** JSDoc */
  function instrumentDOM() {
      if (!('document' in global)) {
          return;
      }
      // Capture breadcrumbs from any click that is unhandled / bubbled up all the way
      // to the document. Do this before we instrument addEventListener.
      global.document.addEventListener('click', domEventHandler('click', triggerHandlers.bind(null, 'dom')), false);
      global.document.addEventListener('keypress', keypressEventHandler(triggerHandlers.bind(null, 'dom')), false);
      // After hooking into document bubbled up click and keypresses events, we also hook into user handled click & keypresses.
      ['EventTarget', 'Node'].forEach(function (target) {
          /* eslint-disable @typescript-eslint/no-unsafe-member-access */
          var proto = global[target] && global[target].prototype;
          // eslint-disable-next-line no-prototype-builtins
          if (!proto || !proto.hasOwnProperty || !proto.hasOwnProperty('addEventListener')) {
              return;
          }
          /* eslint-enable @typescript-eslint/no-unsafe-member-access */
          object_1.fill(proto, 'addEventListener', function (original) {
              return function (eventName, fn, options) {
                  if (fn && fn.handleEvent) {
                      if (eventName === 'click') {
                          object_1.fill(fn, 'handleEvent', function (innerOriginal) {
                              return function (event) {
                                  domEventHandler('click', triggerHandlers.bind(null, 'dom'))(event);
                                  return innerOriginal.call(this, event);
                              };
                          });
                      }
                      if (eventName === 'keypress') {
                          object_1.fill(fn, 'handleEvent', function (innerOriginal) {
                              return function (event) {
                                  keypressEventHandler(triggerHandlers.bind(null, 'dom'))(event);
                                  return innerOriginal.call(this, event);
                              };
                          });
                      }
                  }
                  else {
                      if (eventName === 'click') {
                          domEventHandler('click', triggerHandlers.bind(null, 'dom'), true)(this);
                      }
                      if (eventName === 'keypress') {
                          keypressEventHandler(triggerHandlers.bind(null, 'dom'))(this);
                      }
                  }
                  return original.call(this, eventName, fn, options);
              };
          });
          object_1.fill(proto, 'removeEventListener', function (original) {
              return function (eventName, fn, options) {
                  try {
                      original.call(this, eventName, fn.__sentry_wrapped__, options);
                  }
                  catch (e) {
                      // ignore, accessing __sentry_wrapped__ will throw in some Selenium environments
                  }
                  return original.call(this, eventName, fn, options);
              };
          });
      });
  }
  var debounceDuration = 1000;
  var debounceTimer = 0;
  var keypressTimeout;
  var lastCapturedEvent;
  /**
   * Wraps addEventListener to capture UI breadcrumbs
   * @param name the event name (e.g. "click")
   * @param handler function that will be triggered
   * @param debounce decides whether it should wait till another event loop
   * @returns wrapped breadcrumb events handler
   * @hidden
   */
  function domEventHandler(name, handler, debounce) {
      if (debounce === void 0) { debounce = false; }
      return function (event) {
          // reset keypress timeout; e.g. triggering a 'click' after
          // a 'keypress' will reset the keypress debounce so that a new
          // set of keypresses can be recorded
          keypressTimeout = undefined;
          // It's possible this handler might trigger multiple times for the same
          // event (e.g. event propagation through node ancestors). Ignore if we've
          // already captured the event.
          if (!event || lastCapturedEvent === event) {
              return;
          }
          lastCapturedEvent = event;
          if (debounceTimer) {
              clearTimeout(debounceTimer);
          }
          if (debounce) {
              debounceTimer = setTimeout(function () {
                  handler({ event: event, name: name });
              });
          }
          else {
              handler({ event: event, name: name });
          }
      };
  }
  /**
   * Wraps addEventListener to capture keypress UI events
   * @param handler function that will be triggered
   * @returns wrapped keypress events handler
   * @hidden
   */
  function keypressEventHandler(handler) {
      // TODO: if somehow user switches keypress target before
      //       debounce timeout is triggered, we will only capture
      //       a single breadcrumb from the FIRST target (acceptable?)
      return function (event) {
          var target;
          try {
              target = event.target;
          }
          catch (e) {
              // just accessing event properties can throw an exception in some rare circumstances
              // see: https://github.com/getsentry/raven-js/issues/838
              return;
          }
          var tagName = target && target.tagName;
          // only consider keypress events on actual input elements
          // this will disregard keypresses targeting body (e.g. tabbing
          // through elements, hotkeys, etc)
          if (!tagName || (tagName !== 'INPUT' && tagName !== 'TEXTAREA' && !target.isContentEditable)) {
              return;
          }
          // record first keypress in a series, but ignore subsequent
          // keypresses until debounce clears
          if (!keypressTimeout) {
              domEventHandler('input', handler)(event);
          }
          clearTimeout(keypressTimeout);
          keypressTimeout = setTimeout(function () {
              keypressTimeout = undefined;
          }, debounceDuration);
      };
  }
  var _oldOnErrorHandler = null;
  /** JSDoc */
  function instrumentError() {
      _oldOnErrorHandler = global.onerror;
      global.onerror = function (msg, url, line, column, error) {
          triggerHandlers('error', {
              column: column,
              error: error,
              line: line,
              msg: msg,
              url: url,
          });
          if (_oldOnErrorHandler) {
              // eslint-disable-next-line prefer-rest-params
              return _oldOnErrorHandler.apply(this, arguments);
          }
          return false;
      };
  }
  var _oldOnUnhandledRejectionHandler = null;
  /** JSDoc */
  function instrumentUnhandledRejection() {
      _oldOnUnhandledRejectionHandler = global.onunhandledrejection;
      global.onunhandledrejection = function (e) {
          triggerHandlers('unhandledrejection', e);
          if (_oldOnUnhandledRejectionHandler) {
              // eslint-disable-next-line prefer-rest-params
              return _oldOnUnhandledRejectionHandler.apply(this, arguments);
          }
          return true;
      };
  }
  //# sourceMappingURL=instrument.js.map