import { __assign, __awaiter, __generator, __rest } from "tslib";
import * as child from 'child_process';
import { app } from 'electron';
import { platform, release } from 'os';
import { join } from 'path';
import { getNameFallback } from '../common';
import { readDirAsync, readFileAsync } from './fs';
import { SDK_VERSION } from './version';
export var SDK_NAME = 'sentry.javascript.electron';
/** Mapping of Node's platform names to actual OS names. */
var PLATFORM_NAMES = {
    aix: 'IBM AIX',
    freebsd: 'FreeBSD',
    openbsd: 'OpenBSD',
    sunos: 'SunOS',
    win32: 'Windows',
};
/** Mapping of linux release files located in /etc to distributions. */
var LINUX_DISTROS = [
    { name: 'fedora-release', distros: ['Fedora'] },
    { name: 'redhat-release', distros: ['Red Hat Linux', 'Centos'] },
    { name: 'redhat_version', distros: ['Red Hat Linux'] },
    { name: 'SuSE-release', distros: ['SUSE Linux'] },
    { name: 'lsb-release', distros: ['Ubuntu Linux', 'Arch Linux'] },
    { name: 'debian_version', distros: ['Debian'] },
    { name: 'debian_release', distros: ['Debian'] },
    { name: 'arch-release', distros: ['Arch Linux'] },
    { name: 'gentoo-release', distros: ['Gentoo Linux'] },
    { name: 'novell-release', distros: ['SUSE Linux'] },
    { name: 'alpine-release', distros: ['Alpine Linux'] },
];
/** Functions to extract the OS version from Linux release files. */
var LINUX_VERSIONS = {
    alpine: function (content) { return content; },
    arch: function (content) { return matchFirst(/distrib_release=(.*)/, content); },
    centos: function (content) { return matchFirst(/release ([^ ]+)/, content); },
    debian: function (content) { return content; },
    fedora: function (content) { return matchFirst(/release (..)/, content); },
    mint: function (content) { return matchFirst(/distrib_release=(.*)/, content); },
    red: function (content) { return matchFirst(/release ([^ ]+)/, content); },
    suse: function (content) { return matchFirst(/VERSION = (.*)\n/, content); },
    ubuntu: function (content) { return matchFirst(/distrib_release=(.*)/, content); },
};
/** Cached event prototype with default values. */
var defaultsPromise;
/**
 * Executes a regular expression with one capture group.
 *
 * @param regex A regular expression to execute.
 * @param text Content to execute the RegEx on.
 * @returns The captured string if matched; otherwise undefined.
 */
function matchFirst(regex, text) {
    var match = regex.exec(text);
    return match ? match[1] : undefined;
}
/** Returns the build type of this app, if possible. */
function getBuildType() {
    if (process.mas) {
        return 'app-store';
    }
    if (process.windowsStore) {
        return 'windows-store';
    }
    return undefined;
}
/** Loads the macOS operating system context. */
function getDarwinInfo() {
    return __awaiter(this, void 0, void 0, function () {
        var darwinInfo, output, e_1;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    darwinInfo = {
                        kernel_version: release(),
                        name: 'Mac OS X',
                        version: "10." + (Number(release().split('.')[0]) - 4),
                    };
                    _a.label = 1;
                case 1:
                    _a.trys.push([1, 3, , 4]);
                    return [4 /*yield*/, new Promise(function (resolve, reject) {
                            child.execFile('/usr/bin/sw_vers', function (error, stdout) {
                                if (error) {
                                    reject(error);
                                    return;
                                }
                                resolve(stdout);
                            });
                        })];
                case 2:
                    output = _a.sent();
                    darwinInfo.name = matchFirst(/^ProductName:\s+(.*)$/m, output);
                    darwinInfo.version = matchFirst(/^ProductVersion:\s+(.*)$/m, output);
                    darwinInfo.build = matchFirst(/^BuildVersion:\s+(.*)$/m, output);
                    return [3 /*break*/, 4];
                case 3:
                    e_1 = _a.sent();
                    return [3 /*break*/, 4];
                case 4: return [2 /*return*/, darwinInfo];
            }
        });
    });
}
/** Returns a distribution identifier to look up version callbacks. */
function getLinuxDistroId(name) {
    return name.split(' ')[0].toLowerCase();
}
/** Loads the Linux operating system context. */
function getLinuxInfo() {
    return __awaiter(this, void 0, void 0, function () {
        var linuxInfo, etcFiles_1, distroFile, distroPath, contents_1, distros, id, e_2;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    linuxInfo = {
                        kernel_version: release(),
                        name: 'Linux',
                    };
                    _a.label = 1;
                case 1:
                    _a.trys.push([1, 4, , 5]);
                    return [4 /*yield*/, readDirAsync('/etc')];
                case 2:
                    etcFiles_1 = _a.sent();
                    distroFile = LINUX_DISTROS.find(function (file) { return etcFiles_1.includes(file.name); });
                    if (!distroFile) {
                        return [2 /*return*/, linuxInfo];
                    }
                    distroPath = join('/etc', distroFile.name);
                    return [4 /*yield*/, readFileAsync(distroPath, { encoding: 'utf-8' })];
                case 3:
                    contents_1 = (_a.sent()).toLowerCase();
                    distros = distroFile.distros;
                    linuxInfo.name = distros.find(function (d) { return contents_1.indexOf(getLinuxDistroId(d)) >= 0; }) || distros[0];
                    id = getLinuxDistroId(linuxInfo.name);
                    linuxInfo.version = LINUX_VERSIONS[id](contents_1);
                    return [3 /*break*/, 5];
                case 4:
                    e_2 = _a.sent();
                    return [3 /*break*/, 5];
                case 5: return [2 /*return*/, linuxInfo];
            }
        });
    });
}
/**
 * Returns the operating system context.
 *
 * Based on the current platform, this uses a different strategy to provide the
 * most accurate OS information. Since this might involve spawning subprocesses
 * or accessing the file system, this should only be executed lazily and cached.
 *
 *  - On macOS (Darwin), this will execute the `sw_vers` utility. The context
 *    has a `name`, `version`, `build` and `kernel_version` set.
 *  - On Linux, this will try to load a distribution release from `/etc` and set
 *    the `name`, `version` and `kernel_version` fields.
 *  - On all other platforms, only a `name` and `version` will be returned. Note
 *    that `version` might actually be the kernel version.
 */
