Object.defineProperty(exports, "__esModule", { value: true }); var tslib_1 = require("tslib"); /* eslint-disable max-lines */ var hub_1 = require("@sentry/hub"); var types_1 = require("@sentry/types"); var utils_1 = require("@sentry/utils"); var integration_1 = require("./integration"); /** * 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 { * 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 call being processed */ this._processing = 0; this._backend = new backendClass(options); this._options = options; if (options.dsn) { this._dsn = new utils_1.Dsn(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; 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 = utils_1.isPrimitive(message) ? this._getBackend().eventFromMessage("" + 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) { 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 (!session.release) { utils_1.logger.warn('Discarded session because of missing release'); } else { this._sendSession(session); } }; /** * @inheritDoc */ BaseClient.prototype.getDsn = function () { return this._dsn; }; /** * @inheritDoc */ BaseClient.prototype.getOptions = function () { return this._options; }; /** * @inheritDoc */ BaseClient.prototype.flush = function (timeout) { var _this = this; return this._isClientProcessing(timeout).then(function (ready) { return _this._getBackend() .getTransport() .close(timeout) .then(function (transportFlushed) { return ready && 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 = integration_1.setupIntegrations(this._options); } }; /** * @inheritDoc */ BaseClient.prototype.getIntegration = function (integration) { try { return this._integrations[integration.id] || null; } catch (_oO) { utils_1.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 userAgent; var exceptions = event.exception && event.exception.values; if (exceptions) { errored = true; try { for (var exceptions_1 = tslib_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; } } } var user = event.user; if (!session.userAgent) { var headers = event.request ? event.request.headers : {}; for (var key in headers) { if (key.toLowerCase() === 'user-agent') { userAgent = headers[key]; break; } } } session.update(tslib_1.__assign(tslib_1.__assign({}, (crashed && { status: types_1.SessionStatus.Crashed })), { user: user, userAgent: userAgent, errors: session.errors + Number(errored || crashed) })); }; /** Deliver captured session to Sentry */ BaseClient.prototype._sendSession = function (session) { this._getBackend().sendSession(session); }; /** Waits for the client to be done with processing. */ BaseClient.prototype._isClientProcessing = function (timeout) { var _this = this; return new utils_1.SyncPromise(function (resolve) { var ticked = 0; var tick = 1; var interval = setInterval(function () { if (_this._processing == 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 = tslib_1.__assign(tslib_1.__assign({}, event), { event_id: event.event_id || (hint && hint.event_id ? hint.event_id : utils_1.uuid4()), timestamp: event.timestamp || utils_1.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 = hub_1.Scope.clone(finalScope).update(hint.captureContext); } // We prepare the result here with a resolved Event. var result = utils_1.SyncPromise.resolve(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 (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 = tslib_1.__assign(tslib_1.__assign(tslib_1.__assign(tslib_1.__assign(tslib_1.__assign({}, event), (event.breadcrumbs && { breadcrumbs: event.breadcrumbs.map(function (b) { return (tslib_1.__assign(tslib_1.__assign({}, b), (b.data && { data: utils_1.normalize(b.data, depth), }))); }), })), (event.user && { user: utils_1.normalize(event.user, depth), })), (event.contexts && { contexts: utils_1.normalize(event.contexts, depth), })), (event.extra && { extra: utils_1.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; } 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 = utils_1.truncate(event.message, maxValueLength); } var exception = event.exception && event.exception.values && event.exception.values[0]; if (exception && exception.value) { exception.value = utils_1.truncate(exception.value, maxValueLength); } var request = event.request; if (request && request.url) { request.url = utils_1.truncate(request.url, maxValueLength); } }; /** * This function adds all used integrations to the SDK info in the event. * @param sdkInfo The sdkInfo of the event that will be filled with all integrations. */ BaseClient.prototype._applyIntegrationsMetadata = function (event) { var sdkInfo = event.sdk; var integrationsArray = Object.keys(this._integrations); if (sdkInfo && integrationsArray.length > 0) { sdkInfo.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) { utils_1.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; if (!this._isEnabled()) { return utils_1.SyncPromise.reject(new utils_1.SentryError('SDK not enabled, will not send 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) { return utils_1.SyncPromise.reject(new utils_1.SentryError('This event has been sampled, will not send event.')); } return this._prepareEvent(event, scope, hint) .then(function (prepared) { if (prepared === null) { throw new utils_1.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); if (typeof beforeSendResult === 'undefined') { throw new utils_1.SentryError('`beforeSend` method has to return `null` or a valid event.'); } else if (utils_1.isThenable(beforeSendResult)) { return beforeSendResult.then(function (event) { return event; }, function (e) { throw new utils_1.SentryError("beforeSend rejected with " + e); }); } return beforeSendResult; }) .then(function (processedEvent) { if (processedEvent === null) { throw new utils_1.SentryError('`beforeSend` returned `null`, will not send event.'); } var session = scope && scope.getSession(); if (!isTransaction && session) { _this._updateSessionFromEvent(session, processedEvent); } _this._sendEvent(processedEvent); return processedEvent; }) .then(null, function (reason) { if (reason instanceof utils_1.SentryError) { throw reason; } _this.captureException(reason, { data: { __sentry__: true, }, originalException: reason, }); throw new utils_1.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._processing += 1; promise.then(function (value) { _this._processing -= 1; return value; }, function (reason) { _this._processing -= 1; return reason; }); }; return BaseClient; }()); exports.BaseClient = BaseClient; //# sourceMappingURL=baseclient.js.map