text: import { __assign, __read, __spread, __values } from "tslib";
  /* eslint-disable max-lines */
  import { Scope } from '@sentry/hub';
  import { checkOrSetAlreadyCaught, dateTimestampInSeconds, isDebugBuild, isPlainObject, isPrimitive, isThenable, logger, makeDsn, normalize, rejectedSyncPromise, resolvedSyncPromise, SentryError, SyncPromise, truncate, uuid4, } from '@sentry/utils';
  import { setupIntegrations } from './integration';
  var ALREADY_SEEN_ERROR = "Not capturing exception because it's already been captured.";
  /**
   * Base implementation for all JavaScript SDK clients.
   *
   * Call the constructor with the corresponding backend constructor and options
   * specific to the client subclass. To access these options later, use
   * {@link Client.getOptions}. Also, the Backend instance is available via
   * {@link Client.getBackend}.
   *
   * If a Dsn is specified in the options, it will be parsed and stored. Use
   * {@link Client.getDsn} to retrieve the Dsn at any moment. In case the Dsn is
   * invalid, the constructor will throw a {@link SentryException}. Note that
   * without a valid Dsn, the SDK will not send any events to Sentry.
   *
   * Before sending an event via the backend, it is passed through
   * {@link BaseClient._prepareEvent} to add SDK information and scope data
   * (breadcrumbs and context). To add more custom information, override this
   * method and extend the resulting prepared event.
   *
   * To issue automatically created events (e.g. via instrumentation), use
   * {@link Client.captureEvent}. It will prepare the event and pass it through
   * the callback lifecycle. To issue auto-breadcrumbs, use
   * {@link Client.addBreadcrumb}.
   *
   * @example
   * class NodeClient extends BaseClient<NodeBackend, NodeOptions> {
   *   public constructor(options: NodeOptions) {
   *     super(NodeBackend, options);
   *   }
   *
   *   // ...
   * }
   */
  var BaseClient = /** @class */ (function () {
      /**
       * Initializes this client instance.
       *
       * @param backendClass A constructor function to create the backend.
       * @param options Options for the client.
       */
      function BaseClient(backendClass, options) {
          /** Array of used integrations. */
          this._integrations = {};
          /** Number of calls being processed */
          this._numProcessing = 0;
          this._backend = new backendClass(options);
          this._options = options;
          if (options.dsn) {
              this._dsn = makeDsn(options.dsn);
          }
      }
      /**
       * @inheritDoc
       */
      // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
      BaseClient.prototype.captureException = function (exception, hint, scope) {
          var _this = this;
          // ensure we haven't captured this very object before
          if (checkOrSetAlreadyCaught(exception)) {
              logger.log(ALREADY_SEEN_ERROR);
              return;
          }
          var eventId = hint && hint.event_id;
          this._process(this._getBackend()
              .eventFromException(exception, hint)
              .then(function (event) { return _this._captureEvent(event, hint, scope); })
              .then(function (result) {
              eventId = result;
          }));
          return eventId;
      };
      /**
       * @inheritDoc
       */
      BaseClient.prototype.captureMessage = function (message, level, hint, scope) {
          var _this = this;
          var eventId = hint && hint.event_id;
          var promisedEvent = isPrimitive(message)
              ? this._getBackend().eventFromMessage(String(message), level, hint)
              : this._getBackend().eventFromException(message, hint);
          this._process(promisedEvent
              .then(function (event) { return _this._captureEvent(event, hint, scope); })
              .then(function (result) {
              eventId = result;
          }));
          return eventId;
      };
      /**
       * @inheritDoc
       */
      BaseClient.prototype.captureEvent = function (event, hint, scope) {
          // ensure we haven't captured this very object before
          if (hint && hint.originalException && checkOrSetAlreadyCaught(hint.originalException)) {
              logger.log(ALREADY_SEEN_ERROR);
              return;
          }
          var eventId = hint && hint.event_id;
          this._process(this._captureEvent(event, hint, scope).then(function (result) {
              eventId = result;
          }));
          return eventId;
      };
      /**
       * @inheritDoc
       */
      BaseClient.prototype.captureSession = function (session) {
          if (!this._isEnabled()) {
              if (isDebugBuild()) {
                  logger.warn('SDK not enabled, will not capture session.');
              }
              return;
          }
          if (!(typeof session.release === 'string')) {
              if (isDebugBuild()) {
                  logger.warn('Discarded session because of missing or non-string release');
              }
          }
          else {
              this._sendSession(session);
              // After sending, we set init false to indicate it's not the first occurrence
              session.update({ init: false });
          }
      };
      /**
       * @inheritDoc
       */
      BaseClient.prototype.getDsn = function () {
          return this._dsn;
      };
      /**
       * @inheritDoc
       */
      BaseClient.prototype.getOptions = function () {
          return this._options;
      };
      /**
       * @inheritDoc
       */
      BaseClient.prototype.getTransport = function () {
          return this._getBackend().getTransport();
      };
      /**
       * @inheritDoc
       */
      BaseClient.prototype.flush = function (timeout) {
          var _this = this;
          return this._isClientDoneProcessing(timeout).then(function (clientFinished) {
              return _this.getTransport()
                  .close(timeout)
                  .then(function (transportFlushed) { return clientFinished && transportFlushed; });
          });
      };
      /**
       * @inheritDoc
       */
      BaseClient.prototype.close = function (timeout) {
          var _this = this;
          return this.flush(timeout).then(function (result) {
              _this.getOptions().enabled = false;
              return result;
          });
      };
      /**
       * Sets up the integrations
       */
      BaseClient.prototype.setupIntegrations = function () {
          if (this._isEnabled() && !this._integrations.initialized) {
              this._integrations = setupIntegrations(this._options);
          }
      };
      /**
       * @inheritDoc
       */
      BaseClient.prototype.getIntegration = function (integration) {
          try {
              return this._integrations[integration.id] || null;
          }
          catch (_oO) {
              logger.warn("Cannot retrieve integration " + integration.id + " from the current Client");
              return null;
          }
      };
      /** Updates existing session based on the provided event */
      BaseClient.prototype._updateSessionFromEvent = function (session, event) {
          var e_1, _a;
          var crashed = false;
          var errored = false;
          var exceptions = event.exception && event.exception.values;
          if (exceptions) {
              errored = true;
              try {
                  for (var exceptions_1 = __values(exceptions), exceptions_1_1 = exceptions_1.next(); !exceptions_1_1.done; exceptions_1_1 = exceptions_1.next()) {
                      var ex = exceptions_1_1.value;
                      var mechanism = ex.mechanism;
                      if (mechanism && mechanism.handled === false) {
                          crashed = true;
                          break;
                      }
                  }
              }
              catch (e_1_1) { e_1 = { error: e_1_1 }; }
              finally {
                  try {
                      if (exceptions_1_1 && !exceptions_1_1.done && (_a = exceptions_1.return)) _a.call(exceptions_1);
                  }
                  finally { if (e_1) throw e_1.error; }
              }
          }
          // A session is updated and that session update is sent in only one of the two following scenarios:
          // 1. Session with non terminal status and 0 errors + an error occurred -> Will set error count to 1 and send update
          // 2. Session with non terminal status and 1 error + a crash occurred -> Will set status crashed and send update
          var sessionNonTerminal = session.status === 'ok';
          var shouldUpdateAndSend = (sessionNonTerminal && session.errors === 0) || (sessionNonTerminal && crashed);
          if (shouldUpdateAndSend) {
              session.update(__assign(__assign({}, (crashed && { status: 'crashed' })), { errors: session.errors || Number(errored || crashed) }));
              this.captureSession(session);
          }
      };
      /** Deliver captured session to Sentry */
      BaseClient.prototype._sendSession = function (session) {
          this._getBackend().sendSession(session);
      };
      /**
       * Determine if the client is finished processing. Returns a promise because it will wait `timeout` ms before saying
       * "no" (resolving to `false`) in order to give the client a chance to potentially finish first.
       *
       * @param timeout The time, in ms, after which to resolve to `false` if the client is still busy. Passing `0` (or not
       * passing anything) will make the promise wait as long as it takes for processing to finish before resolving to
       * `true`.
       * @returns A promise which will resolve to `true` if processing is already done or finishes before the timeout, and
       * `false` otherwise
       */
      BaseClient.prototype._isClientDoneProcessing = function (timeout) {
          var _this = this;
          return new SyncPromise(function (resolve) {
              var ticked = 0;
              var tick = 1;
              var interval = setInterval(function () {
                  if (_this._numProcessing == 0) {
                      clearInterval(interval);
                      resolve(true);
                  }
                  else {
                      ticked += tick;
                      if (timeout && ticked >= timeout) {
                          clearInterval(interval);
                          resolve(false);
                      }
                  }
              }, tick);
          });
      };
      /** Returns the current backend. */
      BaseClient.prototype._getBackend = function () {
          return this._backend;
      };
      /** Determines whether this SDK is enabled and a valid Dsn is present. */
      BaseClient.prototype._isEnabled = function () {
          return this.getOptions().enabled !== false && this._dsn !== undefined;
      };
      /**
       * Adds common information to events.
       *
       * The information includes release and environment from `options`,
       * breadcrumbs and context (extra, tags and user) from the scope.
       *
       * Information that is already present in the event is never overwritten. For
       * nested objects, such as the context, keys are merged.
       *
       * @param event The original event.
       * @param hint May contain additional information about the original exception.
       * @param scope A scope containing event metadata.
       * @returns A new event with more information.
       */
      BaseClient.prototype._prepareEvent = function (event, scope, hint) {
          var _this = this;
          var _a = this.getOptions().normalizeDepth, normalizeDepth = _a === void 0 ? 3 : _a;
          var prepared = __assign(__assign({}, event), { event_id: event.event_id || (hint && hint.event_id ? hint.event_id : uuid4()), timestamp: event.timestamp || dateTimestampInSeconds() });
          this._applyClientOptions(prepared);
          this._applyIntegrationsMetadata(prepared);
          // If we have scope given to us, use it as the base for further modifications.
          // This allows us to prevent unnecessary copying of data if `captureContext` is not provided.
          var finalScope = scope;
          if (hint && hint.captureContext) {
              finalScope = Scope.clone(finalScope).update(hint.captureContext);
          }
          // We prepare the result here with a resolved Event.
          var result = resolvedSyncPromise(prepared);
          // This should be the last thing called, since we want that
          // {@link Hub.addEventProcessor} gets the finished prepared event.
          if (finalScope) {
              // In case we have a hub we reassign it.
              result = finalScope.applyToEvent(prepared, hint);
          }
          return result.then(function (evt) {
              if (evt) {
                  // TODO this is more of the hack trying to solve https://github.com/getsentry/sentry-javascript/issues/2809
                  // it is only attached as extra data to the event if the event somehow skips being normalized
                  evt.sdkProcessingMetadata = __assign(__assign({}, evt.sdkProcessingMetadata), { normalizeDepth: normalize(normalizeDepth) });
              }
              if (typeof normalizeDepth === 'number' && normalizeDepth > 0) {
                  return _this._normalizeEvent(evt, normalizeDepth);
              }
              return evt;
          });
      };
      /**
       * Applies `normalize` function on necessary `Event` attributes to make them safe for serialization.
       * Normalized keys:
       * - `breadcrumbs.data`
       * - `user`
       * - `contexts`
       * - `extra`
       * @param event Event
       * @returns Normalized event
       */
      BaseClient.prototype._normalizeEvent = function (event, depth) {
          if (!event) {
              return null;
          }
          var normalized = __assign(__assign(__assign(__assign(__assign({}, event), (event.breadcrumbs && {
              breadcrumbs: event.breadcrumbs.map(function (b) { return (__assign(__assign({}, b), (b.data && {
                  data: normalize(b.data, depth),
              }))); }),
          })), (event.user && {
              user: normalize(event.user, depth),
          })), (event.contexts && {
              contexts: normalize(event.contexts, depth),
          })), (event.extra && {
              extra: normalize(event.extra, depth),
          }));
          // event.contexts.trace stores information about a Transaction. Similarly,
          // event.spans[] stores information about child Spans. Given that a
          // Transaction is conceptually a Span, normalization should apply to both
          // Transactions and Spans consistently.
          // For now the decision is to skip normalization of Transactions and Spans,
          // so this block overwrites the normalized event to add back the original
          // Transaction information prior to normalization.
          if (event.contexts && event.contexts.trace) {
              // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
              normalized.contexts.trace = event.contexts.trace;
          }
          event.sdkProcessingMetadata = __assign(__assign({}, event.sdkProcessingMetadata), { baseClientNormalized: true });
          return normalized;
      };
      /**
       *  Enhances event using the client configuration.
       *  It takes care of all "static" values like environment, release and `dist`,
       *  as well as truncating overly long values.
       * @param event event instance to be enhanced
       */
      BaseClient.prototype._applyClientOptions = function (event) {
          var options = this.getOptions();
          var environment = options.environment, release = options.release, dist = options.dist, _a = options.maxValueLength, maxValueLength = _a === void 0 ? 250 : _a;
          if (!('environment' in event)) {
              event.environment = 'environment' in options ? environment : 'production';
          }
          if (event.release === undefined && release !== undefined) {
              event.release = release;
          }
          if (event.dist === undefined && dist !== undefined) {
              event.dist = dist;
          }
          if (event.message) {
              event.message = truncate(event.message, maxValueLength);
          }
          var exception = event.exception && event.exception.values && event.exception.values[0];
          if (exception && exception.value) {
              exception.value = truncate(exception.value, maxValueLength);
          }
          var request = event.request;
          if (request && request.url) {
              request.url = truncate(request.url, maxValueLength);
          }
      };
      /**
       * This function adds all used integrations to the SDK info in the event.
       * @param event The event that will be filled with all integrations.
       */
      BaseClient.prototype._applyIntegrationsMetadata = function (event) {
          var integrationsArray = Object.keys(this._integrations);
          if (integrationsArray.length > 0) {
              event.sdk = event.sdk || {};
              event.sdk.integrations = __spread((event.sdk.integrations || []), integrationsArray);
          }
      };
      /**
       * Tells the backend to send this event
       * @param event The Sentry event to send
       */
      BaseClient.prototype._sendEvent = function (event) {
          this._getBackend().sendEvent(event);
      };
      /**
       * Processes the event and logs an error in case of rejection
       * @param event
       * @param hint
       * @param scope
       */
      BaseClient.prototype._captureEvent = function (event, hint, scope) {
          return this._processEvent(event, hint, scope).then(function (finalEvent) {
              return finalEvent.event_id;
          }, function (reason) {
              logger.error(reason);
              return undefined;
          });
      };
      /**
       * Processes an event (either error or message) and sends it to Sentry.
       *
       * This also adds breadcrumbs and context information to the event. However,
       * platform specific meta data (such as the User's IP address) must be added
       * by the SDK implementor.
       *
       *
       * @param event The event to send to Sentry.
       * @param hint May contain additional information about the original exception.
       * @param scope A scope containing event metadata.
       * @returns A SyncPromise that resolves with the event or rejects in case event was/will not be send.
       */
      BaseClient.prototype._processEvent = function (event, hint, scope) {
          var _this = this;
          // eslint-disable-next-line @typescript-eslint/unbound-method
          var _a = this.getOptions(), beforeSend = _a.beforeSend, sampleRate = _a.sampleRate;
          var transport = this.getTransport();
          function recordLostEvent(outcome, category) {
              if (transport.recordLostEvent) {
                  transport.recordLostEvent(outcome, category);
              }
          }
          if (!this._isEnabled()) {
              return rejectedSyncPromise(new SentryError('SDK not enabled, will not capture event.'));
          }
          var isTransaction = event.type === 'transaction';
          // 1.0 === 100% events are sent
          // 0.0 === 0% events are sent
          // Sampling for transaction happens somewhere else
          if (!isTransaction && typeof sampleRate === 'number' && Math.random() > sampleRate) {
              recordLostEvent('sample_rate', 'event');
              return rejectedSyncPromise(new SentryError("Discarding event because it's not included in the random sample (sampling rate = " + sampleRate + ")"));
          }
          return this._prepareEvent(event, scope, hint)
              .then(function (prepared) {
              if (prepared === null) {
                  recordLostEvent('event_processor', event.type || 'event');
                  throw new SentryError('An event processor returned null, will not send event.');
              }
              var isInternalException = hint && hint.data && hint.data.__sentry__ === true;
              if (isInternalException || isTransaction || !beforeSend) {
                  return prepared;
              }
              var beforeSendResult = beforeSend(prepared, hint);
              return _ensureBeforeSendRv(beforeSendResult);
          })
              .then(function (processedEvent) {
              if (processedEvent === null) {
                  recordLostEvent('before_send', event.type || 'event');
                  throw new SentryError('`beforeSend` returned `null`, will not send event.');
              }
              var session = scope && scope.getSession && scope.getSession();
              if (!isTransaction && session) {
                  _this._updateSessionFromEvent(session, processedEvent);
              }
              _this._sendEvent(processedEvent);
              return processedEvent;
          })
              .then(null, function (reason) {
              if (reason instanceof SentryError) {
                  throw reason;
              }
              _this.captureException(reason, {
                  data: {
                      __sentry__: true,
                  },
                  originalException: reason,
              });
              throw new SentryError("Event processing pipeline threw an error, original event will not be sent. Details have been sent as a new event.\nReason: " + reason);
          });
      };
      /**
       * Occupies the client with processing and event
       */
      BaseClient.prototype._process = function (promise) {
          var _this = this;
          this._numProcessing += 1;
          void promise.then(function (value) {
              _this._numProcessing -= 1;
              return value;
          }, function (reason) {
              _this._numProcessing -= 1;
              return reason;
          });
      };
      return BaseClient;
  }());
  export { BaseClient };
  /**
   * Verifies that return value of configured `beforeSend` is of expected type.
   */
  function _ensureBeforeSendRv(rv) {
      var nullErr = '`beforeSend` method has to return `null` or a valid event.';
      if (isThenable(rv)) {
          return rv.then(function (event) {
              if (!(isPlainObject(event) || event === null)) {
                  throw new SentryError(nullErr);
              }
              return event;
          }, function (e) {
              throw new SentryError("beforeSend rejected with " + e);
          });
      }
      else if (!(isPlainObject(rv) || rv === null)) {
          throw new SentryError(nullErr);
      }
      return rv;
  }
  //# sourceMappingURL=baseclient.js.map