const tslib = require('tslib');
const core = require('@sentry/core');
const utils = require('@sentry/utils');
const electron = require('electron');
const context = require('../context.js');
const electronNormalize = require('../electron-normalize.js');
const sessions = require('../sessions.js');
const merge = require('../../common/merge.js');
const normalize = require('../../common/normalize.js');

/** Is object defined and has keys */
function hasKeys(obj) {
    return obj !== undefined && Object.keys(obj).length > 0;
}
/** Gets a Scope object with user, tags and extra */
function getScope(options) {
    const scope = core.getCurrentScope().getScopeData();
    if (!scope) {
        return {};
    }
    return Object.assign(Object.assign(Object.assign({ release: options.release, environment: options.environment }, (hasKeys(scope.user) && { user: scope.user })), (hasKeys(scope.tags) && { tags: scope.tags })), (hasKeys(scope.extra) && { extra: scope.extra }));
}
/** Chunks up event JSON into 1 or more parameters for use with the native Electron uploader
 *
 * Returns chunks with keys and values:
 * {
 *    sentry__1: '{ json...',
 *    sentry__2: 'more json...',
 *    sentry__x: 'end json }',
 * }
 */
function getNativeUploaderExtraParams(event) {
    const maxBytes = 20300;
    /** Max chunk sizes are in bytes so we can't chunk by characters or UTF8 could bite us.
     *
     * We attempt to split by space (32) and double quote characters (34) as there are plenty in JSON
     * and they are guaranteed to not be the first byte of a multi-byte UTF8 character.
     */
    let buf = Buffer.from(JSON.stringify(event));
    const chunks = [];
    while (buf.length) {
        // Find last '"'
        let i = buf.lastIndexOf(34, maxBytes + 1);
        // Or find last ' '
        if (i < 0)
            i = buf.lastIndexOf(32, maxBytes + 1);
        // Or find first '"'
        if (i < 0)
            i = buf.indexOf(34, maxBytes);
        // Or find first ' '
        if (i < 0)
            i = buf.indexOf(32, maxBytes);
        // We couldn't find any space or quote chars so split at maxBytes and hope for the best 🤷‍♂️
        if (i < 0)
            i = maxBytes;
        chunks.push(buf.subarray(0, i + 1).toString());
        buf = buf.subarray(i + 1);
    }
    return chunks.reduce((acc, cur, i) => {
        acc[`sentry__${i + 1}`] = cur;
        return acc;
    }, {});
}
/**
 * Returns the minidump endpoint in Sentry
 * @param dsn Dsn
 */
function minidumpUrlFromDsn(dsn) {
    const dsnComponents = utils.makeDsn(dsn);
    if (!dsnComponents) {
        return undefined;
    }
    const { host, path, projectId, port, protocol, publicKey } = dsnComponents;
    return `${protocol}://${host}${port !== '' ? `:${port}` : ''}${path !== '' ? `/${path}` : ''}/api/${projectId}/minidump/?sentry_key=${publicKey}`;
}
const INTEGRATION_NAME = 'ElectronMinidump';
/**
 * Sends minidumps via the Electron built-in uploader.
 */
