"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var types_1 = require("@sentry/types");
var async_1 = require("@sentry/utils/async");
var logger_1 = require("@sentry/utils/logger");
var misc_1 = require("@sentry/utils/misc");
var string_1 = require("@sentry/utils/string");
var dsn_1 = require("./dsn");
var integration_1 = require("./integration");
/**
 * Default maximum number of breadcrumbs added to an event. Can be overwritten
 * with {@link Options.maxBreadcrumbs}.
 */
var DEFAULT_BREADCRUMBS = 30;
/**
 * Absolute maximum number of breadcrumbs added to an event. The
 * `maxBreadcrumbs` option cannot be higher than this value.
 */
var MAX_BREADCRUMBS = 100;
/**
 * By default, truncates URL values to 250 chars
 */
var MAX_URL_LENGTH = 250;
/**
 * 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) {
        this.backend = new backendClass(options);
        this.options = options;
        if (options.dsn) {
            this.dsn = new dsn_1.Dsn(options.dsn);
        }
        // We have to setup the integrations in the constructor since we do not want
        // that anyone needs to call client.install();
        this.integrations = integration_1.setupIntegrations(this.options);
    }
    /**
     * @inheritDoc
     */
    BaseClient.prototype.install = function () {
        if (!this.isEnabled()) {
            return (this.installed = false);
        }
        var backend = this.getBackend();
        if (!this.installed && backend.install) {
            backend.install();
        }
        return (this.installed = true);
    };
    /**
     * Internal helper function to buffer promises.
     *
     * @param promise Any promise, but in this case Promise<SentryResponse>.
     */
    BaseClient.prototype.buffer = function (promise) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            return tslib_1.__generator(this, function (_a) {
                return [2 /*return*/, this.getBackend()
                        .getBuffer()
                        .add(promise)];
            });
        });
    };
    /**
     * @inheritDoc
     */
    BaseClient.prototype.captureException = function (exception, hint, scope) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var _this = this;
            return tslib_1.__generator(this, function (_a) {
                return [2 /*return*/, this.buffer((function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
                        var event;
                        return tslib_1.__generator(this, function (_a) {
                            switch (_a.label) {
                                case 0: return [4 /*yield*/, this.getBackend().eventFromException(exception, hint)];
                                case 1:
                                    event = _a.sent();
                                    return [2 /*return*/, this.captureEvent(event, hint, scope)];
                            }
                        });
                    }); })())];
            });
        });
    };
    /**
     * @inheritDoc
     */
    BaseClient.prototype.captureMessage = function (message, level, hint, scope) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var _this = this;
            return tslib_1.__generator(this, function (_a) {
                return [2 /*return*/, this.buffer((function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
                        var event;
                        return tslib_1.__generator(this, function (_a) {
                            switch (_a.label) {
                                case 0: return [4 /*yield*/, this.getBackend().eventFromMessage(message, level, hint)];
                                case 1:
                                    event = _a.sent();
                                    return [2 /*return*/, this.captureEvent(event, hint, scope)];
                            }
                        });
                    }); })())];
            });
        });
    };
    /**
     * @inheritDoc
     */
    BaseClient.prototype.captureEvent = function (event, hint, scope) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var _this = this;
            return tslib_1.__generator(this, function (_a) {
                // Adding this here is technically not correct since if you call captureMessage/captureException it's already
                // buffered. But since we not really need the count and we only need to know if the buffer is full or not,
                // This is fine...
                return [2 /*return*/, this.buffer((function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
                        var _this = this;
                        return tslib_1.__generator(this, function (_a) {
                            return [2 /*return*/, this.processEvent(event, function (finalEvent) { return tslib_1.__awaiter(_this, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) {
                                    return [2 /*return*/, this.getBackend().sendEvent(finalEvent)];
                                }); }); }, hint, scope)];
                        });
                    }); })())];
            });
        });
    };
    /**
     * @inheritDoc
     */
    BaseClient.prototype.addBreadcrumb = function (breadcrumb, hint, scope) {
        var _a = this.getOptions(), beforeBreadcrumb = _a.beforeBreadcrumb, _b = _a.maxBreadcrumbs, maxBreadcrumbs = _b === void 0 ? DEFAULT_BREADCRUMBS : _b;
        if (maxBreadcrumbs <= 0) {
            return;
        }
        var timestamp = new Date().getTime() / 1000;
        var mergedBreadcrumb = tslib_1.__assign({ timestamp: timestamp }, breadcrumb);
        var finalBreadcrumb = beforeBreadcrumb
            ? misc_1.consoleSandbox(function () { return beforeBreadcrumb(mergedBreadcrumb, hint); })
            : mergedBreadcrumb;
        if (finalBreadcrumb === null) {
            return;
        }
        if (this.getBackend().storeBreadcrumb(finalBreadcrumb) && scope) {
            scope.addBreadcrumb(finalBreadcrumb, Math.min(maxBreadcrumbs, MAX_BREADCRUMBS));
        }
    };
    /**
     * @inheritDoc
     */
    BaseClient.prototype.getDsn = function () {
        return this.dsn;
    };
    /**
     * @inheritDoc
     */
    BaseClient.prototype.getOptions = function () {
        return this.options;
    };
    /** 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 informartion 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) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var _a, environment, _b, maxBreadcrumbs, release, dist, prepared, exception, request;
            return tslib_1.__generator(this, function (_c) {
                _a = this.getOptions(), environment = _a.environment, _b = _a.maxBreadcrumbs, maxBreadcrumbs = _b === void 0 ? DEFAULT_BREADCRUMBS : _b, release = _a.release, dist = _a.dist;
                prepared = tslib_1.__assign({}, event);
                if (prepared.environment === undefined && environment !== undefined) {
                    prepared.environment = environment;
                }
                if (prepared.release === undefined && release !== undefined) {
                    prepared.release = release;
                }
                if (prepared.dist === undefined && dist !== undefined) {
                    prepared.dist = dist;
                }
                if (prepared.message) {
                    prepared.message = string_1.truncate(prepared.message, MAX_URL_LENGTH);
                }
                exception = prepared.exception && prepared.exception.values && prepared.exception.values[0];
                if (exception && exception.value) {
                    exception.value = string_1.truncate(exception.value, MAX_URL_LENGTH);
                }
                request = prepared.request;
                if (request && request.url) {
                    request.url = string_1.truncate(request.url, MAX_URL_LENGTH);
                }
                if (prepared.event_id === undefined) {
                    prepared.event_id = misc_1.uuid4();
                }
                // This should be the last thing called, since we want that
                // {@link Hub.addEventProcessor} gets the finished prepared event.
                if (scope) {
                    return [2 /*return*/, scope.applyToEvent(prepared, hint, Math.min(maxBreadcrumbs, MAX_BREADCRUMBS))];
                }
                return [2 /*return*/, prepared];
            });
        });
    };
    /**
     * 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.
     *
     * The returned event status offers clues to whether the event was sent to
     * Sentry and accepted there. If the {@link Options.shouldSend} hook returns
     * `false`, the status will be {@link SendStatus.Skipped}. If the rate limit
     * was exceeded, the status will be {@link SendStatus.RateLimit}.
     *
     * @param event The event to send to Sentry.
     * @param send A function to actually send the event.
     * @param scope A scope containing event metadata.
     * @param hint May contain additional informartion about the original exception.
     * @returns A Promise that resolves with the event status.
     */
    BaseClient.prototype.processEvent = function (event, send, hint, scope) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var _a, beforeSend, sampleRate, prepared, finalEvent, isInternalException, exception_1, response;
            return tslib_1.__generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        if (!this.isEnabled()) {
                            return [2 /*return*/, {
                                    status: types_1.Status.Skipped,
                                }];
                        }
                        _a = this.getOptions(), beforeSend = _a.beforeSend, sampleRate = _a.sampleRate;
                        // 1.0 === 100% events are sent
                        // 0.0 === 0% events are sent
                        if (typeof sampleRate === 'number' && Math.random() > sampleRate) {
                            return [2 /*return*/, {
                                    status: types_1.Status.Skipped,
                                }];
                        }
                        return [4 /*yield*/, this.prepareEvent(event, scope, hint)];
                    case 1:
                        prepared = _b.sent();
                        if (prepared === null) {
                            return [2 /*return*/, {
                                    status: types_1.Status.Skipped,
                                }];
                        }
                        finalEvent = prepared;
                        _b.label = 2;
                    case 2:
                        _b.trys.push([2, 5, , 6]);
                        isInternalException = hint && hint.data && hint.data.__sentry__ === true;
                        if (!(!isInternalException && beforeSend)) return [3 /*break*/, 4];
                        return [4 /*yield*/, beforeSend(prepared, hint)];
                    case 3:
                        finalEvent = _b.sent();
                        if (typeof finalEvent === 'undefined') {
                            logger_1.logger.error('`beforeSend` method has to return `null` or a valid event');
                        }
                        _b.label = 4;
                    case 4: return [3 /*break*/, 6];
                    case 5:
                        exception_1 = _b.sent();
                        async_1.forget(this.captureException(exception_1, {
                            data: {
                                __sentry__: true,
                            },
                            originalException: exception_1,
                        }));
                        return [2 /*return*/, {
                                reason: 'Event processing in beforeSend method threw an exception',
                                status: types_1.Status.Invalid,
                            }];
                    case 6:
                        if (finalEvent === null) {
                            return [2 /*return*/, {
                                    reason: 'Event dropped due to being discarded by beforeSend method',
                                    status: types_1.Status.Skipped,
                                }];
                        }
                        return [4 /*yield*/, send(finalEvent)];
                    case 7:
                        response = _b.sent();
                        response.event = finalEvent;
                        if (response.status === types_1.Status.RateLimit) {
                            // TODO: Handle rate limits and maintain a queue. For now, we require SDK
                            // implementors to override this method and handle it themselves.
                        }
                        return [2 /*return*/, response];
                }
            });
        });
    };
    /**
     * @inheritDoc
     */
    BaseClient.prototype.close = function (timeout) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            return tslib_1.__generator(this, function (_a) {
                return [2 /*return*/, this.getBackend()
                        .getBuffer()
                        .drain(timeout)];
            });
        });
    };
    /**
     * @inheritDoc
     */
    BaseClient.prototype.getIntegrations = function () {
        return this.integrations || {};
    };
    /**
     * @inheritDoc
     */
    BaseClient.prototype.getIntegration = function (integration) {
        try {
            return this.integrations[integration.id] || null;
        }
        catch (_oO) {
            logger_1.logger.warn("Cannot retrieve integration " + integration.id + " from the current Client");
            return null;
        }
    };
    return BaseClient;
}());
exports.BaseClient = BaseClient;
//# sourceMappingURL=baseclient.js.map