import { defineIntegration, debug, makeDsn, uuid4, applyScopeDataToEvent } from '@sentry/core';
import { app, crashReporter } from 'electron';
import { addScopeListener, getScopeData } from '../../common/scope.js';
import { getSdkInfo, getEventDefaults } from '../context.js';
import { CRASH_REASONS } from '../electron-normalize.js';
import { mergeEvents } from '../merge.js';
import { normalizePaths } from '../normalize.js';
import { sessionCrashed, unreportedDuringLastSession, checkPreviousSession } from '../sessions.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 = getScopeData();
    if (!scope) {
        return {};
    }
    return {
        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 = 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}`;
}
/**
 * Sends minidumps via the Electron built-in uploader.
 */
const electronMinidumpIntegration = defineIntegration(() => {
    /** Counter used to ensure no race condition when updating extra params */
    let updateEpoch = 0;
    let lastParamCount = 0;
    async function getNativeUploaderEvent(client, scope) {
        const { userInfo: inferIpAddress } = client.getDataCollectionOptions();
        const event = mergeEvents(await getEventDefaults(client), {
            sdk: getSdkInfo(inferIpAddress),
            event_id: uuid4(),
            level: 'fatal',
            platform: 'native',
            tags: { 'event.environment': 'native' },
        });
        applyScopeDataToEvent(event, scope);
        delete event.sdkProcessingMetadata;
        // Normalise paths
        return normalizePaths(event, app.getAppPath());
    }
    function updateExtraParams(client, scope) {
        updateEpoch += 1;
        const currentEpoch = updateEpoch;
        getNativeUploaderEvent(client, scope)
            .then((event) => {
            if (currentEpoch !== updateEpoch) {
                return;
            }
            // Update the extra parameters in the main process
            const mainParams = getNativeUploaderExtraParams(event);
            const count = Object.keys(mainParams).length;
            // Remove any extra parameters that extend beyond the current count
            if (lastParamCount > count) {
                for (let i = count + 1; i <= lastParamCount; i++) {
                    crashReporter.removeExtraParameter(`sentry__${i}`);
                }
            }
            for (const [key, value] of Object.entries(mainParams)) {
                crashReporter.addExtraParameter(key, value);
            }
            // Track how many parameters we added so we can remove extras next time
            lastParamCount = count;
        })
            .catch((error) => debug.error(error));
    }
    function startCrashReporter(options) {
        const submitURL = minidumpUrlFromDsn(options.dsn || '');
        if (!submitURL) {
            debug.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 = { sentry___initialScope: JSON.stringify(getScope(options)) };
        debug.log('Starting Electron crashReporter');
        crashReporter.start({
            companyName: '',
            ignoreSystemCrashHandler: true,
            productName: app.name || app.getName(),
            submitURL,
            uploadToServer: true,
            compress: true,
            globalExtra,
        });
    }
    function setupScopeListener(client) {
        addScopeListener((scope) => {
            updateExtraParams(client, scope);
        });
    }
    return {
        name: 'ElectronMinidump',
        setup(client) {
            // 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;
            }
            const clientOptions = client.getOptions();
            if (!clientOptions?.dsn) {
                throw new Error('Attempted to enable Electron native crash reporter but no DSN was supplied');
            }
            startCrashReporter(clientOptions);
            // If a renderer process crashes, mark any existing session as crashed
            app.on('render-process-gone', (_, __, details) => {
                if (CRASH_REASONS.includes(details.reason)) {
                    sessionCrashed();
                }
            });
            // Set extra parameters whenever the scope updates
            setupScopeListener(client);
            // Check if last crash report was likely to have been unreported in the last session
            unreportedDuringLastSession(crashReporter.getLastCrashReport()?.date).then((crashed) => {
                // Check if a previous session was not closed
                return checkPreviousSession(crashed);
            }, debug.error);
        },
    };
});

export { electronMinidumpIntegration, minidumpUrlFromDsn };
//# sourceMappingURL=electron-minidump.js.map