const electronMinidumpIntegration = core.defineIntegration(() => {
    /** Counter used to ensure no race condition when updating extra params */
    let updateEpoch = 0;
    let customRelease;
    function getNativeUploaderEvent(scope) {
        return tslib.__awaiter(this, void 0, void 0, function* () {
            const event = merge.mergeEvents(yield context.getEventDefaults(customRelease), {
                sdk: context.getSdkInfo(),
                event_id: utils.uuid4(),
                level: 'fatal',
                platform: 'native',
                tags: { 'event.environment': 'native', event_type: 'native' },
            });
            core.applyScopeDataToEvent(event, scope);
            delete event.sdkProcessingMetadata;
            // Normalise paths
            return normalize.normalizeEvent(event, electron.app.getAppPath());
        });
    }
    function updateExtraParams(scope) {
        updateEpoch += 1;
        const currentEpoch = updateEpoch;
        getNativeUploaderEvent(scope)
            .then((event) => {
            if (currentEpoch !== updateEpoch) {
                return;
            }
            // Update the extra parameters in the main process
            const mainParams = getNativeUploaderExtraParams(event);
            for (const key of Object.keys(mainParams)) {
                electron.crashReporter.addExtraParameter(key, mainParams[key]);
            }
        })
            .catch((error) => utils.logger.error(error));
    }
    function startCrashReporter(options) {
        const submitURL = minidumpUrlFromDsn(options.dsn || '');
        if (!submitURL) {
            utils.logger.log('Invalid DSN. Cannot start Electron crashReporter');
            return;
        }
        // We don't add globalExtra when Breakpad is in use because it doesn't support JSON like strings:
        // https://github.com/electron/electron/issues/29711
        const globalExtra = electronNormalize.usesCrashpad() ? { sentry___initialScope: JSON.stringify(getScope(options)) } : undefined;
        utils.logger.log('Starting Electron crashReporter');
        electron.crashReporter.start({
            companyName: '',
            ignoreSystemCrashHandler: true,
            productName: electron.app.name || electron.app.getName(),
            submitURL,
            uploadToServer: true,
            compress: true,
            globalExtra,
        });
    }
    function setupScopeListener() {
        core.getCurrentScope().addScopeListener((updatedScope) => {
            const scope = updatedScope.getScopeData();
            scope.eventProcessors = [];
            updateExtraParams(scope);
        });
    }
    return {
        name: INTEGRATION_NAME,
        setupOnce() {
            // noop
        },
        setup(client) {
            var _a;
            // Mac AppStore builds cannot run the crash reporter due to the sandboxing
            // requirements. In this case, we prevent enabling native crashes entirely.
            // https://electronjs.org/docs/tutorial/mac-app-store-submission-guide#limitations-of-mas-build
            if (process.mas) {
                return;
            }
            if (electronNormalize.rendererRequiresCrashReporterStart()) {
                throw new utils.SentryError(`The '${INTEGRATION_NAME}' integration is only supported with Electron >= v9`);
            }
            const clientOptions = client.getOptions();
            if (!(clientOptions === null || clientOptions === void 0 ? void 0 : clientOptions.dsn)) {
                throw new utils.SentryError('Attempted to enable Electron native crash reporter but no DSN was supplied');
            }
            customRelease = clientOptions.release;
            startCrashReporter(clientOptions);
            // If a renderer process crashes, mark any existing session as crashed
            electronNormalize.onRendererProcessGone(electronNormalize.CRASH_REASONS, (_, __) => {
                sessions.sessionCrashed();
            });
            // If we're using the Crashpad minidump uploader, we set extra parameters whenever the scope updates
            if (electronNormalize.usesCrashpad()) {
                setupScopeListener();
            }
            // Check if last crash report was likely to have been unreported in the last session
            sessions.unreportedDuringLastSession((_a = electron.crashReporter.getLastCrashReport()) === null || _a === void 0 ? void 0 : _a.date).then((crashed) => {
                // Check if a previous session was not closed
                return sessions.checkPreviousSession(crashed);
            }, utils.logger.error);
        },
    };
});
/**
 * Sends minidumps via the Electron built-in uploader.
 *
 * @deprecated Use `electronMinidumpIntegration()` instead
 */
// eslint-disable-next-line deprecation/deprecation
const ElectronMinidump = core.convertIntegrationFnToClass(INTEGRATION_NAME, electronMinidumpIntegration);

exports.ElectronMinidump = ElectronMinidump;
exports.electronMinidumpIntegration = electronMinidumpIntegration;
exports.minidumpUrlFromDsn = minidumpUrlFromDsn;
//# sourceMappingURL=electron-minidump.js.map