function getOsContext() {
    return __awaiter(this, void 0, void 0, function () {
        var platformId;
        return __generator(this, function (_a) {
            platformId = platform();
            switch (platformId) {
                case 'darwin':
                    return [2 /*return*/, getDarwinInfo()];
                case 'linux':
                    return [2 /*return*/, getLinuxInfo()];
                default:
                    return [2 /*return*/, {
                            name: PLATFORM_NAMES[platformId] || platformId,
                            version: release(),
                        }];
            }
            return [2 /*return*/];
        });
    });
}
/**
 * Computes Electron-specific default fields for events.
 *
 * The event defaults include contexts for the Electron, Node and Chrome
 * runtimes, limited device information, operating system context and defaults
 * for the release and environment.
 */
function getEventDefaults(appName) {
    return __awaiter(this, void 0, void 0, function () {
        var name;
        var _a, _b;
        return __generator(this, function (_c) {
            switch (_c.label) {
                case 0:
                    name = appName || getNameFallback();
                    _a = {};
                    _b = {
                        app: {
                            app_name: name,
                            app_version: app.getVersion(),
                            build_type: getBuildType(),
                        },
                        browser: {
                            name: 'Chrome',
                        },
                        chrome: {
                            name: 'Chrome',
                            type: 'runtime',
                            version: process.versions.chrome,
                        },
                        device: {
                            arch: process.arch,
                            family: 'Desktop',
                        },
                        node: {
                            name: 'Node',
                            type: 'runtime',
                            version: process.versions.node,
                        }
                    };
                    return [4 /*yield*/, getOsContext()];
                case 1: return [2 /*return*/, (_a.contexts = (_b.os = (_c.sent()),
                        _b.runtime = {
                            name: 'Electron',
                            version: process.versions.electron,
                        },
                        _b),
                        _a.environment = process.defaultApp ? 'development' : 'production',
                        _a.extra = { crashed_process: 'browser' },
                        _a.release = name.replace(/\W/g, '-') + "@" + app.getVersion(),
                        _a.user = { ip_address: '{{auto}}' },
                        _a)];
            }
        });
    });
}
/** Merges the given event payload with SDK defaults. */
export function addEventDefaults(appName, event) {
    return __awaiter(this, void 0, void 0, function () {
        var _a, contexts, _b, _c, defaultContexts, defaults;
        return __generator(this, function (_d) {
            switch (_d.label) {
                case 0:
                    // The event defaults are cached as long as the app is running. We create the
                    // promise here synchronously to avoid multiple events computing them at the
                    // same time.
                    if (!defaultsPromise) {
                        defaultsPromise = getEventDefaults(appName);
                    }
                    _a = event.contexts, contexts = _a === void 0 ? {} : _a;
                    return [4 /*yield*/, defaultsPromise];
                case 1:
                    _b = _d.sent(), _c = _b.contexts, defaultContexts = _c === void 0 ? {} : _c, defaults = __rest(_b, ["contexts"]);
                    // Perform a manual deep merge of the defaults with the event data.
                    // TODO: Use a proper deep merge here, instead.
                    return [2 /*return*/, __assign(__assign(__assign({}, defaults), event), { sdk: {
                                name: SDK_NAME,
                                packages: [
                                    {
                                        name: 'npm:@sentry/electron',
                                        version: SDK_VERSION,
                                    },
                                ],
                                version: SDK_VERSION,
                            }, contexts: __assign(__assign(__assign({}, defaultContexts), contexts), { app: __assign(__assign({}, defaultContexts.app), contexts.app), device: __assign(__assign({}, defaultContexts.device), contexts.device), os: __assign(__assign({}, defaultContexts.os), contexts.os), runtime: __assign(__assign({}, defaultContexts.runtime), contexts.runtime) }), extra: __assign(__assign({}, defaults.extra), event.extra), user: __assign(__assign({}, defaults.user), event.user) })];
            }
        });
    });
}
//# sourceMappingURL=context.js.map