baseclient.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  1. Object.defineProperty(exports, '__esModule', { value: true });
  2. const utils = require('@sentry/utils');
  3. const api = require('./api.js');
  4. const debugBuild = require('./debug-build.js');
  5. const envelope = require('./envelope.js');
  6. const exports$1 = require('./exports.js');
  7. const hub = require('./hub.js');
  8. const integration = require('./integration.js');
  9. const envelope$1 = require('./metrics/envelope.js');
  10. const session = require('./session.js');
  11. const dynamicSamplingContext = require('./tracing/dynamicSamplingContext.js');
  12. const prepareEvent = require('./utils/prepareEvent.js');
  13. const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been captured.";
  14. /**
  15. * Base implementation for all JavaScript SDK clients.
  16. *
  17. * Call the constructor with the corresponding options
  18. * specific to the client subclass. To access these options later, use
  19. * {@link Client.getOptions}.
  20. *
  21. * If a Dsn is specified in the options, it will be parsed and stored. Use
  22. * {@link Client.getDsn} to retrieve the Dsn at any moment. In case the Dsn is
  23. * invalid, the constructor will throw a {@link SentryException}. Note that
  24. * without a valid Dsn, the SDK will not send any events to Sentry.
  25. *
  26. * Before sending an event, it is passed through
  27. * {@link BaseClient._prepareEvent} to add SDK information and scope data
  28. * (breadcrumbs and context). To add more custom information, override this
  29. * method and extend the resulting prepared event.
  30. *
  31. * To issue automatically created events (e.g. via instrumentation), use
  32. * {@link Client.captureEvent}. It will prepare the event and pass it through
  33. * the callback lifecycle. To issue auto-breadcrumbs, use
  34. * {@link Client.addBreadcrumb}.
  35. *
  36. * @example
  37. * class NodeClient extends BaseClient<NodeOptions> {
  38. * public constructor(options: NodeOptions) {
  39. * super(options);
  40. * }
  41. *
  42. * // ...
  43. * }
  44. */
  45. class BaseClient {
  46. /**
  47. * A reference to a metrics aggregator
  48. *
  49. * @experimental Note this is alpha API. It may experience breaking changes in the future.
  50. */
  51. /** Options passed to the SDK. */
  52. /** The client Dsn, if specified in options. Without this Dsn, the SDK will be disabled. */
  53. /** Array of set up integrations. */
  54. /** Indicates whether this client's integrations have been set up. */
  55. /** Number of calls being processed */
  56. /** Holds flushable */
  57. // eslint-disable-next-line @typescript-eslint/ban-types
  58. /**
  59. * Initializes this client instance.
  60. *
  61. * @param options Options for the client.
  62. */
  63. constructor(options) {
  64. this._options = options;
  65. this._integrations = {};
  66. this._integrationsInitialized = false;
  67. this._numProcessing = 0;
  68. this._outcomes = {};
  69. this._hooks = {};
  70. this._eventProcessors = [];
  71. if (options.dsn) {
  72. this._dsn = utils.makeDsn(options.dsn);
  73. } else {
  74. debugBuild.DEBUG_BUILD && utils.logger.warn('No DSN provided, client will not send events.');
  75. }
  76. if (this._dsn) {
  77. const url = api.getEnvelopeEndpointWithUrlEncodedAuth(this._dsn, options);
  78. this._transport = options.transport({
  79. recordDroppedEvent: this.recordDroppedEvent.bind(this),
  80. ...options.transportOptions,
  81. url,
  82. });
  83. }
  84. }
  85. /**
  86. * @inheritDoc
  87. */
  88. // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
  89. captureException(exception, hint, scope) {
  90. // ensure we haven't captured this very object before
  91. if (utils.checkOrSetAlreadyCaught(exception)) {
  92. debugBuild.DEBUG_BUILD && utils.logger.log(ALREADY_SEEN_ERROR);
  93. return;
  94. }
  95. let eventId = hint && hint.event_id;
  96. this._process(
  97. this.eventFromException(exception, hint)
  98. .then(event => this._captureEvent(event, hint, scope))
  99. .then(result => {
  100. eventId = result;
  101. }),
  102. );
  103. return eventId;
  104. }
  105. /**
  106. * @inheritDoc
  107. */
  108. captureMessage(
  109. message,
  110. // eslint-disable-next-line deprecation/deprecation
  111. level,
  112. hint,
  113. scope,
  114. ) {
  115. let eventId = hint && hint.event_id;
  116. const eventMessage = utils.isParameterizedString(message) ? message : String(message);
  117. const promisedEvent = utils.isPrimitive(message)
  118. ? this.eventFromMessage(eventMessage, level, hint)
  119. : this.eventFromException(message, hint);
  120. this._process(
  121. promisedEvent
  122. .then(event => this._captureEvent(event, hint, scope))
  123. .then(result => {
  124. eventId = result;
  125. }),
  126. );
  127. return eventId;
  128. }
  129. /**
  130. * @inheritDoc
  131. */
  132. captureEvent(event, hint, scope) {
  133. // ensure we haven't captured this very object before
  134. if (hint && hint.originalException && utils.checkOrSetAlreadyCaught(hint.originalException)) {
  135. debugBuild.DEBUG_BUILD && utils.logger.log(ALREADY_SEEN_ERROR);
  136. return;
  137. }
  138. let eventId = hint && hint.event_id;
  139. const sdkProcessingMetadata = event.sdkProcessingMetadata || {};
  140. const capturedSpanScope = sdkProcessingMetadata.capturedSpanScope;
  141. this._process(
  142. this._captureEvent(event, hint, capturedSpanScope || scope).then(result => {
  143. eventId = result;
  144. }),
  145. );
  146. return eventId;
  147. }
  148. /**
  149. * @inheritDoc
  150. */
  151. captureSession(session$1) {
  152. if (!(typeof session$1.release === 'string')) {
  153. debugBuild.DEBUG_BUILD && utils.logger.warn('Discarded session because of missing or non-string release');
  154. } else {
  155. this.sendSession(session$1);
  156. // After sending, we set init false to indicate it's not the first occurrence
  157. session.updateSession(session$1, { init: false });
  158. }
  159. }
  160. /**
  161. * @inheritDoc
  162. */
  163. getDsn() {
  164. return this._dsn;
  165. }
  166. /**
  167. * @inheritDoc
  168. */
  169. getOptions() {
  170. return this._options;
  171. }
  172. /**
  173. * @see SdkMetadata in @sentry/types
  174. *
  175. * @return The metadata of the SDK
  176. */
  177. getSdkMetadata() {
  178. return this._options._metadata;
  179. }
  180. /**
  181. * @inheritDoc
  182. */
  183. getTransport() {
  184. return this._transport;
  185. }
  186. /**
  187. * @inheritDoc
  188. */
  189. flush(timeout) {
  190. const transport = this._transport;
  191. if (transport) {
  192. if (this.metricsAggregator) {
  193. this.metricsAggregator.flush();
  194. }
  195. return this._isClientDoneProcessing(timeout).then(clientFinished => {
  196. return transport.flush(timeout).then(transportFlushed => clientFinished && transportFlushed);
  197. });
  198. } else {
  199. return utils.resolvedSyncPromise(true);
  200. }
  201. }
  202. /**
  203. * @inheritDoc
  204. */
  205. close(timeout) {
  206. return this.flush(timeout).then(result => {
  207. this.getOptions().enabled = false;
  208. if (this.metricsAggregator) {
  209. this.metricsAggregator.close();
  210. }
  211. return result;
  212. });
  213. }
  214. /** Get all installed event processors. */
  215. getEventProcessors() {
  216. return this._eventProcessors;
  217. }
  218. /** @inheritDoc */
  219. addEventProcessor(eventProcessor) {
  220. this._eventProcessors.push(eventProcessor);
  221. }
  222. /**
  223. * This is an internal function to setup all integrations that should run on the client.
  224. * @deprecated Use `client.init()` instead.
  225. */
  226. setupIntegrations(forceInitialize) {
  227. if ((forceInitialize && !this._integrationsInitialized) || (this._isEnabled() && !this._integrationsInitialized)) {
  228. this._setupIntegrations();
  229. }
  230. }
  231. /** @inheritdoc */
  232. init() {
  233. if (this._isEnabled()) {
  234. this._setupIntegrations();
  235. }
  236. }
  237. /**
  238. * Gets an installed integration by its `id`.
  239. *
  240. * @returns The installed integration or `undefined` if no integration with that `id` was installed.
  241. * @deprecated Use `getIntegrationByName()` instead.
  242. */
  243. getIntegrationById(integrationId) {
  244. return this.getIntegrationByName(integrationId);
  245. }
  246. /**
  247. * Gets an installed integration by its name.
  248. *
  249. * @returns The installed integration or `undefined` if no integration with that `name` was installed.
  250. */
  251. getIntegrationByName(integrationName) {
  252. return this._integrations[integrationName] ;
  253. }
  254. /**
  255. * Returns the client's instance of the given integration class, it any.
  256. * @deprecated Use `getIntegrationByName()` instead.
  257. */
  258. getIntegration(integration) {
  259. try {
  260. return (this._integrations[integration.id] ) || null;
  261. } catch (_oO) {
  262. debugBuild.DEBUG_BUILD && utils.logger.warn(`Cannot retrieve integration ${integration.id} from the current Client`);
  263. return null;
  264. }
  265. }
  266. /**
  267. * @inheritDoc
  268. */
  269. addIntegration(integration$1) {
  270. const isAlreadyInstalled = this._integrations[integration$1.name];
  271. // This hook takes care of only installing if not already installed
  272. integration.setupIntegration(this, integration$1, this._integrations);
  273. // Here we need to check manually to make sure to not run this multiple times
  274. if (!isAlreadyInstalled) {
  275. integration.afterSetupIntegrations(this, [integration$1]);
  276. }
  277. }
  278. /**
  279. * @inheritDoc
  280. */
  281. sendEvent(event, hint = {}) {
  282. this.emit('beforeSendEvent', event, hint);
  283. let env = envelope.createEventEnvelope(event, this._dsn, this._options._metadata, this._options.tunnel);
  284. for (const attachment of hint.attachments || []) {
  285. env = utils.addItemToEnvelope(
  286. env,
  287. utils.createAttachmentEnvelopeItem(
  288. attachment,
  289. this._options.transportOptions && this._options.transportOptions.textEncoder,
  290. ),
  291. );
  292. }
  293. const promise = this._sendEnvelope(env);
  294. if (promise) {
  295. promise.then(sendResponse => this.emit('afterSendEvent', event, sendResponse), null);
  296. }
  297. }
  298. /**
  299. * @inheritDoc
  300. */
  301. sendSession(session) {
  302. const env = envelope.createSessionEnvelope(session, this._dsn, this._options._metadata, this._options.tunnel);
  303. // _sendEnvelope should not throw
  304. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  305. this._sendEnvelope(env);
  306. }
  307. /**
  308. * @inheritDoc
  309. */
  310. recordDroppedEvent(reason, category, _event) {
  311. // Note: we use `event` in replay, where we overwrite this hook.
  312. if (this._options.sendClientReports) {
  313. // We want to track each category (error, transaction, session, replay_event) separately
  314. // but still keep the distinction between different type of outcomes.
  315. // We could use nested maps, but it's much easier to read and type this way.
  316. // A correct type for map-based implementation if we want to go that route
  317. // would be `Partial<Record<SentryRequestType, Partial<Record<Outcome, number>>>>`
  318. // With typescript 4.1 we could even use template literal types
  319. const key = `${reason}:${category}`;
  320. debugBuild.DEBUG_BUILD && utils.logger.log(`Adding outcome: "${key}"`);
  321. // The following works because undefined + 1 === NaN and NaN is falsy
  322. this._outcomes[key] = this._outcomes[key] + 1 || 1;
  323. }
  324. }
  325. /**
  326. * @inheritDoc
  327. */
  328. captureAggregateMetrics(metricBucketItems) {
  329. debugBuild.DEBUG_BUILD && utils.logger.log(`Flushing aggregated metrics, number of metrics: ${metricBucketItems.length}`);
  330. const metricsEnvelope = envelope$1.createMetricEnvelope(
  331. metricBucketItems,
  332. this._dsn,
  333. this._options._metadata,
  334. this._options.tunnel,
  335. );
  336. // _sendEnvelope should not throw
  337. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  338. this._sendEnvelope(metricsEnvelope);
  339. }
  340. // Keep on() & emit() signatures in sync with types' client.ts interface
  341. /* eslint-disable @typescript-eslint/unified-signatures */
  342. /** @inheritdoc */
  343. /** @inheritdoc */
  344. on(hook, callback) {
  345. if (!this._hooks[hook]) {
  346. this._hooks[hook] = [];
  347. }
  348. // @ts-expect-error We assue the types are correct
  349. this._hooks[hook].push(callback);
  350. }
  351. /** @inheritdoc */
  352. /** @inheritdoc */
  353. emit(hook, ...rest) {
  354. if (this._hooks[hook]) {
  355. this._hooks[hook].forEach(callback => callback(...rest));
  356. }
  357. }
  358. /* eslint-enable @typescript-eslint/unified-signatures */
  359. /** Setup integrations for this client. */
  360. _setupIntegrations() {
  361. const { integrations } = this._options;
  362. this._integrations = integration.setupIntegrations(this, integrations);
  363. integration.afterSetupIntegrations(this, integrations);
  364. // TODO v8: We don't need this flag anymore
  365. this._integrationsInitialized = true;
  366. }
  367. /** Updates existing session based on the provided event */
  368. _updateSessionFromEvent(session$1, event) {
  369. let crashed = false;
  370. let errored = false;
  371. const exceptions = event.exception && event.exception.values;
  372. if (exceptions) {
  373. errored = true;
  374. for (const ex of exceptions) {
  375. const mechanism = ex.mechanism;
  376. if (mechanism && mechanism.handled === false) {
  377. crashed = true;
  378. break;
  379. }
  380. }
  381. }
  382. // A session is updated and that session update is sent in only one of the two following scenarios:
  383. // 1. Session with non terminal status and 0 errors + an error occurred -> Will set error count to 1 and send update
  384. // 2. Session with non terminal status and 1 error + a crash occurred -> Will set status crashed and send update
  385. const sessionNonTerminal = session$1.status === 'ok';
  386. const shouldUpdateAndSend = (sessionNonTerminal && session$1.errors === 0) || (sessionNonTerminal && crashed);
  387. if (shouldUpdateAndSend) {
  388. session.updateSession(session$1, {
  389. ...(crashed && { status: 'crashed' }),
  390. errors: session$1.errors || Number(errored || crashed),
  391. });
  392. this.captureSession(session$1);
  393. }
  394. }
  395. /**
  396. * Determine if the client is finished processing. Returns a promise because it will wait `timeout` ms before saying
  397. * "no" (resolving to `false`) in order to give the client a chance to potentially finish first.
  398. *
  399. * @param timeout The time, in ms, after which to resolve to `false` if the client is still busy. Passing `0` (or not
  400. * passing anything) will make the promise wait as long as it takes for processing to finish before resolving to
  401. * `true`.
  402. * @returns A promise which will resolve to `true` if processing is already done or finishes before the timeout, and
  403. * `false` otherwise
  404. */
  405. _isClientDoneProcessing(timeout) {
  406. return new utils.SyncPromise(resolve => {
  407. let ticked = 0;
  408. const tick = 1;
  409. const interval = setInterval(() => {
  410. if (this._numProcessing == 0) {
  411. clearInterval(interval);
  412. resolve(true);
  413. } else {
  414. ticked += tick;
  415. if (timeout && ticked >= timeout) {
  416. clearInterval(interval);
  417. resolve(false);
  418. }
  419. }
  420. }, tick);
  421. });
  422. }
  423. /** Determines whether this SDK is enabled and a transport is present. */
  424. _isEnabled() {
  425. return this.getOptions().enabled !== false && this._transport !== undefined;
  426. }
  427. /**
  428. * Adds common information to events.
  429. *
  430. * The information includes release and environment from `options`,
  431. * breadcrumbs and context (extra, tags and user) from the scope.
  432. *
  433. * Information that is already present in the event is never overwritten. For
  434. * nested objects, such as the context, keys are merged.
  435. *
  436. * @param event The original event.
  437. * @param hint May contain additional information about the original exception.
  438. * @param scope A scope containing event metadata.
  439. * @returns A new event with more information.
  440. */
  441. _prepareEvent(
  442. event,
  443. hint,
  444. scope,
  445. isolationScope = hub.getIsolationScope(),
  446. ) {
  447. const options = this.getOptions();
  448. const integrations = Object.keys(this._integrations);
  449. if (!hint.integrations && integrations.length > 0) {
  450. hint.integrations = integrations;
  451. }
  452. this.emit('preprocessEvent', event, hint);
  453. return prepareEvent.prepareEvent(options, event, hint, scope, this, isolationScope).then(evt => {
  454. if (evt === null) {
  455. return evt;
  456. }
  457. const propagationContext = {
  458. ...isolationScope.getPropagationContext(),
  459. ...(scope ? scope.getPropagationContext() : undefined),
  460. };
  461. const trace = evt.contexts && evt.contexts.trace;
  462. if (!trace && propagationContext) {
  463. const { traceId: trace_id, spanId, parentSpanId, dsc } = propagationContext;
  464. evt.contexts = {
  465. trace: {
  466. trace_id,
  467. span_id: spanId,
  468. parent_span_id: parentSpanId,
  469. },
  470. ...evt.contexts,
  471. };
  472. const dynamicSamplingContext$1 = dsc ? dsc : dynamicSamplingContext.getDynamicSamplingContextFromClient(trace_id, this, scope);
  473. evt.sdkProcessingMetadata = {
  474. dynamicSamplingContext: dynamicSamplingContext$1,
  475. ...evt.sdkProcessingMetadata,
  476. };
  477. }
  478. return evt;
  479. });
  480. }
  481. /**
  482. * Processes the event and logs an error in case of rejection
  483. * @param event
  484. * @param hint
  485. * @param scope
  486. */
  487. _captureEvent(event, hint = {}, scope) {
  488. return this._processEvent(event, hint, scope).then(
  489. finalEvent => {
  490. return finalEvent.event_id;
  491. },
  492. reason => {
  493. if (debugBuild.DEBUG_BUILD) {
  494. // If something's gone wrong, log the error as a warning. If it's just us having used a `SentryError` for
  495. // control flow, log just the message (no stack) as a log-level log.
  496. const sentryError = reason ;
  497. if (sentryError.logLevel === 'log') {
  498. utils.logger.log(sentryError.message);
  499. } else {
  500. utils.logger.warn(sentryError);
  501. }
  502. }
  503. return undefined;
  504. },
  505. );
  506. }
  507. /**
  508. * Processes an event (either error or message) and sends it to Sentry.
  509. *
  510. * This also adds breadcrumbs and context information to the event. However,
  511. * platform specific meta data (such as the User's IP address) must be added
  512. * by the SDK implementor.
  513. *
  514. *
  515. * @param event The event to send to Sentry.
  516. * @param hint May contain additional information about the original exception.
  517. * @param scope A scope containing event metadata.
  518. * @returns A SyncPromise that resolves with the event or rejects in case event was/will not be send.
  519. */
  520. _processEvent(event, hint, scope) {
  521. const options = this.getOptions();
  522. const { sampleRate } = options;
  523. const isTransaction = isTransactionEvent(event);
  524. const isError = isErrorEvent(event);
  525. const eventType = event.type || 'error';
  526. const beforeSendLabel = `before send for type \`${eventType}\``;
  527. // 1.0 === 100% events are sent
  528. // 0.0 === 0% events are sent
  529. // Sampling for transaction happens somewhere else
  530. if (isError && typeof sampleRate === 'number' && Math.random() > sampleRate) {
  531. this.recordDroppedEvent('sample_rate', 'error', event);
  532. return utils.rejectedSyncPromise(
  533. new utils.SentryError(
  534. `Discarding event because it's not included in the random sample (sampling rate = ${sampleRate})`,
  535. 'log',
  536. ),
  537. );
  538. }
  539. const dataCategory = eventType === 'replay_event' ? 'replay' : eventType;
  540. const sdkProcessingMetadata = event.sdkProcessingMetadata || {};
  541. const capturedSpanIsolationScope = sdkProcessingMetadata.capturedSpanIsolationScope;
  542. return this._prepareEvent(event, hint, scope, capturedSpanIsolationScope)
  543. .then(prepared => {
  544. if (prepared === null) {
  545. this.recordDroppedEvent('event_processor', dataCategory, event);
  546. throw new utils.SentryError('An event processor returned `null`, will not send event.', 'log');
  547. }
  548. const isInternalException = hint.data && (hint.data ).__sentry__ === true;
  549. if (isInternalException) {
  550. return prepared;
  551. }
  552. const result = processBeforeSend(options, prepared, hint);
  553. return _validateBeforeSendResult(result, beforeSendLabel);
  554. })
  555. .then(processedEvent => {
  556. if (processedEvent === null) {
  557. this.recordDroppedEvent('before_send', dataCategory, event);
  558. throw new utils.SentryError(`${beforeSendLabel} returned \`null\`, will not send event.`, 'log');
  559. }
  560. const session = scope && scope.getSession();
  561. if (!isTransaction && session) {
  562. this._updateSessionFromEvent(session, processedEvent);
  563. }
  564. // None of the Sentry built event processor will update transaction name,
  565. // so if the transaction name has been changed by an event processor, we know
  566. // it has to come from custom event processor added by a user
  567. const transactionInfo = processedEvent.transaction_info;
  568. if (isTransaction && transactionInfo && processedEvent.transaction !== event.transaction) {
  569. const source = 'custom';
  570. processedEvent.transaction_info = {
  571. ...transactionInfo,
  572. source,
  573. };
  574. }
  575. this.sendEvent(processedEvent, hint);
  576. return processedEvent;
  577. })
  578. .then(null, reason => {
  579. if (reason instanceof utils.SentryError) {
  580. throw reason;
  581. }
  582. this.captureException(reason, {
  583. data: {
  584. __sentry__: true,
  585. },
  586. originalException: reason,
  587. });
  588. throw new utils.SentryError(
  589. `Event processing pipeline threw an error, original event will not be sent. Details have been sent as a new event.\nReason: ${reason}`,
  590. );
  591. });
  592. }
  593. /**
  594. * Occupies the client with processing and event
  595. */
  596. _process(promise) {
  597. this._numProcessing++;
  598. void promise.then(
  599. value => {
  600. this._numProcessing--;
  601. return value;
  602. },
  603. reason => {
  604. this._numProcessing--;
  605. return reason;
  606. },
  607. );
  608. }
  609. /**
  610. * @inheritdoc
  611. */
  612. _sendEnvelope(envelope) {
  613. this.emit('beforeEnvelope', envelope);
  614. if (this._isEnabled() && this._transport) {
  615. return this._transport.send(envelope).then(null, reason => {
  616. debugBuild.DEBUG_BUILD && utils.logger.error('Error while sending event:', reason);
  617. });
  618. } else {
  619. debugBuild.DEBUG_BUILD && utils.logger.error('Transport disabled');
  620. }
  621. }
  622. /**
  623. * Clears outcomes on this client and returns them.
  624. */
  625. _clearOutcomes() {
  626. const outcomes = this._outcomes;
  627. this._outcomes = {};
  628. return Object.keys(outcomes).map(key => {
  629. const [reason, category] = key.split(':') ;
  630. return {
  631. reason,
  632. category,
  633. quantity: outcomes[key],
  634. };
  635. });
  636. }
  637. /**
  638. * @inheritDoc
  639. */
  640. // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
  641. }
  642. /**
  643. * Verifies that return value of configured `beforeSend` or `beforeSendTransaction` is of expected type, and returns the value if so.
  644. */
  645. function _validateBeforeSendResult(
  646. beforeSendResult,
  647. beforeSendLabel,
  648. ) {
  649. const invalidValueError = `${beforeSendLabel} must return \`null\` or a valid event.`;
  650. if (utils.isThenable(beforeSendResult)) {
  651. return beforeSendResult.then(
  652. event => {
  653. if (!utils.isPlainObject(event) && event !== null) {
  654. throw new utils.SentryError(invalidValueError);
  655. }
  656. return event;
  657. },
  658. e => {
  659. throw new utils.SentryError(`${beforeSendLabel} rejected with ${e}`);
  660. },
  661. );
  662. } else if (!utils.isPlainObject(beforeSendResult) && beforeSendResult !== null) {
  663. throw new utils.SentryError(invalidValueError);
  664. }
  665. return beforeSendResult;
  666. }
  667. /**
  668. * Process the matching `beforeSendXXX` callback.
  669. */
  670. function processBeforeSend(
  671. options,
  672. event,
  673. hint,
  674. ) {
  675. const { beforeSend, beforeSendTransaction } = options;
  676. if (isErrorEvent(event) && beforeSend) {
  677. return beforeSend(event, hint);
  678. }
  679. if (isTransactionEvent(event) && beforeSendTransaction) {
  680. return beforeSendTransaction(event, hint);
  681. }
  682. return event;
  683. }
  684. function isErrorEvent(event) {
  685. return event.type === undefined;
  686. }
  687. function isTransactionEvent(event) {
  688. return event.type === 'transaction';
  689. }
  690. /**
  691. * Add an event processor to the current client.
  692. * This event processor will run for all events processed by this client.
  693. */
  694. function addEventProcessor(callback) {
  695. const client = exports$1.getClient();
  696. if (!client || !client.addEventProcessor) {
  697. return;
  698. }
  699. client.addEventProcessor(callback);
  700. }
  701. exports.BaseClient = BaseClient;
  702. exports.addEventProcessor = addEventProcessor;
  703. //# sourceMappingURL=baseclient.js.map