'use strict';

var electron = require('electron');
var child_process = require('child_process');
var fs = require('fs');
var rimraf = require('rimraf');
var redux = require('redux');
var path = require('path');
var i18next = require('i18next');
var reselect = require('reselect');
var ElectronStore = require('electron-store');
var detectBrowsers = require('detect-browsers');
var log = require('electron-log');
var tls = require('node:tls');
var semver$1 = require('semver');
var os = require('os');
var electronDl = require('electron-dl');
var Bugsnag = require('@bugsnag/js');
var util = require('util');
var archiver = require('archiver');
var https = require('https');
var axios = require('axios');
var xhr = require('@ewsjs/xhr');
var ewsJavascriptApi = require('ews-javascript-api');
var promises = require('node:fs/promises');
var node_path = require('node:path');
var jwt = require('jsonwebtoken');
require('moment');
var process$1 = require('process');
var electronUpdater = require('electron-updater');

function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }

function _interopNamespace(e) {
  if (e && e.__esModule) return e;
  var n = Object.create(null);
  if (e) {
    Object.keys(e).forEach(function (k) {
      if (k !== 'default') {
        var d = Object.getOwnPropertyDescriptor(e, k);
        Object.defineProperty(n, k, d.get ? d : {
          enumerable: true,
          get: function () { return e[k]; }
        });
      }
    });
  }
  n.default = e;
  return Object.freeze(n);
}

var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
var path__default = /*#__PURE__*/_interopDefault(path);
var i18next__default = /*#__PURE__*/_interopDefault(i18next);
var ElectronStore__default = /*#__PURE__*/_interopDefault(ElectronStore);
var log__default = /*#__PURE__*/_interopDefault(log);
var tls__default = /*#__PURE__*/_interopDefault(tls);
var os__default = /*#__PURE__*/_interopDefault(os);
var electronDl__default = /*#__PURE__*/_interopDefault(electronDl);
var Bugsnag__default = /*#__PURE__*/_interopDefault(Bugsnag);
var archiver__default = /*#__PURE__*/_interopDefault(archiver);
var https__default = /*#__PURE__*/_interopDefault(https);
var axios__default = /*#__PURE__*/_interopDefault(axios);
var jwt__default = /*#__PURE__*/_interopDefault(jwt);
var process__default = /*#__PURE__*/_interopDefault(process$1);

var files = [
	"app/**/*",
	"package.json"
];
var extraResources = [
	"build/icon.ico",
	"servers.json"
];
var appId = "chat.rocket";
var protocols = {
	name: "Rocket.Chat",
	schemes: [
		"rocketchat"
	]
};
var afterPack = "./build/afterPack.js";
var mac = {
	category: "public.app-category.productivity",
	target: [
		"dmg",
		"pkg",
		"zip",
		"mas"
	],
	icon: "build/icon.icns",
	bundleVersion: "26050",
	helperBundleId: "chat.rocket.electron.helper",
	type: "distribution",
	artifactName: "rocketchat-${version}-${os}.${ext}",
	mergeASARs: false,
	extendInfo: {
		NSMicrophoneUsageDescription: "I need access to your microphone to record the audio you want to send.",
		NSCameraUsageDescription: "I need access to your camera to record the video you want to send.",
		NSScreenCaptureDescription: "I need access to your screen to share it through video calls when you want to."
	},
	hardenedRuntime: true,
	gatekeeperAssess: false,
	provisioningProfile: "Desktop.provisionprofile",
	entitlements: "build/entitlements.mac.plist",
	entitlementsInherit: "build/entitlements.mac.plist"
};
var dmg = {
	background: "build/background.png",
	contents: [
		{
			type: "dir",
			x: 100,
			y: 211
		},
		{
			type: "link",
			path: "/Applications",
			x: 500,
			y: 211
		}
	]
};
var pkg = {
	isRelocatable: false,
	overwriteAction: "upgrade"
};
var mas = {
	entitlements: "build/entitlements.mas.plist",
	entitlementsInherit: "build/entitlements.mas.inherit.plist",
	entitlementsLoginHelper: "build/entitlements.mas.inherit.plist",
	hardenedRuntime: false,
	asarUnpack: [
		"node_modules"
	],
	artifactName: "rocketchat-${version}-mas.${ext}"
};
var win = {
	target: [
		{
			target: "nsis",
			arch: [
				"x64",
				"ia32",
				"arm64"
			]
		},
		{
			target: "msi",
			arch: [
				"x64",
				"ia32",
				"arm64"
			]
		},
		{
			target: "zip",
			arch: [
				"x64",
				"ia32",
				"arm64"
			]
		}
	],
	icon: "build/icon.ico",
	legalTrademarks: "",
	verifyUpdateCodeSignature: true,
	requestedExecutionLevel: "asInvoker",
	signAndEditExecutable: true,
	artifactName: "rocketchat-${version}-${os}-${arch}.${ext}",
	signtoolOptions: {
		sign: "./build/winSignKms.js",
		signingHashAlgorithms: [
			"sha256"
		],
		timeStampServer: "http://timestamp.digicert.com"
	}
};
var nsis = {
	oneClick: false,
	perMachine: false,
	allowElevation: true,
	allowToChangeInstallationDirectory: true,
	installerIcon: "build/installerIcon.ico",
	uninstallerIcon: "build/uninstallerIcon.ico",
	installerSidebar: "build/installerSidebar.bmp",
	uninstallerSidebar: "build/uninstallerSidebar.bmp",
	uninstallDisplayName: "${productName} ${version}",
	include: "build/installer.nsh",
	license: "LICENSE",
	deleteAppDataOnUninstall: false,
	displayLanguageSelector: false,
	unicode: true,
	warningsAsErrors: true,
	runAfterFinish: true,
	createDesktopShortcut: "always",
	createStartMenuShortcut: true,
	menuCategory: false
};
var msiProjectCreated = "./build/msiProjectCreated.js";
var msi = {
	perMachine: true,
	warningsAsErrors: false,
	additionalWixArgs: [
		"-sval"
	]
};
var appx = {
	backgroundColor: "#2f343d",
	languages: [
		"en-US",
		"en-GB",
		"pt-BR"
	],
	identityName: "0B67C87C.RocketChat",
	publisher: "CN=BBFF7141-7CAF-4FCD-8930-083FCDE6E854",
	publisherDisplayName: "Rocket.Chat Technologies Corp.",
	applicationId: "RocketChat",
	displayName: "Rocket.Chat"
};
var linux = {
	target: [
		"AppImage",
		"tar.gz",
		"deb",
		"rpm",
		"snap",
		"flatpak"
	],
	executableName: "rocketchat-desktop",
	category: "GNOME;GTK;Network;InstantMessaging",
	desktop: {
		entry: {
			Name: "Rocket.Chat",
			Comment: "Official Rocket.Chat Desktop Client",
			GenericName: "Rocket.Chat",
			Categories: "GNOME;GTK;Network;InstantMessaging"
		}
	},
	artifactName: "rocketchat-${version}-${os}-${arch}.${ext}"
};
var deb = {
	fpm: [
		"--after-install=build/linux/postinst.sh"
	],
	recommends: [
		"xdg-desktop-portal",
		"xdg-desktop-portal-gtk"
	]
};
var rpm = {
	fpm: [
		"--rpm-rpmbuild-define=_build_id_links none",
		"--rpm-tag=Recommends: xdg-desktop-portal",
		"--rpm-tag=Recommends: xdg-desktop-portal-gtk"
	]
};
var snap = {
	base: "core22",
	allowNativeWayland: false,
	plugs: [
		"desktop",
		"desktop-legacy",
		"home",
		"x11",
		"wayland",
		"unity7",
		"browser-support",
		"network",
		"gsettings",
		"pulseaudio",
		"opengl",
		"camera",
		"audio-playback",
		"audio-record",
		"screen-inhibit-control",
		"upower-observe",
		"pipewire"
	]
};
var afterSign = "./build/notarize.js";
var generateUpdatesFilesForAllChannels = true;
var publish = [
	{
		provider: "github",
		owner: "RocketChat",
		repo: "Rocket.Chat.Electron",
		vPrefixedTagName: false
	}
];
var electronBuilderJson = {
	files: files,
	extraResources: extraResources,
	appId: appId,
	protocols: protocols,
	afterPack: afterPack,
	mac: mac,
	dmg: dmg,
	pkg: pkg,
	mas: mas,
	win: win,
	nsis: nsis,
	msiProjectCreated: msiProjectCreated,
	msi: msi,
	appx: appx,
	linux: linux,
	deb: deb,
	rpm: rpm,
	snap: snap,
	afterSign: afterSign,
	generateUpdatesFilesForAllChannels: generateUpdatesFilesForAllChannels,
	publish: publish
};

var workspaces = [
	"workspaces/*"
];
var productName = "Rocket.Chat";
var name = "rocketchat";
var description = "Official OSX, Windows, and Linux Desktop Clients for Rocket.Chat";
var version = "4.14.1";
var author = "Rocket.Chat Support <support@rocket.chat>";
var copyright = "© 2016-2026, Rocket.Chat";
var homepage = "https://rocket.chat";
var license = "MIT";
var goUrlShortener = "go.rocket.chat";
var keywords = [
	"rocketchat",
	"desktop",
	"electron"
];
var repository = {
	type: "git",
	url: "git+https://github.com/RocketChat/Rocket.Chat.Electron.git"
};
var bugs = {
	url: "https://github.com/RocketChat/Rocket.Chat.Electron/issues"
};
var main$1 = "app/main.js";
var scripts = {
	postinstall: "run-s install-app-deps clean patch-package",
	"patch-package": "patch-package",
	start: "run-s build:watch",
	clean: "rimraf app dist",
	build: "rollup -c",
	"build:watch": "rollup -c -w",
	"build-mac": "yarn build && yarn electron-builder --publish never --mac --universal",
	"build-mas": "yarn build && yarn electron-builder --publish never --mac mas --universal",
	"build-win": "yarn build && yarn electron-builder --publish never --win",
	"build-linux": "yarn build && yarn electron-builder --publish never --linux",
	"build-assets": "ts-node -O '{\"module\":\"commonjs\"}' src/buildAssets.ts",
	"build-assets-win": "ts-node -O \"{\\\"module\\\":\\\"commonjs\\\"}\" src/buildAssets.ts",
	release: "yarn electron-builder --publish onTagOrDraft --x64",
	"install-app-deps": "electron-builder install-app-deps",
	test: "xvfb-maybe jest --forceExit --detectOpenHandles --maxWorkers=1",
	changelog: "conventional-changelog -p angular -i CHANGELOG.md -s",
	lint: "run-s .:lint:eslint .:lint:tsc",
	".:lint:eslint": "eslint .",
	".:lint:tsc": "tsc --noEmit",
	"lint-fix": "run-s .:lint-fix:eslint .:lint:tsc",
	".:lint-fix:eslint": "eslint --fix .",
	"workspaces:build": "yarn workspaces foreach -At run build",
	"release:tag": "ts-node -O '{\"module\":\"commonjs\"}' scripts/release-tag.ts",
	"release:tag:win": "ts-node -O \"{\\\"module\\\":\\\"commonjs\\\"}\" scripts/release-tag.ts"
};
var dependencies = {
	"@bugsnag/js": "~8.8.1",
	"@emotion/css": "~11.11.2",
	"@emotion/react": "~11.11.3",
	"@emotion/styled": "~11.11.0",
	"@ewsjs/xhr": "patch:@ewsjs/xhr@npm%3A2.0.2#~/.yarn/patches/@ewsjs-xhr-npm-2.0.2-77506b0a6c.patch",
	"@rocket.chat/css-in-js": "~0.31.25",
	"@rocket.chat/desktop-api": "~1.1.0",
	"@rocket.chat/fuselage": "0.58.0",
	"@rocket.chat/fuselage-hooks": "~0.33.1",
	"@rocket.chat/fuselage-polyfills": "~0.31.25",
	"@rocket.chat/icons": "0.37.0",
	archiver: "~7.0.1",
	axios: "~1.13.6",
	"detect-browsers": "~6.1.0",
	dompurify: "~3.3.3",
	"electron-dl": "4.0.0",
	"electron-log": "~5.4.3",
	"electron-store": "~8.1.0",
	"electron-updater": "^6.3.9",
	"ews-javascript-api": "~0.13.2",
	"github-markdown-css": "~5.9.0",
	"highlight.js": "~11.11.1",
	i18next: "~23.7.16",
	jsonwebtoken: "~9.0.2",
	marked: "~17.0.5",
	"marked-highlight": "~2.2.3",
	moment: "~2.30.1",
	react: "~18.3.1",
	"react-dom": "~18.3.1",
	"react-hook-form": "~7.49.2",
	"react-i18next": "~14.0.0",
	"react-keyed-flatten-children": "~3.0.0",
	"react-redux": "~9.0.4",
	"react-virtuoso": "~4.6.2",
	redux: "~5.0.1",
	reselect: "~5.0.1",
	rimraf: "~5.0.7",
	semver: "~7.5.4"
};
var devDependencies = {
	"@babel/core": "~7.23.9",
	"@babel/eslint-parser": "~7.23.3",
	"@babel/plugin-proposal-class-properties": "~7.18.6",
	"@babel/plugin-proposal-function-bind": "~7.23.3",
	"@babel/preset-env": "~7.23.7",
	"@babel/preset-react": "~7.23.3",
	"@babel/preset-typescript": "~7.23.3",
	"@electron/fuses": "~1.8.0",
	"@fiahfy/icns-convert": "~0.0.12",
	"@fiahfy/ico-convert": "~0.0.12",
	"@kayahr/jest-electron-runner": "29.14.0",
	"@rocket.chat/eslint-config": "~0.7.0",
	"@rocket.chat/prettier-config": "~0.31.25",
	"@rollup/plugin-babel": "~6.0.4",
	"@rollup/plugin-commonjs": "~25.0.7",
	"@rollup/plugin-json": "~6.1.0",
	"@rollup/plugin-node-resolve": "~15.2.3",
	"@rollup/plugin-replace": "~5.0.5",
	"@types/archiver": "~7.0.0",
	"@types/dompurify": "~3.2.0",
	"@types/electron-devtools-installer": "~2.2.5",
	"@types/jest": "~29.5.11",
	"@types/jsonwebtoken": "~9.0.5",
	"@types/node": "~25.0.10",
	"@types/react": "~18.3.18",
	"@types/react-dom": "~18.3.5",
	"@typescript-eslint/eslint-plugin": "~6.17.0",
	"@typescript-eslint/parser": "~6.17.0",
	"builtin-modules": "~3.3.0",
	chokidar: "~3.5.3",
	"conventional-changelog-cli": "~4.1.0",
	"convert-svg-to-png": "~0.6.4",
	electron: "40.8.5",
	"electron-builder": "26.0.3",
	"electron-devtools-installer": "^3.2.0",
	"electron-notarize": "^1.2.2",
	eslint: "~8.56.0",
	"eslint-config-prettier": "~9.1.0",
	"eslint-import-resolver-typescript": "~3.6.1",
	"eslint-plugin-import": "~2.26.0",
	"eslint-plugin-prettier": "~5.1.2",
	"eslint-plugin-react": "~7.33.2",
	"eslint-plugin-react-hooks": "~4.6.0",
	jest: "~29.7.0",
	"jest-environment-jsdom": "~29.7.0",
	jimp: "~0.22.10",
	nock: "~14.0.0",
	"npm-run-all": "~4.1.5",
	"patch-package": "~8.0.0",
	prettier: "~3.2.5",
	puppeteer: "23.1.1",
	rollup: "~4.32.0",
	"rollup-plugin-copy": "~3.5.0",
	"ts-jest": "~29.1.4",
	"ts-node": "~10.9.2",
	typescript: "~5.7.3",
	"xvfb-maybe": "~0.2.1"
};
var optionalDependencies = {
	fsevents: "2.3.3"
};
var engines = {
	node: ">=24.11.1"
};
var devEngines = {
	runtime: {
		name: "node",
		version: ">=22.0.0"
	}
};
var resolutions = {
	"@fiahfy/icns-convert/sharp": "0.29.3",
	"@fiahfy/ico-convert/sharp": "0.29.3",
	"@ewsjs/xhr@npm:^2.0.2": "patch:@ewsjs/xhr@npm%3A2.0.2#~/.yarn/patches/@ewsjs-xhr-npm-2.0.2-77506b0a6c.patch",
	"cross-spawn": "7.0.6",
	braces: "3.0.3",
	ws: "8.18.0",
	axios: "1.13.6",
	"follow-redirects": "1.15.9",
	"form-data": "4.0.5",
	"tar-fs": "3.0.8",
	undici: "5.28.5"
};
var volta = {
	node: "22.22.1",
	yarn: "4.6.0"
};
var packageManager = "yarn@4.6.0";
var packageJson = {
	"private": true,
	workspaces: workspaces,
	productName: productName,
	name: name,
	description: description,
	version: version,
	author: author,
	copyright: copyright,
	homepage: homepage,
	license: license,
	goUrlShortener: goUrlShortener,
	keywords: keywords,
	repository: repository,
	bugs: bugs,
	main: main$1,
	scripts: scripts,
	dependencies: dependencies,
	devDependencies: devDependencies,
	optionalDependencies: optionalDependencies,
	engines: engines,
	devEngines: devEngines,
	resolutions: resolutions,
	volta: volta,
	packageManager: packageManager
};

const JITSI_SERVER_CAPTURE_SCREEN_PERMISSION_UPDATED = 'jitsi-server-capture-screen-permission-updated';
const JITSI_SERVER_CAPTURE_SCREEN_PERMISSIONS_CLEARED = 'jitsi-server-capture-screen-permissions-cleared';

function _defineProperty(e, r, t) {
  return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
    value: t,
    enumerable: true,
    configurable: true,
    writable: true
  }) : e[r] = t, e;
}
function _toPrimitive(t, r) {
  if ("object" != typeof t || !t) return t;
  var e = t[Symbol.toPrimitive];
  if (undefined !== e) {
    var i = e.call(t, r || "default");
    if ("object" != typeof i) return i;
    throw new TypeError("@@toPrimitive must return a primitive value.");
  }
  return ("string" === r ? String : Number)(t);
}
function _toPropertyKey(t) {
  var i = _toPrimitive(t, "string");
  return "symbol" == typeof i ? i : i + "";
}

const isFSA = action => typeof action === 'object' && action !== null && !Array.isArray(action) && 'type' in action && typeof action.type === 'string';
const hasMeta = action => 'meta' in action && typeof action.meta === 'object' && action.meta !== null;
const isResponse = action => hasMeta(action) && action.meta.response === true;
const isLocallyScoped = action => hasMeta(action) && action.meta.scope === 'local';
const isSingleScoped = action => {
  var _ipcMeta, _ipcMeta2;
  return ((_ipcMeta = action.ipcMeta) === null || _ipcMeta === undefined ? undefined : _ipcMeta.webContentsId) && ((_ipcMeta2 = action.ipcMeta) === null || _ipcMeta2 === undefined ? undefined : _ipcMeta2.scope) === 'single';
};
const isErrored = action => 'meta' in action && action.error === true && action.payload instanceof Error;
const hasPayload = action => 'payload' in action;
const isResponseTo = (id, ...types) => action => isResponse(action) && types.includes(action.type) && action.meta.id === id;

const invoke = (webContents, channel, ...args) => new Promise((resolve, reject) => {
  const id = Math.random().toString(16).slice(2);
  electron.ipcMain.once(`${channel}@${id}`, (_, {
    resolved,
    rejected
  }) => {
    if (rejected) {
      const error = new Error(rejected.message);
      error.name = rejected.name;
      error.stack = rejected.stack;
      reject(error);
      return;
    }
    resolve(resolved);
  });
  webContents.send(channel, id, ...args);
});
const handle = (channel, handler) => {
  electron.ipcMain.handle(channel, (event, ...args) => handler(event.sender, ...args));
  return () => {
    electron.ipcMain.removeHandler(channel);
  };
};

var ActionScope = /*#__PURE__*/function (ActionScope) {
  ActionScope["LOCAL"] = "local";
  ActionScope["SINGLE"] = "single";
  return ActionScope;
}(ActionScope || {});
const forwardToRenderers = api => {
  const renderers = new Set();
  handle('redux/get-initial-state', async webContents => {
    renderers.add(webContents);
    webContents.addListener('destroyed', () => {
      renderers.delete(webContents);
    });
    return api.getState();
  });
  handle('redux/action-dispatched', async (webContents, action) => {
    var _webContents$hostWebC, _webContents$hostWebC2;
    api.dispatch({
      ...action,
      ipcMeta: {
        webContentsId: webContents.id,
        ...(((_webContents$hostWebC = webContents.hostWebContents) === null || _webContents$hostWebC === undefined ? undefined : _webContents$hostWebC.id) && {
          viewInstanceId: (_webContents$hostWebC2 = webContents.hostWebContents) === null || _webContents$hostWebC2 === undefined ? undefined : _webContents$hostWebC2.id
        }),
        ...action.ipcMeta
      }
    });
  });
  return next => action => {
    if (!isFSA(action) || isLocallyScoped(action)) {
      return next(action);
    }
    const rendererAction = {
      ...action,
      meta: {
        ...(hasMeta(action) && action.meta),
        scope: ActionScope.LOCAL
      }
    };
    if (isSingleScoped(action)) {
      const {
        webContentsId,
        viewInstanceId
      } = action.ipcMeta;
      [...renderers].filter(w => w.id === webContentsId || viewInstanceId && w.id === viewInstanceId).forEach(w => invoke(w, 'redux/action-dispatched', rendererAction));
      return next(action);
    }
    renderers.forEach(webContents => {
      invoke(webContents, 'redux/action-dispatched', rendererAction);
    });
    return next(action);
  };
};

const APP_PATH_SET = 'app/path-set';
const APP_VERSION_SET = 'app/version-set';
const APP_SETTINGS_LOADED = 'app/settings-loaded';
const APP_ALLOWED_NTLM_CREDENTIALS_DOMAINS_SET = 'app/allowed-ntlm-credentials-domains-set';
const APP_MAIN_WINDOW_TITLE_SET = 'app/main-window-title-set';
const APP_MACHINE_THEME_SET = 'app/machine-theme-set';
const APP_SCREEN_CAPTURE_FALLBACK_FORCED_SET = 'app/screen-capture-fallback-forced-set';

const allowedNTLMCredentialsDomains = (state = null, action) => {
  switch (action.type) {
    case APP_SETTINGS_LOADED:
      {
        const {
          allowedNTLMCredentialsDomains = state
        } = action.payload;
        return allowedNTLMCredentialsDomains;
      }
    case APP_ALLOWED_NTLM_CREDENTIALS_DOMAINS_SET:
      {
        if (action.payload === null) return null;
        return action.payload;
      }
    default:
      return state;
  }
};

const appPath = (state = null, action) => {
  switch (action.type) {
    case APP_PATH_SET:
      return action.payload;
    default:
      return state;
  }
};

const appVersion = (state = null, action) => {
  switch (action.type) {
    case APP_VERSION_SET:
      return action.payload;
    default:
      return state;
  }
};

const machineTheme = (state = 'light', action) => {
  switch (action.type) {
    case APP_MACHINE_THEME_SET:
      {
        return action.payload;
      }
    default:
      return state;
  }
};

const mainWindowTitle = (state = null, action) => {
  switch (action.type) {
    case APP_MAIN_WINDOW_TITLE_SET:
      return action.payload;
    default:
      return state;
  }
};

const screenCaptureFallbackForced = (state = false, action) => {
  switch (action.type) {
    case APP_SCREEN_CAPTURE_FALLBACK_FORCED_SET:
      return action.payload;
    default:
      return state;
  }
};

const DOWNLOAD_CREATED = 'downloads/created';
const DOWNLOAD_REMOVED = 'downloads/removed';
const DOWNLOADS_CLEARED = 'downloads/cleared';
const DOWNLOAD_UPDATED = 'downloads/updated';

const DownloadStatus = {
  ALL: 'All',
  PAUSED: 'Paused',
  CANCELLED: 'Cancelled'
};

const downloads = (state = {}, action) => {
  switch (action.type) {
    case APP_SETTINGS_LOADED:
      {
        var _action$payload$downl;
        const initDownloads = (_action$payload$downl = action.payload.downloads) !== null && _action$payload$downl !== undefined ? _action$payload$downl : {};
        Object.values(initDownloads).forEach(value => {
          if (value.state === 'progressing' || value.state === 'paused') {
            value.state = 'cancelled';
            value.status = DownloadStatus.CANCELLED;
          }
        });
        return initDownloads !== null && initDownloads !== undefined ? initDownloads : {};
      }
    case DOWNLOAD_CREATED:
      {
        const download = action.payload;
        return {
          ...state,
          [download.itemId]: download
        };
      }
    case DOWNLOAD_UPDATED:
      {
        const existingDownload = state[action.payload.itemId];
        if (!existingDownload) {
          return state; // Don't update if download doesn't exist
        }
        const newState = {
          ...state
        };
        newState[action.payload.itemId] = {
          ...existingDownload,
          ...action.payload
        };
        return newState;
      }
    case DOWNLOAD_REMOVED:
      {
        const newState = {
          ...state
        };
        delete newState[action.payload];
        return newState;
      }
    case DOWNLOADS_CLEARED:
      return {};
    default:
      return state;
  }
};

const allowedJitsiServers = (state = {}, action) => {
  switch (action.type) {
    case APP_SETTINGS_LOADED:
      {
        const {
          allowedJitsiServers = {}
        } = action.payload;
        state = allowedJitsiServers;
        return state;
      }
    case JITSI_SERVER_CAPTURE_SCREEN_PERMISSION_UPDATED:
      {
        state = {
          ...state,
          [action.payload.jitsiServer]: action.payload.allowed
        };
        return state;
      }
    case JITSI_SERVER_CAPTURE_SCREEN_PERMISSIONS_CLEARED:
      {
        state = {};
        return state;
      }
    default:
      return state;
  }
};

const CERTIFICATES_CLEARED = 'certificates/cleared';
const CERTIFICATES_LOADED = 'certificates/loaded';
const CERTIFICATES_CLIENT_CERTIFICATE_REQUESTED = 'certificates/client-certificate-requested';
const TRUSTED_CERTIFICATES_UPDATED = 'trusted-certificates/updated';
const NOT_TRUSTED_CERTIFICATES_UPDATED = 'not-trusted-certificates/updated';
const SELECT_CLIENT_CERTIFICATE_DIALOG_CERTIFICATE_SELECTED = 'select-client-certificate-dialog/certificate-selected';
const SELECT_CLIENT_CERTIFICATE_DIALOG_DISMISSED = 'select-client-certificate-dialog/dismissed';
const EXTERNAL_PROTOCOL_PERMISSION_UPDATED = 'navigation/external-protocol-permission-updated';

const clientCertificates = (state = [], action) => {
  switch (action.type) {
    case CERTIFICATES_CLIENT_CERTIFICATE_REQUESTED:
      return action.payload;
    case SELECT_CLIENT_CERTIFICATE_DIALOG_CERTIFICATE_SELECTED:
    case SELECT_CLIENT_CERTIFICATE_DIALOG_DISMISSED:
      return [];
    default:
      return state;
  }
};
const trustedCertificates = (state = {}, action) => {
  switch (action.type) {
    case CERTIFICATES_LOADED:
    case TRUSTED_CERTIFICATES_UPDATED:
      return action.payload;
    case CERTIFICATES_CLEARED:
      return {};
    case APP_SETTINGS_LOADED:
      {
        const {
          trustedCertificates = state
        } = action.payload;
        return trustedCertificates;
      }
    default:
      return state;
  }
};
const notTrustedCertificates = (state = {}, action) => {
  switch (action.type) {
    case NOT_TRUSTED_CERTIFICATES_UPDATED:
      return action.payload;
    case CERTIFICATES_CLEARED:
      return {};
    case APP_SETTINGS_LOADED:
      {
        const {
          notTrustedCertificates = state
        } = action.payload;
        return notTrustedCertificates;
      }
    default:
      return state;
  }
};
const externalProtocols = (state = {}, action) => {
  switch (action.type) {
    case APP_SETTINGS_LOADED:
      {
        const {
          externalProtocols = {}
        } = action.payload;
        state = externalProtocols;
        return state;
      }
    case EXTERNAL_PROTOCOL_PERMISSION_UPDATED:
      {
        state = {
          ...state,
          [action.payload.protocol]: action.payload.allowed
        };
        return state;
      }
    default:
      return state;
  }
};

/**
 * Controls whether to bypass SSL certificate validation for Outlook calendar connections.
 * This setting is intended for air-gapped environments where Exchange servers use
 * self-signed certificates or internal CA certificates that are not in the system trust store.
 *
 * Configurable via overridden-settings.json:
 * { "allowInsecureOutlookConnections": true }
 *
 * Defaults to false (secure connections with certificate validation).
 */
const allowInsecureOutlookConnections = (state = false, action) => {
  switch (action.type) {
    case APP_SETTINGS_LOADED:
      {
        const {
          allowInsecureOutlookConnections = state
        } = action.payload;
        return allowInsecureOutlookConnections;
      }
    default:
      return state;
  }
};

const ABOUT_DIALOG_DISMISSED = 'about-dialog/dismissed';
const ABOUT_DIALOG_TOGGLE_UPDATE_ON_START = 'about-dialog/toggle-update-on-start';
const ABOUT_DIALOG_UPDATE_CHANNEL_CHANGED = 'about-dialog/update-channel-changed';
const ADD_SERVER_VIEW_SERVER_ADDED = 'add-server/view-server-added';
const CLEAR_CACHE_TRIGGERED = 'clear-cache/triggered';
const CLEAR_CACHE_DIALOG_DELETE_LOGIN_DATA_CLICKED = 'clear-cache-dialog/delete-login-data-clicked';
const CLEAR_CACHE_DIALOG_KEEP_LOGIN_DATA_CLICKED = 'clear-cache-dialog/keep-login-data-clicked';
const LOADING_ERROR_VIEW_RELOAD_SERVER_CLICKED = 'loading-error-view/reload-server-clicked';
const MENU_BAR_ABOUT_CLICKED = 'menu-bar/about-clicked';
const MENU_BAR_ADD_NEW_SERVER_CLICKED = 'menu-bar/add-new-server-clicked';
const MENU_BAR_SELECT_SERVER_CLICKED = 'menu-bar/select-server-clicked';
const MENU_BAR_TOGGLE_IS_MENU_BAR_ENABLED_CLICKED = 'menu-bar/toggle-is-menu-bar-enabled-clicked';
const MENU_BAR_TOGGLE_IS_SHOW_WINDOW_ON_UNREAD_CHANGED_ENABLED_CLICKED = 'menu-bar/toggle-is-show-window-on-unread-changed-enabled-clicked';
const MENU_BAR_TOGGLE_IS_SIDE_BAR_ENABLED_CLICKED = 'menu-bar/toggle-is-side-bar-enabled-clicked';
const MENU_BAR_TOGGLE_IS_TRAY_ICON_ENABLED_CLICKED = 'menu-bar/toggle-is-tray-icon-enabled-clicked';
const MENU_BAR_TOGGLE_IS_DEVELOPER_MODE_ENABLED_CLICKED = 'menu-bar/toggle-is-developer-mode-enabled-clicked';
const MENU_BAR_TOGGLE_IS_VIDEO_CALL_DEVTOOLS_AUTO_OPEN_ENABLED_CLICKED = 'menu-bar/toggle-is-video-call-devtools-auto-open-enabled-clicked';
const ROOT_WINDOW_ICON_CHANGED = 'root-window/icon-changed';
const ROOT_WINDOW_STATE_CHANGED = 'root-window/state-changed';
const VIDEO_CALL_WINDOW_STATE_CHANGED = 'video-call-window/state-changed';
const SIDE_BAR_ADD_NEW_SERVER_CLICKED = 'side-bar/add-new-server-clicked';
const SIDE_BAR_CONTEXT_MENU_TRIGGERED = 'side-bar/context-menu-triggered';
const SIDE_BAR_DOWNLOADS_BUTTON_CLICKED = 'side-bar/downloads-button-clicked';
const SIDE_BAR_SETTINGS_BUTTON_CLICKED = 'side-bar/settings-button-clicked';
const SIDE_BAR_REMOVE_SERVER_CLICKED = 'side-bar/remove-server-clicked';
const SIDE_BAR_SERVER_SELECTED = 'side-bar/server-selected';
const SIDE_BAR_SERVERS_SORTED = 'side-bar/servers-sorted';
const TOUCH_BAR_FORMAT_BUTTON_TOUCHED = 'touch-bar/format-button-touched';
const TOUCH_BAR_SELECT_SERVER_TOUCHED = 'touch-bar/select-server-touched';
const UPDATE_DIALOG_DISMISSED = 'update-dialog/dismissed';
const UPDATE_DIALOG_INSTALL_BUTTON_CLICKED = 'update-dialog/install-button-clicked';
const UPDATE_DIALOG_REMIND_UPDATE_LATER_CLICKED = 'update-dialog/remind-update-later-clicked';
const UPDATE_DIALOG_SKIP_UPDATE_CLICKED = 'update-dialog/skip-update-clicked';
const WEBVIEW_READY = 'webview/ready';
const WEBVIEW_ATTACHED = 'webview/attached';
const WEBVIEW_DID_FAIL_LOAD = 'webview/did-fail-load';
const WEBVIEW_DID_NAVIGATE = 'webview/did-navigate';
const WEBVIEW_DID_START_LOADING = 'webview/did-start-loading';
const WEBVIEW_FAVICON_CHANGED = 'webview/favicon-changed';
const WEBVIEW_FOCUS_REQUESTED = 'webview/focus-requested';
const WEBVIEW_MESSAGE_BOX_BLURRED = 'webview/message-box-blurred';
const WEBVIEW_MESSAGE_BOX_FOCUSED = 'webview/message-box-focused';
const WEBVIEW_SCREEN_SHARING_SOURCE_REQUESTED = 'webview/screen-sharing-source-requested';
const WEBVIEW_SCREEN_SHARING_SOURCE_RESPONDED = 'webview/screen-sharing-source-responded';
const WEBVIEW_SIDEBAR_STYLE_CHANGED = 'webview/sidebar-style-changed';
const WEBVIEW_SIDEBAR_CUSTOM_THEME_CHANGED = 'webview/sidebar-custom-theme-changed';
const WEBVIEW_GIT_COMMIT_HASH_CHANGED = 'webview/git-commit-hash-changed';
const WEBVIEW_GIT_COMMIT_HASH_CHECK = 'webview/git-commit-hash-check';
const WEBVIEW_TITLE_CHANGED = 'webview/title-changed';
const WEBVIEW_PAGE_TITLE_CHANGED = 'webview/page-title-changed';
const WEBVIEW_UNREAD_CHANGED = 'webview/unread-changed';
const WEBVIEW_USER_LOGGED_IN = 'webview/user-loggedin';
const WEBVIEW_ALLOWED_REDIRECTS_CHANGED = 'webview/allowed-redirects-changed';
const SETTINGS_SET_REPORT_OPT_IN_CHANGED = 'settings/set-bugsnag-opt-in-changed';
const SETTINGS_SET_FLASHFRAME_OPT_IN_CHANGED = 'settings/set-flashframe-opt-in-changed';
const SETTINGS_SET_HARDWARE_ACCELERATION_OPT_IN_CHANGED = 'settings/set-hardware-acceleration-opt-in-changed';
const SETTINGS_SET_INTERNALVIDEOCHATWINDOW_OPT_IN_CHANGED = 'settings/set-internalvideochatwindow-opt-in-changed';
const SETTINGS_SET_MINIMIZE_ON_CLOSE_OPT_IN_CHANGED = 'settings/set-minimize-on-close-opt-in-changed';
const SETTINGS_SET_IS_TRAY_ICON_ENABLED_CHANGED = 'settings/set-is-tray-icon-enabled-changed';
const SETTINGS_SET_IS_SIDE_BAR_ENABLED_CHANGED = 'settings/set-is-side-bar-enabled-changed';
const SETTINGS_SET_IS_MENU_BAR_ENABLED_CHANGED = 'settings/set-is-menu-bar-enabled-changed';
const SETTINGS_SET_IS_VIDEO_CALL_WINDOW_PERSISTENCE_ENABLED_CHANGED = 'settings/set-is-video-call-window-persistence-enabled-changed';
const SETTINGS_SET_IS_TRANSPARENT_WINDOW_ENABLED_CHANGED = 'settings/set-is-transparent-window-enabled-changed';
const SETTINGS_SET_IS_DEVELOPER_MODE_ENABLED_CHANGED = 'settings/set-is-developer-mode-enabled-changed';
const SETTINGS_SET_IS_VIDEO_CALL_DEVTOOLS_AUTO_OPEN_ENABLED_CHANGED = 'settings/set-is-video-call-devtools-auto-open-enabled-changed';
const SETTINGS_SET_IS_VIDEO_CALL_SCREEN_CAPTURE_FALLBACK_ENABLED_CHANGED = 'settings/set-is-video-call-screen-capture-fallback-enabled-changed';
const SETTINGS_CLEAR_PERMITTED_SCREEN_CAPTURE_PERMISSIONS = 'settings/clear-permitted-screen-capture-permissions';
const SETTINGS_SET_OUTLOOK_CALENDAR_SYNC_INTERVAL_CHANGED = 'settings/set-outlook-calendar-sync-interval-changed';
const SETTINGS_NTLM_CREDENTIALS_CHANGED = 'settings/ntlm-credentials-changed';
const SETTINGS_AVAILABLE_BROWSERS_UPDATED = 'settings/available-browsers-updated';
const SETTINGS_SELECTED_BROWSER_CHANGED = 'settings/selected-browser-changed';
const SETTINGS_USER_THEME_PREFERENCE_CHANGED = 'settings/user-theme-preference-changed';
const SETTINGS_SET_VERBOSE_OUTLOOK_LOGGING_CHANGED = 'settings/set-verbose-outlook-logging-changed';
const SETTINGS_SET_DETAILED_EVENTS_LOGGING_CHANGED = 'settings/set-detailed-events-logging-changed';
const SETTINGS_SET_DEBUG_LOGGING_CHANGED = 'settings/set-debug-logging-changed';
const SET_HAS_TRAY_MINIMIZE_NOTIFICATION_SHOWN = 'notifications/set-has-tray-minimize-notification-shown';
const DOWNLOADS_BACK_BUTTON_CLICKED = 'downloads/back-button-clicked';
const WEBVIEW_SERVER_SUPPORTED_VERSIONS_UPDATED = 'webview/server-supported-versions-updated';
const WEBVIEW_SERVER_UNIQUE_ID_UPDATED = 'webview/server-workspace-uid-updated';
const WEBVIEW_SERVER_IS_SUPPORTED_VERSION = 'webview/server-is-supported-version';
const WEBVIEW_SERVER_SUPPORTED_VERSIONS_LOADING = 'webview/server-supported-versions-loading';
const WEBVIEW_SERVER_SUPPORTED_VERSIONS_ERROR = 'webview/server-supported-versions-error';
const WEBVIEW_SERVER_VERSION_UPDATED = 'webview/version-updated';
const SUPPORTED_VERSION_DIALOG_DISMISS = 'supported-versions-dialog/dismiss';
const WEBVIEW_SERVER_RELOADED = 'webview/server-reloaded';
const WEBVIEW_PDF_VIEWER_ATTACHED = 'webview/pdf-viewer/attached';
const SIDE_BAR_SERVER_RELOAD = 'side-bar/server-reload';
const SIDE_BAR_SERVER_COPY_URL = 'side-bar/server-copy-url';
const SIDE_BAR_SERVER_OPEN_DEV_TOOLS = 'side-bar/server-open-dev-tools';
const SIDE_BAR_SERVER_FORCE_RELOAD = 'side-bar/server-force-reload';
const SIDE_BAR_SERVER_REMOVE = 'side-bar/server-remove';
const WEBVIEW_FORCE_RELOAD_WITH_CACHE_CLEAR = 'webview/force-reload-with-cache-clear';
const OPEN_SERVER_INFO_MODAL = 'server-info-modal/open';
const CLOSE_SERVER_INFO_MODAL = 'server-info-modal/close';

const outlookCalendarSyncInterval = (state = 60, action) => {
  switch (action.type) {
    case SETTINGS_SET_OUTLOOK_CALENDAR_SYNC_INTERVAL_CHANGED:
      return action.payload;
    case APP_SETTINGS_LOADED:
      {
        const {
          outlookCalendarSyncInterval = state
        } = action.payload;
        return outlookCalendarSyncInterval;
      }
    default:
      return state;
  }
};

const outlookCalendarSyncIntervalOverride = (state = null, action) => {
  switch (action.type) {
    case APP_SETTINGS_LOADED:
      {
        const {
          outlookCalendarSyncIntervalOverride = state
        } = action.payload;
        return outlookCalendarSyncIntervalOverride !== null && outlookCalendarSyncIntervalOverride !== undefined ? outlookCalendarSyncIntervalOverride : null;
      }
    default:
      return state;
  }
};

const DEEP_LINKS_SERVER_ADDED = 'deep-links/server-added';
const DEEP_LINKS_SERVER_FOCUSED = 'deep-links/server-focused';

const OUTLOOK_CALENDAR_SET_CREDENTIALS = 'outlook-calendar/set-credentials';
const OUTLOOK_CALENDAR_ASK_CREDENTIALS = 'outlook-calendar/ask-credentials';
const OUTLOOK_CALENDAR_DIALOG_DISMISSED = 'outlook-calendar/dialog-dismissed';
const OUTLOOK_CALENDAR_SAVE_CREDENTIALS = 'outlook-calendar/save-credentials';

const SERVERS_LOADED = 'servers/loaded';
const SERVER_URL_RESOLUTION_REQUESTED = 'server/url-resolution-requested';
const SERVER_URL_RESOLVED = 'server/url-resolved';
const SERVER_DOCUMENT_VIEWER_OPEN_URL = 'server/document-viewer/open-url';

/* eslint-disable complexity */

const ensureUrlFormat = serverUrl => {
  if (serverUrl) {
    return new URL(serverUrl).href;
  }
  throw new Error('cannot handle null server URLs');
};
const upsert = (state, server) => {
  const index = state.findIndex(({
    url
  }) => url === server.url);
  if (index === -1) {
    return [...state, server];
  }
  return state.map((_server, i) => i === index ? {
    ..._server,
    ...server
  } : _server);
};
const update = (state, server) => {
  const index = state.findIndex(({
    url
  }) => url === server.url);
  if (index === -1) {
    return state;
  }
  return state.map((_server, i) => i === index ? {
    ..._server,
    ...server
  } : _server);
};
const servers = (state = [], action) => {
  switch (action.type) {
    case ADD_SERVER_VIEW_SERVER_ADDED:
    case DEEP_LINKS_SERVER_ADDED:
      {
        const url = action.payload;
        return upsert(state, {
          url,
          title: url
        });
      }
    case SIDE_BAR_REMOVE_SERVER_CLICKED:
      {
        const _url = action.payload;
        return state.filter(({
          url
        }) => url !== _url);
      }
    case SIDE_BAR_SERVERS_SORTED:
      {
        const urls = action.payload;
        return state.sort(({
          url: a
        }, {
          url: b
        }) => urls.indexOf(a) - urls.indexOf(b));
      }
    case WEBVIEW_TITLE_CHANGED:
      {
        const {
          url,
          title = url
        } = action.payload;
        return upsert(state, {
          url,
          title
        });
      }
    case WEBVIEW_PAGE_TITLE_CHANGED:
      {
        const {
          url,
          pageTitle
        } = action.payload;
        return upsert(state, {
          url,
          pageTitle
        });
      }
    case WEBVIEW_SERVER_SUPPORTED_VERSIONS_UPDATED:
      {
        const {
          url,
          supportedVersions,
          source
        } = action.payload;
        return upsert(state, {
          url,
          supportedVersions,
          supportedVersionsSource: source,
          supportedVersionsFetchState: 'success'
        });
      }
    case WEBVIEW_SERVER_SUPPORTED_VERSIONS_LOADING:
      {
        const {
          url
        } = action.payload;
        return upsert(state, {
          url,
          supportedVersionsFetchState: 'loading'
        });
      }
    case WEBVIEW_SERVER_SUPPORTED_VERSIONS_ERROR:
      {
        const {
          url
        } = action.payload;
        return upsert(state, {
          url,
          supportedVersionsFetchState: 'error'
        });
      }
    case SUPPORTED_VERSION_DIALOG_DISMISS:
      {
        const {
          url
        } = action.payload;
        return upsert(state, {
          url,
          expirationMessageLastTimeShown: new Date()
        });
      }
    case WEBVIEW_SERVER_UNIQUE_ID_UPDATED:
      {
        const {
          url,
          uniqueID
        } = action.payload;
        return upsert(state, {
          url,
          uniqueID
        });
      }
    case WEBVIEW_SERVER_IS_SUPPORTED_VERSION:
      {
        const {
          url,
          isSupportedVersion
        } = action.payload;
        return upsert(state, {
          url,
          isSupportedVersion,
          supportedVersionsValidatedAt: new Date()
        });
      }
    case WEBVIEW_SERVER_VERSION_UPDATED:
      {
        const {
          url,
          version
        } = action.payload;
        return upsert(state, {
          url,
          version
        });
      }
    case WEBVIEW_UNREAD_CHANGED:
      {
        const {
          url,
          badge
        } = action.payload;
        return upsert(state, {
          url,
          badge
        });
      }
    case WEBVIEW_USER_LOGGED_IN:
      {
        const {
          url,
          userLoggedIn
        } = action.payload;
        return upsert(state, {
          url,
          userLoggedIn
        });
      }
    case WEBVIEW_ALLOWED_REDIRECTS_CHANGED:
      {
        const {
          url,
          allowedRedirects
        } = action.payload;
        return upsert(state, {
          url,
          allowedRedirects
        });
      }
    case WEBVIEW_SIDEBAR_STYLE_CHANGED:
      {
        const {
          url,
          style
        } = action.payload;
        return upsert(state, {
          url,
          style
        });
      }
    case WEBVIEW_SIDEBAR_CUSTOM_THEME_CHANGED:
      {
        const {
          url,
          customTheme
        } = action.payload;
        return upsert(state, {
          url,
          customTheme
        });
      }
    case WEBVIEW_GIT_COMMIT_HASH_CHANGED:
      {
        const {
          url,
          gitCommitHash
        } = action.payload;
        return upsert(state, {
          url,
          gitCommitHash
        });
      }
    case WEBVIEW_FAVICON_CHANGED:
      {
        const {
          url,
          favicon
        } = action.payload;
        return upsert(state, {
          url,
          favicon
        });
      }
    case WEBVIEW_DID_NAVIGATE:
      {
        const {
          url,
          pageUrl
        } = action.payload;
        if (pageUrl !== null && pageUrl !== undefined && pageUrl.includes(url)) {
          return upsert(state, {
            url,
            lastPath: pageUrl
          });
        }
        return state;
      }
    case WEBVIEW_DID_START_LOADING:
      {
        const {
          url
        } = action.payload;
        return upsert(state, {
          url,
          failed: false
        });
      }
    case WEBVIEW_DID_FAIL_LOAD:
      {
        const {
          url,
          isMainFrame
        } = action.payload;
        if (isMainFrame) {
          return upsert(state, {
            url,
            failed: true
          });
        }
        return state;
      }
    case SERVERS_LOADED:
      {
        const {
          servers = state
        } = action.payload;
        return servers.map(server => ({
          ...server,
          url: ensureUrlFormat(server.url)
        }));
      }
    case APP_SETTINGS_LOADED:
      {
        const {
          servers = state
        } = action.payload;
        return servers.map(server => ({
          ...server,
          url: ensureUrlFormat(server.url),
          documentViewerOpenUrl: '',
          documentViewerFormat: ''
        }));
      }
    case WEBVIEW_READY:
      {
        const {
          url,
          webContentsId
        } = action.payload;
        return update(state, {
          url,
          webContentsId
        });
      }
    case WEBVIEW_ATTACHED:
      {
        const {
          url,
          webContentsId
        } = action.payload;
        return update(state, {
          url,
          webContentsId
        });
      }
    case OUTLOOK_CALENDAR_SAVE_CREDENTIALS:
      {
        const {
          url,
          outlookCredentials
        } = action.payload;
        return upsert(state, {
          url,
          outlookCredentials
        });
      }
    case SERVER_DOCUMENT_VIEWER_OPEN_URL:
      {
        const {
          server,
          documentUrl,
          documentFormat
        } = action.payload;
        return upsert(state, {
          url: server,
          documentViewerOpenUrl: documentUrl,
          documentViewerFormat: documentFormat !== null && documentFormat !== undefined ? documentFormat : ''
        });
      }
    default:
      return state;
  }
};

const availableBrowsers = (state = [], action) => {
  switch (action.type) {
    case SETTINGS_AVAILABLE_BROWSERS_UPDATED:
      return action.payload;
    case APP_SETTINGS_LOADED:
      {
        return state;
      }
    default:
      return state;
  }
};

const currentView = (state = 'add-new-server', action) => {
  switch (action.type) {
    case ADD_SERVER_VIEW_SERVER_ADDED:
    case DEEP_LINKS_SERVER_ADDED:
    case DEEP_LINKS_SERVER_FOCUSED:
    case MENU_BAR_SELECT_SERVER_CLICKED:
    case TOUCH_BAR_SELECT_SERVER_TOUCHED:
    case SIDE_BAR_SERVER_SELECTED:
      {
        const url = action.payload;
        return {
          url
        };
      }
    case WEBVIEW_FOCUS_REQUESTED:
      {
        const {
          url,
          view
        } = action.payload;
        if (view === 'downloads') return 'downloads';
        return {
          url
        };
      }
    case SERVERS_LOADED:
      {
        const {
          selected
        } = action.payload;
        return selected ? {
          url: selected
        } : 'add-new-server';
      }
    case APP_SETTINGS_LOADED:
      {
        const {
          currentView = state
        } = action.payload;
        return currentView;
      }
    case MENU_BAR_ADD_NEW_SERVER_CLICKED:
    case SIDE_BAR_ADD_NEW_SERVER_CLICKED:
      return 'add-new-server';
    case SIDE_BAR_REMOVE_SERVER_CLICKED:
      {
        if (typeof state === 'object' && state.url === action.payload) {
          return 'add-new-server';
        }
        return state;
      }
    case SIDE_BAR_DOWNLOADS_BUTTON_CLICKED:
      return 'downloads';
    case SIDE_BAR_SETTINGS_BUTTON_CLICKED:
      return 'settings';
    case DOWNLOADS_BACK_BUTTON_CLICKED:
      return {
        url: action.payload
      };
    default:
      return state;
  }
};

const initialServerInfoModalState = {
  isOpen: false,
  serverData: null
};
const serverInfoModal = (state = initialServerInfoModalState, action) => {
  switch (action.type) {
    case OPEN_SERVER_INFO_MODAL:
      return {
        isOpen: true,
        serverData: action.payload
      };
    case CLOSE_SERVER_INFO_MODAL:
      return initialServerInfoModalState;
    default:
      return state;
  }
};
const dialogs = (state = {
  serverInfoModal: initialServerInfoModalState
}, action) => {
  switch (action.type) {
    case OPEN_SERVER_INFO_MODAL:
    case CLOSE_SERVER_INFO_MODAL:
      return {
        ...state,
        serverInfoModal: serverInfoModal(state.serverInfoModal, action)
      };
    default:
      return state;
  }
};

const hasHideOnTrayNotificationShown = (state = false, action) => {
  switch (action.type) {
    case APP_SETTINGS_LOADED:
      return Boolean(action.payload.hasHideOnTrayNotificationShown);
    case SET_HAS_TRAY_MINIMIZE_NOTIFICATION_SHOWN:
      return action.payload;
    default:
      return state;
  }
};

const isAddNewServersEnabled = (state = true, action) => {
  switch (action.type) {
    case APP_SETTINGS_LOADED:
      return Boolean(action.payload.isAddNewServersEnabled);
    default:
      return state;
  }
};

const isDebugLoggingEnabled = (state = false, action) => {
  switch (action.type) {
    case SETTINGS_SET_DEBUG_LOGGING_CHANGED:
      return action.payload;
    case APP_SETTINGS_LOADED:
      {
        const {
          isDebugLoggingEnabled = state
        } = action.payload;
        return isDebugLoggingEnabled;
      }
    default:
      return state;
  }
};

const isDetailedEventsLoggingEnabled = (state = false, action) => {
  switch (action.type) {
    case SETTINGS_SET_DETAILED_EVENTS_LOGGING_CHANGED:
      return action.payload;
    case APP_SETTINGS_LOADED:
      {
        const {
          isDetailedEventsLoggingEnabled = state
        } = action.payload;
        return isDetailedEventsLoggingEnabled;
      }
    default:
      return state;
  }
};

const isDeveloperModeEnabled = (state = false, action) => {
  switch (action.type) {
    case SETTINGS_SET_IS_DEVELOPER_MODE_ENABLED_CHANGED:
    case MENU_BAR_TOGGLE_IS_DEVELOPER_MODE_ENABLED_CLICKED:
      return action.payload;
    case APP_SETTINGS_LOADED:
      {
        const {
          isDeveloperModeEnabled = state
        } = action.payload;
        return isDeveloperModeEnabled;
      }
    default:
      return state;
  }
};

const UPDATE_SKIPPED = 'update/skipped';
const UPDATES_CHECK_FOR_UPDATES_REQUESTED = 'updates/check-for-updates-requested';
const UPDATES_CHECKING_FOR_UPDATE = 'updates/checking-for-update';
const UPDATES_ERROR_THROWN = 'updates/error-thrown';
const UPDATES_NEW_VERSION_AVAILABLE = 'updates/new-version-available';
const UPDATES_NEW_VERSION_NOT_AVAILABLE = 'updates/new-version-not-available';
const UPDATES_READY = 'updates/ready';
const UPDATES_CHANNEL_CHANGED = 'updates/channel-changed';

const isFlashFrameEnabled = (state = false, action) => {
  switch (action.type) {
    case APP_SETTINGS_LOADED:
      return Boolean(action.payload.isFlashFrameEnabled);
    case UPDATES_READY:
      return action.payload.isFlashFrameEnabled;
    case SETTINGS_SET_FLASHFRAME_OPT_IN_CHANGED:
      {
        return action.payload;
      }
    default:
      return state;
  }
};

const isHardwareAccelerationEnabled = (state = false, action) => {
  switch (action.type) {
    case APP_SETTINGS_LOADED:
      return Boolean(action.payload.isHardwareAccelerationEnabled);
    case UPDATES_READY:
      return action.payload.isHardwareAccelerationEnabled;
    case SETTINGS_SET_HARDWARE_ACCELERATION_OPT_IN_CHANGED:
      {
        return action.payload;
      }
    default:
      return state;
  }
};

const isInternalVideoChatWindowEnabled = (state = false, action) => {
  switch (action.type) {
    case APP_SETTINGS_LOADED:
      return Boolean(action.payload.isInternalVideoChatWindowEnabled);
    case UPDATES_READY:
      return action.payload.isInternalVideoChatWindowEnabled;
    case SETTINGS_SET_INTERNALVIDEOCHATWINDOW_OPT_IN_CHANGED:
      {
        return action.payload;
      }
    default:
      return state;
  }
};

const isMenuBarEnabled = (state = true, action) => {
  switch (action.type) {
    case SETTINGS_SET_IS_MENU_BAR_ENABLED_CHANGED:
    case MENU_BAR_TOGGLE_IS_MENU_BAR_ENABLED_CLICKED:
      return action.payload;
    case APP_SETTINGS_LOADED:
      {
        const {
          isMenuBarEnabled = state
        } = action.payload;
        return isMenuBarEnabled;
      }
    default:
      return state;
  }
};

const isMessageBoxFocused = (state = false, action) => {
  switch (action.type) {
    case WEBVIEW_MESSAGE_BOX_FOCUSED:
      return true;
    case WEBVIEW_DID_START_LOADING:
    case WEBVIEW_MESSAGE_BOX_BLURRED:
    case WEBVIEW_DID_FAIL_LOAD:
      return false;
    default:
      return state;
  }
};

const isMinimizeOnCloseEnabled = (state = process.platform === 'win32', action) => {
  switch (action.type) {
    case SETTINGS_SET_MINIMIZE_ON_CLOSE_OPT_IN_CHANGED:
      {
        return action.payload;
      }
    case APP_SETTINGS_LOADED:
      {
        const {
          isMinimizeOnCloseEnabled = state
        } = action.payload;
        return isMinimizeOnCloseEnabled;
      }
    default:
      return state;
  }
};

const isNTLMCredentialsEnabled = (state = false, action) => {
  switch (action.type) {
    case APP_SETTINGS_LOADED:
      {
        const {
          isNTLMCredentialsEnabled = state
        } = action.payload;
        return isNTLMCredentialsEnabled;
      }
    case SETTINGS_NTLM_CREDENTIALS_CHANGED:
      {
        return action.payload;
      }
    default:
      return state;
  }
};

const isReportEnabled = (state = false, action) => {
  switch (action.type) {
    case APP_SETTINGS_LOADED:
      return Boolean(action.payload.isReportEnabled);
    case UPDATES_READY:
      return action.payload.isReportEnabled;
    case SETTINGS_SET_REPORT_OPT_IN_CHANGED:
      {
        return action.payload;
      }
    default:
      return state;
  }
};

const isShowWindowOnUnreadChangedEnabled = (state = false, action) => {
  switch (action.type) {
    case MENU_BAR_TOGGLE_IS_SHOW_WINDOW_ON_UNREAD_CHANGED_ENABLED_CLICKED:
      return action.payload;
    case APP_SETTINGS_LOADED:
      {
        const {
          isShowWindowOnUnreadChangedEnabled = state
        } = action.payload;
        return isShowWindowOnUnreadChangedEnabled;
      }
    default:
      return state;
  }
};

const isSideBarEnabled = (state = true, action) => {
  switch (action.type) {
    case SETTINGS_SET_IS_SIDE_BAR_ENABLED_CHANGED:
    case MENU_BAR_TOGGLE_IS_SIDE_BAR_ENABLED_CLICKED:
      return action.payload;
    case APP_SETTINGS_LOADED:
      {
        const {
          isSideBarEnabled = state
        } = action.payload;
        return isSideBarEnabled;
      }
    default:
      return state;
  }
};

const isTransparentWindowEnabled = (state = false, action) => {
  switch (action.type) {
    case SETTINGS_SET_IS_TRANSPARENT_WINDOW_ENABLED_CHANGED:
      {
        const {
          payload
        } = action;
        if (typeof payload === 'boolean') {
          return payload;
        }
        console.warn(`Invalid payload type for ${SETTINGS_SET_IS_TRANSPARENT_WINDOW_ENABLED_CHANGED}: expected boolean, got ${typeof payload}`);
        return state;
      }
    case APP_SETTINGS_LOADED:
      {
        const {
          isTransparentWindowEnabled = state
        } = action.payload;
        return isTransparentWindowEnabled;
      }
    default:
      return state;
  }
};

const isTrayIconEnabled = (state = process.platform !== 'linux', action) => {
  switch (action.type) {
    case SETTINGS_SET_IS_TRAY_ICON_ENABLED_CHANGED:
    case MENU_BAR_TOGGLE_IS_TRAY_ICON_ENABLED_CLICKED:
      return action.payload;
    case APP_SETTINGS_LOADED:
      {
        const {
          isTrayIconEnabled = state
        } = action.payload;
        return isTrayIconEnabled;
      }
    default:
      return state;
  }
};

const isVerboseOutlookLoggingEnabled = (state = false, action) => {
  switch (action.type) {
    case SETTINGS_SET_VERBOSE_OUTLOOK_LOGGING_CHANGED:
      return action.payload;
    case APP_SETTINGS_LOADED:
      {
        const {
          isVerboseOutlookLoggingEnabled = state
        } = action.payload;
        return isVerboseOutlookLoggingEnabled;
      }
    default:
      return state;
  }
};

const isVideoCallDevtoolsAutoOpenEnabled = (state = false, action) => {
  switch (action.type) {
    case SETTINGS_SET_IS_VIDEO_CALL_DEVTOOLS_AUTO_OPEN_ENABLED_CHANGED:
    case MENU_BAR_TOGGLE_IS_VIDEO_CALL_DEVTOOLS_AUTO_OPEN_ENABLED_CLICKED:
      return action.payload;
    case APP_SETTINGS_LOADED:
      {
        const {
          isVideoCallDevtoolsAutoOpenEnabled = state
        } = action.payload;
        return isVideoCallDevtoolsAutoOpenEnabled;
      }
    default:
      return state;
  }
};

const isVideoCallScreenCaptureFallbackEnabled = (state = false, action) => {
  switch (action.type) {
    case APP_SETTINGS_LOADED:
      return Boolean(action.payload.isVideoCallScreenCaptureFallbackEnabled);
    case UPDATES_READY:
      {
        const {
          isVideoCallScreenCaptureFallbackEnabled = state
        } = action.payload;
        return isVideoCallScreenCaptureFallbackEnabled;
      }
    case SETTINGS_SET_IS_VIDEO_CALL_SCREEN_CAPTURE_FALLBACK_ENABLED_CHANGED:
      return action.payload;
    default:
      return state;
  }
};

const isVideoCallWindowPersistenceEnabled = (state = true,
// Enabled by default
action) => {
  switch (action.type) {
    case SETTINGS_SET_IS_VIDEO_CALL_WINDOW_PERSISTENCE_ENABLED_CHANGED:
      return action.payload;
    case APP_SETTINGS_LOADED:
      {
        const {
          isVideoCallWindowPersistenceEnabled = state
        } = action.payload;
        return isVideoCallWindowPersistenceEnabled;
      }
    default:
      return state;
  }
};

const lastSelectedServerUrl = (state = '', action) => {
  switch (action.type) {
    case APP_SETTINGS_LOADED:
      {
        const {
          lastSelectedServerUrl = state,
          servers
        } = action.payload;
        if (state === '' && servers && servers.length > 0) {
          return servers[0].url;
        }
        return lastSelectedServerUrl;
      }
    case SIDE_BAR_SERVER_SELECTED:
      return action.payload;
    default:
      return state;
  }
};

const SCREEN_SHARING_DIALOG_DISMISSED = 'screen-sharing-dialog/dismissed';

const openDialog = (state = null, action) => {
  switch (action.type) {
    case MENU_BAR_ABOUT_CLICKED:
      return 'about';
    case WEBVIEW_SCREEN_SHARING_SOURCE_REQUESTED:
      return 'screen-sharing';
    case UPDATES_NEW_VERSION_AVAILABLE:
      return 'update';
    case CERTIFICATES_CLIENT_CERTIFICATE_REQUESTED:
      return 'select-client-certificate';
    case ABOUT_DIALOG_DISMISSED:
      if (state === 'about') {
        return null;
      }
      return state;
    case OUTLOOK_CALENDAR_ASK_CREDENTIALS:
      return 'outlook-credentials';
    case SCREEN_SHARING_DIALOG_DISMISSED:
    case WEBVIEW_SCREEN_SHARING_SOURCE_RESPONDED:
    case SELECT_CLIENT_CERTIFICATE_DIALOG_CERTIFICATE_SELECTED:
    case SELECT_CLIENT_CERTIFICATE_DIALOG_DISMISSED:
    case UPDATE_DIALOG_DISMISSED:
    case UPDATE_DIALOG_SKIP_UPDATE_CLICKED:
    case UPDATE_DIALOG_REMIND_UPDATE_LATER_CLICKED:
    case UPDATE_DIALOG_INSTALL_BUTTON_CLICKED:
    case OUTLOOK_CALENDAR_DIALOG_DISMISSED:
    case OUTLOOK_CALENDAR_SET_CREDENTIALS:
      return null;
    default:
      return state;
  }
};

const rootWindowIcon = (state = null, action) => {
  switch (action.type) {
    case ROOT_WINDOW_ICON_CHANGED:
      {
        return action.payload;
      }
    default:
      return state;
  }
};

const rootWindowState = (state = {
  focused: true,
  visible: true,
  maximized: false,
  minimized: false,
  fullscreen: false,
  normal: true,
  bounds: {
    x: undefined,
    y: undefined,
    width: 1000,
    height: 600
  }
}, action) => {
  switch (action.type) {
    case ROOT_WINDOW_STATE_CHANGED:
      return action.payload;
    case APP_SETTINGS_LOADED:
      {
        const {
          rootWindowState = state
        } = action.payload;
        return rootWindowState;
      }
    default:
      return state;
  }
};

const selectedBrowser = (state = null, action) => {
  switch (action.type) {
    case SETTINGS_SELECTED_BROWSER_CHANGED:
      return action.payload;
    case APP_SETTINGS_LOADED:
      {
        const {
          selectedBrowser = state
        } = action.payload;
        return selectedBrowser;
      }
    default:
      return state;
  }
};

const isValidTheme = value => value === 'auto' || value === 'light' || value === 'dark';
const userThemePreference = (state = 'auto', action) => {
  switch (action.type) {
    case SETTINGS_USER_THEME_PREFERENCE_CHANGED:
      if (isValidTheme(action.payload)) {
        return action.payload;
      }
      console.warn(`Invalid payload for ${SETTINGS_USER_THEME_PREFERENCE_CHANGED}: ${action.payload}`);
      return state;
    case APP_SETTINGS_LOADED:
      {
        const {
          userThemePreference = state
        } = action.payload;
        if (isValidTheme(userThemePreference)) {
          return userThemePreference;
        }
        console.warn(`Invalid userThemePreference in settings: ${userThemePreference}`);
        return state;
      }
    default:
      return state;
  }
};

const videoCallWindowState = (state = {
  focused: true,
  visible: true,
  maximized: false,
  minimized: false,
  fullscreen: false,
  normal: true,
  bounds: {
    x: undefined,
    y: undefined,
    width: 0,
    height: 0
  }
}, action) => {
  switch (action.type) {
    case VIDEO_CALL_WINDOW_STATE_CHANGED:
      return action.payload;
    case APP_SETTINGS_LOADED:
      {
        const {
          videoCallWindowState = state
        } = action.payload;
        return videoCallWindowState;
      }
    default:
      return state;
  }
};

const doCheckForUpdatesOnStartup = (state = true, action) => {
  switch (action.type) {
    case UPDATES_READY:
      {
        const {
          doCheckForUpdatesOnStartup
        } = action.payload;
        return doCheckForUpdatesOnStartup;
      }
    case ABOUT_DIALOG_TOGGLE_UPDATE_ON_START:
      {
        const doCheckForUpdatesOnStartup = action.payload;
        return doCheckForUpdatesOnStartup;
      }
    case APP_SETTINGS_LOADED:
      {
        const {
          doCheckForUpdatesOnStartup = state
        } = action.payload;
        return doCheckForUpdatesOnStartup;
      }
    default:
      return state;
  }
};
const isCheckingForUpdates = (state = false, action) => {
  switch (action.type) {
    case UPDATES_CHECKING_FOR_UPDATE:
      return true;
    case UPDATES_ERROR_THROWN:
      return false;
    case UPDATES_NEW_VERSION_NOT_AVAILABLE:
      return false;
    case UPDATES_NEW_VERSION_AVAILABLE:
      return false;
    default:
      return state;
  }
};
const isEachUpdatesSettingConfigurable = (state = true, action) => {
  switch (action.type) {
    case UPDATES_READY:
      {
        const {
          isEachUpdatesSettingConfigurable
        } = action.payload;
        return isEachUpdatesSettingConfigurable;
      }
    case APP_SETTINGS_LOADED:
      {
        const {
          isEachUpdatesSettingConfigurable = state
        } = action.payload;
        return isEachUpdatesSettingConfigurable;
      }
    default:
      return state;
  }
};
const isUpdatingAllowed = (state = true, action) => {
  switch (action.type) {
    case UPDATES_READY:
      {
        const {
          isUpdatingAllowed
        } = action.payload;
        return isUpdatingAllowed;
      }
    default:
      return state;
  }
};
const isUpdatingEnabled = (state = true, action) => {
  switch (action.type) {
    case UPDATES_READY:
      {
        const {
          isUpdatingEnabled
        } = action.payload;
        return isUpdatingEnabled;
      }
    case APP_SETTINGS_LOADED:
      {
        const {
          isUpdatingEnabled = state
        } = action.payload;
        return isUpdatingEnabled;
      }
    default:
      return state;
  }
};
const newUpdateVersion = (state = null, action) => {
  switch (action.type) {
    case UPDATES_NEW_VERSION_AVAILABLE:
      {
        const newUpdateVersion = action.payload;
        return newUpdateVersion;
      }
    case UPDATES_NEW_VERSION_NOT_AVAILABLE:
    case UPDATE_SKIPPED:
      {
        return null;
      }
    default:
      return state;
  }
};
const skippedUpdateVersion = (state = null, action) => {
  switch (action.type) {
    case UPDATES_READY:
      {
        const {
          skippedUpdateVersion
        } = action.payload;
        return skippedUpdateVersion;
      }
    case UPDATE_SKIPPED:
      {
        const skippedUpdateVersion = action.payload;
        return skippedUpdateVersion;
      }
    case APP_SETTINGS_LOADED:
      {
        const {
          skippedUpdateVersion = state
        } = action.payload;
        return skippedUpdateVersion;
      }
    default:
      return state;
  }
};
const updateError = (state = null, action) => {
  switch (action.type) {
    case UPDATES_CHECKING_FOR_UPDATE:
      return null;
    case UPDATES_ERROR_THROWN:
      return action.payload;
    case UPDATES_NEW_VERSION_NOT_AVAILABLE:
      return null;
    case UPDATES_NEW_VERSION_AVAILABLE:
      return null;
    default:
      return state;
  }
};
const updateChannel = (state = 'latest', action) => {
  switch (action.type) {
    case ABOUT_DIALOG_UPDATE_CHANNEL_CHANGED:
    case UPDATES_CHANNEL_CHANGED:
      {
        return action.payload;
      }
    case UPDATES_READY:
      {
        const {
          updateChannel
        } = action.payload;
        return updateChannel;
      }
    case APP_SETTINGS_LOADED:
      {
        const {
          updateChannel = state
        } = action.payload;
        return updateChannel;
      }
    default:
      return state;
  }
};

const rootReducer = redux.combineReducers({
  allowedJitsiServers,
  allowInsecureOutlookConnections,
  outlookCalendarSyncInterval,
  outlookCalendarSyncIntervalOverride,
  appPath,
  appVersion,
  availableBrowsers,
  clientCertificates,
  currentView,
  dialogs,
  doCheckForUpdatesOnStartup,
  downloads,
  externalProtocols,
  isCheckingForUpdates,
  isEachUpdatesSettingConfigurable,
  isMenuBarEnabled,
  isMessageBoxFocused,
  isShowWindowOnUnreadChangedEnabled,
  isSideBarEnabled,
  isTrayIconEnabled,
  isMinimizeOnCloseEnabled,
  isUpdatingAllowed,
  isUpdatingEnabled,
  mainWindowTitle,
  machineTheme,
  newUpdateVersion,
  openDialog,
  rootWindowIcon,
  rootWindowState,
  selectedBrowser,
  servers,
  userThemePreference,
  skippedUpdateVersion,
  trustedCertificates,
  notTrustedCertificates,
  updateError,
  isReportEnabled,
  isFlashFrameEnabled,
  isHardwareAccelerationEnabled,
  isInternalVideoChatWindowEnabled,
  isAddNewServersEnabled,
  hasHideOnTrayNotificationShown,
  lastSelectedServerUrl,
  allowedNTLMCredentialsDomains,
  isNTLMCredentialsEnabled,
  videoCallWindowState,
  isVideoCallWindowPersistenceEnabled,
  isDeveloperModeEnabled,
  isDebugLoggingEnabled,
  isDetailedEventsLoggingEnabled,
  isVerboseOutlookLoggingEnabled,
  updateChannel,
  screenCaptureFallbackForced,
  isVideoCallDevtoolsAutoOpenEnabled,
  isTransparentWindowEnabled,
  isVideoCallScreenCaptureFallbackEnabled
});

let reduxStore;
let lastAction;
const catchLastAction = () => next => action => {
  lastAction = action;
  return next(action);
};
const createMainReduxStore = () => {
  const middlewares = redux.applyMiddleware(catchLastAction, forwardToRenderers);
  reduxStore = redux.createStore(rootReducer, {}, middlewares);
};
const dispatch = action => {
  if (!reduxStore) return;
  reduxStore.dispatch(action);
};
const dispatchSingle = action => {
  if (!reduxStore) return;
  reduxStore.dispatch({
    ...action,
    ipcMeta: {
      ...action.ipcMeta,
      scope: 'single'
    }
  });
};
const dispatchLocal = action => {
  if (!reduxStore) return;
  reduxStore.dispatch({
    ...action,
    ipcMeta: {
      ...action.ipcMeta,
      scope: 'local'
    },
    meta: {
      scope: 'local'
    }
  });
};
const select = selector => selector(reduxStore.getState());
const watch = (selector, watcher) => {
  if (!reduxStore) {
    console.warn('[store] watch() called before store initialized');
    return () => undefined;
  }
  const initial = select(selector);
  watcher(initial, undefined);
  let prev = initial;
  return reduxStore.subscribe(() => {
    const curr = select(selector);
    if (Object.is(prev, curr)) {
      return;
    }
    watcher(curr, prev);
    prev = curr;
  });
};
const listen = (typeOrPredicate, listener) => {
  if (!reduxStore) return () => undefined;
  const effectivePredicate = typeof typeOrPredicate === 'function' ? typeOrPredicate : action => action.type === typeOrPredicate;
  return reduxStore.subscribe(() => {
    if (!effectivePredicate(lastAction)) {
      return;
    }
    listener(lastAction);
  });
};
class Service {
  constructor() {
    _defineProperty(this, "unsubscribers", new Set());
  }
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  initialize() {}

  // eslint-disable-next-line @typescript-eslint/no-empty-function
  destroy() {}
  watch(selector, watcher) {
    this.unsubscribers.add(watch(selector, watcher));
  }

  // eslint-disable-next-line no-dupe-class-members

  // eslint-disable-next-line no-dupe-class-members
  listen(typeOrPredicate, listener) {
    if (typeof typeOrPredicate === 'string') {
      this.unsubscribers.add(listen(typeOrPredicate, listener));
      return;
    }
    this.unsubscribers.add(listen(typeOrPredicate, listener));
  }
  setUp() {
    this.initialize();
  }
  tearDown() {
    this.unsubscribers.forEach(unsubscribe => unsubscribe());
    this.destroy();
  }
}

// const isResponseTo = <Response extends RootAction>(id: unknown, type: Response['type']) =>
//   (action: RootAction): action is Response =>
//     isResponse(action) && action.type === type && action.meta.id === id;

const request = (requestAction, ...types) => new Promise((resolve, reject) => {
  if (!reduxStore) {
    reject(new Error('Store not initialized'));
    return;
  }
  const id = Math.random().toString(36).slice(2);
  const unsubscribe = listen(isResponseTo(id, ...types), action => {
    unsubscribe();
    if (isErrored(action)) {
      reject(action.payload);
      return;
    }
    if (hasPayload(action)) {
      resolve(action.payload);
    }
  });
  dispatch({
    ...requestAction,
    meta: {
      request: true,
      id
    }
  });
});

const readSetting = key => {
  try {
    const filePath = path__default.default.join(electron.app.getPath('userData'), 'config.json');
    const content = fs__namespace.default.readFileSync(filePath, 'utf8');
    const json = JSON.parse(content);
    return json[key];
  } catch (e) {
    return null;
  }
};

var readSetting$1 = /*#__PURE__*/Object.freeze({
  __proto__: null,
  readSetting: readSetting
});

const migrations = {
  '>=3.1.0': before => {
    var _rest$currentView;
    const {
      currentServerUrl,
      ...rest
    } = before;
    return {
      ...rest,
      currentView: currentServerUrl ? {
        url: currentServerUrl
      } : (_rest$currentView = rest.currentView) !== null && _rest$currentView !== undefined ? _rest$currentView : 'add-new-server',
      downloads: {}
    };
  },
  '>=3.5.0': before => ({
    ...before,
    isReportEnabled: true,
    isInternalVideoChatWindowEnabled: true,
    isFlashFrameEnabled: process.platform === 'win32' || process.platform === 'darwin'
  }),
  '>=3.7.9': before => ({
    ...before,
    isMinimizeOnCloseEnabled: process.platform === 'win32'
  }),
  '>=3.8.0': before => ({
    ...before,
    isReportEnabled: !process.mas
  }),
  '>=3.8.4': before => ({
    ...before,
    isInternalVideoChatWindowEnabled: !process.mas,
    isAddNewServersEnabled: true
  }),
  '>=3.8.7': before => ({
    ...before,
    isHardwareAccelerationEnabled: true
  }),
  '>=3.8.9': before => ({
    ...before,
    isAddNewServersEnabled: true
  }),
  '>=3.8.12': before => ({
    ...before,
    hasHideOnTrayNotificationShown: false
  }),
  '>=3.9.6': before => ({
    ...before,
    isNTLMCredentialsEnabled: false,
    allowedNTLMCredentialsDomains: null,
    lastSelectedServerUrl: ''
  }),
  '>=4.1.0': before => ({
    ...before,
    mainWindowTitle: 'Rocket.Chat',
    machineTheme: 'light'
  }),
  '>=4.2.0': before => ({
    ...before,
    selectedBrowser: null
  }),
  '>=4.4.0': before => ({
    ...before,
    isDeveloperModeEnabled: false
  }),
  '>=4.5.0': before => ({
    ...before,
    updateChannel: 'latest'
  }),
  '>=4.7.2': before => ({
    ...before,
    isVideoCallDevtoolsAutoOpenEnabled: false
  }),
  '>=4.9.0': before => ({
    ...before,
    isTransparentWindowEnabled: false,
    isVideoCallScreenCaptureFallbackEnabled: false
  }),
  '>=4.10.0': before => ({
    ...before,
    userThemePreference: 'auto'
  }),
  '>=4.11.0': before => ({
    ...before,
    outlookCalendarSyncInterval: 60,
    isVerboseOutlookLoggingEnabled: false,
    isDetailedEventsLoggingEnabled: false
  }),
  '>=4.13.0': before => ({
    ...before,
    isDebugLoggingEnabled: false
  })
};

let electronStore;
const getElectronStore = () => {
  if (!electronStore) {
    electronStore = new ElectronStore__default.default({
      migrations: Object.fromEntries(Object.entries(migrations).map(([semver, transform]) => [semver, store => {
        store.store = transform(store.store);
      }])),
      projectVersion: electron.app.getVersion(),
      configFileMode: 0o666
    });
  }
  return electronStore;
};
const getPersistedValues = () => getElectronStore().store;
let lastSavedTime = 0;
const persistValues = values => {
  if (Date.now() - lastSavedTime > 1000) {
    try {
      getElectronStore().set(values);
    } catch (error) {
      error instanceof Error && console.error(error);
    }
    lastSavedTime = Date.now();
  }
};

const selectGlobalBadge = reselect.createSelector(({
  servers
}) => servers, servers => {
  const badges = servers.map(({
    badge
  }) => badge);
  const mentionCount = badges.filter(badge => Number.isInteger(badge)).reduce((sum, count) => sum + count, 0);
  return mentionCount || badges.some(badge => !!badge) && '•' || undefined;
});
const selectGlobalBadgeText = reselect.createSelector(selectGlobalBadge, badge => {
  if (badge === '•') {
    return '•';
  }
  if (Number.isInteger(badge)) {
    return String(badge);
  }
  return '';
});
const isBadgeCount = badge => Number.isInteger(badge);
const selectGlobalBadgeCount = reselect.createSelector(selectGlobalBadge, badge => isBadgeCount(badge) ? badge : 0);

function debounce(cb, wait = 20) {
  let h;
  const callable = (...args) => {
    h && clearTimeout(h);
    h = setTimeout(() => cb(...args), wait);
  };
  return callable;
}

const getAppIconPath = ({
  platform
}) => {
  if (platform !== 'win32') {
    throw Error('only win32 platform is supported');
  }
  return `${electron.app.getAppPath()}/app/images/icon.ico`;
};
const getMacOSTrayIconPath = badge => path__default.default.join(electron.app.getAppPath(), `app/images/tray/darwin/${badge ? 'notification' : 'default'}Template.png`);
const getWindowsTrayIconPath = badge => {
  const name = !badge && 'default' || badge === '•' && 'notification-dot' || typeof badge === 'number' && badge > 9 && 'notification-plus-9' || `notification-${badge}`;
  return path__default.default.join(electron.app.getAppPath(), `app/images/tray/win32/${name}.ico`);
};
const getLinuxTrayIconPath = badge => {
  const name = !badge && 'default' || badge === '•' && 'notification-dot' || typeof badge === 'number' && badge > 9 && 'notification-plus-9' || `notification-${badge}`;
  return path__default.default.join(electron.app.getAppPath(), `app/images/tray/linux/${name}.png`);
};
const getTrayIconPath = ({
  badge,
  platform
}) => {
  switch (platform !== null && platform !== undefined ? platform : process.platform) {
    case 'darwin':
      return getMacOSTrayIconPath(badge);
    case 'win32':
      return getWindowsTrayIconPath(badge);
    case 'linux':
      return getLinuxTrayIconPath(badge);
    default:
      throw Error(`unsupported platform (${platform})`);
  }
};

const webPreferences = {
  nodeIntegration: true,
  nodeIntegrationInSubFrames: true,
  contextIsolation: false,
  webviewTag: true
};
const selectRootWindowState = ({
  rootWindowState
}) => rootWindowState !== null && rootWindowState !== undefined ? rootWindowState : {
  bounds: {
    x: 0,
    y: 0,
    width: 0,
    height: 0
  },
  focused: false,
  fullscreen: false,
  maximized: false,
  minimized: false,
  normal: false,
  visible: false
};
let _rootWindow;
let tempWindow;
const getRootWindow = () => new Promise((resolve, reject) => {
  setTimeout(() => {
    if (!_rootWindow) {
      reject(new Error('Root window not initialized'));
      return;
    }
    if (_rootWindow.isDestroyed()) {
      reject(new Error('Root window has been destroyed'));
      return;
    }
    resolve(_rootWindow);
  }, 300);
});
const platformTitleBarStyle = process.platform === 'darwin' ? 'hidden' : 'default';
const isMac = process.platform === 'darwin';
const getEnableVibrancy = () => {
  if (!isMac) {
    return false;
  }
  try {
    const persistedValues = getPersistedValues();
    return (persistedValues === null || persistedValues === void 0 ? void 0 : persistedValues.isTransparentWindowEnabled) === true;
  } catch (error) {
    return false;
  }
};
const getInitialBackgroundColor = enableVibrancy => {
  if (enableVibrancy) return '#00000000';
  return electron.nativeTheme.shouldUseDarkColors ? '#2f343d' : '#ffffff';
};
const createRootWindow = () => {
  const enableVibrancy = getEnableVibrancy();
  _rootWindow = new electron.BrowserWindow({
    width: 1000,
    height: 600,
    minWidth: 400,
    minHeight: 400,
    titleBarStyle: platformTitleBarStyle,
    backgroundColor: getInitialBackgroundColor(enableVibrancy),
    show: false,
    webPreferences,
    ...(enableVibrancy ? {
      transparent: true,
      vibrancy: 'sidebar',
      visualEffectState: 'active'
    } : {})
  });

  // Block navigation to smb:// protocol
  _rootWindow.webContents.on('will-navigate', (event, url) => {
    if (typeof url === 'string' && url.toLowerCase().startsWith('smb://')) {
      event.preventDefault();
    }
  });
  _rootWindow.webContents.setWindowOpenHandler(({
    url
  }) => {
    if (url.toLowerCase().startsWith('smb://')) {
      return {
        action: 'deny'
      };
    }
    return {
      action: 'allow'
    };
  });
  _rootWindow.addListener('close', event => {
    event.preventDefault();
  });
  tempWindow.destroy();
};
const normalizeNumber = value => value && isFinite(1 / value) ? value : 0;
const isInsideSomeScreen = ({
  x,
  y,
  width,
  height
}) => electron.screen.getAllDisplays().some(({
  bounds
}) => x >= bounds.x && y >= bounds.y && x + width <= bounds.x + bounds.width && y + height <= bounds.y + bounds.height);
const applyRootWindowState = browserWindow => {
  const rootWindowState = select(selectRootWindowState);
  const isTrayIconEnabled = select(({
    isTrayIconEnabled
  }) => isTrayIconEnabled);
  let {
    x = null,
    y = null
  } = rootWindowState.bounds;
  let {
    width,
    height
  } = rootWindowState.bounds;
  if (x === null || y === null || !isInsideSomeScreen({
    x,
    y,
    width,
    height
  })) {
    const primaryDisplay = electron.screen.getPrimaryDisplay();
    const {
      bounds: {
        width: primaryDisplayWidth,
        height: primaryDisplayHeight
      }
    } = primaryDisplay;
    x = Math.round((primaryDisplayWidth - width) / 2);
    y = Math.round((primaryDisplayHeight - height) / 2);
    width = normalizeNumber(primaryDisplay.workAreaSize.width * 0.9);
    height = normalizeNumber(primaryDisplay.workAreaSize.height * 0.9);
  }
  if (browserWindow.isVisible()) {
    return;
  }
  x = normalizeNumber(x);
  y = normalizeNumber(y);
  width = normalizeNumber(width);
  height = normalizeNumber(height);
  if (browserWindow && Number.isInteger(width) && Number.isInteger(height) && Number.isInteger(x) && Number.isInteger(y)) {
    browserWindow.setBounds({
      width,
      height,
      x,
      y
    });
  }
  if (rootWindowState.maximized) {
    browserWindow.maximize();
  }
  if (rootWindowState.minimized) {
    browserWindow.minimize();
  }
  if (rootWindowState.fullscreen) {
    browserWindow.setFullScreen(true);
  }
  if (rootWindowState.visible || !isTrayIconEnabled) {
    browserWindow.show();
  }
  if (rootWindowState.focused) {
    browserWindow.focus();
  }
};
const fetchRootWindowState = async () => {
  const browserWindow = await getRootWindow();
  return {
    focused: browserWindow === null || browserWindow === undefined ? undefined : browserWindow.isFocused(),
    visible: browserWindow === null || browserWindow === undefined ? undefined : browserWindow.isVisible(),
    maximized: browserWindow === null || browserWindow === undefined ? undefined : browserWindow.isMaximized(),
    minimized: browserWindow === null || browserWindow === undefined ? undefined : browserWindow.isMinimized(),
    fullscreen: browserWindow === null || browserWindow === undefined ? undefined : browserWindow.isFullScreen(),
    normal: browserWindow === null || browserWindow === undefined ? undefined : browserWindow.isNormal(),
    bounds: browserWindow === null || browserWindow === undefined ? undefined : browserWindow.getNormalBounds()
  };
};
const setupRootWindow = () => {
  const safeWindowOperation = async (operation, operationName) => {
    try {
      const window = await getRootWindow();
      if (window.isDestroyed()) {
        return;
      }
      return await operation(window);
    } catch (error) {
    }
  };
  const unsubscribers = [watch(selectGlobalBadgeCount, async globalBadgeCount => {
    await safeWindowOperation(async browserWindow => {
      if (browserWindow.isFocused() || globalBadgeCount === 0) {
        return;
      }
      const {
        isShowWindowOnUnreadChangedEnabled,
        isFlashFrameEnabled
      } = select(({
        isShowWindowOnUnreadChangedEnabled,
        isFlashFrameEnabled
      }) => ({
        isShowWindowOnUnreadChangedEnabled,
        isFlashFrameEnabled
      }));
      if (isShowWindowOnUnreadChangedEnabled && !browserWindow.isVisible()) {
        const isMinimized = browserWindow.isMinimized();
        const isMaximized = browserWindow.isMaximized();
        browserWindow.showInactive();
        if (isMinimized) {
          browserWindow.minimize();
        }
        if (isMaximized) {
          browserWindow.maximize();
        }
        return;
      }
      if (isFlashFrameEnabled && process.platform !== 'darwin') {
        browserWindow.flashFrame(true);
      }
    });
  }), watch(({
    currentView,
    servers
  }) => {
    const currentServer = typeof currentView === 'object' ? servers.find(({
      url
    }) => url === currentView.url) : null;
    return (currentServer === null || currentServer === undefined ? undefined : currentServer.pageTitle) || (currentServer === null || currentServer === undefined ? undefined : currentServer.title) || electron.app.name;
  }, async windowTitle => {
    await safeWindowOperation(browserWindow => {
      browserWindow.setTitle(windowTitle);
      dispatch({
        type: APP_MAIN_WINDOW_TITLE_SET,
        payload: windowTitle
      });
    });
  }), listen(WEBVIEW_FOCUS_REQUESTED, async () => {
    await safeWindowOperation(rootWindow => {
      rootWindow.focus();
      rootWindow.show();
    });
  })];
  const fetchAndDispatchWindowState = debounce(async () => {
    try {
      const state = await fetchRootWindowState();
      dispatchLocal({
        type: ROOT_WINDOW_STATE_CHANGED,
        payload: state
      });
    } catch (error) {
    }
  }, 1000);
  getRootWindow().then(rootWindow => {
    rootWindow.addListener('show', fetchAndDispatchWindowState);
    rootWindow.addListener('hide', fetchAndDispatchWindowState);
    rootWindow.addListener('focus', fetchAndDispatchWindowState);
    rootWindow.addListener('blur', fetchAndDispatchWindowState);
    rootWindow.addListener('maximize', fetchAndDispatchWindowState);
    rootWindow.addListener('unmaximize', fetchAndDispatchWindowState);
    rootWindow.addListener('minimize', fetchAndDispatchWindowState);
    rootWindow.addListener('restore', fetchAndDispatchWindowState);
    rootWindow.addListener('resize', fetchAndDispatchWindowState);
    rootWindow.addListener('move', fetchAndDispatchWindowState);
    fetchAndDispatchWindowState();
    rootWindow.addListener('focus', async () => {
      rootWindow.flashFrame(false);
    });
    rootWindow.addListener('close', async event => {
      try {
        if (rootWindow.isDestroyed()) {
          return;
        }
        if (rootWindow.isFullScreen()) {
          await new Promise(resolve => rootWindow.once('leave-full-screen', () => resolve()));
          rootWindow.setFullScreen(false);
        }
        if (process.platform !== 'linux' && !rootWindow.isDestroyed()) {
          rootWindow.blur();
        }
        let isTrayIconEnabled;
        let isMinimizeOnCloseEnabled;
        try {
          isTrayIconEnabled = select(({
            isTrayIconEnabled
          }) => isTrayIconEnabled !== null && isTrayIconEnabled !== void 0 ? isTrayIconEnabled : true);
          isMinimizeOnCloseEnabled = select(({
            isMinimizeOnCloseEnabled
          }) => isMinimizeOnCloseEnabled !== null && isMinimizeOnCloseEnabled !== void 0 ? isMinimizeOnCloseEnabled : true);
        } catch (error) {
          console.warn('Failed to access application state during close:', error);
          isTrayIconEnabled = true;
          isMinimizeOnCloseEnabled = true;
        }
        if (process.platform === 'darwin' || isTrayIconEnabled) {
          if (!rootWindow.isDestroyed()) {
            rootWindow.hide();
          }
          return;
        }
        if (process.platform === 'win32' && isMinimizeOnCloseEnabled) {
          if (!rootWindow.isDestroyed()) {
            rootWindow.minimize();
          }
          return;
        }

        // Prevent race condition: window destruction during app.quit()
        event.preventDefault();
        electron.app.quit();
      } catch (error) {
        console.error('Error in close event handler:', error);
        event.preventDefault();
        electron.app.quit();
      }
    });
    unsubscribers.push(() => {
      try {
        if (rootWindow && !rootWindow.isDestroyed()) {
          rootWindow.removeAllListeners();
          setImmediate(() => {
            if (rootWindow && !rootWindow.isDestroyed()) {
              rootWindow.close();
            }
          });
        }
      } catch (error) {
        console.error('Error during root window cleanup:', error);
      }
    });
  });
  if (process.platform === 'linux' || process.platform === 'win32') {
    const selectRootWindowIcon = reselect.createStructuredSelector({
      globalBadge: selectGlobalBadge,
      rootWindowIcon: ({
        rootWindowIcon
      }) => rootWindowIcon
    });
    unsubscribers.push(watch(selectRootWindowIcon, async ({
      globalBadge,
      rootWindowIcon
    }) => {
      await safeWindowOperation(async browserWindow => {
        if (!rootWindowIcon) {
          browserWindow.setIcon(electron.nativeImage.createFromPath(getTrayIconPath({
            platform: process.platform,
            badge: globalBadge
          })));
          return;
        }
        const icon = electron.nativeImage.createEmpty();
        const {
          scaleFactor
        } = electron.screen.getPrimaryDisplay();
        if (process.platform === 'linux') {
          rootWindowIcon.icon.forEach(representation => {
            icon.addRepresentation({
              ...representation,
              scaleFactor
            });
          });
        }
        if (process.platform === 'win32') {
          for (const representation of rootWindowIcon.icon) {
            var _representation$width;
            icon.addRepresentation({
              ...representation,
              scaleFactor: Math.max(((_representation$width = representation.width) !== null && _representation$width !== void 0 ? _representation$width : 0) / 32, 1)
            });
          }
        }
        browserWindow.setIcon(icon);
        if (process.platform === 'win32') {
          let overlayIcon = null;
          const overlayDescription = typeof globalBadge === 'number' && i18next__default.default.t('unreadMention', {
            appName: electron.app.name,
            count: globalBadge
          }) || globalBadge === '•' && i18next__default.default.t('unreadMessage', {
            appName: electron.app.name
          }) || i18next__default.default.t('noUnreadMessage', {
            appName: electron.app.name
          });
          if (rootWindowIcon.overlay) {
            overlayIcon = electron.nativeImage.createEmpty();
            for (const representation of rootWindowIcon.overlay) {
              overlayIcon.addRepresentation({
                ...representation,
                scaleFactor: 1
              });
            }
          }
          const isTrayIconEnabled = select(({
            isTrayIconEnabled
          }) => isTrayIconEnabled !== null && isTrayIconEnabled !== void 0 ? isTrayIconEnabled : true);
          if (!isTrayIconEnabled) {
            const t = i18next__default.default.t.bind(i18next__default.default);
            const translate = `taskbar.${overlayDescription}`;
            const taskbarTitle = globalBadge !== undefined ? `(${globalBadge}) ${t(translate)}` : t(translate);
            browserWindow.setTitle(taskbarTitle);
          }
          browserWindow.setOverlayIcon(overlayIcon, overlayDescription);
        }
      });
    }), watch(({
      isMenuBarEnabled
    }) => isMenuBarEnabled, async isMenuBarEnabled => {
      await safeWindowOperation(browserWindow => {
        browserWindow.autoHideMenuBar = !isMenuBarEnabled;
        browserWindow.setMenuBarVisibility(isMenuBarEnabled);
      });
    }));
  }
  electron.app.addListener('before-quit', () => {
    unsubscribers.forEach(unsubscriber => {
      try {
        unsubscriber();
      } catch (error) {
        console.warn('Unsubscriber error during quit:', error);
      }
    });
  });
};
const createRootWindowContextMenu = ({
  editFlags: {
    canUndo = false,
    canRedo = false,
    canCut = false,
    canCopy = false,
    canPaste = false,
    canSelectAll = false
  }
}) => {
  const t = i18next__default.default.t.bind(i18next__default.default);
  return electron.Menu.buildFromTemplate([{
    label: t('contextMenu.undo'),
    role: 'undo',
    accelerator: 'CommandOrControl+Z',
    enabled: canUndo
  }, {
    label: t('contextMenu.redo'),
    role: 'redo',
    accelerator: process.platform === 'win32' ? 'Control+Y' : 'CommandOrControl+Shift+Z',
    enabled: canRedo
  }, {
    type: 'separator'
  }, {
    label: t('contextMenu.cut'),
    role: 'cut',
    accelerator: 'CommandOrControl+X',
    enabled: canCut
  }, {
    label: t('contextMenu.copy'),
    role: 'copy',
    accelerator: 'CommandOrControl+C',
    enabled: canCopy
  }, {
    label: t('contextMenu.paste'),
    role: 'paste',
    accelerator: 'CommandOrControl+V',
    enabled: canPaste
  }, {
    label: t('contextMenu.selectAll'),
    role: 'selectAll',
    accelerator: 'CommandOrControl+A',
    enabled: canSelectAll
  }]);
};
const showRootWindow = async () => {
  const browserWindow = await getRootWindow();
  browserWindow.webContents.on('context-menu', (event, params) => {
    event.preventDefault();
    const menu = createRootWindowContextMenu(params);
    menu.popup({
      window: browserWindow
    });
  });
  browserWindow.loadFile(path__default.default.join(electron.app.getAppPath(), 'app/index.html'));
  return new Promise(resolve => {
    browserWindow.once('ready-to-show', () => {
      applyRootWindowState(browserWindow);
      const isTrayIconEnabled = select(({
        isTrayIconEnabled
      }) => isTrayIconEnabled);
      if (electron.app.commandLine.hasSwitch('start-hidden') && isTrayIconEnabled) {
        console.debug('Start application in background');
        browserWindow.hide();
      }
      setupRootWindow();
      resolve();
    });
  });
};
const watchMachineTheme = () => {
  dispatchMachineTheme();
  electron.nativeTheme.on('updated', () => {
    dispatchMachineTheme();
  });
};
const dispatchMachineTheme = () => {
  const isDarkMode = electron.nativeTheme.shouldUseDarkColors;
  dispatch({
    type: APP_MACHINE_THEME_SET,
    payload: isDarkMode ? 'dark' : 'light'
  });
};
const exportLocalStorage = async () => {
  try {
    tempWindow = new electron.BrowserWindow({
      show: false,
      webPreferences
    });
    tempWindow.loadFile(path__default.default.join(electron.app.getAppPath(), 'app/index.html'));
    await new Promise(resolve => {
      tempWindow.once('ready-to-show', () => {
        resolve();
      });
    });
    return tempWindow.webContents.executeJavaScript(`(() => {
      const data = ({...localStorage})
      localStorage.clear();
      return data;
    })()`);
  } catch (error) {
    console.error(error);
    return {};
  }
};

const t$8 = i18next__default.default.t.bind(i18next__default.default);
const askForAppDataReset = async parentWindow => {
  const {
    response
  } = await electron.dialog.showMessageBox(await getRootWindow(), {
    type: 'question',
    buttons: [t$8('dialog.resetAppData.yes'), t$8('dialog.resetAppData.cancel')],
    defaultId: 1,
    title: t$8('dialog.resetAppData.title'),
    message: t$8('dialog.resetAppData.message')
  });
  return response === 0;
};
const askForServerAddition = async (serverUrl, parentWindow) => {
  const {
    response
  } = await electron.dialog.showMessageBox(await getRootWindow(), {
    type: 'question',
    buttons: [t$8('dialog.addServer.add'), t$8('dialog.addServer.cancel')],
    defaultId: 0,
    title: t$8('dialog.addServer.title'),
    message: t$8('dialog.addServer.message', {
      host: serverUrl
    })
  });
  return response === 0;
};
const warnAboutInvalidServerUrl = (_serverUrl, _reason, _parentWindow) => {
  // TODO
  throw Error('not implemented');
};
let AskUpdateInstallResponse = /*#__PURE__*/function (AskUpdateInstallResponse) {
  AskUpdateInstallResponse[AskUpdateInstallResponse["INSTALL_LATER"] = 0] = "INSTALL_LATER";
  AskUpdateInstallResponse[AskUpdateInstallResponse["INSTALL_NOW"] = 1] = "INSTALL_NOW";
  return AskUpdateInstallResponse;
}({});
const askUpdateInstall = async parentWindow => {
  const {
    response
  } = await electron.dialog.showMessageBox(await getRootWindow(), {
    type: 'question',
    title: t$8('dialog.updateReady.title'),
    message: t$8('dialog.updateReady.message'),
    buttons: [t$8('dialog.updateReady.installLater'), t$8('dialog.updateReady.installNow')],
    defaultId: 1
  });
  if (response === 1) {
    return AskUpdateInstallResponse.INSTALL_NOW;
  }
  return AskUpdateInstallResponse.INSTALL_LATER;
};
const warnAboutInstallUpdateLater = async parentWindow => {
  await electron.dialog.showMessageBox(await getRootWindow(), {
    type: 'info',
    title: t$8('dialog.updateInstallLater.title'),
    message: t$8('dialog.updateInstallLater.message'),
    buttons: [t$8('dialog.updateInstallLater.ok')],
    defaultId: 0
  });
};
let AskForCertificateTrustResponse = /*#__PURE__*/function (AskForCertificateTrustResponse) {
  AskForCertificateTrustResponse[AskForCertificateTrustResponse["YES"] = 0] = "YES";
  AskForCertificateTrustResponse[AskForCertificateTrustResponse["NO"] = 1] = "NO";
  return AskForCertificateTrustResponse;
}({});
const askForCertificateTrust = async (issuerName, detail, parentWindow) => {
  const {
    response
  } = await electron.dialog.showMessageBox(await getRootWindow(), {
    title: t$8('dialog.certificateError.title'),
    message: t$8('dialog.certificateError.message', {
      issuerName
    }),
    detail,
    type: 'warning',
    buttons: [t$8('dialog.certificateError.yes'), t$8('dialog.certificateError.no')],
    cancelId: 1,
    defaultId: 1
  });
  if (response === 0) {
    return AskForCertificateTrustResponse.YES;
  }
  return AskForCertificateTrustResponse.NO;
};
const warnAboutUpdateDownload = async parentWindow => {
  await electron.dialog.showMessageBox(await getRootWindow(), {
    type: 'info',
    title: t$8('dialog.updateDownloading.title'),
    message: t$8('dialog.updateDownloading.message'),
    buttons: [t$8('dialog.updateDownloading.ok')],
    defaultId: 0
  });
};
const warnAboutUpdateSkipped = async parentWindow => {
  await electron.dialog.showMessageBox(await getRootWindow(), {
    type: 'warning',
    title: t$8('dialog.updateSkip.title'),
    message: t$8('dialog.updateSkip.message'),
    buttons: [t$8('dialog.updateSkip.ok')],
    defaultId: 0
  });
};
const askForOpeningExternalProtocol = async (url, parentWindow) => {
  const {
    response,
    checkboxChecked
  } = await electron.dialog.showMessageBox(await getRootWindow(), {
    type: 'warning',
    buttons: [t$8('dialog.openingExternalProtocol.yes'), t$8('dialog.openingExternalProtocol.no')],
    defaultId: 1,
    title: t$8('dialog.openingExternalProtocol.title'),
    message: t$8('dialog.openingExternalProtocol.message', {
      protocol: url.protocol
    }),
    detail: t$8('dialog.openingExternalProtocol.detail', {
      url: url.toString()
    }),
    checkboxLabel: t$8('dialog.openingExternalProtocol.dontAskAgain'),
    checkboxChecked: false
  });
  return {
    allowed: response === 0,
    dontAskAgain: checkboxChecked
  };
};
const askForJitsiCaptureScreenPermission = async (url, parentWindow) => {
  const {
    response,
    checkboxChecked
  } = await electron.dialog.showMessageBox(await getRootWindow(), {
    type: 'warning',
    buttons: [t$8('dialog.allowVideoCallCaptureScreen.yes'), t$8('dialog.allowVideoCallCaptureScreen.no')],
    defaultId: 1,
    title: t$8('dialog.allowVideoCallCaptureScreen.title'),
    message: t$8('dialog.allowVideoCallCaptureScreen.message'),
    detail: t$8('dialog.allowVideoCallCaptureScreen.detail', {
      url: url.toString()
    }),
    checkboxLabel: t$8('dialog.allowVideoCallCaptureScreen.dontAskAgain'),
    checkboxChecked: false
  });
  return {
    allowed: response === 0,
    dontAskAgain: checkboxChecked
  };
};
const askForClearScreenCapturePermission = async parentWindow => {
  const {
    response
  } = await electron.dialog.showMessageBox(await getRootWindow(), {
    type: 'question',
    buttons: [t$8('dialog.clearPermittedScreenCaptureServers.yes'), t$8('dialog.clearPermittedScreenCaptureServers.cancel')],
    defaultId: 1,
    title: t$8('dialog.clearPermittedScreenCaptureServers.title'),
    message: t$8('dialog.clearPermittedScreenCaptureServers.message')
  });
  return response === 0;
};
const askForMediaPermissionSettings = async (permissionType, parentWindow) => {
  parentWindow === null || parentWindow === undefined || parentWindow.show();
  const {
    response
  } = await electron.dialog.showMessageBox(parentWindow !== null && parentWindow !== undefined ? parentWindow : await getRootWindow(), {
    type: 'warning',
    buttons: [t$8('dialog.mediaPermission.openSettings'), t$8('dialog.mediaPermission.cancel')],
    defaultId: 0,
    title: t$8('dialog.mediaPermission.title'),
    message: t$8('dialog.mediaPermission.message', {
      permissionType: t$8(`dialog.mediaPermission.${permissionType}`)
    }),
    detail: t$8('dialog.mediaPermission.detail')
  });
  return response === 0;
};

// Cache browsers to avoid repeatedly fetching them
let cachedBrowsers = null;
let browserLoadPromise = null;

/**
 * Lazy load browsers asynchronously
 * This ensures we don't slow down app startup
 */
const loadBrowsersLazy = () => {
  if (cachedBrowsers) {
    return Promise.resolve(cachedBrowsers);
  }
  if (!browserLoadPromise) {
    // Start loading browsers asynchronously after a delay to not block the app startup
    browserLoadPromise = new Promise(resolve => {
      // Delay browser detection for 2 seconds after this function is first called
      // to avoid slowing down app startup and initial interactions
      setTimeout(async () => {
        try {
          const browsers = await detectBrowsers.getAvailableBrowsers();
          cachedBrowsers = browsers;
          const browserIds = browsers.map(browser => browser.browser);
          if (browserIds.length > 0) {
            dispatch({
              type: SETTINGS_AVAILABLE_BROWSERS_UPDATED,
              payload: browserIds
            });
          }
          resolve(browsers);
        } catch (error) {
          console.error('Error detecting browsers:', error);
          resolve([]);
        }
      }, 2000);
    });
  }
  return browserLoadPromise;
};

/**
 * Launches a URL in the selected browser from settings or falls back to system default
 *
 * @param url The URL to open
 * @returns Promise that resolves when the browser is launched
 */
const openExternal = async url => {
  // Get the selected browser from settings
  const selectedBrowser = readSetting('selectedBrowser');

  // If no specific browser is selected, use the system default
  if (!selectedBrowser) {
    return electron.shell.openExternal(url);
  }
  try {
    // Lazy load browsers when needed
    const browsers = await loadBrowsersLazy();

    // Find the selected browser in the available browsers
    const browser = browsers.find(browser => browser.browser === selectedBrowser);
    if (browser) {
      // Launch the selected browser with the URL
      return detectBrowsers.launchBrowser(browser, url);
    }
    // If the selected browser isn't available, fall back to system default
    console.warn(`Selected browser "${selectedBrowser}" not found, using system default.`);
    return electron.shell.openExternal(url);
  } catch (error) {
    console.error('Error launching browser:', error);
    // Fall back to shell.openExternal on error
    return electron.shell.openExternal(url);
  }
};

/**
 * Trigger preloading of browsers in the background
 * Call this function when the app is fully loaded
 */
const preloadBrowsersList = () => {
  // Begin loading browsers in the background after app is ready
  setTimeout(() => {
    loadBrowsersLazy().catch(error => {
      console.error('Failed to preload browsers list:', error);
    });
  }, 5000); // Delay for 5 seconds after this function is called
};

const packageJsonInformation = {
  productName: packageJson.productName,
  goUrlShortener: packageJson.goUrlShortener
};
const electronBuilderJsonInformation = {
  appId: electronBuilderJson.appId,
  protocol: electronBuilderJson.protocols.schemes[0]
};
let isScreenCaptureFallbackForced = false;
const getPlatformName = () => {
  switch (process.platform) {
    case 'win32':
      return 'Windows';
    case 'linux':
      return 'Linux';
    case 'darwin':
      return 'macOS';
    default:
      return 'Unknown';
  }
};
const relaunchApp = (...args) => {
  // For AppImage, use spawn to relaunch because app.relaunch() doesn't work reliably
  if (process.env.APPIMAGE) {
    console.log('Relaunching AppImage:', {
      appImage: process.env.APPIMAGE,
      args
    });

    // Spawn the AppImage as a detached process
    child_process.spawn(process.env.APPIMAGE, args, {
      detached: true,
      stdio: 'ignore'
    }).unref();
    electron.app.exit();
    return;
  }

  // For non-AppImage, use the standard relaunch method
  const command = process.argv.slice(1, electron.app.isPackaged ? 1 : 2);
  electron.app.relaunch({
    args: [...command, ...args]
  });
  electron.app.exit();
};
const performElectronStartup = () => {
  electron.app.setAsDefaultProtocolClient(electronBuilderJsonInformation.protocol);
  electron.app.setAppUserModelId(electronBuilderJsonInformation.appId);
  electron.app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required');
  const disabledChromiumFeatures = ['HardwareMediaKeyHandling', 'MediaSessionService'];
  if (getPlatformName() === 'macOS' && process.mas) {
    electron.app.commandLine.appendSwitch('disable-accelerated-video-decode');
  }
  const args = process.argv.slice(electron.app.isPackaged ? 1 : 2);
  if (args.includes('--reset-app-data')) {
    rimraf.rimraf.sync(electron.app.getPath('userData'));
    relaunchApp();
    return;
  }
  const canStart = process.mas || electron.app.requestSingleInstanceLock();
  if (!canStart) {
    electron.app.exit();
    return;
  }
  const isHardwareAccelerationEnabled = readSetting('isHardwareAccelerationEnabled');
  const isScreenCaptureFallbackEnabled = readSetting('isVideoCallScreenCaptureFallbackEnabled');
  isScreenCaptureFallbackForced = false;
  if (args.includes('--disable-gpu') || isHardwareAccelerationEnabled === false) {
    console.log('Disabling Hardware acceleration');
    electron.app.disableHardwareAcceleration();
    electron.app.commandLine.appendSwitch('disable-2d-canvas-image-chromium');
    electron.app.commandLine.appendSwitch('disable-accelerated-2d-canvas');
    electron.app.commandLine.appendSwitch('disable-gpu');
  }
  if (process.platform === 'win32') {
    const sessionName = process.env.SESSIONNAME;
    const isRdpSession = typeof sessionName === 'string' && sessionName !== 'Console';
    isScreenCaptureFallbackForced = isRdpSession;
    if (isScreenCaptureFallbackEnabled || isRdpSession) {
      console.log('Disabling Windows Graphics Capture for video calls', JSON.stringify({
        reason: isScreenCaptureFallbackEnabled ? 'user-setting' : 'rdp-session',
        sessionName
      }));
      disabledChromiumFeatures.push('WebRtcAllowWgcDesktopCapturer');
      disabledChromiumFeatures.push('WebRtcAllowWgcScreenCapturer');
    }
  }

  // Apply all disabled features in a single call
  electron.app.commandLine.appendSwitch('disable-features', disabledChromiumFeatures.join(','));

  // Enable PipeWire screen capture for Linux (Wayland support)
  if (process.platform === 'linux') {
    electron.app.commandLine.appendSwitch('enable-features', 'WebRTCPipeWireCapturer');

    // Detect display server and force X11 if not in Wayland session
    // This must happen BEFORE Electron tries to auto-select platform
    // to prevent segfaults when Wayland is attempted but unavailable
    // Note: ELECTRON_OZONE_PLATFORM_HINT was removed in Electron 38
    const hasOzonePlatformOverride = args.some(arg => arg.startsWith('--ozone-platform='));
    if (!hasOzonePlatformOverride) {
      const sessionType = process.env.XDG_SESSION_TYPE;
      const waylandDisplay = process.env.WAYLAND_DISPLAY;

      // Normalize values: trim whitespace and handle empty strings
      const normalizedSessionType = (sessionType === null || sessionType === undefined ? undefined : sessionType.trim()) || '';
      const normalizedWaylandDisplay = (waylandDisplay === null || waylandDisplay === undefined ? undefined : waylandDisplay.trim()) || '';

      // Only use Wayland if we're actually in a Wayland session AND have a valid socket
      // This covers all edge cases:
      // - X11 sessions (sessionType === 'x11' or unset) → force X11
      // - Invalid session types (tty, mir, etc.) → force X11
      // - Wayland session but no display → force X11
      // - Wayland session but socket doesn't exist → force X11
      const checkWaylandSocket = () => {
        if (normalizedSessionType !== 'wayland' || normalizedWaylandDisplay === '') {
          return false;
        }
        try {
          var _process$getuid, _process$getuid2, _process;
          const runtimeDir = process.env.XDG_RUNTIME_DIR || `/run/user/${(_process$getuid = (_process$getuid2 = (_process = process).getuid) === null || _process$getuid2 === void 0 ? void 0 : _process$getuid2.call(_process)) !== null && _process$getuid !== void 0 ? _process$getuid : 1000}`;
          const socketPath = `${runtimeDir}/${normalizedWaylandDisplay}`;
          const stats = fs__namespace.statSync(socketPath);
          return stats.isSocket();
        } catch {
          return false;
        }
      };
      const isWaylandSession = checkWaylandSocket();
      if (isWaylandSession) {
        console.log('Using Wayland platform', JSON.stringify({
          sessionType: normalizedSessionType,
          waylandDisplay: normalizedWaylandDisplay
        }));
        // Let Electron use Wayland (default auto behavior)
        // Don't set ozone-platform, let Electron auto-detect
      } else {
        let reason;
        if (normalizedSessionType === 'wayland' && normalizedWaylandDisplay !== '') {
          reason = 'socket-not-found';
        } else if (normalizedSessionType === 'wayland') {
          reason = 'no-wayland-display';
        } else if (normalizedSessionType === 'x11') {
          reason = 'x11-session';
        } else if (normalizedSessionType === '') {
          reason = 'no-session-type';
        } else {
          reason = 'invalid-session-type';
        }
        console.log('Forcing X11 platform', JSON.stringify({
          sessionType: normalizedSessionType || 'unset',
          waylandDisplay: normalizedWaylandDisplay || 'unset',
          reason
        }));
        electron.app.commandLine.appendSwitch('ozone-platform', 'x11');
      }
    }
  }
};
const initializeScreenCaptureFallbackState = () => {
  dispatch({
    type: APP_SCREEN_CAPTURE_FALLBACK_FORCED_SET,
    payload: isScreenCaptureFallbackForced
  });
};
const setupGpuCrashHandler = () => {
  if (process.platform !== 'linux') {
    return;
  }
  const args = process.argv.slice(electron.app.isPackaged ? 1 : 2);
  if (args.includes('--disable-gpu')) {
    return;
  }

  // Handle GPU process crashes (runtime failures)
  electron.app.on('child-process-gone', (_event, details) => {
    if (details.type !== 'GPU') {
      return;
    }
    console.log('GPU process crashed, disabling GPU and relaunching with X11');
    const userArgs = process.argv.slice(electron.app.isPackaged ? 1 : 2);
    relaunchApp('--disable-gpu', '--ozone-platform=x11', ...userArgs);
  });

  // Proactive GPU detection: check GPU status once info is available
  electron.app.once('gpu-info-update', () => {
    const gpuFeatures = electron.app.getGPUFeatureStatus();
    const {
      gpu_compositing: gpuCompositing,
      webgl
    } = gpuFeatures;

    // Check if key GPU features are disabled/unavailable (includes _software variants)
    const isGpuBroken = status => !status || status.startsWith('disabled') || status.startsWith('unavailable');
    if (isGpuBroken(gpuCompositing) || isGpuBroken(webgl)) {
      console.log('GPU features unavailable, disabling GPU and relaunching with X11', JSON.stringify(gpuFeatures));
      const userArgs = process.argv.slice(electron.app.isPackaged ? 1 : 2);
      relaunchApp('--disable-gpu', '--ozone-platform=x11', ...userArgs);
      return;
    }
    console.log('GPU features status:', JSON.stringify(gpuFeatures));
  });
};
const setupApp = () => {
  electron.app.addListener('activate', async () => {
    try {
      const browserWindow = await getRootWindow();
      if (!browserWindow.isVisible()) {
        const wasMinimized = browserWindow.isMinimized();
        const wasMaximized = browserWindow.isMaximized();
        browserWindow.showInactive();
        if (wasMinimized) browserWindow.minimize();
        if (wasMaximized) browserWindow.maximize();
      }
      browserWindow.focus();
    } catch (error) {
      console.warn('Could not activate window:', error);
    }
  });
  electron.app.addListener('window-all-closed', () => {
    // Don't quit immediately if this might be caused by video call window closure
    // especially during first launch when main window might not be fully ready
    setTimeout(() => {
      const allWindows = electron.BrowserWindow.getAllWindows();

      // Only quit if there are truly no windows left after a brief delay
      // This prevents crashes when video call window closes before main window is established
      if (allWindows.length === 0) {
        console.log('No windows remaining after delay, quitting application');
        electron.app.quit();
      } else {
        console.log(`${allWindows.length} window(s) still exist, not quitting`);
      }
    }, 100); // Brief delay to let window state stabilize
  });
  electron.app.whenReady().then(() => preloadBrowsersList());
  listen(SETTINGS_SET_HARDWARE_ACCELERATION_OPT_IN_CHANGED, () => {
    relaunchApp();
  });
  listen(SETTINGS_SET_IS_TRANSPARENT_WINDOW_ENABLED_CHANGED, () => {
    relaunchApp();
  });
  listen(SETTINGS_SET_IS_VIDEO_CALL_SCREEN_CAPTURE_FALLBACK_ENABLED_CHANGED, action => {
    const newSettingValue = action.payload;
    const currentPersistedSetting = readSetting('isVideoCallScreenCaptureFallbackEnabled');
    const sessionName = process.env.SESSIONNAME;
    const isRdpSession = typeof sessionName === 'string' && sessionName !== 'Console';
    if (newSettingValue !== currentPersistedSetting && !isRdpSession) {
      relaunchApp();
    } else if (isRdpSession) {
      console.log('Screen Capture Fallback setting changed in RDP session. Change will apply when running locally. No restart needed now since WGC is already disabled by RDP detection.');
    }
  });
  listen(APP_ALLOWED_NTLM_CREDENTIALS_DOMAINS_SET, action => {
    if (action.payload.length > 0) {
      electron.session.defaultSession.allowNTLMCredentialsForDomains(action.payload);
    }
  });
  listen(SETTINGS_NTLM_CREDENTIALS_CHANGED, action => {
    if (action.payload === true) {
      const allowedNTLMCredentialsDomains = readSetting('allowedNTLMCredentialsDomains');
      if (allowedNTLMCredentialsDomains) {
        console.log('Setting NTLM credentials', allowedNTLMCredentialsDomains);
        electron.session.defaultSession.allowNTLMCredentialsForDomains(allowedNTLMCredentialsDomains);
      }
    } else {
      console.log('Clearing NTLM credentials');
      electron.session.defaultSession.allowNTLMCredentialsForDomains('');
    }
  });
  listen(SETTINGS_CLEAR_PERMITTED_SCREEN_CAPTURE_PERMISSIONS, async () => {
    const permitted = await askForClearScreenCapturePermission();
    if (permitted) {
      dispatch({
        type: JITSI_SERVER_CAPTURE_SCREEN_PERMISSIONS_CLEARED,
        payload: {}
      });
    }
  });
  const allowedNTLMCredentialsDomains = readSetting('allowedNTLMCredentialsDomains');
  const isNTLMCredentialsEnabled = readSetting('isNTLMCredentialsEnabled');
  if (isNTLMCredentialsEnabled && allowedNTLMCredentialsDomains.length > 0) {
    console.log('Setting NTLM credentials', allowedNTLMCredentialsDomains);
    electron.session.defaultSession.allowNTLMCredentialsForDomains(allowedNTLMCredentialsDomains);
  }
  dispatch({
    type: APP_PATH_SET,
    payload: electron.app.getAppPath()
  });
  dispatch({
    type: APP_VERSION_SET,
    payload: electron.app.getVersion()
  });
  dispatch({
    type: APP_MAIN_WINDOW_TITLE_SET,
    payload: 'Rocket.Chat'
  });
};

// Maps webContentsId → server hostname (for context lookup)
const webContentsToHost = new Map();

// Define the log context interface

/**
 * Get process context information
 */
const getProcessContext = () => {
  if (typeof process === 'undefined') return 'unknown';
  if (process.type === 'browser') {
    return 'main';
  }
  if (process.type === 'renderer') {
    // Try to detect renderer type based on environment
    if (typeof window !== 'undefined') {
      var _window$location, _window$location2, _window$location3;
      // Check if this is the root window
      if (((_window$location = window.location) === null || _window$location === undefined ? undefined : _window$location.pathname) === '/index.html' || ((_window$location2 = window.location) === null || _window$location2 === undefined ? undefined : _window$location2.pathname) === '/') {
        return 'renderer:root';
      }

      // Check if this is the video call window
      if (((_window$location3 = window.location) === null || _window$location3 === undefined ? undefined : _window$location3.pathname) === '/video-call-window.html') {
        return 'renderer:videocall';
      }

      // Default to webview for other renderer processes
      return 'renderer:webview';
    }
    return 'renderer:unknown';
  }
  return 'preload';
};

/**
 * Extract host (hostname + port when non-default) from a URL.
 */
const getHost = url => {
  try {
    return new URL(url).host;
  } catch {
    return url;
  }
};

/**
 * Register a webContentsId with a server URL (stores server tag).
 */
const registerWebContentsServer = (webContentsId, serverUrl) => {
  webContentsToHost.set(webContentsId, getHost(serverUrl));
};

/**
 * Get server context for webContents (hostname-based).
 */
const getServerContext = webContents => {
  if (!webContents) return 'local';
  const hostname = webContentsToHost.get(webContents.id);
  if (hostname) {
    return hostname;
  }
  if (webContents.getType() === 'window') {
    return 'local';
  }
  return 'unknown';
};

/**
 * Get component context based on the calling code location
 */
// eslint-disable-next-line complexity
const getComponentContext = (captureStack = false) => {
  if (!captureStack) {
    return 'general';
  }
  const error = new Error();
  const stack = error.stack || '';

  // Analyze stack trace to determine component context (privacy-safe)
  if (stack.includes('auth') || stack.includes('login') || stack.includes('Login')) {
    return 'auth';
  }
  if (stack.includes('connection') || stack.includes('websocket') || stack.includes('network')) {
    return 'connection';
  }
  if (stack.includes('notification') || stack.includes('Notification')) {
    return 'notifications';
  }
  if (stack.includes('update') || stack.includes('Update')) {
    return 'updates';
  }
  if (stack.includes('outlook') || stack.includes('Outlook') || stack.includes('outlookCalendar') || stack.includes('getOutlookEvents') || stack.includes('syncEventsWithRocketChatServer')) {
    return 'outlook';
  }
  if (stack.includes('server') || stack.includes('Server') || stack.includes('servers')) {
    return 'servers';
  }
  if (stack.includes('video') || stack.includes('Video') || stack.includes('jitsi')) {
    return 'videocall';
  }
  if (stack.includes('preload')) {
    return 'preload';
  }
  if (stack.includes('ipc') || stack.includes('IPC')) {
    return 'ipc';
  }
  if (stack.includes('storage') || stack.includes('Storage') || stack.includes('store')) {
    return 'storage';
  }
  if (stack.includes('ui') || stack.includes('UI') || stack.includes('component')) {
    return 'ui';
  }
  if (stack.includes('app') || stack.includes('App') || stack.includes('main')) {
    return 'app';
  }
  return 'general';
};

/**
 * Get complete log context
 */
const getLogContext = (webContents, captureComponentStack = false) => {
  const context = {
    processType: getProcessContext()
  };
  if (webContents) {
    context.webContentsType = webContents.getType();
    context.webContentsId = webContents.id;
    const serverContext = getServerContext(webContents);
    context.serverInfo = {
      url: serverContext,
      name: serverContext
    };
  }
  context.component = getComponentContext(captureComponentStack);
  return context;
};

/**
 * Format context for logging
 */
const formatLogContext = context => {
  var _context$serverInfo;
  const parts = [];

  // Always include process
  parts.push(context.processType);

  // Add server context if available
  if ((_context$serverInfo = context.serverInfo) !== null && _context$serverInfo !== undefined && _context$serverInfo.name && context.serverInfo.name !== 'local') {
    parts.push(context.serverInfo.name);
  }

  // Add component context if available
  if (context.component && context.component !== 'general') {
    parts.push(context.component);
  }
  return `[${parts.join('] [')}]`;
};

/**
 * Clean up webContents association when destroyed.
 */
const cleanupServerContext = webContentsId => {
  webContentsToHost.delete(webContentsId);
};

/**
 * Simple log deduplication: if the current message is identical to the
 * last one logged, skip it.  Errors are never skipped.
 *
 * Two integration points:
 * 1. `shouldLog()` — call before IPC messages reach electron-log
 * 2. `createFileHook()` — electron-log hook for main-process messages
 *    (file transport only, console stays verbose)
 *
 * Each integration point tracks its own last-key so they don't interfere.
 */
class LogDeduplicator {
  constructor() {
    /** Key of the last IPC message that was actually written */
    _defineProperty(this, "lastIpcKey", '');
    /** Key of the last file-transport message that was actually written */
    _defineProperty(this, "lastFileKey", '');
  }
  /**
   * Build a dedup key from level + message text.
   * Strips dynamic numbers (timestamps, IDs) so messages that differ
   * only by a changing number still match.
   */
  makeKey(level, args) {
    const text = args.map(a => {
      if (typeof a === 'string') return a;
      try {
        return JSON.stringify(a);
      } catch {
        return String(a);
      }
    }).join(' ').replace(/\b\d{4,}\b/g, '#').replace(/\b\d+\.\d+\b/g, '#');
    return `${level}|${text}`;
  }

  /**
   * Check whether an IPC message should be logged.
   * Returns false if it is identical to the last logged message.
   * Errors always return true.
   */
  shouldLog(level, contextStr, args) {
    if (level === 'error') {
      this.lastIpcKey = '';
      return true;
    }
    const key = this.makeKey(level, [contextStr, ...args]);
    if (key === this.lastIpcKey) return false;
    this.lastIpcKey = key;
    return true;
  }

  /**
   * Electron-log hook for the file transport.
   * Suppresses consecutive duplicate non-error messages.
   * Console transport is unaffected.
   */
  createFileHook() {
    return (message, _transport, transportName) => {
      var _message$data;
      if (transportName !== 'file') return message;
      if (!message) return message;
      if (message.level === 'error') {
        this.lastFileKey = '';
        return message;
      }
      const text = ((_message$data = message.data) === null || _message$data === undefined ? undefined : _message$data.map(item => {
        if (typeof item === 'string') return item;
        try {
          return JSON.stringify(item);
        } catch {
          return String(item);
        }
      }).join(' ')) || '';
      const key = `${message.level}|${text.replace(/\b\d{4,}\b/g, '#').replace(/\b\d+\.\d+\b/g, '#')}`;
      if (key === this.lastFileKey) return null; // suppress duplicate

      this.lastFileKey = key;
      return message;
    };
  }
}

const fallbackLog = (level, ...args) => {
  try {
    const timestamp = new Date().toISOString();
    const message = args.map(a => {
      if (a instanceof Error) return `${a.message}\n${a.stack}`;
      if (typeof a === 'object' && a !== null) {
        try {
          return JSON.stringify(a);
        } catch {
          return String(a);
        }
      }
      return String(a);
    }).join(' ');
    process.stderr.write(`[${timestamp}] [${level.toUpperCase()}] [FALLBACK] ${message}\n`);
  } catch {
    /* nothing more we can do */
  }
};
const logLoggingFailure = (error, context) => {
  fallbackLog('error', `Logging failed in ${context}:`, error instanceof Error ? error.message : String(error));
};

/**
 * Privacy hooks for electron-log
 * Filters sensitive data before writing to log files
 *
 * Strategy:
 * 1. Field-based redaction (object keys) — precise, low false positives
 * 2. Pattern-based redaction (string content) — catches secrets in messages
 * 3. Partial masking where possible — preserves debugging utility
 */

// ── Helpers ──────────────────────────────────────────────────────────────────

/** Mask a value showing first/last N chars: "eyJhbG...xK9s" */
const mask = (value, keep = 4) => {
  if (value.length <= keep * 2 + 3) return '[REDACTED]';
  return `${value.slice(0, keep)}...${value.slice(-keep)}`;
};

/** Luhn checksum — validates credit card numbers to avoid false positives */
const luhnCheck = digits => {
  let sum = 0;
  let alternate = false;
  for (let i = digits.length - 1; i >= 0; i--) {
    let n = parseInt(digits[i], 10);
    if (alternate) {
      n *= 2;
      if (n > 9) n -= 9;
    }
    sum += n;
    alternate = !alternate;
  }
  return sum % 10 === 0;
};

// ── Pattern-based redaction (applied to string content) ──────────────────────

/**
 * Key-value credential patterns.
 * Requires the value portion to be 8+ chars to avoid matching
 * short debug strings like "token expired" or "secret: no".
 */
const CREDENTIAL_KV_PATTERNS = [{
  pattern: /password["'\s:=]+["']?([^"'\s,}\]]{8,})/gi,
  label: 'password'
}, {
  pattern: /passwd["'\s:=]+["']?([^"'\s,}\]]{8,})/gi,
  label: 'passwd'
}, {
  pattern: /secret["'\s:=]+["']?([^"'\s,}\]]{8,})/gi,
  label: 'secret'
}, {
  pattern: /api[_-]?key["'\s:=]+["']?([^"'\s,}\]]{8,})/gi,
  label: 'api_key'
}, {
  pattern: /auth[_-]?token["'\s:=]+["']?([^"'\s,}\]]{8,})/gi,
  label: 'auth_token'
}, {
  pattern: /access[_-]?token["'\s:=]+["']?([^"'\s,}\]]{8,})/gi,
  label: 'access_token'
}, {
  pattern: /refresh[_-]?token["'\s:=]+["']?([^"'\s,}\]]{8,})/gi,
  label: 'refresh_token'
}, {
  pattern: /x-auth-token["'\s:=]+["']?([^"'\s,}\]]{8,})/gi,
  label: 'x-auth-token'
}, {
  pattern: /authorization["'\s:=]+["']?((?:\w+\s+)?[^"',}\]]{8,})/gi,
  label: 'authorization'
}, {
  pattern: /client[_-]?secret["'\s:=]+["']?([^"'\s,}\]]{8,})/gi,
  label: 'client_secret'
}, {
  pattern: /private[_-]?key["'\s:=]+["']?([^"'\s,}\]]{8,})/gi,
  label: 'private_key'
}, {
  pattern: /webhook[_-]?url["'\s:=]+["']?([^"'\s,}\]]{8,})/gi,
  label: 'webhook_url'
}];

/** Standalone token/secret patterns (not key-value) */
const TOKEN_PATTERNS = [
// Bearer tokens
{
  pattern: /bearer\s+([a-zA-Z0-9._-]{20,})/gi,
  replacer: (_m, token) => `Bearer ${mask(token)}`
},
// JWTs: three base64url segments separated by dots
{
  pattern: /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]+/g,
  replacer: m => mask(m, 6)
},
// URLs with embedded credentials: https://user:pass@host
{
  pattern: /(https?:\/\/)([^:]+):([^@]+)@/gi,
  replacer: (_m, proto) => `${proto}[REDACTED]:[REDACTED]@`
},
// Long hex strings (32+ chars) — likely API keys, hashes, tokens
{
  pattern: /\b[a-fA-F0-9]{32,}\b/g,
  replacer: m => mask(m, 6)
}];

/** PII patterns */
const PII_PATTERNS = [
// Email addresses — require 2+ char local part, exclude npm-style package scopes
// Negative lookbehind for @ prevents matching @scope/package patterns
{
  pattern: /\b[A-Za-z0-9._%+-]{2,}@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
  replacer: m => {
    // Don't redact package scopes or internal identifiers
    if (m.includes('/') || m.endsWith('.js') || m.endsWith('.ts') || m.endsWith('.log') || /^\d+@\d+/.test(m)) {
      return m;
    }
    const [local, domain] = m.split('@');
    return `${local[0]}***@${domain}`;
  }
},
// Credit card numbers (13-19 digits, optionally separated by spaces/dashes)
{
  pattern: /\b(?:\d[ -]*?){13,19}\b/g,
  replacer: m => {
    const digits = m.replace(/[^0-9]/g, '');
    if (digits.length < 13 || digits.length > 19) return m;
    if (!luhnCheck(digits)) return m; // Not a valid CC — don't redact
    return `****${digits.slice(-4)}`;
  }
},
// IP addresses (IPv4) — partial mask preserving subnet
{
  pattern: /\b(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\b/g,
  replacer: m => {
    // Don't redact localhost or private ranges (RFC 1918)
    if (m === '127.0.0.1' || m === '0.0.0.0' || m.startsWith('192.168.') || m.startsWith('10.') || /^172\.(1[6-9]|2[0-9]|3[01])\./.test(m)) {
      return m;
    }
    const parts = m.split('.');
    return `${parts[0]}.${parts[1]}.*.*`;
  }
}];

// ── Field-based redaction (applied to object keys) ───────────────────────────

const SENSITIVE_KEY_PATTERNS = ['password', 'passwd', 'token', 'secret', 'apikey', 'api_key', 'authorization', 'cookie', 'session_id', 'sessionid', 'credential', 'private_key', 'privatekey', 'access_key', 'accesskey', 'client_secret', 'webhook'];

/** Keys that are safe even though they partially match sensitive patterns */
const SAFE_KEY_PATTERNS = ['token_type', 'tokentype', 'token_expired', 'token_expiry', 'session_count', 'password_changed', 'password_policy', 'webhook_enabled'];
const isSensitiveKey$1 = key => {
  const lower = key.toLowerCase();
  // Check safe patterns first (allowlist)
  if (SAFE_KEY_PATTERNS.some(safe => lower === safe)) return false;
  return SENSITIVE_KEY_PATTERNS.some(pattern => lower.includes(pattern));
};

// ── Redux state dump detection ───────────────────────────────────────────────

const STATE_SIGNATURE_KEYS = ['apppath', 'appversion', 'servers', 'clientcertificates', 'currentview', 'allowedjitsiServers', 'downloads', 'isupdatingenabled', 'istrayiconenabled', 'rootwindowstate'];
const isStateDump = obj => {
  if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
    return false;
  }
  const keys = Object.keys(obj).map(k => k.toLowerCase());
  const matchCount = STATE_SIGNATURE_KEYS.filter(k => keys.includes(k)).length;
  return matchCount >= 3;
};

// ── Core redaction functions ─────────────────────────────────────────────────

const redactSensitiveData = text => {
  let result = text;

  // Key-value credential patterns — replace the whole match
  for (const {
    pattern,
    label
  } of CREDENTIAL_KV_PATTERNS) {
    pattern.lastIndex = 0;
    result = result.replace(pattern, `${label}: [REDACTED]`);
  }

  // Standalone token patterns — use custom replacers for partial masking
  for (const {
    pattern,
    replacer
  } of TOKEN_PATTERNS) {
    pattern.lastIndex = 0;
    result = result.replace(pattern, replacer);
  }

  // PII patterns — use custom replacers for partial masking
  for (const {
    pattern,
    replacer
  } of PII_PATTERNS) {
    pattern.lastIndex = 0;
    result = result.replace(pattern, replacer);
  }
  return result;
};
const redactObject = (obj, depth = 0, seen = new WeakSet()) => {
  if (obj === null || obj === undefined) return obj;
  if (typeof obj === 'string') return redactSensitiveData(obj);

  // Prevent stack overflow on deeply nested objects
  if (depth >= 10) return '[Nested Object Redacted]';
  if (typeof obj === 'object') {
    if (seen.has(obj)) return '[Circular]';
    seen.add(obj);
  }
  if (Array.isArray(obj)) return obj.map(item => redactObject(item, depth + 1, seen));
  if (typeof obj === 'object') {
    if (isStateDump(obj)) {
      return '[Redux State Redacted]';
    }
    if (obj.state && isStateDump(obj.state)) {
      const {
        state: _state,
        ...rest
      } = obj;
      const redactedRest = redactObject(rest, depth + 1, seen);
      if (typeof redactedRest === 'object' && redactedRest !== null) {
        return {
          ...redactedRest,
          state: '[Redux State Redacted]'
        };
      }
      return {
        state: '[Redux State Redacted]'
      };
    }
    const result = {};

    // Error properties (message, stack, name) are non-enumerable;
    // preserve them so redacted logs still contain useful diagnostics.
    if (obj instanceof Error) {
      if (obj.message) result.message = redactSensitiveData(obj.message);
      if (obj.stack) result.stack = redactSensitiveData(obj.stack);
      if (obj.name) result.name = obj.name;
    }
    for (const key of Object.keys(obj)) {
      if (isSensitiveKey$1(key)) {
        const val = obj[key];
        // Partial mask string values for debugging context
        result[key] = typeof val === 'string' && val.length > 8 ? mask(val) : '[REDACTED]';
      } else {
        result[key] = redactObject(obj[key], depth + 1, seen);
      }
    }
    return result;
  }
  return obj;
};

// ── electron-log hook ────────────────────────────────────────────────────────

let privacyHookFailed = false;
const createPrivacyHook = () => {
  return (message, _transport, _transportName) => {
    try {
      const data = Array.isArray(message.data) ? message.data : [message.data];
      const sanitizedData = data.map(item => {
        if (typeof item === 'string') return redactSensitiveData(item);
        if (typeof item === 'object' && item !== null) return redactObject(item);
        return item;
      });
      return {
        ...message,
        data: sanitizedData
      };
    } catch {
      if (!privacyHookFailed) {
        privacyHookFailed = true;
        process.stderr.write('[privacy] redaction hook threw; falling back to placeholder\n');
      }
      return {
        level: message.level,
        date: message.date,
        data: ['[Privacy redaction failed]']
      };
    }
  };
};

// Cache process context at module load (doesn't change at runtime)
`[${getProcessContext()}]`;

const DEFAULT_RETENTION_DAYS = 30;
const PROTECTED_FILES = new Set(['main.log', 'errors.jsonl']);

/**
 * Clean up old log files based on retention policy
 * @param retentionDays - Number of days to keep logs (default 30)
 */
const cleanupOldLogs = (retentionDays = DEFAULT_RETENTION_DAYS) => {
  try {
    if (!Number.isFinite(retentionDays) || retentionDays <= 0) {
      console.warn('[logging] Invalid retentionDays; skipping log cleanup');
      return;
    }
    const logsPath = electron.app.getPath('logs');
    const cutoffTime = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
    if (!fs__namespace.default.existsSync(logsPath)) return;
    const files = fs__namespace.default.readdirSync(logsPath);
    let deletedCount = 0;
    for (const file of files) {
      if (PROTECTED_FILES.has(file)) continue;
      const filePath = path__default.default.join(logsPath, file);
      try {
        const stats = fs__namespace.default.statSync(filePath);
        if (stats.isFile() && stats.mtime.getTime() < cutoffTime) {
          fs__namespace.default.unlinkSync(filePath);
          deletedCount++;
        }
      } catch {
        // Skip files we can't access
      }
    }
    if (deletedCount > 0) {
      console.info(`[logging] Cleaned up ${deletedCount} old log file(s)`);
    }
  } catch (error) {
    console.warn('[logging] Failed to cleanup old logs:', error);
  }
};

// Shared dedup instance — created once, used by both file hook and IPC handler
let logDeduplicator = null;
const originalConsole = {
  log: console.log.bind(console),
  info: console.info.bind(console),
  warn: console.warn.bind(console),
  error: console.error.bind(console),
  debug: console.debug.bind(console)
};

// Enhanced console override with context
const overrideConsole = () => {
  try {
    // Override console.log to use electron-log debug level with context
    console.log = (...args) => {
      const context = getLogContext();
      const contextStr = formatLogContext(context);
      log__default.default.debug(contextStr, ...args);
    };

    // Override console.info to use electron-log info level with context
    console.info = (...args) => {
      const context = getLogContext();
      const contextStr = formatLogContext(context);
      log__default.default.info(contextStr, ...args);
    };

    // Override console.warn to use electron-log warn level with context
    console.warn = (...args) => {
      const context = getLogContext();
      const contextStr = formatLogContext(context);
      log__default.default.warn(contextStr, ...args);
    };

    // Override console.error to use electron-log error level with context
    console.error = (...args) => {
      const context = getLogContext();
      const contextStr = formatLogContext(context);
      log__default.default.error(contextStr, ...args);
    };

    // Override console.debug to use electron-log debug level with context
    console.debug = (...args) => {
      const context = getLogContext();
      const contextStr = formatLogContext(context);
      log__default.default.debug(contextStr, ...args);
    };

    // Add a way to access original console if needed
    console.original = originalConsole;
  } catch (error) {
    // If override fails, restore original console
    Object.assign(console, originalConsole);
    originalConsole.warn('Failed to override console methods:', error);
  }
};

// Simple configuration that works in both main and renderer processes
const configureLogging = () => {
  try {
    var _log$transports, _log$transports2;
    // Only configure transports if they exist (main process)
    if ((_log$transports = log__default.default.transports) !== null && _log$transports !== void 0 && _log$transports.console && (_log$transports2 = log__default.default.transports) !== null && _log$transports2 !== void 0 && _log$transports2.file) {
      // Set log level based on environment
      if ("production" === 'development') ; else {
        log__default.default.transports.console.level = 'info';
        log__default.default.transports.file.level = 'info';
      }

      // Configure file transport with enhanced format
      log__default.default.transports.file.maxSize = 10 * 1024 * 1024; // 10MB
      log__default.default.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}';
      // Enable async writes — batches multiple log lines into single fs.writeFile calls
      log__default.default.transports.file.sync = false;
      // Let electron-log use its default path: ~/Library/Logs/{app name}/main.log

      // Configure console transport with enhanced format
      log__default.default.transports.console.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}';

      // Use original console to prevent recursion when console is overridden
      log__default.default.transports.console.writeFn = ({
        message
      }) => {
        originalConsole.log(message);
      };

      // Add privacy hook FIRST so all data is redacted before other hooks
      log__default.default.hooks.push(createPrivacyHook());

      // Add dedup hook — suppresses repeated non-error messages at the file
      // transport only (console stays verbose for live debugging)
      logDeduplicator = new LogDeduplicator();
      log__default.default.hooks.push(logDeduplicator.createFileHook());

      // Add structured NDJSON logging for errors (useful for error reporting)
      const errorJsonlPath = path__default.default.join(electron.app.getPath('logs'), 'errors.jsonl');
      const MAX_ERROR_LOG_SIZE = 5 * 1024 * 1024; // 5MB
      const errorJsonlBuffer = [];
      const ERRORS_FLUSH_INTERVAL_MS = 10_000;
      const MAX_BUFFERED_ERRORS = 50;
      const flushErrorJsonl = (sync = false) => {
        if (errorJsonlBuffer.length === 0) return;
        const batch = errorJsonlBuffer.splice(0);
        const content = batch.join('');

        // Check size and truncate if needed before appending
        try {
          if (fs__namespace.default.existsSync(errorJsonlPath)) {
            const stats = fs__namespace.default.statSync(errorJsonlPath);
            if (stats.size > MAX_ERROR_LOG_SIZE) {
              const fileContent = fs__namespace.default.readFileSync(errorJsonlPath, 'utf-8');
              const lines = fileContent.split('\n');
              const halfIndex = Math.floor(lines.length / 2);
              fs__namespace.default.writeFileSync(errorJsonlPath, lines.slice(halfIndex).join('\n'));
            }
          }
        } catch {
          // Non-critical: truncation failure
        }
        if (sync) {
          try {
            fs__namespace.default.appendFileSync(errorJsonlPath, content);
          } catch (err) {
            originalConsole.error('Failed to write error log:', err);
          }
        } else {
          fs__namespace.default.promises.appendFile(errorJsonlPath, content).catch(err => {
            originalConsole.error('Failed to write error log:', err);
          });
        }
      };
      const errorFlushTimer = setInterval(flushErrorJsonl, ERRORS_FLUSH_INTERVAL_MS);
      log__default.default.hooks.push(message => {
        if (message.level === 'error') {
          try {
            var _message$data;
            const rawText = ((_message$data = message.data) === null || _message$data === void 0 ? void 0 : _message$data.join(' ')) || '';
            errorJsonlBuffer.push(`${JSON.stringify({
              timestamp: new Date().toISOString(),
              level: message.level,
              text: redactSensitiveData(rawText),
              version: electron.app.getVersion()
            })}\n`);
            if (errorJsonlBuffer.length >= MAX_BUFFERED_ERRORS) {
              flushErrorJsonl();
            }
          } catch {
            // Ignore JSON logging failures
          }
        }
        return message;
      });

      // Flush error buffer before app quits
      if (process.type === 'browser') {
        electron.app.on('before-quit', () => {
          flushErrorJsonl(true);
          clearInterval(errorFlushTimer);
        });
      }

      // Initialize for renderer processes if we're in main process
      if (process.type === 'browser') {
        log__default.default.initialize();
      }

      // Restrict log file permissions to owner-only (0600)
      try {
        const logFilePath = path__default.default.join(electron.app.getPath('logs'), 'main.log');
        if (fs__namespace.default.existsSync(logFilePath)) {
          fs__namespace.default.chmodSync(logFilePath, 0o600);
        }
        if (fs__namespace.default.existsSync(errorJsonlPath)) {
          fs__namespace.default.chmodSync(errorJsonlPath, 0o600);
        }
      } catch {
        // Non-critical: chmod may fail on Windows
      }
    }

    // Override console.log to use electron-log
    overrideConsole();
  } catch (error) {
    logLoggingFailure(error, 'configureLogging');
  }
  return log__default.default;
};

// Function to setup logging in webviews and all webContents
const setupWebContentsLogging = () => {
  if (process.type !== 'browser') return;
  try {
    // Use the static import for store instead of dynamic imports
    let selectFunction = null;
    try {
      selectFunction = select;
    } catch (importError) {
      // If store import fails, continue without server context mapping
      console.warn('[main] [app] Store module not available for server context mapping:', importError.message);
    }

    // Synchronous IPC handler for preload scripts to get their server tag
    electron.ipcMain.on('log-viewer-window/get-server-tag', (event, origin) => {
      try {
        if (selectFunction && origin) {
          const servers = selectFunction(state => state.servers);
          const matchedServer = servers.find(s => s.url && origin.startsWith(s.url.replace(/\/$/, '')));
          if (matchedServer !== null && matchedServer !== void 0 && matchedServer.url) {
            event.returnValue = getHost(matchedServer.url);
            return;
          }
        }
      } catch {
        // Non-critical
      }
      event.returnValue = '';
    });

    // Rate limiting for console-log IPC to prevent flooding from compromised webviews
    const ipcRateLimit = new Map();
    const MAX_IPC_MESSAGES_PER_SECOND = 100;

    // Listen for new webContents creation
    electron.app.on('web-contents-created', (_event, webContents) => {
      // Skip if this is the main renderer process (it already has logging)
      if (webContents.getType() === 'window') return;

      // For webviews and other renderer processes, inject console override
      webContents.on('dom-ready', () => {
        try {
          // Get server URL for this webContents
          // For webviews, derive from the webContents URL by matching against known servers
          let serverUrl = 'unknown';
          if (selectFunction && webContents.getType() === 'webview') {
            try {
              const currentUrl = webContents.getURL();
              if (currentUrl) {
                const servers = selectFunction(state => state.servers);
                const server = servers.find(s => s.url && currentUrl.startsWith(s.url.replace(/\/$/, '')));
                serverUrl = (server === null || server === void 0 ? void 0 : server.url) || 'unknown';
              }
            } catch (storeError) {
              logLoggingFailure(storeError, 'setupWebContentsLogging - store access');
            }
          }

          // Register server context early so all logs from this webContents
          // get tagged with the server hostname, even if console injection fails
          if (serverUrl !== 'unknown') {
            registerWebContentsServer(webContents.id, serverUrl);
          }

          // TODO: Replace executeJavaScript injection with a preload script for
          // better maintainability and debuggability (see src/logging/preload.ts)
          // Inject enhanced console override directly into the webContents
          const consoleOverrideScript = `
            (function() {
              try {
                const { ipcRenderer } = require('electron');
                
                // Store original console methods
                const originalConsole = {
                  log: console.log,
                  info: console.info,
                  warn: console.warn,
                  error: console.error,
                  debug: console.debug,
                };

                 // Get webContents ID and server URL for context
                 const webContentsId = ${webContents.id};
                 const serverUrl = ${JSON.stringify(serverUrl)};


                // Override console methods to send to main process with context
                console.log = (...args) => {
                  originalConsole.log(...args);
                  ipcRenderer.send('console-log', 'debug', webContentsId, serverUrl, ...args);
                };
                
                console.info = (...args) => {
                  originalConsole.info(...args);
                  ipcRenderer.send('console-log', 'info', webContentsId, serverUrl, ...args);
                };
                
                console.warn = (...args) => {
                  originalConsole.warn(...args);
                  ipcRenderer.send('console-log', 'warn', webContentsId, serverUrl, ...args);
                };
                
                console.error = (...args) => {
                  originalConsole.error(...args);
                  ipcRenderer.send('console-log', 'error', webContentsId, serverUrl, ...args);
                };
                
                console.debug = (...args) => {
                  originalConsole.debug(...args);
                  ipcRenderer.send('console-log', 'debug', webContentsId, serverUrl, ...args);
                };

                // Add marker to know console override is active
                console.original = originalConsole;
               } catch (error) {
                 console.error('[logging] Failed to override console in webContents:', error);
               }
            })();
          `;
          webContents.executeJavaScript(consoleOverrideScript).catch(err => {
            log__default.default.warn(`[logging] Failed to inject console override into webContents ${webContents.id}:`, err);
          });
        } catch (error) {
          logLoggingFailure(error, 'setupWebContentsLogging - webContents injection');
        }
      });

      // Clean up context and rate limit state when webContents is destroyed
      webContents.on('destroyed', () => {
        cleanupServerContext(webContents.id);
        ipcRateLimit.delete(webContents.id);
      });
    });

    // Handle console messages from renderer processes with enhanced context
    electron.ipcMain.on('console-log', (event, level, _webContentsId, _serverUrl, ...args) => {
      try {
        // Use authenticated sender identity — never trust renderer-supplied IDs
        const senderWebContents = event.sender;
        const senderId = senderWebContents.id;
        const now = Date.now();
        let rateState = ipcRateLimit.get(senderId);
        if (!rateState || now > rateState.resetTime) {
          rateState = {
            count: 0,
            resetTime: now + 1000
          };
          ipcRateLimit.set(senderId, rateState);
        }
        rateState.count++;
        if (rateState.count > MAX_IPC_MESSAGES_PER_SECOND) {
          return;
        }

        // Register webContents → server URL mapping using the authenticated
        // sender's URL — never trust renderer-supplied values.
        if (selectFunction && senderWebContents.getType() === 'webview') {
          try {
            const currentUrl = senderWebContents.getURL();
            if (currentUrl) {
              const currentOrigin = new URL(currentUrl).origin;
              const servers = selectFunction(state => state.servers);
              const matchedServer = servers.find(s => {
                try {
                  return s.url && new URL(s.url).origin === currentOrigin;
                } catch {
                  return false;
                }
              });
              if (matchedServer !== null && matchedServer !== void 0 && matchedServer.url) {
                registerWebContentsServer(senderWebContents.id, matchedServer.url);
              }
            }
          } catch {
            // Non-critical: server URL registration failed
          }
        }

        // Create enhanced context string with server info
        const context = getLogContext(senderWebContents);
        const contextStr = formatLogContext(context);

        // Dedup non-error IPC messages before they reach electron-log
        if (logDeduplicator && !logDeduplicator.shouldLog(level, contextStr, args)) {
          return;
        }

        // Log with enhanced context
        switch (level) {
          case 'debug':
          case 'verbose':
          case 'silly':
            log__default.default.debug(contextStr, ...args);
            break;
          case 'info':
            log__default.default.info(contextStr, ...args);
            break;
          case 'warn':
            log__default.default.warn(contextStr, ...args);
            break;
          case 'error':
            log__default.default.error(contextStr, ...args);
            break;
          default:
            log__default.default.info(contextStr, ...args);
            break;
        }
      } catch (error) {
        logLoggingFailure(error, 'console-log IPC handler');
      }
    });
  } catch (error) {
    console.warn('[main] [app] Failed to setup webContents logging:', error);
  }
};

/**
 * Set log level at runtime (useful for debugging without restart)
 */
const setLogLevel = level => {
  var _log$transports3, _log$transports4;
  if ((_log$transports3 = log__default.default.transports) !== null && _log$transports3 !== undefined && _log$transports3.console) {
    log__default.default.transports.console.level = level;
  }
  if ((_log$transports4 = log__default.default.transports) !== null && _log$transports4 !== undefined && _log$transports4.file) {
    log__default.default.transports.file.level = level;
  }
  console.info(`[logging] Log level changed to: ${level}`);
};

/**
 * Watch the debug logging toggle in Redux and update log level at runtime.
 * Called after store is initialized.
 */
const setupDebugLoggingWatch = () => {
  if (process.type !== 'browser') return;
  try {
    const isEnabled = select(state => state.isDebugLoggingEnabled);
    if (isEnabled) {
      setLogLevel('debug');
    }
    watch(state => state.isDebugLoggingEnabled, enabled => {
      setLogLevel(enabled ? 'debug' : 'info');
    });
  } catch {
    // Store may not be initialized yet — will apply on next toggle
  }
};

// Export the configured logger
const logger = configureLogging();

let status = {
  applied: false,
  certCount: 0
};
const readUseSystemCertificatesSetting = () => {
  const locations = [path__default.default.join(electron.app.getPath('userData'), 'overridden-settings.json'), path__default.default.join(electron.app.getAppPath(), electron.app.getAppPath().endsWith('app.asar') ? '..' : '.', 'overridden-settings.json')];
  let enabled = true;
  for (const filePath of locations) {
    try {
      const content = fs__namespace.default.readFileSync(filePath, 'utf8');
      const json = JSON.parse(content);
      if (json && typeof json === 'object' && 'useSystemCertificates' in json) {
        enabled = json.useSystemCertificates !== false && String(json.useSystemCertificates).toLowerCase() !== 'false';
      }
    } catch {
      // File doesn't exist or is invalid — continue to next location
    }
  }
  return enabled;
};
const applySystemCertificates = () => {
  try {
    const enabled = readUseSystemCertificatesSetting();
    if (!enabled) {
      logger.info('System CA certificates: disabled by overridden-settings.json');
      return;
    }
    const systemCerts = tls__default.default.getCACertificates('system');
    if (!systemCerts || systemCerts.length === 0) {
      logger.info('System CA certificates: none found in OS trust store, using bundled CAs only');
      return;
    }
    const defaultCerts = tls__default.default.getCACertificates();
    tls__default.default.setDefaultCACertificates([...defaultCerts, ...systemCerts]);
    status = {
      applied: true,
      certCount: systemCerts.length
    };
    logger.info(`System CA certificates: loaded ${systemCerts.length} certificates from OS trust store`);
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    logger.error('System CA certificates: failed to load —', message);
    status = {
      applied: false,
      certCount: 0,
      error: message
    };
  }
};
const getSystemCertificateStatus = () => status;

const selectPersistableValues = reselect.createStructuredSelector({
  currentView: ({
    currentView
  }) => currentView,
  doCheckForUpdatesOnStartup: ({
    doCheckForUpdatesOnStartup
  }) => doCheckForUpdatesOnStartup,
  downloads: ({
    downloads
  }) => downloads,
  machineTheme: ({
    machineTheme
  }) => machineTheme,
  isMenuBarEnabled: ({
    isMenuBarEnabled
  }) => isMenuBarEnabled,
  isShowWindowOnUnreadChangedEnabled: ({
    isShowWindowOnUnreadChangedEnabled
  }) => isShowWindowOnUnreadChangedEnabled,
  isSideBarEnabled: ({
    isSideBarEnabled
  }) => isSideBarEnabled,
  isTrayIconEnabled: ({
    isTrayIconEnabled
  }) => isTrayIconEnabled,
  rootWindowState: ({
    rootWindowState
  }) => rootWindowState,
  servers: ({
    servers
  }) => servers,
  skippedUpdateVersion: ({
    skippedUpdateVersion
  }) => skippedUpdateVersion,
  trustedCertificates: ({
    trustedCertificates
  }) => trustedCertificates,
  notTrustedCertificates: ({
    notTrustedCertificates
  }) => notTrustedCertificates,
  isEachUpdatesSettingConfigurable: ({
    isEachUpdatesSettingConfigurable
  }) => isEachUpdatesSettingConfigurable,
  isUpdatingEnabled: ({
    isUpdatingEnabled
  }) => isUpdatingEnabled,
  isHardwareAccelerationEnabled: ({
    isHardwareAccelerationEnabled
  }) => isHardwareAccelerationEnabled,
  externalProtocols: ({
    externalProtocols
  }) => externalProtocols,
  allowedJitsiServers: ({
    allowedJitsiServers
  }) => allowedJitsiServers,
  isReportEnabled: ({
    isReportEnabled
  }) => isReportEnabled,
  isFlashFrameEnabled: ({
    isFlashFrameEnabled
  }) => isFlashFrameEnabled,
  isInternalVideoChatWindowEnabled: ({
    isInternalVideoChatWindowEnabled
  }) => isInternalVideoChatWindowEnabled,
  isMinimizeOnCloseEnabled: ({
    isMinimizeOnCloseEnabled
  }) => isMinimizeOnCloseEnabled,
  isAddNewServersEnabled: ({
    isAddNewServersEnabled
  }) => isAddNewServersEnabled,
  isDeveloperModeEnabled: ({
    isDeveloperModeEnabled
  }) => isDeveloperModeEnabled,
  hasHideOnTrayNotificationShown: ({
    hasHideOnTrayNotificationShown
  }) => hasHideOnTrayNotificationShown,
  lastSelectedServerUrl: ({
    lastSelectedServerUrl
  }) => lastSelectedServerUrl,
  allowedNTLMCredentialsDomains: ({
    allowedNTLMCredentialsDomains
  }) => allowedNTLMCredentialsDomains,
  isNTLMCredentialsEnabled: ({
    isNTLMCredentialsEnabled
  }) => isNTLMCredentialsEnabled,
  mainWindowTitle: ({
    mainWindowTitle
  }) => mainWindowTitle || 'Rocket.Chat',
  selectedBrowser: ({
    selectedBrowser
  }) => selectedBrowser,
  videoCallWindowState: ({
    videoCallWindowState
  }) => videoCallWindowState,
  isVideoCallWindowPersistenceEnabled: ({
    isVideoCallWindowPersistenceEnabled
  }) => isVideoCallWindowPersistenceEnabled,
  isVideoCallDevtoolsAutoOpenEnabled: ({
    isVideoCallDevtoolsAutoOpenEnabled
  }) => isVideoCallDevtoolsAutoOpenEnabled,
  isTransparentWindowEnabled: ({
    isTransparentWindowEnabled
  }) => isTransparentWindowEnabled,
  isVideoCallScreenCaptureFallbackEnabled: ({
    isVideoCallScreenCaptureFallbackEnabled
  }) => isVideoCallScreenCaptureFallbackEnabled,
  updateChannel: ({
    updateChannel
  }) => updateChannel,
  userThemePreference: ({
    userThemePreference
  }) => userThemePreference,
  outlookCalendarSyncInterval: ({
    outlookCalendarSyncInterval
  }) => outlookCalendarSyncInterval,
  isVerboseOutlookLoggingEnabled: ({
    isVerboseOutlookLoggingEnabled
  }) => isVerboseOutlookLoggingEnabled,
  isDetailedEventsLoggingEnabled: ({
    isDetailedEventsLoggingEnabled
  }) => isDetailedEventsLoggingEnabled,
  isDebugLoggingEnabled: ({
    isDebugLoggingEnabled
  }) => isDebugLoggingEnabled
});

const loadUserDataOverriddenSettings = async () => {
  try {
    const filePath = path__default.default.join(electron.app.getPath('userData'), 'overridden-settings.json');
    const content = await fs__namespace.default.promises.readFile(filePath, 'utf8');
    const json = JSON.parse(content);
    return json && typeof json === 'object' ? json : {};
  } catch (error) {
    return {};
  }
};
const loadAppAsarOverriddenSettings = async () => {
  try {
    const filePath = path__default.default.join(electron.app.getAppPath(), electron.app.getAppPath().endsWith('app.asar') ? '..' : '.', 'overridden-settings.json');
    const content = await fs__namespace.default.promises.readFile(filePath, 'utf8');
    const json = JSON.parse(content);
    return json && typeof json === 'object' ? json : {};
  } catch (error) {
    return {};
  }
};
const mergePersistableValues = async localStorage => {
  var _userRootWindowState$, _values, _userRootWindowState$2, _values2, _userRootWindowState$3, _values3, _userRootWindowState$4, _values4, _userRootWindowState$5, _values5, _userRootWindowState$6, _values6, _userRootWindowState$7, _values7;
  const initialValues = select(selectPersistableValues);
  const electronStoreValues = getPersistedValues();
  const userDataOverriddenSettings = await loadUserDataOverriddenSettings();
  const appAsarOverriddenSettings = await loadAppAsarOverriddenSettings();
  const localStorageValues = Object.fromEntries(Object.entries(localStorage).map(([key, value]) => {
    try {
      return [key, JSON.parse(value)];
    } catch (error) {
      return [];
    }
  }));
  const {
    outlookCalendarSyncInterval: _udSync,
    ...userDataPersistableOverrides
  } = userDataOverriddenSettings;
  const {
    outlookCalendarSyncInterval: _aaSync,
    ...appAsarPersistableOverrides
  } = appAsarOverriddenSettings;
  let values = selectPersistableValues({
    ...initialValues,
    ...electronStoreValues,
    ...localStorageValues,
    ...userDataPersistableOverrides,
    ...appAsarPersistableOverrides
  });
  if (localStorage.autohideMenu) {
    values = {
      ...values,
      isMenuBarEnabled: localStorage.autohideMenu !== 'true'
    };
  }
  if (localStorage.showWindowOnUnreadChanged) {
    values = {
      ...values,
      isShowWindowOnUnreadChangedEnabled: localStorage.showWindowOnUnreadChanged === 'true'
    };
  }
  if (localStorage['sidebar-closed']) {
    values = {
      ...values,
      isSideBarEnabled: localStorage['sidebar-closed'] !== 'true'
    };
  }
  if (localStorage.hideTray) {
    values = {
      ...values,
      isTrayIconEnabled: localStorage.hideTray !== 'true'
    };
  }
  const userRootWindowState = await (async () => {
    try {
      const filePath = path__default.default.join(electron.app.getPath('userData'), 'main-window-state.json');
      const content = await fs__namespace.default.promises.readFile(filePath, 'utf8');
      const json = JSON.parse(content);
      await fs__namespace.default.promises.unlink(filePath);
      return json && typeof json === 'object' ? json : {};
    } catch (error) {
      return {};
    }
  })();
  values = {
    ...values,
    rootWindowState: {
      focused: true,
      visible: !((_userRootWindowState$ = userRootWindowState === null || userRootWindowState === undefined ? undefined : userRootWindowState.isHidden) !== null && _userRootWindowState$ !== undefined ? _userRootWindowState$ : !((_values = values) !== null && _values !== undefined && (_values = _values.rootWindowState) !== null && _values !== undefined && _values.visible)),
      maximized: (_userRootWindowState$2 = userRootWindowState === null || userRootWindowState === undefined ? undefined : userRootWindowState.isMaximized) !== null && _userRootWindowState$2 !== undefined ? _userRootWindowState$2 : (_values2 = values) === null || _values2 === undefined || (_values2 = _values2.rootWindowState) === null || _values2 === undefined ? undefined : _values2.maximized,
      minimized: (_userRootWindowState$3 = userRootWindowState === null || userRootWindowState === undefined ? undefined : userRootWindowState.isMinimized) !== null && _userRootWindowState$3 !== undefined ? _userRootWindowState$3 : (_values3 = values) === null || _values3 === undefined || (_values3 = _values3.rootWindowState) === null || _values3 === undefined ? undefined : _values3.minimized,
      fullscreen: false,
      normal: !(userRootWindowState !== null && userRootWindowState !== undefined && userRootWindowState.isMinimized || userRootWindowState !== null && userRootWindowState !== undefined && userRootWindowState.isMaximized),
      bounds: {
        x: (_userRootWindowState$4 = userRootWindowState === null || userRootWindowState === undefined ? undefined : userRootWindowState.x) !== null && _userRootWindowState$4 !== undefined ? _userRootWindowState$4 : normalizeNumber((_values4 = values) === null || _values4 === undefined || (_values4 = _values4.rootWindowState) === null || _values4 === undefined || (_values4 = _values4.bounds) === null || _values4 === undefined ? undefined : _values4.x),
        y: (_userRootWindowState$5 = userRootWindowState === null || userRootWindowState === undefined ? undefined : userRootWindowState.y) !== null && _userRootWindowState$5 !== undefined ? _userRootWindowState$5 : normalizeNumber((_values5 = values) === null || _values5 === undefined || (_values5 = _values5.rootWindowState) === null || _values5 === undefined || (_values5 = _values5.bounds) === null || _values5 === undefined ? undefined : _values5.y),
        width: (_userRootWindowState$6 = userRootWindowState === null || userRootWindowState === undefined ? undefined : userRootWindowState.width) !== null && _userRootWindowState$6 !== undefined ? _userRootWindowState$6 : (_values6 = values) === null || _values6 === undefined || (_values6 = _values6.rootWindowState) === null || _values6 === undefined || (_values6 = _values6.bounds) === null || _values6 === undefined ? undefined : _values6.width,
        height: (_userRootWindowState$7 = userRootWindowState === null || userRootWindowState === undefined ? undefined : userRootWindowState.height) !== null && _userRootWindowState$7 !== undefined ? _userRootWindowState$7 : (_values7 = values) === null || _values7 === undefined || (_values7 = _values7.rootWindowState) === null || _values7 === undefined || (_values7 = _values7.bounds) === null || _values7 === undefined ? undefined : _values7.height
      }
    }
  };
  if (!values.isMenuBarEnabled && !values.isSideBarEnabled) {
    values = {
      ...values,
      isSideBarEnabled: true
    };
  }
  const mergedOverrides = {
    ...userDataOverriddenSettings,
    ...appAsarOverriddenSettings
  };
  const outlookCalendarSyncIntervalOverride = mergedOverrides.outlookCalendarSyncInterval !== undefined ? (() => {
    const raw = Number(mergedOverrides.outlookCalendarSyncInterval);
    return Number.isFinite(raw) && Number.isInteger(raw) ? Math.max(1, Math.min(60, raw)) : 60;
  })() : null;
  const isInsecureOutlookEnabled = mergedOverrides.allowInsecureOutlookConnections === true || String(mergedOverrides.allowInsecureOutlookConnections).toLowerCase() === 'true';
  dispatch({
    type: APP_SETTINGS_LOADED,
    payload: {
      ...values,
      allowInsecureOutlookConnections: isInsecureOutlookEnabled,
      outlookCalendarSyncIntervalOverride
    }
  });
  if (isInsecureOutlookEnabled && getSystemCertificateStatus().applied) {
    logger.warn('Both allowInsecureOutlookConnections and system CA certificates are active. ' + 'System CAs should make the insecure bypass unnecessary. ' + 'Consider removing allowInsecureOutlookConnections from overridden-settings.json.');
  }
};
const watchAndPersistChanges = () => {
  watch(selectPersistableValues, values => {
    persistValues(values);
  });
};

const startBrowserHandler = () => {
  handle('browser/open-url', async (_event, url) => {
    try {
      const parsed = new URL(url);
      if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
        return;
      }
      await openExternal(parsed.toString());
    } catch {
      // Invalid URL — ignore
    }
  });
};

let ServerUrlResolutionStatus = /*#__PURE__*/function (ServerUrlResolutionStatus) {
  ServerUrlResolutionStatus["OK"] = "ok";
  ServerUrlResolutionStatus["INVALID_URL"] = "invalid-url";
  ServerUrlResolutionStatus["TIMEOUT"] = "timeout";
  ServerUrlResolutionStatus["INVALID"] = "invalid";
  return ServerUrlResolutionStatus;
}({});
const isServerUrlResolutionResult = obj => {
  if (!Array.isArray(obj)) {
    return false;
  }
  return obj.length === 3 && typeof obj[0] === 'string' && [ServerUrlResolutionStatus.INVALID, ServerUrlResolutionStatus.INVALID_URL, ServerUrlResolutionStatus.TIMEOUT].includes(obj[1]) && typeof obj[2] === 'object' || obj.length === 2 && typeof obj[0] === 'string' && obj[1] === ServerUrlResolutionStatus.OK;
};

const t$7 = i18next__default.default.t.bind(i18next__default.default);
const loadUserTrustedCertificates = async () => {
  try {
    const filePath = path__default.default.join(electron.app.getPath('userData'), 'certificate.json');
    const content = await fs__namespace.default.promises.readFile(filePath, 'utf8');
    const json = JSON.parse(content);
    await fs__namespace.default.promises.unlink(filePath);
    return json && typeof json === 'object' ? json : {};
  } catch (error) {
    return {};
  }
};
const serializeCertificate = certificate => `${certificate.issuerName}\n${certificate.data.toString()}`;
const queuedTrustRequests = new Map();
const setupNavigation = async () => {
  electron.app.addListener('certificate-error', async (event, _webContents, requestedUrl, error, certificate, callback) => {
    var _queuedTrustRequests$2, _queuedTrustRequests$3;
    event.preventDefault();
    const serialized = serializeCertificate(certificate);
    const {
      host
    } = new URL(requestedUrl);
    let trustedCertificates = select(({
      trustedCertificates
    }) => trustedCertificates);
    const isTrusted = !!trustedCertificates[host] && trustedCertificates[host] === serialized;
    if (isTrusted) {
      callback(true);
      return;
    }
    let notTrustedCertificates = select(({
      notTrustedCertificates
    }) => notTrustedCertificates);
    const isNotTrusted = !!notTrustedCertificates[host] && notTrustedCertificates[host] === serialized;
    if (isNotTrusted) {
      callback(false);
      return;
    }
    if (queuedTrustRequests.has(certificate.fingerprint)) {
      var _queuedTrustRequests$;
      (_queuedTrustRequests$ = queuedTrustRequests.get(certificate.fingerprint)) === null || _queuedTrustRequests$ === undefined || _queuedTrustRequests$.push(callback);
      return;
    }
    queuedTrustRequests.set(certificate.fingerprint, [callback]);
    let detail = `URL: ${requestedUrl}\nError: ${error}`;
    if (trustedCertificates[host]) {
      detail = t$7('error.differentCertificate', {
        detail
      });
    }
    const response = await askForCertificateTrust(certificate.issuerName, detail);
    const isTrustedByUser = response;
    (_queuedTrustRequests$2 = queuedTrustRequests.get(certificate.fingerprint)) === null || _queuedTrustRequests$2 === undefined || _queuedTrustRequests$2.forEach(cb => cb(isTrustedByUser === AskForCertificateTrustResponse.YES));
    queuedTrustRequests.delete(certificate.fingerprint);
    trustedCertificates = select(({
      trustedCertificates
    }) => trustedCertificates);
    if (isTrustedByUser === AskForCertificateTrustResponse.YES) {
      dispatch({
        type: TRUSTED_CERTIFICATES_UPDATED,
        payload: {
          ...trustedCertificates,
          [host]: serialized
        }
      });
    }
    (_queuedTrustRequests$3 = queuedTrustRequests.get(certificate.fingerprint)) === null || _queuedTrustRequests$3 === undefined || _queuedTrustRequests$3.forEach(cb => cb(isTrustedByUser === AskForCertificateTrustResponse.NO));
    queuedTrustRequests.delete(certificate.fingerprint);
    notTrustedCertificates = select(({
      notTrustedCertificates
    }) => notTrustedCertificates);
    if (isTrustedByUser === AskForCertificateTrustResponse.NO) {
      dispatch({
        type: NOT_TRUSTED_CERTIFICATES_UPDATED,
        payload: {
          ...notTrustedCertificates,
          [host]: serialized
        }
      });
    }
  });
  electron.app.addListener('select-client-certificate', async (event, _webContents, _url, certificateList, callback) => {
    event.preventDefault();
    if (certificateList.length === 1) {
      callback(certificateList[0]);
      return;
    }
    const fingerprint = await request({
      type: CERTIFICATES_CLIENT_CERTIFICATE_REQUESTED,
      payload: JSON.parse(JSON.stringify(certificateList))
    }, SELECT_CLIENT_CERTIFICATE_DIALOG_CERTIFICATE_SELECTED, SELECT_CLIENT_CERTIFICATE_DIALOG_DISMISSED);
    const certificate = certificateList.find(certificate => certificate.fingerprint === fingerprint);
    if (!certificate) {
      callback(undefined);
      return;
    }
    callback(certificate);
  });
  electron.app.addListener('login', (event, _webContents, authenticationResponseDetails, _authInfo, callback) => {
    event.preventDefault();
    const servers = select(({
      servers
    }) => servers);
    for (const server of servers) {
      const {
        host: serverHost,
        username,
        password
      } = new URL(server.url);
      const requestHost = new URL(authenticationResponseDetails.url).host;
      if (serverHost !== requestHost || !username) {
        callback();
        return;
      }
      callback(username, password);
    }
  });
  const trustedCertificates = select(({
    trustedCertificates
  }) => trustedCertificates);
  const userTrustedCertificates = await loadUserTrustedCertificates();
  dispatch({
    type: CERTIFICATES_LOADED,
    payload: {
      ...trustedCertificates,
      ...userTrustedCertificates
    }
  });
};
const isProtocolAllowed = async rawUrl => {
  const url = new URL(rawUrl);
  const instrinsicProtocols = ['http:', 'https:', 'mailto:'];
  const persistedProtocols = Object.entries(select(({
    externalProtocols
  }) => externalProtocols)).filter(([, allowed]) => allowed).map(([protocol]) => protocol);
  const allowedProtocols = [...instrinsicProtocols, ...persistedProtocols];
  if (allowedProtocols.includes(url.protocol)) {
    return true;
  }
  const {
    allowed,
    dontAskAgain
  } = await askForOpeningExternalProtocol(url);
  if (dontAskAgain) {
    dispatch({
      type: EXTERNAL_PROTOCOL_PERMISSION_UPDATED,
      payload: {
        protocol: url.protocol,
        allowed
      }
    });
  }
  return allowed;
};

const DEFAULT_TIMEOUT = 60000;
class ScreenSharingRequestTracker {
  constructor(responseChannel, label, timeoutMs = DEFAULT_TIMEOUT) {
    this.responseChannel = responseChannel;
    this.label = label;
    this.timeoutMs = timeoutMs;
    _defineProperty(this, "activeListener", null);
    _defineProperty(this, "activeRequestId", null);
    _defineProperty(this, "timeout", null);
    _defineProperty(this, "isPending", false);
  }
  cleanup() {
    if (this.activeListener) {
      electron.ipcMain.removeListener(this.responseChannel, this.activeListener);
      this.activeListener = null;
    }
    if (this.timeout) {
      clearTimeout(this.timeout);
      this.timeout = null;
    }
    this.activeRequestId = null;
    this.isPending = false;
  }
  removeListenerOnly() {
    if (this.activeListener) {
      electron.ipcMain.removeListener(this.responseChannel, this.activeListener);
      this.activeListener = null;
    }
    if (this.timeout) {
      clearTimeout(this.timeout);
      this.timeout = null;
    }
  }
  markComplete() {
    this.activeRequestId = null;
    this.isPending = false;
  }
  get pending() {
    return this.isPending;
  }
  createRequest(cb, sendOpenPicker) {
    if (this.isPending) {
      console.warn(`${this.label}: request already pending, ignoring`);
      cb({
        video: false
      });
      return;
    }
    this.cleanup();
    const requestId = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
    this.activeRequestId = requestId;
    this.isPending = true;
    let callbackInvoked = false;
    const listener = async (_event, sourceId) => {
      if (this.activeRequestId !== requestId) {
        return;
      }
      if (callbackInvoked) {
        return;
      }
      callbackInvoked = true;
      this.removeListenerOnly();
      this.markComplete();
      if (!sourceId) {
        cb({
          video: false
        });
        return;
      }
      try {
        const sources = await electron.desktopCapturer.getSources({
          types: ['window', 'screen']
        });
        const selectedSource = sources.find(s => s.id === sourceId);
        if (!selectedSource) {
          console.warn(`${this.label}: selected source no longer available:`, sourceId);
          cb({
            video: false
          });
          return;
        }
        cb({
          video: selectedSource
        });
      } catch (error) {
        console.error(`${this.label}: error validating source:`, error);
        cb({
          video: false
        });
      }
    };
    this.activeListener = listener;
    this.timeout = setTimeout(() => {
      if (this.activeRequestId !== requestId) {
        return;
      }
      if (callbackInvoked) {
        return;
      }
      callbackInvoked = true;
      console.warn(`${this.label}: request timed out, cleaning up`);
      this.removeListenerOnly();
      this.markComplete();
      cb({
        video: false
      });
    }, this.timeoutMs);
    electron.ipcMain.once(this.responseChannel, listener);
    sendOpenPicker();
  }
}

const DESKTOP_CAPTURER_STALE_THRESHOLD = 3000;
const SOURCE_VALIDATION_CACHE_TTL = 30000;
let desktopCapturerCache = null;
let desktopCapturerPromise = null;
const sourceValidationCache = new Set();
let sourceValidationCacheTimestamp = 0;
const refreshDesktopCapturerCache = options => {
  if (desktopCapturerPromise) return;
  desktopCapturerPromise = (async () => {
    try {
      const sources = await electron.desktopCapturer.getSources(options);
      const validSources = sources.filter(source => {
        if (!source.name || source.name.trim() === '') {
          return false;
        }
        const now = Date.now();
        const cacheExpired = now - sourceValidationCacheTimestamp > SOURCE_VALIDATION_CACHE_TTL;
        if (!cacheExpired && sourceValidationCache.has(source.id)) {
          return true;
        }
        if (source.thumbnail.isEmpty()) {
          return false;
        }
        if (cacheExpired) {
          sourceValidationCache.clear();
          sourceValidationCacheTimestamp = now;
        }
        sourceValidationCache.add(source.id);
        return true;
      });
      desktopCapturerCache = {
        sources: validSources,
        timestamp: Date.now()
      };
      return validSources;
    } catch (error) {
      var _desktopCapturerCache;
      console.error('Background cache refresh failed:', error);
      return ((_desktopCapturerCache = desktopCapturerCache) === null || _desktopCapturerCache === undefined ? undefined : _desktopCapturerCache.sources) || [];
    } finally {
      desktopCapturerPromise = null;
    }
  })();
};
const prewarmDesktopCapturerCache = () => {
  refreshDesktopCapturerCache({
    types: ['window', 'screen']
  });
};
const clearDesktopCapturerCache = () => {
  desktopCapturerCache = null;
  desktopCapturerPromise = null;
  sourceValidationCache.clear();
  sourceValidationCacheTimestamp = 0;
};
const getDesktopCapturerCacheStatus = () => ({
  cached: desktopCapturerCache !== null,
  pending: desktopCapturerPromise !== null
});
const handleDesktopCapturerGetSources = () => {
  handle('desktop-capturer-get-sources', async (_webContents, opts) => {
    try {
      const options = Array.isArray(opts) ? opts[0] : opts;
      if (desktopCapturerCache) {
        const isStale = Date.now() - desktopCapturerCache.timestamp > DESKTOP_CAPTURER_STALE_THRESHOLD;
        if (isStale && !desktopCapturerPromise) {
          refreshDesktopCapturerCache(options);
        }
        return desktopCapturerCache.sources;
      }
      if (desktopCapturerPromise) {
        return await desktopCapturerPromise;
      }
      refreshDesktopCapturerCache(options);
      if (desktopCapturerPromise) {
        return await desktopCapturerPromise;
      }
      return [];
    } catch (error) {
      var _desktopCapturerCache2;
      console.error('Error in desktop capturer handler:', error);
      return ((_desktopCapturerCache2 = desktopCapturerCache) === null || _desktopCapturerCache2 === undefined ? undefined : _desktopCapturerCache2.sources) || [];
    }
  });
};

const checkScreenRecordingPermission = async () => {
  if (process.platform === 'darwin') {
    const permission = electron.systemPreferences.getMediaAccessStatus('screen');
    return permission === 'granted';
  }
  return true;
};

const serverViewTracker = new ScreenSharingRequestTracker('screen-picker/source-responded', 'Server view screen sharing');
const createRootWindowPickerHandler = () => cb => {
  prewarmDesktopCapturerCache();
  serverViewTracker.createRequest(cb, () => {
    getRootWindow().then(rootWindow => {
      if (rootWindow && !rootWindow.isDestroyed()) {
        rootWindow.webContents.send('screen-picker/open');
      } else {
        console.warn('Server view screen sharing: root window not available');
      }
    });
  });
};
let provider = null;
let providerReady = false;
let initPromise = null;
const initializeProvider = () => {
  if (initPromise) return initPromise;
  initPromise = (async () => {
    const {
      detectPickerType,
      InternalPickerProvider,
      PortalPickerProvider
    } = await Promise.resolve().then(function () { return require('./index-DUAIsPdo.js'); });
    const pickerType = detectPickerType();
    if (pickerType === 'portal') {
      provider = new PortalPickerProvider();
    } else {
      const internalProvider = new InternalPickerProvider();
      internalProvider.setHandleRequestHandler(createRootWindowPickerHandler());
      provider = internalProvider;
    }
    providerReady = true;
    console.log(`Server view screen sharing: using ${provider.type} provider`);
  })();
  return initPromise;
};
const setupServerViewDisplayMedia = guestWebContents => {
  const setupHandler = () => {
    if (!provider || !providerReady) return;
    const currentProvider = provider;
    try {
      guestWebContents.session.setDisplayMediaRequestHandler((_request, cb) => {
        try {
          currentProvider.handleDisplayMediaRequest(cb);
        } catch (error) {
          console.error('Server view screen sharing: error in handler:', error);
          cb({
            video: false
          });
        }
      }, {
        useSystemPicker: false
      });
      if (currentProvider.requiresCacheWarming) {
        prewarmDesktopCapturerCache();
      }
    } catch (error) {
      console.error('Server view screen sharing: error setting up handler:', error);
    }
  };
  if (providerReady) {
    setupHandler();
  } else {
    initializeProvider().then(() => {
      if (!guestWebContents.isDestroyed()) {
        setupHandler();
      }
    }).catch(error => {
      console.error('Server view screen sharing: error initializing provider:', error);
    });
  }
};
const startServerViewScreenSharingHandler = () => {
  handle('screen-picker/screen-recording-is-permission-granted', async () => checkScreenRecordingPermission());
  handle('screen-picker/open-url', async (_webContents, url) => {
    const allowed = await isProtocolAllowed(url);
    if (!allowed) {
      return;
    }
    await openExternal(url);
  });
};

const computeMediaNeeds = mediaTypes => {
  return {
    needsAudio: mediaTypes.includes('audio'),
    needsVideo: mediaTypes.includes('video')
  };
};
const readMediaStatusesAndInitializeAllowed = async (needsAudio, needsVideo, handleNotDetermined) => {
  let micStatus;
  let camStatus;
  if (needsAudio) {
    micStatus = electron.systemPreferences.getMediaAccessStatus('microphone');
  }
  if (needsVideo) {
    camStatus = electron.systemPreferences.getMediaAccessStatus('camera');
  }
  let microphoneAllowed = !needsAudio || micStatus === 'granted';
  let cameraAllowed = !needsVideo || camStatus === 'granted';
  if (handleNotDetermined) {
    if (micStatus === 'not-determined') {
      microphoneAllowed = await electron.systemPreferences.askForMediaAccess('microphone');
    }
    if (camStatus === 'not-determined') {
      cameraAllowed = await electron.systemPreferences.askForMediaAccess('camera');
    }
  }
  return {
    micStatus,
    camStatus,
    microphoneAllowed,
    cameraAllowed
  };
};
const handleDeniedPermissions = async (parentWindow, micStatus, camStatus, _microphoneAllowed, _cameraAllowed, openers, isDeniedStatus) => {
  const micDenied = isDeniedStatus(micStatus);
  const camDenied = isDeniedStatus(camStatus);
  if (micDenied || camDenied) {
    if (parentWindow && !parentWindow.isDestroyed()) {
      let permissionType;
      if (micDenied && camDenied) {
        permissionType = 'both';
      } else if (micDenied) {
        permissionType = 'microphone';
      } else {
        permissionType = 'camera';
      }
      try {
        const openSettings = await askForMediaPermissionSettings(permissionType, parentWindow);
        if (openSettings) {
          electron.shell.openExternal(openers[permissionType]);
        }
      } catch (error) {
        console.warn('Failed to open media permission settings dialog:', error);
      }
    }
  }
};
const handleMediaPermissionRequest = async (mediaTypes, parentWindow, _actionType, callback) => {
  if (process.platform === 'darwin') {
    const {
      needsAudio,
      needsVideo
    } = computeMediaNeeds(mediaTypes);
    const {
      micStatus,
      camStatus,
      microphoneAllowed,
      cameraAllowed
    } = await readMediaStatusesAndInitializeAllowed(needsAudio, needsVideo, true);
    const openers = {
      microphone: 'x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone',
      camera: 'x-apple.systempreferences:com.apple.preference.security?Privacy_Camera',
      both: 'x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone'
    };
    const isDeniedStatus = status => status === 'denied' || status === 'restricted';
    await handleDeniedPermissions(parentWindow, micStatus, camStatus, microphoneAllowed, cameraAllowed, openers, isDeniedStatus);
    callback(microphoneAllowed && cameraAllowed);
    return;
  }
  if (process.platform === 'win32') {
    const {
      needsAudio,
      needsVideo
    } = computeMediaNeeds(mediaTypes);
    const {
      micStatus,
      camStatus,
      microphoneAllowed,
      cameraAllowed
    } = await readMediaStatusesAndInitializeAllowed(needsAudio, needsVideo, false);
    const openers = {
      microphone: 'ms-settings:privacy-microphone',
      camera: 'ms-settings:privacy-webcam',
      both: 'ms-settings:privacy-microphone'
    };
    const isDeniedStatus = status => status === 'denied';
    await handleDeniedPermissions(parentWindow, micStatus, camStatus, microphoneAllowed, cameraAllowed, openers, isDeniedStatus);
    callback(microphoneAllowed && cameraAllowed);
    return;
  }
  if (process.platform === 'linux') {
    // Linux: Electron's systemPreferences APIs are not available on Linux.
    // Permission handling is delegated to:
    // 1. Browser permission prompts (Chromium will show native permission dialogs)
    // 2. System-level audio configuration (PulseAudio, PipeWire, ALSA)
    // We allow the request and let the browser and OS handle the permission flow.
    callback(true);
    return;
  }

  // Other platforms (unsupported): Allow access and let browser handle permissions
  callback(true);
};

const SPELL_CHECKING_TOGGLED = 'spell-checking/toggled';
const SPELL_CHECKING_LANGUAGE_TOGGLED = 'spell-checking/language-toggled';

const t$6 = i18next__default.default.t.bind(i18next__default.default);
const createSpellCheckingMenuTemplate = (serverViewWebContents, {
  isEditable,
  dictionarySuggestions
}) => {
  if (!isEditable) {
    return [];
  }
  const {
    availableSpellCheckerLanguages
  } = serverViewWebContents.session;
  const spellCheckerLanguages = serverViewWebContents.session.getSpellCheckerLanguages();
  return [...(spellCheckerLanguages.length > 0 && dictionarySuggestions ? [...(dictionarySuggestions.length === 0 ? [{
    label: t$6('contextMenu.noSpellingSuggestions'),
    enabled: false
  }] : dictionarySuggestions.slice(0, 6).map(dictionarySuggestion => ({
    label: dictionarySuggestion,
    click: () => {
      serverViewWebContents.replaceMisspelling(dictionarySuggestion);
    }
  }))), ...(dictionarySuggestions.length > 6 ? [{
    label: t$6('contextMenu.moreSpellingSuggestions'),
    submenu: dictionarySuggestions.slice(6).map(dictionarySuggestion => ({
      label: dictionarySuggestion,
      click: () => {
        serverViewWebContents.replaceMisspelling(dictionarySuggestion);
      }
    }))
  }] : []), {
    type: 'separator'
  }] : []), ...(process.platform === 'darwin' ? [{
    label: t$6('contextMenu.spelling'),
    type: 'checkbox',
    checked: spellCheckerLanguages.length > 0,
    click: ({
      checked
    }) => {
      dispatch({
        type: SPELL_CHECKING_TOGGLED,
        payload: checked
      });
    }
  }] : [{
    label: t$6('contextMenu.spellingLanguages'),
    enabled: availableSpellCheckerLanguages.length > 0,
    submenu: [...availableSpellCheckerLanguages.map(availableSpellCheckerLanguage => ({
      label: availableSpellCheckerLanguage,
      type: 'checkbox',
      checked: spellCheckerLanguages.includes(availableSpellCheckerLanguage),
      click: ({
        checked
      }) => {
        dispatch({
          type: SPELL_CHECKING_LANGUAGE_TOGGLED,
          payload: {
            name: availableSpellCheckerLanguage,
            enabled: checked
          }
        });
      }
    }))]
  }]), {
    type: 'separator'
  }];
};
const createImageMenuTemplate = (serverViewWebContents, {
  mediaType,
  srcURL,
  x,
  y
}) => mediaType === 'image' ? [{
  label: t$6('contextMenu.saveImageAs'),
  click: () => serverViewWebContents.downloadURL(srcURL)
}, {
  label: t$6('contextMenu.copyImage'),
  click: () => serverViewWebContents.copyImageAt(x, y)
}, {
  type: 'separator'
}] : [];
const createLinkMenuTemplate = (_serverViewWebContents, {
  linkURL,
  linkText
}) => linkURL ? [{
  label: t$6('contextMenu.openLink'),
  click: () => {
    isProtocolAllowed(linkURL).then(allowed => {
      if (!allowed) {
        return;
      }
      openExternal(linkURL);
    });
  }
}, {
  label: t$6('contextMenu.copyLinkText'),
  click: () => electron.clipboard.write({
    text: linkText,
    bookmark: linkText
  }),
  enabled: !!linkText
}, {
  label: t$6('contextMenu.copyLinkAddress'),
  click: () => electron.clipboard.write({
    text: linkURL,
    bookmark: linkText
  })
}, {
  type: 'separator'
}] : [];
const createDefaultMenuTemplate = (_serverViewWebContents, {
  editFlags: {
    canUndo = false,
    canRedo = false,
    canCut = false,
    canCopy = false,
    canPaste = false,
    canSelectAll = false
  }
}) => [{
  label: t$6('contextMenu.undo'),
  role: 'undo',
  accelerator: 'CommandOrControl+Z',
  enabled: canUndo
}, {
  label: t$6('contextMenu.redo'),
  role: 'redo',
  accelerator: process.platform === 'win32' ? 'Control+Y' : 'CommandOrControl+Shift+Z',
  enabled: canRedo
}, {
  type: 'separator'
}, {
  label: t$6('contextMenu.cut'),
  role: 'cut',
  accelerator: 'CommandOrControl+X',
  enabled: canCut
}, {
  label: t$6('contextMenu.copy'),
  role: 'copy',
  accelerator: 'CommandOrControl+C',
  enabled: canCopy
}, {
  label: t$6('contextMenu.paste'),
  role: 'paste',
  accelerator: 'CommandOrControl+V',
  enabled: canPaste
}, {
  label: t$6('contextMenu.selectAll'),
  role: 'selectAll',
  accelerator: 'CommandOrControl+A',
  enabled: canSelectAll
}];
const createPopupMenuForServerView = (serverViewWebContents, params) => electron.Menu.buildFromTemplate([...createSpellCheckingMenuTemplate(serverViewWebContents, params), ...createImageMenuTemplate(serverViewWebContents, params), ...createLinkMenuTemplate(serverViewWebContents, params), ...createDefaultMenuTemplate(serverViewWebContents, params)]);

const t$5 = i18next__default.default.t.bind(i18next__default.default);
const webContentsByServerUrl = new Map();
const VIDEO_CALL_PRELOAD_PATH = 'app/preload/preload.js';

/**
 * Determines if a webview is a video call webview based on partition and frame name.
 */
const isVideoCallWebview = (partition, frameName) => {
  if (partition === 'persist:video-call-session') {
    return true;
  }
  if (frameName === 'Video Call') {
    return true;
  }
  return false;
};

/**
 * Resolves and validates preload file paths, falling back to safe defaults.
 * Returns the preload path or null if no valid preload is available.
 */
const resolvePreloadPath = isVideoCall => {
  const appPath = electron.app.getAppPath();
  const defaultPreload = path__default.default.join(appPath, 'app/preload.js');
  const videoCallPreload = path__default.default.join(appPath, VIDEO_CALL_PRELOAD_PATH);
  const targetPreload = isVideoCall ? videoCallPreload : defaultPreload;
  const fallbackPreload = isVideoCall ? defaultPreload : null;
  if (fs__namespace.default.existsSync(targetPreload)) {
    return targetPreload;
  }
  if (fallbackPreload && fs__namespace.default.existsSync(fallbackPreload)) {
    console.warn(`Preload file not found: ${targetPreload}, falling back to: ${fallbackPreload}`);
    return fallbackPreload;
  }
  console.warn(`Preload file not found: ${targetPreload}${fallbackPreload ? ` or fallback: ${fallbackPreload}` : ''}. Preload will be disabled.`);
  return null;
};
const getWebContentsByServerUrl = url => webContentsByServerUrl.get(url);
const getServerUrlByWebContentsId = webContentsId => {
  var _Array$from$find;
  const targetWebContents = electron.webContents.fromId(webContentsId);
  if (!targetWebContents) {
    return undefined;
  }
  return (_Array$from$find = Array.from(webContentsByServerUrl.entries()).find(([, wc]) => wc === targetWebContents)) === null || _Array$from$find === undefined ? undefined : _Array$from$find[0];
};
const initializeServerWebContentsAfterReady = (_serverUrl, guestWebContents, rootWindow) => {
  const handleContextMenu = async (event, params) => {
    event.preventDefault();
    const menu = createPopupMenuForServerView(guestWebContents, params);
    menu.popup({
      window: rootWindow
    });
  };
  guestWebContents.addListener('context-menu', handleContextMenu);
  guestWebContents.on('page-title-updated', (_event, pageTitle) => {
    dispatch({
      type: WEBVIEW_PAGE_TITLE_CHANGED,
      payload: {
        url: _serverUrl,
        pageTitle
      }
    });
  });
};
const serverReloadView = async serverUrl => {
  const url = new URL(serverUrl).href;
  const guestWebContents = getWebContentsByServerUrl(url);
  if (!guestWebContents) {
    return;
  }
  try {
    await guestWebContents.loadURL(url);
  } catch (error) {
    console.error('Failed to load URL for guestWebContents:', error);
  }
  dispatch({
    type: WEBVIEW_SERVER_RELOADED,
    payload: {
      url
    }
  });
};
const initializeServerWebContentsAfterAttach = (serverUrl, guestWebContents, rootWindow) => {
  webContentsByServerUrl.set(serverUrl, guestWebContents);
  const webviewSession = guestWebContents.session;

  // Intercept markdown file downloads and open in document viewer
  webviewSession.on('will-download', (_event, item) => {
    if (item.getFilename().endsWith('.md')) {
      const downloadUrl = item.getURL();
      item.cancel();
      dispatch({
        type: SERVER_DOCUMENT_VIEWER_OPEN_URL,
        payload: {
          server: serverUrl,
          documentUrl: downloadUrl,
          documentFormat: 'markdown'
        }
      });
    }
  });
  guestWebContents.addListener('destroyed', () => {
    guestWebContents.removeAllListeners();
    webviewSession.removeAllListeners();
    webContentsByServerUrl.delete(serverUrl);
    const canPurge = select(({
      servers
    }) => !servers.some(server => server.url === serverUrl));
    if (canPurge) {
      webviewSession.clearStorageData();
      return;
    }
    webviewSession.flushStorageData();
  });
  const handleDidStartLoading = () => {
    dispatch({
      type: WEBVIEW_DID_START_LOADING,
      payload: {
        url: serverUrl
      }
    });
    rootWindow.webContents.send(WEBVIEW_DID_START_LOADING, serverUrl);
  };
  const handleDidFailLoad = (_event, errorCode, _errorDescription, _validatedURL, isMainFrame, _frameProcessId, _frameRoutingId) => {
    if (errorCode === -3) {
      console.warn('Ignoring likely spurious did-fail-load with errorCode -3, cf https://github.com/electron/electron/issues/14004');
      return;
    }
    dispatch({
      type: WEBVIEW_DID_FAIL_LOAD,
      payload: {
        url: serverUrl,
        isMainFrame
      }
    });
  };
  const handleDidNavigateInPage = (_event, pageUrl, _isMainFrame, _frameProcessId, _frameRoutingId) => {
    dispatch({
      type: WEBVIEW_DID_NAVIGATE,
      payload: {
        url: serverUrl,
        pageUrl
      }
    });
  };
  let isGuestInHtmlFullscreen = false;
  guestWebContents.addListener('enter-html-full-screen', () => {
    isGuestInHtmlFullscreen = true;
  });
  guestWebContents.addListener('leave-html-full-screen', () => {
    isGuestInHtmlFullscreen = false;
  });
  const handleBeforeInputEvent = (_event, {
    type,
    key
  }) => {
    if (type !== 'keyUp' && type !== 'keyDown') {
      return;
    }
    const shortcutKey = process.platform === 'darwin' ? 'Meta' : 'Control';
    if (key !== shortcutKey && key !== 'Escape') {
      return;
    }

    // On macOS, forwarding ESC to the root window while the guest is in
    // HTML5 fullscreen (e.g. a video player) causes the native window to
    // also exit fullscreen. This does not occur on Windows/Linux.
    if (key === 'Escape' && isGuestInHtmlFullscreen) {
      return;
    }
    rootWindow.webContents.sendInputEvent({
      type,
      keyCode: key,
      modifiers: []
    });
  };
  guestWebContents.addListener('did-start-loading', handleDidStartLoading);
  guestWebContents.addListener('did-fail-load', handleDidFailLoad);
  guestWebContents.addListener('did-navigate-in-page', handleDidNavigateInPage);
  guestWebContents.addListener('before-input-event', handleBeforeInputEvent);
};
const attachGuestWebContentsEvents = async () => {
  const rootWindow = await getRootWindow();
  const handleWillAttachWebview = (_event, webPreferences, _params) => {
    delete webPreferences.enableBlinkFeatures;
    const isVideoCall = isVideoCallWebview(_params.partition, _params.frameName);
    const preloadPath = resolvePreloadPath(isVideoCall);
    if (preloadPath) {
      webPreferences.preload = preloadPath;
    } else {
      delete webPreferences.preload;
    }
    webPreferences.nodeIntegration = false;
    webPreferences.nodeIntegrationInWorker = false;
    webPreferences.nodeIntegrationInSubFrames = false;
    webPreferences.webSecurity = true;
    webPreferences.contextIsolation = true;
    webPreferences.sandbox = false;
  };
  const handleDidAttachWebview = (_event, webContents) => {
    webContents.setWindowOpenHandler(({
      url,
      frameName,
      disposition
    }) => {
      if (disposition === 'foreground-tab' || disposition === 'background-tab') {
        isProtocolAllowed(url).then(allowed => {
          if (!allowed) {
            return {
              action: 'deny'
            };
          }
          openExternal(url);
          return {
            action: 'deny'
          };
        });
        return {
          action: 'deny'
        };
      }
      const isVideoCall = isVideoCallWebview(undefined, frameName);
      const preloadPath = resolvePreloadPath(isVideoCall);
      return {
        action: 'allow',
        overrideBrowserWindowOptions: {
          ...(isVideoCall ? {
            webPreferences: {
              ...(preloadPath ? {
                preload: preloadPath
              } : {}),
              sandbox: false
            }
          } : {}),
          show: false
        }
      };
    });
    webContents.addListener('did-create-window', (window, {
      url,
      frameName,
      disposition,
      referrer,
      postBody
    }) => {
      window.once('ready-to-show', () => {
        window.show();
      });
      isProtocolAllowed(url).then(allowed => {
        if (!allowed) {
          window.destroy();
          return;
        }
        const isGoogleSignIn = frameName === 'Login' && disposition === 'new-window' && new URL(url).hostname.match(/(\.)?google\.com$/);
        window.loadURL(url, {
          userAgent: isGoogleSignIn ? electron.app.userAgentFallback.replace(`Electron/${process.versions.electron} `, '').replace(`${electron.app.name}/${electron.app.getVersion()} `, '') : electron.app.userAgentFallback,
          httpReferrer: referrer,
          ...(postBody && {
            extraHeaders: `Content-Type: ${postBody.contentType}; boundary=${postBody.boundary}`,
            postData: postBody.data
          })
        });
      });
    });
  };
  const handlePermissionRequest = async (_webContents, permission, callback, details) => {
    console.log('Permission request', permission, details);
    switch (permission) {
      case 'media':
        {
          const {
            mediaTypes = []
          } = details;
          await handleMediaPermissionRequest(mediaTypes, rootWindow, 'recordMessage', callback);
          return;
        }
      case 'geolocation':
      case 'notifications':
      case 'midiSysex':
      case 'pointerLock':
      case 'fullscreen':
        callback(true);
        return;
      case 'openExternal':
        {
          if (!details.externalURL) {
            callback(false);
            return;
          }
          const allowed = await isProtocolAllowed(details.externalURL);
          callback(allowed);
          return;
        }
      default:
        callback(false);
    }
  };
  listen(WEBVIEW_READY, action => {
    const guestWebContents = electron.webContents.fromId(action.payload.webContentsId);
    initializeServerWebContentsAfterReady(action.payload.url, guestWebContents, rootWindow);
    guestWebContents.session.setPermissionRequestHandler(handlePermissionRequest);
    setupServerViewDisplayMedia(guestWebContents);

    // Download handling is now managed by electron-dl in main.ts
    // and integrated with our downloads system via setupDownloads()

    // prevents the webview from navigating because of twitter preview links
    guestWebContents.on('will-navigate', (e, redirectUrl) => {
      const preventNavigateHosts = ['t.co', 'twitter.com'];
      if (preventNavigateHosts.includes(new URL(redirectUrl).hostname)) {
        e.preventDefault();
        isProtocolAllowed(redirectUrl).then(allowed => {
          if (!allowed) {
            return;
          }
          openExternal(redirectUrl);
        });
      }
    });
  });
  listen(WEBVIEW_ATTACHED, action => {
    const guestWebContents = electron.webContents.fromId(action.payload.webContentsId);
    initializeServerWebContentsAfterAttach(action.payload.url, guestWebContents, rootWindow);
  });
  listen(LOADING_ERROR_VIEW_RELOAD_SERVER_CLICKED, action => {
    const guestWebContents = getWebContentsByServerUrl(action.payload.url);
    if (!guestWebContents) {
      return;
    }
    guestWebContents.loadURL(action.payload.url).catch(error => {
      console.error('Failed to load URL for guestWebContents:', error);
    });
  });
  listen(SIDE_BAR_SERVER_RELOAD, action => {
    serverReloadView(action.payload);
  });
  listen(SIDE_BAR_SERVER_COPY_URL, async action => {
    const guestWebContents = getWebContentsByServerUrl(action.payload);
    const currentUrl = await (guestWebContents === null || guestWebContents === undefined ? undefined : guestWebContents.getURL());
    electron.clipboard.writeText(currentUrl || '');
  });
  listen(SIDE_BAR_SERVER_OPEN_DEV_TOOLS, action => {
    const guestWebContents = getWebContentsByServerUrl(action.payload);
    guestWebContents === null || guestWebContents === undefined || guestWebContents.openDevTools();
  });
  listen(SIDE_BAR_SERVER_FORCE_RELOAD, action => {
    const guestWebContents = getWebContentsByServerUrl(action.payload);
    if (!guestWebContents) {
      return;
    }
    dispatch({
      type: CLEAR_CACHE_TRIGGERED,
      payload: guestWebContents.id
    });
  });
  listen(SIDE_BAR_SERVER_REMOVE, action => {
    dispatch({
      type: SIDE_BAR_REMOVE_SERVER_CLICKED,
      payload: action.payload
    });
  });
  listen(SIDE_BAR_CONTEXT_MENU_TRIGGERED, action => {
    const {
      payload: serverUrl
    } = action;
    const menuTemplate = [{
      label: t$5('sidebar.item.reload'),
      click: () => {
        const guestWebContents = getWebContentsByServerUrl(serverUrl);
        if (!guestWebContents) {
          return;
        }
        guestWebContents.loadURL(serverUrl).catch(error => {
          console.error('Failed to load URL for guestWebContents:', error);
        });
        if (serverUrl) {
          dispatch({
            type: WEBVIEW_SERVER_RELOADED,
            payload: {
              url: serverUrl
            }
          });
        }
      }
    }, {
      label: t$5('sidebar.item.remove'),
      click: () => {
        dispatch({
          type: SIDE_BAR_REMOVE_SERVER_CLICKED,
          payload: serverUrl
        });
      }
    }, {
      type: 'separator'
    }, {
      label: t$5('sidebar.item.openDevTools'),
      click: () => {
        const guestWebContents = getWebContentsByServerUrl(serverUrl);
        guestWebContents === null || guestWebContents === undefined || guestWebContents.openDevTools();
      }
    }, {
      label: t$5('sidebar.item.copyCurrentUrl'),
      click: async () => {
        const guestWebContents = getWebContentsByServerUrl(serverUrl);
        const currentUrl = await (guestWebContents === null || guestWebContents === undefined ? undefined : guestWebContents.getURL());
        electron.clipboard.writeText(currentUrl || '');
      }
    }, {
      label: t$5('sidebar.item.reloadClearingCache'),
      click: async () => {
        const guestWebContents = getWebContentsByServerUrl(serverUrl);
        if (!guestWebContents) {
          return;
        }
        dispatch({
          type: CLEAR_CACHE_TRIGGERED,
          payload: guestWebContents.id
        });
      }
    }];
    const menu = electron.Menu.buildFromTemplate(menuTemplate);
    menu.popup({
      window: rootWindow
    });
  });
  listen(CERTIFICATES_CLEARED, () => {
    for (const serverViewWebContents of webContentsByServerUrl.values()) {
      serverViewWebContents.reloadIgnoringCache();
    }
  });
  rootWindow.webContents.addListener('will-attach-webview', handleWillAttachWebview);
  rootWindow.webContents.addListener('did-attach-webview', handleDidAttachWebview);
  handle('server-view/get-url', async webContents => {
    var _Array$from$find2;
    return (_Array$from$find2 = Array.from(webContentsByServerUrl.entries()).find(([, v]) => v === webContents)) === null || _Array$from$find2 === undefined ? undefined : _Array$from$find2[0];
  });
  let injectableCode;
  handle('server-view/ready', async webContents => {
    if (!injectableCode) {
      injectableCode = await fs__namespace.default.promises.readFile(path__default.default.join(electron.app.getAppPath(), 'app/injected.js'), 'utf8');
    }
    webContents.executeJavaScript(injectableCode, true);
  });
  handle('server-view/open-url-on-browser', async (_webContents, url) => {
    const allowed = await isProtocolAllowed(url);
    if (!allowed) {
      return;
    }
    openExternal(url);
  });
  listen(WEBVIEW_FORCE_RELOAD_WITH_CACHE_CLEAR, async action => {
    const serverUrl = action.payload;
    const guestWebContents = getWebContentsByServerUrl(serverUrl);
    if (!guestWebContents) {
      return;
    }

    // Clear cache keeping login data using the same method as Force Reload
    await guestWebContents.session.clearCache();
    await guestWebContents.session.clearStorageData({
      storages: ['cookies', 'indexdb', 'filesystem', 'shadercache', 'websql', 'serviceworkers', 'cachestorage']
    });

    // Reload ignoring cache
    guestWebContents.reloadIgnoringCache();
    dispatch({
      type: WEBVIEW_SERVER_RELOADED,
      payload: {
        url: serverUrl
      }
    });
  });
};

// This module contains all the urls used in the app

const rocketchat = {
  site: 'https://rocket.chat',
  subdomain: subdomain => `https://${subdomain}.rocket.chat`
};
const supportedVersions = ({
  domain,
  uniqueId
}) => `https://releases.rocket.chat/v2/server/supportedVersions?domain=${domain}&uniqueId=${uniqueId}&source=desktop`;
const server = serverUrl => ({
  uniqueId: `${serverUrl}api/v1/settings.public?_id=uniqueID`,
  setting: id => `${serverUrl}api/v1/settings.public?query=${encodeURIComponent(JSON.stringify({
    _id: id
  }))}`,
  info: `${serverUrl}api/info`,
  calendarEvents: {
    list: `${serverUrl}api/v1/calendar-events.list`,
    import: `${serverUrl}api/v1/calendar-events.import`,
    update: `${serverUrl}api/v1/calendar-events.update`,
    delete: `${serverUrl}api/v1/calendar-events.delete`
  }
});
const docs = {
  index: 'https://docs.rocket.chat/',
  // TODO: should it be a go link?
  supportedVersions: 'https://go.rocket.chat/i/supported-versions',
  newIssue: 'https://github.com/RocketChat/Rocket.Chat/issues/new' // TODO: should it be a go link?
};

const REQUIRED_SERVER_VERSION_RANGE = '>=2.0.0';
const convertToURL = input => {
  let url;
  if (/^https?:\/\//.test(input)) {
    url = new URL(input);
  } else {
    url = new URL(`https://${input}`);
  }
  const {
    protocol,
    username,
    password,
    hostname,
    port,
    pathname
  } = url;
  return Object.assign(new URL('https://0.0.0.0'), {
    protocol,
    username,
    password,
    hostname,
    port: port,
    pathname: /\/$/.test(pathname) ? pathname : `${pathname}/`
  });
};
const fetchServerInformation = async url => {
  const {
    webContents
  } = await getRootWindow();
  const [urlHref, version] = await invoke(webContents, 'servers/fetch-info', url.href);
  return [convertToURL(urlHref), version];
};
const resolveServerUrl = async input => {
  let url;
  try {
    url = convertToURL(input);
  } catch (error) {
    return [input, ServerUrlResolutionStatus.INVALID_URL, error];
  }
  let version;
  try {
    [url, version] = await fetchServerInformation(url);
  } catch (error) {
    if (!(error instanceof Error)) {
      throw error;
    }
    if (!/(^https?:\/\/)|(\.)|(^([^:]+:[^@]+@)?localhost(:\d+)?$)/.test(input)) {
      return resolveServerUrl(rocketchat.subdomain(input));
    }
    if ((error === null || error === undefined ? undefined : error.name) === 'AbortError') {
      return [url.href, ServerUrlResolutionStatus.TIMEOUT, error];
    }
    return [url.href, ServerUrlResolutionStatus.INVALID, error];
  }
  const semver = semver$1.coerce(version);
  if (!semver || !semver$1.satisfies(semver, REQUIRED_SERVER_VERSION_RANGE)) {
    return [url.href, ServerUrlResolutionStatus.INVALID, new Error(`incompatible server version (${version}, expected ${REQUIRED_SERVER_VERSION_RANGE})`)];
  }
  return [url.href, ServerUrlResolutionStatus.OK];
};
const loadAppServers = async () => {
  try {
    let filePath = path__default.default.join(electron.app.getAppPath(), electron.app.getAppPath().endsWith('app.asar') ? '..' : '.', 'servers.json');
    if (process.platform === 'darwin') {
      const darwinFilePath = `/Library/Preferences/${packageJsonInformation.productName}/servers.json`;
      if (fs__namespace.default.existsSync(darwinFilePath)) filePath = darwinFilePath;
    }
    const content = await fs__namespace.default.promises.readFile(filePath, 'utf8');
    const json = JSON.parse(content);
    return json && typeof json === 'object' ? json : {};
  } catch (error) {
    return {};
  }
};
const loadUserServers = async () => {
  try {
    const filePath = path__default.default.join(electron.app.getPath('userData'), 'servers.json');
    const content = await fs__namespace.default.promises.readFile(filePath, 'utf8');
    const json = JSON.parse(content);
    await fs__namespace.default.promises.unlink(filePath);
    return json && typeof json === 'object' ? json : {};
  } catch (error) {
    return {};
  }
};
const setupServers = async localStorage => {
  var _ref, _serversMap$get$url, _serversMap$get, _servers$, _servers$2;
  listen(SERVER_URL_RESOLUTION_REQUESTED, async action => {
    if (!hasMeta(action)) {
      return;
    }
    try {
      dispatch({
        type: SERVER_URL_RESOLVED,
        payload: await resolveServerUrl(action.payload),
        meta: {
          response: true,
          id: action.meta.id
        }
      });
    } catch (error) {
      isServerUrlResolutionResult(error) && dispatch({
        type: SERVER_URL_RESOLVED,
        payload: error,
        error: true,
        meta: {
          response: true,
          id: action.meta.id
        }
      });
    }
  });
  listen(WEBVIEW_GIT_COMMIT_HASH_CHECK, async action => {
    const {
      url,
      gitCommitHash
    } = action.payload;
    const servers = select(({
      servers
    }) => servers);
    const server = servers.find(server => server.url === url);
    if ((server === null || server === undefined ? undefined : server.gitCommitHash) !== gitCommitHash && (server === null || server === undefined ? undefined : server.gitCommitHash) !== undefined) {
      dispatch({
        type: WEBVIEW_GIT_COMMIT_HASH_CHANGED,
        payload: {
          url,
          gitCommitHash
        }
      });
      const guestWebContents = getWebContentsByServerUrl(url);
      await (guestWebContents === null || guestWebContents === undefined ? undefined : guestWebContents.session.clearStorageData({
        storages: ['indexdb']
      }));
      await (guestWebContents === null || guestWebContents === undefined ? undefined : guestWebContents.session.clearCache());
      guestWebContents === null || guestWebContents === undefined || guestWebContents.reload();
    }
  });
  let servers = select(({
    servers
  }) => servers);
  let currentServerUrl = select(({
    currentView
  }) => typeof currentView === 'object' ? currentView.url : null);
  const serversMap = new Map(servers.filter(Boolean).filter(({
    url,
    title
  }) => typeof url === 'string' && typeof title === 'string').map(server => [server.url, server]));
  if (localStorage['rocket.chat.hosts']) {
    try {
      const storedString = JSON.parse(localStorage['rocket.chat.hosts']);
      if (/^https?:\/\//.test(storedString)) {
        serversMap.set(storedString, {
          url: storedString,
          title: storedString
        });
      } else {
        const storedValue = JSON.parse(storedString);
        if (Array.isArray(storedValue)) {
          storedValue.map(url => url.replace(/\/$/, '')).forEach(url => {
            serversMap.set(url, {
              url,
              title: url
            });
          });
        }
      }
    } catch (error) {
      console.warn(error);
    }
  }
  if (serversMap.size === 0) {
    const appConfiguration = await loadAppServers();
    for (const [title, url] of Object.entries(appConfiguration)) {
      serversMap.set(url, {
        url,
        title
      });
    }
    const userConfiguration = await loadUserServers();
    for (const [title, url] of Object.entries(userConfiguration)) {
      serversMap.set(url, {
        url,
        title
      });
    }
  }
  if (localStorage['rocket.chat.currentHost'] && localStorage['rocket.chat.currentHost'] !== 'null') {
    currentServerUrl = localStorage['rocket.chat.currentHost'];
  }
  servers = Array.from(serversMap.values());
  currentServerUrl = currentServerUrl ? (_ref = (_serversMap$get$url = (_serversMap$get = serversMap.get(currentServerUrl)) === null || _serversMap$get === undefined ? undefined : _serversMap$get.url) !== null && _serversMap$get$url !== undefined ? _serversMap$get$url : (_servers$ = servers[0]) === null || _servers$ === undefined ? undefined : _servers$.url) !== null && _ref !== undefined ? _ref : null : (_servers$2 = servers[0]) === null || _servers$2 === undefined ? undefined : _servers$2.url;
  if (localStorage['rocket.chat.sortOrder']) {
    try {
      const sorting = JSON.parse(localStorage['rocket.chat.sortOrder']);
      if (Array.isArray(sorting)) {
        servers = [...serversMap.values()].sort((a, b) => sorting.indexOf(a.url) - sorting.indexOf(b.url));
      }
    } catch (error) {
      console.warn(error);
    }
  }
  dispatch({
    type: SERVERS_LOADED,
    payload: {
      servers,
      selected: currentServerUrl
    }
  });
};

const isDefinedProtocol = parsedUrl => parsedUrl.protocol === `${electronBuilderJsonInformation.protocol}:`;
const isGoUrlShortener = parsedUrl => parsedUrl.protocol === 'https:' && parsedUrl.hostname === packageJsonInformation.goUrlShortener;
const parseDeepLink = input => {
  if (/^--/.test(input)) {
    // input is a CLI flag
    return null;
  }
  let url;
  try {
    url = new URL(input);
  } catch (error) {
    return null;
  }
  if (isDefinedProtocol(url)) {
    const action = url.hostname;
    const args = url.searchParams;
    return {
      action,
      args
    };
  }
  if (isGoUrlShortener(url)) {
    const action = url.pathname;
    const args = url.searchParams;
    return {
      action,
      args
    };
  }
  return null;
};
let processDeepLinksInArgs = async () => undefined;
const performOnServer = async (url, action) => {
  const [serverUrl, status, error] = await resolveServerUrl(url);
  if (status !== ServerUrlResolutionStatus.OK) {
    var _error$message;
    await warnAboutInvalidServerUrl(serverUrl, (_error$message = error === null || error === undefined ? undefined : error.message) !== null && _error$message !== undefined ? _error$message : '');
    return;
  }
  const isServerAdded = select(({
    servers
  }) => servers.some(server => server.url === serverUrl));
  if (isServerAdded) {
    dispatch({
      type: DEEP_LINKS_SERVER_FOCUSED,
      payload: serverUrl
    });
    await action(serverUrl);
    return;
  }
  const permitted = await askForServerAddition(serverUrl);
  if (!permitted) {
    return;
  }
  dispatch({
    type: DEEP_LINKS_SERVER_ADDED,
    payload: serverUrl
  });
  await action(serverUrl);
};
const getWebContents = serverUrl => new Promise(resolve => {
  const poll = () => {
    const webContents = getWebContentsByServerUrl(serverUrl);
    if (webContents) {
      resolve(webContents);
      return;
    }
    setTimeout(poll, 100);
  };
  poll();
});
const performAuthentication = async ({
  host,
  token,
  userId
}) => performOnServer(host, async serverUrl => {
  const url = new URL('home', serverUrl);
  url.searchParams.append('resumeToken', token);
  url.searchParams.append('userId', userId);
  const webContents = await getWebContents(serverUrl);
  webContents.loadURL(url.href);
});

// https://developer.rocket.chat/rocket.chat/deeplink#channel-group-dm
const performOpenRoom = async ({
  host,
  path,
  token,
  userId
}) => performOnServer(host, async serverUrl => {
  if (!path) {
    return;
  }
  if (!/^\/?(direct|group|channel|livechat)\/[0-9a-zA-Z-_.]+/.test(path)) {
    return;
  }
  const url = new URL(path, serverUrl);
  if (token && userId) {
    url.searchParams.append('resumeToken', token);
    url.searchParams.append('userId', userId);
  }
  const webContents = await getWebContents(serverUrl);
  webContents.loadURL(url.href);
});
const performInvite = async ({
  host,
  path
}) => performOnServer(host, async serverUrl => {
  if (!/^invite\//.test(path)) {
    return;
  }
  const webContents = await getWebContents(serverUrl);
  webContents.loadURL(new URL(path, serverUrl).href);
});
const performConference = async ({
  host,
  path
}) => performOnServer(host, async serverUrl => {
  if (!/^conference\//.test(path)) {
    return;
  }
  const webContents = await getWebContents(serverUrl);
  webContents.loadURL(new URL(path, serverUrl).href);
});
const processDeepLink = async deepLink => {
  const parsedDeepLink = parseDeepLink(deepLink);
  if (!parsedDeepLink) {
    return;
  }
  const {
    action,
    args
  } = parsedDeepLink;
  switch (action) {
    case 'auth':
      {
        var _args$get, _args$get2, _args$get3;
        const host = (_args$get = args.get('host')) !== null && _args$get !== undefined ? _args$get : undefined;
        const token = (_args$get2 = args.get('token')) !== null && _args$get2 !== undefined ? _args$get2 : undefined;
        const userId = (_args$get3 = args.get('userId')) !== null && _args$get3 !== undefined ? _args$get3 : undefined;
        if (host && token && userId) {
          await performAuthentication({
            host,
            token,
            userId
          });
        }
        break;
      }
    case 'room':
      {
        var _args$get4, _args$get5, _args$get6, _args$get7;
        const host = (_args$get4 = args.get('host')) !== null && _args$get4 !== undefined ? _args$get4 : undefined;
        const path = (_args$get5 = args.get('path')) !== null && _args$get5 !== undefined ? _args$get5 : undefined;
        const token = (_args$get6 = args.get('token')) !== null && _args$get6 !== undefined ? _args$get6 : undefined;
        const userId = (_args$get7 = args.get('userId')) !== null && _args$get7 !== undefined ? _args$get7 : undefined;
        if (host && path) {
          await performOpenRoom({
            host,
            path,
            token,
            userId
          });
        }
        break;
      }
    case 'invite':
      {
        var _args$get8, _args$get9;
        const host = (_args$get8 = args.get('host')) !== null && _args$get8 !== undefined ? _args$get8 : undefined;
        const path = (_args$get9 = args.get('path')) !== null && _args$get9 !== undefined ? _args$get9 : undefined;
        if (host && path) {
          await performInvite({
            host,
            path
          });
        }
        break;
      }
    case 'conference':
      {
        var _args$get0, _args$get1;
        const host = (_args$get0 = args.get('host')) !== null && _args$get0 !== undefined ? _args$get0 : undefined;
        const path = (_args$get1 = args.get('path')) !== null && _args$get1 !== undefined ? _args$get1 : undefined;
        if (host && path) {
          await performConference({
            host,
            path
          });
        }
        break;
      }
  }
};
const setupDeepLinks = () => {
  electron.app.addListener('open-url', async (event, url) => {
    event.preventDefault();
    const browserWindow = await getRootWindow();
    if (!browserWindow.isVisible()) {
      browserWindow.showInactive();
    }
    browserWindow.focus();
    await processDeepLink(url);
  });
  electron.app.addListener('second-instance', async (event, argv) => {
    event.preventDefault();
    const browserWindow = await getRootWindow();
    if (browserWindow && !browserWindow.isVisible()) {
      browserWindow.showInactive();
    }
    if (browserWindow) browserWindow.focus();
    const args = argv.slice(electron.app.isPackaged ? 1 : 2);
    for (const arg of args) {
      // eslint-disable-next-line no-await-in-loop
      await processDeepLink(arg);
    }
  });
  processDeepLinksInArgs = async () => {
    const args = process.argv.slice(electron.app.isPackaged ? 1 : 2);
    for (const arg of args) {
      // eslint-disable-next-line no-await-in-loop
      await processDeepLink(arg);
    }
  };
};

const startDocumentViewerHandler = () => {
  handle('document-viewer/open-window', async (event, url, format, _options) => {
    const validUrl = new URL(url);
    const allowedProtocols = ['http:', 'https:'];
    if (!allowedProtocols.includes(validUrl.protocol)) {
      return;
    }
    const server = select(({
      servers
    }) => servers.find(s => new URL(s.url).origin === new URL(event.getURL()).origin));
    if (!server) {
      return;
    }
    dispatch({
      type: SERVER_DOCUMENT_VIEWER_OPEN_URL,
      payload: {
        server: server.url,
        documentUrl: url,
        documentFormat: format
      }
    });
  });
  handle('document-viewer/fetch-content', async (_event, url, serverUrl) => {
    const parsedUrl = new URL(url);
    const parsedServerUrl = new URL(serverUrl);
    const allowedProtocols = ['http:', 'https:'];
    if (!allowedProtocols.includes(parsedUrl.protocol)) {
      throw new Error('Invalid URL protocol');
    }
    if (parsedUrl.origin !== parsedServerUrl.origin) {
      throw new Error('URL origin does not match server');
    }
    const partition = `persist:${serverUrl}`;
    const ses = electron.session.fromPartition(partition);
    const response = await ses.fetch(url);
    if (!response.ok) {
      throw new Error(`Failed to fetch: ${response.status}`);
    }
    return response.text();
  });
  listen(WEBVIEW_PDF_VIEWER_ATTACHED, async action => {
    const webContentsId = action.payload.WebContentsId;
    const webContent = electron.webContents.fromId(webContentsId);
    if (!webContent) {
      return;
    }
    webContent.on('will-navigate', (event, url) => {
      // Only prevent navigation for PDF viewer webviews, not video call windows
      // Check if this is actually a PDF viewer by examining the context
      const currentUrl = webContent.getURL();

      // Skip handling if this is a video call window or not a PDF viewer context
      if (currentUrl.includes('video-call-window.html') || currentUrl.includes('app/video-call-window.html')) {
        return;
      }

      // Also check if the navigation URL is an external protocol (like zoommtg://)
      // that should be handled by the system, not intercepted
      try {
        const navUrl = new URL(url);
        const isExternalProtocol = !['http:', 'https:', 'file:', 'data:', 'about:'].includes(navUrl.protocol);

        // If it's an external protocol, let the system handle it normally
        if (isExternalProtocol) {
          return;
        }
      } catch (e) {
        // If URL parsing fails, let the default handling proceed
        return;
      }
      event.preventDefault();
      setTimeout(() => {
        openExternal(url);
      }, 10);
    });
  });
};

let getAbsoluteUrl;

const NOTIFICATIONS_CREATE_REQUESTED = 'notifications/create-requested';
const NOTIFICATIONS_CREATE_RESPONDED = 'notifications/create-responded';
const NOTIFICATIONS_NOTIFICATION_ACTIONED = 'notifications/notification-actioned';
const NOTIFICATIONS_NOTIFICATION_CLICKED = 'notifications/notification-clicked';
const NOTIFICATIONS_NOTIFICATION_CLOSED = 'notifications/notification-closed';
const NOTIFICATIONS_NOTIFICATION_DISMISSED = 'notifications/notification-dismissed';
const NOTIFICATIONS_NOTIFICATION_REPLIED = 'notifications/notification-replied';
const NOTIFICATIONS_NOTIFICATION_SHOWN = 'notifications/notification-shown';

const normalizeIconUrl = iconUrl => {
  if (/^data:/.test(iconUrl)) {
    return iconUrl;
  }
  if (!/^https?:\/\//.test(iconUrl)) {
    return getAbsoluteUrl(iconUrl);
  }
  return iconUrl;
};
const eventHandlers = new Map();
const createNotification$1 = async ({
  title,
  icon,
  onEvent,
  ...options
}) => {
  const id = await request({
    type: NOTIFICATIONS_CREATE_REQUESTED,
    payload: {
      title,
      ...(icon ? {
        icon: normalizeIconUrl(icon)
      } : {}),
      ...options
    }
  }, NOTIFICATIONS_CREATE_RESPONDED);
  eventHandlers.set(id, event => onEvent === null || onEvent === undefined ? undefined : onEvent({
    type: event.type,
    detail: event.detail
  }));
  return id;
};

const items = new Map();
const handleWillDownloadEvent = async (_event, item, serverWebContents) => {
  const itemId = Date.now();
  items.set(itemId, item);
  const server = select(({
    servers
  }) => servers.find(server => server.webContentsId === serverWebContents.id));
  const serverUrl = (server === null || server === undefined ? undefined : server.url) || 'unknown';
  const serverTitle = (server === null || server === undefined ? undefined : server.title) || 'Unknown Server';
  const downloadPayload = {
    itemId,
    state: item.isPaused() ? 'paused' : item.getState(),
    status: item.isPaused() ? DownloadStatus.PAUSED : DownloadStatus.ALL,
    fileName: item.getFilename(),
    receivedBytes: item.getReceivedBytes(),
    totalBytes: item.getTotalBytes(),
    startTime: item.getStartTime() * 1000,
    endTime: undefined,
    url: item.getURL(),
    serverUrl,
    serverTitle,
    mimeType: item.getMimeType(),
    savePath: item.getSavePath()
  };
  dispatch({
    type: DOWNLOAD_CREATED,
    payload: downloadPayload
  });
  item.on('updated', () => {
    dispatch({
      type: DOWNLOAD_UPDATED,
      payload: {
        itemId,
        state: item.isPaused() ? 'paused' : item.getState(),
        status: item.isPaused() ? DownloadStatus.PAUSED : DownloadStatus.ALL,
        fileName: item.getFilename(),
        receivedBytes: item.getReceivedBytes(),
        totalBytes: item.getTotalBytes(),
        startTime: item.getStartTime() * 1000,
        endTime: Date.now(),
        url: item.getURL(),
        mimeType: item.getMimeType(),
        savePath: item.getSavePath()
      }
    });
  });
  item.on('done', (_event, _state) => {
    dispatch({
      type: DOWNLOAD_UPDATED,
      payload: {
        itemId,
        state: item.getState(),
        status: item.getState() === 'cancelled' ? DownloadStatus.CANCELLED : DownloadStatus.ALL,
        fileName: item.getFilename(),
        receivedBytes: item.getReceivedBytes(),
        totalBytes: item.getTotalBytes(),
        startTime: item.getStartTime() * 1000,
        endTime: Date.now(),
        url: item.getURL(),
        mimeType: item.getMimeType(),
        savePath: item.getSavePath()
      }
    });
    items.delete(itemId);
  });
};
const setupDownloads = () => {
  handle('downloads/show-in-folder', async (_webContents, itemId) => {
    const download = select(({
      downloads
    }) => downloads[itemId]);
    if (!download) {
      return;
    }
    electron.shell.showItemInFolder(download.savePath);
  });
  handle('downloads/copy-link', async (_webContent, itemId) => {
    const download = select(({
      downloads
    }) => downloads[itemId]);
    if (!download) {
      return;
    }
    electron.clipboard.writeText(download.url);
  });
  handle('downloads/pause', async (_webContent, itemId) => {
    if (!items.has(itemId)) {
      return;
    }
    const item = items.get(itemId);
    if (item !== null && item !== undefined && item.isPaused()) {
      return;
    }
    item === null || item === undefined || item.pause();
  });
  handle('downloads/resume', async (_webContent, itemId) => {
    if (!items.has(itemId)) {
      return;
    }
    const item = items.get(itemId);
    if (!(item !== null && item !== undefined && item.canResume())) {
      return;
    }
    item === null || item === undefined || item.resume();
  });
  handle('downloads/cancel', async (_webContent, itemId) => {
    if (!items.has(itemId)) {
      return;
    }
    const item = items.get(itemId);
    item === null || item === undefined || item.cancel();
  });
  handle('downloads/retry', async (_webContent, itemId) => {
    var _URL$searchParams$get;
    const {
      download,
      webContentsId
    } = select(({
      downloads,
      servers
    }) => {
      var _servers$find;
      const download = downloads[itemId];
      const {
        webContentsId
      } = (_servers$find = servers.find(server => server.url === download.serverUrl)) !== null && _servers$find !== undefined ? _servers$find : {};
      return {
        download,
        webContentsId
      };
    });
    const downloadStartTimestamp = new URL(download.url).searchParams.get('X-Amz-Date');
    const expiresIn = (_URL$searchParams$get = new URL(download.url).searchParams.get('X-Amz-Expires')) !== null && _URL$searchParams$get !== undefined ? _URL$searchParams$get : 120;
    const parsedStartTime = {
      year: downloadStartTimestamp === null || downloadStartTimestamp === undefined ? undefined : downloadStartTimestamp.substring(0, 4),
      month: downloadStartTimestamp === null || downloadStartTimestamp === undefined ? undefined : downloadStartTimestamp.substring(4, 6),
      day: downloadStartTimestamp === null || downloadStartTimestamp === undefined ? undefined : downloadStartTimestamp.substring(6, 8),
      hour: downloadStartTimestamp === null || downloadStartTimestamp === undefined ? undefined : downloadStartTimestamp.substring(9, 11),
      minute: downloadStartTimestamp === null || downloadStartTimestamp === undefined ? undefined : downloadStartTimestamp.substring(11, 13),
      second: downloadStartTimestamp === null || downloadStartTimestamp === undefined ? undefined : downloadStartTimestamp.substring(13, 15)
    };
    const s3Expired = new Date().getTime() > new Date(`${parsedStartTime.year}-${parsedStartTime.month}-${parsedStartTime.day}T${parsedStartTime.hour}:${parsedStartTime.minute}:${parsedStartTime.second}Z`).getTime() + +expiresIn * 1000;
    if (s3Expired) {
      createNotification$1({
        title: i18next.t('downloads.notifications.downloadExpired'),
        body: i18next.t('downloads.notifications.downloadExpiredMessage'),
        subtitle: download.fileName,
        category: 'DOWNLOADS'
      });
      dispatch({
        type: DOWNLOAD_UPDATED,
        payload: {
          ...download,
          state: 'expired',
          status: DownloadStatus.CANCELLED
        }
      });
      return;
    }
    dispatch({
      type: DOWNLOAD_REMOVED,
      payload: itemId
    });
    if (webContentsId && electron.webContents !== undefined) {
      if (electron.webContents.fromId !== undefined) {
        const webContentsInstance = electron.webContents.fromId(webContentsId);
        if (webContentsInstance !== undefined) {
          webContentsInstance.downloadURL(download.url);
        }
      }
    }
  });
  handle('downloads/clear-all', async () => {
    dispatch({
      type: DOWNLOADS_CLEARED
    });
  });
  handle('downloads/remove', async (_webContent, itemId) => {
    if (items.has(itemId)) {
      const item = items.get(itemId);
      item === null || item === undefined || item.cancel();
    }
    dispatch({
      type: DOWNLOAD_REMOVED,
      payload: itemId
    });
  });
};

// Lazy default download path resolution
let cachedDefaultDownloadPath = null;
function getDefaultDownloadPath() {
  if (cachedDefaultDownloadPath === null) {
    try {
      cachedDefaultDownloadPath = electron.app.getPath('downloads');
    } catch {
      cachedDefaultDownloadPath = os__default.default.tmpdir();
    }
  }
  return cachedDefaultDownloadPath;
}
function isValidDirectory(dir) {
  try {
    return !!dir && fs.existsSync(dir) && fs.statSync(dir).isDirectory();
  } catch {
    return false;
  }
}
const downloadStore = new ElectronStore__default.default({
  name: 'download-preferences',
  schema: {
    lastDownloadDirectory: {
      type: 'string'
    }
  }
});
const setupElectronDlWithTracking = () => {
  electronDl__default.default({
    saveAs: true,
    onStarted: item => {
      try {
        // Set the save dialog options with both directory and filename
        const configuredDir = downloadStore.get('lastDownloadDirectory', getDefaultDownloadPath());
        const lastDownloadDir = configuredDir && isValidDirectory(configuredDir) ? configuredDir : getDefaultDownloadPath();
        const fullPath = path__default.default.join(lastDownloadDir, item.getFilename());
        item.setSaveDialogOptions({
          defaultPath: fullPath
        });

        // Find the webContents that initiated this download
        // According to electron-dl docs, onStarted only receives DownloadItem
        // We need to find an active webContents for tracking purposes
        const webContentsArray = electron.webContents.getAllWebContents();

        // Use the first available webContents for tracking
        let sourceWebContents = null;
        for (const wc of webContentsArray) {
          if (wc && typeof wc.isDestroyed === 'function' && !wc.isDestroyed()) {
            sourceWebContents = wc;
            break;
          }
        }
        if (sourceWebContents) {
          const fakeEvent = {
            defaultPrevented: false,
            preventDefault: () => {}
          };
          handleWillDownloadEvent(fakeEvent, item, sourceWebContents).catch(() => {
            // Silently handle tracking errors
          });
        }
      } catch {
        // Silently handle any other errors in onStarted
      }
    },
    onCompleted: file => {
      try {
        // Remember the directory where the file was saved
        if (file.path) {
          const downloadDirectory = path__default.default.dirname(file.path);
          downloadStore.set('lastDownloadDirectory', downloadDirectory);
        }
        createNotification$1({
          title: i18next.t('downloads.title', {
            defaultValue: 'Downloads'
          }),
          body: file.filename || 'Unknown file',
          subtitle: i18next.t('downloads.notifications.downloadFinished'),
          category: 'DOWNLOADS'
        });
      } catch {
        // Silently handle any errors in onCompleted
      }
    }
  });
};

const DEFAULT_CRITICAL_PATTERNS = ['FATAL', 'Cannot access native module', 'Electron internal error'];
const isCriticalError = error => {
  return DEFAULT_CRITICAL_PATTERNS.some(pattern => {
    var _error$message, _error$stack;
    return ((_error$message = error.message) === null || _error$message === undefined ? undefined : _error$message.includes(pattern)) || ((_error$stack = error.stack) === null || _error$stack === undefined ? undefined : _error$stack.includes(pattern));
  });
};
let _globalHandlersBound = false;
const setupGlobalErrorHandlers = () => {
  if (_globalHandlersBound) return;
  process.on('uncaughtException', error => {
    console.error('Uncaught Exception:', error);
    if (Bugsnag__default.default.isStarted()) {
      Bugsnag__default.default.notify(error);
    }
    if (isCriticalError(error)) {
      console.error('Critical error detected, app cannot continue');
      electron.app.quit();
    }
  });
  process.on('unhandledRejection', reason => {
    const error = reason instanceof Error ? reason : new Error(`Unhandled Promise Rejection: ${String(reason)}`);
    console.error('Unhandled Promise Rejection:', error);
    if (Bugsnag__default.default.isStarted()) {
      Bugsnag__default.default.notify(error);
    }
    if (isCriticalError(error)) {
      console.error('Critical promise rejection, app cannot continue');
      electron.app.quit();
    }
  });
  _globalHandlersBound = true;
};
const listenToBugsnagEnabledToggle = async appType => {
  {
    return;
  }
};
const setupRendererErrorHandling = async appType => {
  await listenToBugsnagEnabledToggle();
};
const setupMainErrorHandling = async () => {
  setupGlobalErrorHandlers();
  await setupRendererErrorHandling();
};

const I18N_LNG_REQUESTED = 'i18n/lng-requested';
const I18N_LNG_RESPONDED = 'i18n/lng-responded';

const fallbackLng = 'en';
const byteUnits = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte', 'petabyte'];
const formatBytes = bytes => {
  const order = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), byteUnits.length - 1);
  const unit = byteUnits[order];
  if (!unit) {
    return '???';
  }
  const formatter = new Intl.NumberFormat(undefined, {
    notation: 'compact',
    style: 'unit',
    unit,
    maximumFractionDigits: 1
  });
  return formatter.format(bytes / Math.pow(1024, order));
};
const formatByteSpeed = bytesPerSecond => {
  const order = Math.min(Math.floor(Math.log(bytesPerSecond) / Math.log(1024)), byteUnits.length - 1);
  const unit = byteUnits[order];
  if (!unit) {
    return '???';
  }
  const formatter = new Intl.NumberFormat(undefined, {
    notation: 'compact',
    style: 'unit',
    unit: `${unit}-per-second`,
    maximumFractionDigits: 1
  });
  return formatter.format(bytesPerSecond / Math.pow(1024, order));
};
const formatPercentage = ratio => {
  const formatter = new Intl.NumberFormat(undefined, {
    style: 'percent',
    maximumFractionDigits: 0
  });
  return formatter.format(ratio);
};
const formatDuration = duration => {
  const formatter = new Intl.RelativeTimeFormat(undefined, {
    style: 'narrow',
    numeric: 'always'
  });
  duration /= 1000;
  if (duration / 60 < 1) {
    return formatter.format(duration, 'second');
  }
  duration /= 60;
  if (duration / 60 < 1) {
    return formatter.format(duration, 'minute');
  }
  duration /= 60;
  if (duration / 24 < 1) {
    return formatter.format(duration, 'hour');
  }
  duration /= 24;
  if (duration / 7 < 1) {
    return formatter.format(duration, 'day');
  }
  duration /= 7;
  if (duration / 30 < 1) {
    return formatter.format(duration, 'week');
  }
  duration /= 30;
  if (duration / 12 < 1) {
    return formatter.format(duration, 'month');
  }
  duration /= 12;
  return formatter.format(duration, 'year');
};
const interpolation = {
  escapeValue: false,
  format: (value, format, lng) => {
    if (value instanceof Date && !Number.isNaN(value.getTime())) {
      return new Intl.DateTimeFormat(lng).format(value);
    }
    switch (format) {
      case 'byteSize':
        return formatBytes(value);
      case 'byteSpeed':
        return formatByteSpeed(value);
      case 'percentage':
        return formatPercentage(value);
      case 'duration':
        return formatDuration(value);
    }
    return String(value);
  }
};

var resources = {
  'de-DE': () => Promise.resolve().then(function () { return require('./de-DE.i18n-LjCz_6v_.js'); }),
  'en': () => Promise.resolve().then(function () { return require('./en.i18n-tSOW0b2p.js'); }),
  'es': () => Promise.resolve().then(function () { return require('./es.i18n-BPEvA6Eu.js'); }),
  'fi': () => Promise.resolve().then(function () { return require('./fi.i18n-c5J-X7bw.js'); }),
  'sv': () => Promise.resolve().then(function () { return require('./sv.i18n-DPPaCtsT.js'); }),
  'no': () => Promise.resolve().then(function () { return require('./no.i18n-DjEA_1t4.js'); }),
  'fr': () => Promise.resolve().then(function () { return require('./fr.i18n-CedrZv2y.js'); }),
  'hu': () => Promise.resolve().then(function () { return require('./hu.i18n-DjHjsJUq.js'); }),
  'it-IT': () => Promise.resolve().then(function () { return require('./it-IT.i18n-DF18Si-C.js'); }),
  'ja': () => Promise.resolve().then(function () { return require('./ja.i18n-DdfYsFJ2.js'); }),
  'pl': () => Promise.resolve().then(function () { return require('./pl.i18n-DgfYDbAT.js'); }),
  'pt-BR': () => Promise.resolve().then(function () { return require('./pt-BR.i18n-C8oYUcCz.js'); }),
  'ru': () => Promise.resolve().then(function () { return require('./ru.i18n-CE4cC6oZ.js'); }),
  'tr-TR': () => Promise.resolve().then(function () { return require('./tr-TR.i18n-CyxkAN-C.js'); }),
  'uk-UA': () => Promise.resolve().then(function () { return require('./uk-UA.i18n-BkqkMhw4.js'); }),
  'zh-CN': () => Promise.resolve().then(function () { return require('./zh-CN.i18n-C-Voh6oB.js'); }),
  'zh-TW': () => Promise.resolve().then(function () { return require('./zh-TW.i18n-BJUiDIeM.js'); })
};

const hasLng = lng => lng in resources;
const getLng = async () => {
  await electron.app.whenReady();
  const locale = electron.app.getSystemLocale();
  let [languageCode, countryCode] = locale.split(/[-_]/);
  if (!languageCode || languageCode.length !== 2) {
    return fallbackLng;
  }
  languageCode = languageCode.toLowerCase();
  const isCountryCodeInexistentOrNonStandard = !countryCode || countryCode.length !== 2;
  countryCode = isCountryCodeInexistentOrNonStandard ? '' : countryCode.toUpperCase();
  const lng = countryCode ? `${languageCode}-${countryCode}` : languageCode;
  if (hasLng(lng)) {
    return lng;
  }
  return Object.keys(resources).find(language => language.startsWith(languageCode));
};
let getLanguage = 'en';
class I18nService extends Service {
  constructor(...args) {
    super(...args);
    _defineProperty(this, "initialization", undefined);
    _defineProperty(this, "t", i18next__default.default.t.bind(i18next__default.default));
  }
  async initializeAsync() {
    const lng = await getLng();
    getLanguage = lng || 'en';
    this.t = await i18next__default.default.init({
      lng,
      fallbackLng,
      resources: {
        ...(lng && lng in resources && {
          [lng]: {
            translation: await resources[lng]()
          }
        }),
        [fallbackLng]: {
          translation: await resources[fallbackLng]()
        }
      },
      interpolation,
      initImmediate: true
    });
  }
  initialize() {
    this.initialization = this.initializeAsync();
    this.listen(I18N_LNG_REQUESTED, action => {
      var _action$meta;
      if (!hasMeta(action) || !action.meta.id) {
        return;
      }
      dispatch({
        type: I18N_LNG_RESPONDED,
        payload: hasLng(i18next__default.default.language) ? i18next__default.default.language : fallbackLng,
        meta: {
          response: true,
          id: (_action$meta = action.meta) === null || _action$meta === undefined ? undefined : _action$meta.id
        }
      });
    });
  }
  wait() {
    var _this$initialization;
    return (_this$initialization = this.initialization) !== null && _this$initialization !== undefined ? _this$initialization : Promise.reject(new Error('not initialized'));
  }
}
var i18n = new I18nService();

var main = /*#__PURE__*/Object.freeze({
  __proto__: null,
  default: i18n,
  get getLanguage () { return getLanguage; }
});

const isJitsiServerAllowed = async rawUrl => {
  const url = new URL(rawUrl);
  const persistedServers = Object.entries(select(({
    allowedJitsiServers
  }) => allowedJitsiServers)).filter(([key]) => key === url.host);
  if (persistedServers.length) {
    return {
      allowed: persistedServers[0][1],
      dontAskAgain: true
    };
  }
  const {
    allowed,
    dontAskAgain
  } = await askForJitsiCaptureScreenPermission(url);
  if (dontAskAgain) {
    dispatch({
      type: JITSI_SERVER_CAPTURE_SCREEN_PERMISSION_UPDATED,
      payload: {
        jitsiServer: url.hostname,
        allowed
      }
    });
  }
  return {
    allowed,
    dontAskAgain
  };
};

let permitted = false;
let dontAskAgain = false;
let firstAskPermission = true;
const handleJitsiDesktopCapturerGetSources = () => {
  handle('jitsi-desktop-capturer-get-sources', async (_webContents, [opts, jitsiDomain]) => {
    if (permitted) return electron.desktopCapturer.getSources(opts);
    if (dontAskAgain) return [];
    if (firstAskPermission) {
      firstAskPermission = false;
      const askResult = await isJitsiServerAllowed(jitsiDomain);
      permitted = askResult.allowed;
      dontAskAgain = askResult.dontAskAgain;
    }
    return [];
  });
};

/**
 * Constants for Log Viewer window
 */


/** Window size as multiplier of screen size */
const WINDOW_SIZE_MULTIPLIER = 0.8;

const t$4 = i18next__default.default.t.bind(i18next__default.default);
const readFile = util.promisify(fs__namespace.default.readFile);
const writeFile = util.promisify(fs__namespace.default.writeFile);
let logViewerWindow = null;
const allowedLogPaths = new Set();
const getLogFilePath = () => {
  const logsPath = electron.app.getPath('logs');
  return path__default.default.join(logsPath, 'main.log');
};
const validateLogFilePath = filePath => {
  const normalizedPath = path__default.default.normalize(filePath);
  if (filePath.includes('..') || normalizedPath.includes('..')) {
    return {
      valid: false,
      error: 'Path traversal not allowed'
    };
  }
  if (!path__default.default.isAbsolute(normalizedPath)) {
    return {
      valid: false,
      error: 'Only absolute paths are allowed'
    };
  }
  const ext = path__default.default.extname(normalizedPath).toLowerCase();
  if (ext !== '.log' && ext !== '.txt') {
    return {
      valid: false,
      error: 'Only .log and .txt files are allowed'
    };
  }
  return {
    valid: true
  };
};
const LOG_ENTRY_REGEX = /^\[([^\]]+)\]\s+\[([^\]]+)\]/;
const getLastNEntries = (content, limit) => {
  if (limit <= 0) {
    return {
      content: '',
      totalEntries: 0
    };
  }
  const lines = content.split(/\r?\n/);
  const entryStartIndices = [];
  lines.forEach((line, index) => {
    if (LOG_ENTRY_REGEX.test(line)) {
      entryStartIndices.push(index);
    }
  });
  const totalEntries = entryStartIndices.length;
  if (totalEntries === 0) {
    return {
      content: lines.slice(-limit).join('\n'),
      totalEntries: 0
    };
  }
  const startEntryIndex = Math.max(0, totalEntries - limit);
  const startLineIndex = entryStartIndices[startEntryIndex];
  return {
    content: lines.slice(startLineIndex).join('\n'),
    totalEntries
  };
};
const openLogViewerWindow = async () => {
  if (logViewerWindow && !logViewerWindow.isDestroyed()) {
    logViewerWindow.focus();
    return;
  }
  const mainWindow = await getRootWindow();
  const winBounds = await mainWindow.getNormalBounds();
  const centeredWindowPosition = {
    x: winBounds.x + winBounds.width / 2,
    y: winBounds.y + winBounds.height / 2
  };
  const actualScreen = electron.screen.getDisplayNearestPoint({
    x: centeredWindowPosition.x,
    y: centeredWindowPosition.y
  });
  const width = Math.round(actualScreen.workAreaSize.width * WINDOW_SIZE_MULTIPLIER);
  const height = Math.round(actualScreen.workAreaSize.height * WINDOW_SIZE_MULTIPLIER);
  const x = Math.round((actualScreen.workArea.width - width) / 2 + actualScreen.workArea.x);
  const y = Math.round((actualScreen.workArea.height - height) / 2 + actualScreen.workArea.y);
  logViewerWindow = new electron.BrowserWindow({
    width,
    height,
    x,
    y,
    title: 'Log Viewer - Rocket.Chat',
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false
    },
    show: false
  });
  logViewerWindow.loadFile(path__default.default.join(electron.app.getAppPath(), 'app/log-viewer-window.html'));
  logViewerWindow.once('ready-to-show', () => {
    var _logViewerWindow, _logViewerWindow2;
    (_logViewerWindow = logViewerWindow) === null || _logViewerWindow === undefined || _logViewerWindow.setTitle(`Log Viewer - ${packageJsonInformation.productName}`);
    (_logViewerWindow2 = logViewerWindow) === null || _logViewerWindow2 === undefined || _logViewerWindow2.show();
  });
  logViewerWindow.on('closed', () => {
    logViewerWindow = null;
    allowedLogPaths.clear();
  });
  logViewerWindow.webContents.on('will-navigate', (event, url) => {
    if (!url.startsWith('file://')) {
      event.preventDefault();
    }
  });
  logViewerWindow.webContents.setWindowOpenHandler(() => {
    return {
      action: 'deny'
    };
  });
};
const startLogViewerWindowHandler = () => {
  handle('log-viewer-window/open-window', openLogViewerWindow);
  handle('log-viewer-window/close-requested', async () => {
    var _logViewerWindow3;
    (_logViewerWindow3 = logViewerWindow) === null || _logViewerWindow3 === undefined || _logViewerWindow3.close();
  });
  handle('log-viewer-window/select-log-file', async () => {
    try {
      if (!logViewerWindow || logViewerWindow.isDestroyed()) {
        return {
          success: false,
          error: 'Log viewer window not found'
        };
      }
      const result = await electron.dialog.showOpenDialog(logViewerWindow, {
        title: 'Select Log File',
        filters: [{
          name: 'Log Files',
          extensions: ['log', 'txt']
        }, {
          name: 'All Files',
          extensions: ['*']
        }],
        properties: ['openFile']
      });
      if (result.canceled || result.filePaths.length === 0) {
        return {
          success: false,
          canceled: true
        };
      }
      const selectedPath = result.filePaths[0];
      const validation = validateLogFilePath(selectedPath);
      if (!validation.valid) {
        return {
          success: false,
          error: validation.error
        };
      }
      const normalizedPath = path__default.default.normalize(selectedPath);
      allowedLogPaths.add(normalizedPath);
      return {
        success: true,
        filePath: normalizedPath,
        fileName: path__default.default.basename(normalizedPath)
      };
    } catch (error) {
      console.error('Failed to select log file:', error);
      return {
        success: false,
        error: error.message
      };
    }
  });
  handle('log-viewer-window/read-logs', async (_, options) => {
    try {
      let logPath;
      if (options !== null && options !== void 0 && options.filePath) {
        const validation = validateLogFilePath(options.filePath);
        if (!validation.valid) {
          return {
            success: false,
            error: validation.error
          };
        }
        const normalizedPath = path__default.default.normalize(options.filePath);
        const defaultLogPath = path__default.default.normalize(getLogFilePath());
        if (normalizedPath !== defaultLogPath && !allowedLogPaths.has(normalizedPath)) {
          return {
            success: false,
            error: 'Log file not authorized. Please select it via the file dialog first.'
          };
        }
        logPath = normalizedPath;
      } else {
        logPath = getLogFilePath();
      }
      const limit = options === null || options === void 0 ? void 0 : options.limit;
      if (!fs__namespace.default.existsSync(logPath)) {
        if (!(options !== null && options !== void 0 && options.filePath)) {
          const logDir = path__default.default.dirname(logPath);
          if (!fs__namespace.default.existsSync(logDir)) {
            fs__namespace.default.mkdirSync(logDir, {
              recursive: true
            });
          }
          await writeFile(logPath, '');
        } else {
          return {
            success: false,
            error: 'Selected log file does not exist'
          };
        }
      }
      let logContent;
      let totalEntries;
      const fileContent = await readFile(logPath, 'utf-8');
      if (limit === 'all' || !limit) {
        logContent = fileContent;
      } else {
        const result = getLastNEntries(fileContent, limit);
        logContent = result.content;
        totalEntries = result.totalEntries;
      }
      const stats = fs__namespace.default.statSync(logPath);
      const lastModifiedTime = stats.mtime.getTime();
      return {
        success: true,
        logs: logContent,
        filePath: logPath,
        fileName: path__default.default.basename(logPath),
        isDefaultLog: !(options !== null && options !== void 0 && options.filePath),
        lastModifiedTime,
        fileSize: stats.size,
        totalEntries
      };
    } catch (error) {
      console.error('Failed to read log file:', error);
      return {
        success: false,
        error: error.message
      };
    }
  });
  handle('log-viewer-window/stat-log', async (_, options) => {
    try {
      let logPath;
      if (options !== null && options !== void 0 && options.filePath) {
        const validation = validateLogFilePath(options.filePath);
        if (!validation.valid) {
          return {
            success: false,
            error: validation.error
          };
        }
        const normalizedPath = path__default.default.normalize(options.filePath);
        const defaultLogPath = path__default.default.normalize(getLogFilePath());
        if (normalizedPath !== defaultLogPath && !allowedLogPaths.has(normalizedPath)) {
          return {
            success: false,
            error: 'Log file not authorized. Please select it via the file dialog first.'
          };
        }
        logPath = normalizedPath;
      } else {
        logPath = getLogFilePath();
      }
      if (!fs__namespace.default.existsSync(logPath)) {
        return {
          success: false,
          error: 'Log file does not exist'
        };
      }
      const stats = fs__namespace.default.statSync(logPath);
      return {
        success: true,
        lastModifiedTime: stats.mtime.getTime(),
        size: stats.size
      };
    } catch (error) {
      console.error('Failed to stat log file:', error);
      return {
        success: false,
        error: error.message
      };
    }
  });
  handle('log-viewer-window/read-logs-tail', async (_, options) => {
    try {
      let logPath;
      if (options.filePath) {
        const validation = validateLogFilePath(options.filePath);
        if (!validation.valid) {
          return {
            success: false,
            error: validation.error
          };
        }
        const normalizedPath = path__default.default.normalize(options.filePath);
        const defaultLogPath = path__default.default.normalize(getLogFilePath());
        if (normalizedPath !== defaultLogPath && !allowedLogPaths.has(normalizedPath)) {
          return {
            success: false,
            error: 'Log file not authorized. Please select it via the file dialog first.'
          };
        }
        logPath = normalizedPath;
      } else {
        logPath = getLogFilePath();
      }
      if (!fs__namespace.default.existsSync(logPath)) {
        return {
          success: false,
          error: 'Log file does not exist'
        };
      }
      const stats = fs__namespace.default.statSync(logPath);
      const rawFromByte = Number(options.fromByte);
      const fromByte = Number.isFinite(rawFromByte) && rawFromByte >= 0 ? Math.min(Math.floor(rawFromByte), stats.size) : 0;
      if (fromByte >= stats.size) {
        return {
          success: true,
          logs: '',
          newSize: stats.size,
          lastModifiedTime: stats.mtime.getTime()
        };
      }
      const chunks = [];
      await new Promise((resolve, reject) => {
        const stream = fs__namespace.default.createReadStream(logPath, {
          start: fromByte,
          encoding: undefined
        });
        stream.on('data', chunk => chunks.push(chunk));
        stream.on('end', () => resolve());
        stream.on('error', err => reject(err));
      });
      const newContent = Buffer.concat(chunks).toString('utf-8');
      return {
        success: true,
        logs: newContent,
        newSize: stats.size,
        lastModifiedTime: stats.mtime.getTime()
      };
    } catch (error) {
      console.error('Failed to read log tail:', error);
      return {
        success: false,
        error: error.message
      };
    }
  });
  handle('log-viewer-window/confirm-clear-logs', async () => {
    if (!logViewerWindow || logViewerWindow.isDestroyed()) {
      return false;
    }
    const {
      response
    } = await electron.dialog.showMessageBox(logViewerWindow, {
      type: 'warning',
      buttons: [t$4('dialog.clearLogs.yes'), t$4('dialog.clearLogs.cancel')],
      defaultId: 1,
      title: t$4('dialog.clearLogs.title'),
      message: t$4('dialog.clearLogs.message'),
      detail: t$4('dialog.clearLogs.detail')
    });
    return response === 0;
  });
  handle('log-viewer-window/clear-logs', async () => {
    try {
      const logPath = getLogFilePath();
      await writeFile(logPath, '');
      return {
        success: true
      };
    } catch (error) {
      console.error('Failed to clear log file:', error);
      return {
        success: false,
        error: error.message
      };
    }
  });
  handle('log-viewer-window/save-logs', async (_, options) => {
    try {
      if (!logViewerWindow || logViewerWindow.isDestroyed()) {
        return {
          success: false,
          error: 'Log viewer window not found'
        };
      }
      const result = await electron.dialog.showSaveDialog(logViewerWindow, {
        title: 'Save Log File',
        defaultPath: options.defaultFileName,
        filters: [{
          name: 'ZIP Files',
          extensions: ['zip']
        }, {
          name: 'All Files',
          extensions: ['*']
        }]
      });
      if (result.canceled || !result.filePath) {
        return {
          success: false,
          canceled: true
        };
      }
      await new Promise((resolve, reject) => {
        const output = fs.createWriteStream(result.filePath);
        const archive = archiver__default.default('zip', {
          zlib: {
            level: 9
          }
        });
        output.on('close', () => {
          resolve();
        });
        output.on('error', err => {
          reject(err);
        });
        archive.on('error', err => {
          reject(err);
        });
        archive.pipe(output);
        const baseName = options.defaultFileName.replace(/\.zip$/i, '').replace(/\.log$/i, '');
        const logFileName = `${baseName}.log`;
        archive.append(options.content, {
          name: logFileName
        });
        archive.finalize();
      });
      return {
        success: true,
        filePath: result.filePath
      };
    } catch (error) {
      console.error('Failed to save log file:', error);
      return {
        success: false,
        error: error.message
      };
    }
  });
  handle('log-viewer-window/get-server-mapping', async () => {
    try {
      const servers = select(state => state.servers);
      if (!servers || !Array.isArray(servers)) {
        return {
          success: true,
          mapping: {}
        };
      }
      const mapping = {};
      servers.forEach(server => {
        if (!server.url) return;
        const host = getHost(server.url);
        mapping[host] = server.title || server.url;
      });
      return {
        success: true,
        mapping
      };
    } catch {
      return {
        success: true,
        mapping: {}
      };
    }
  });
};

var ipc = /*#__PURE__*/Object.freeze({
  __proto__: null,
  openLogViewerWindow: openLogViewerWindow,
  startLogViewerWindowHandler: startLogViewerWindowHandler
});

class AttentionDrawingService extends Service {
  constructor(...args) {
    super(...args);
    _defineProperty(this, "activeVoiceNotifications", new Set());
    _defineProperty(this, "bounceId", null);
  }
  async drawAttention(notificationId) {
    if (this.activeVoiceNotifications.has(notificationId)) {
      return;
    }
    const {
      isFlashFrameEnabled
    } = select(({
      isFlashFrameEnabled
    }) => ({
      isFlashFrameEnabled
    }));
    if (!isFlashFrameEnabled) {
      return;
    }
    try {
      const browserWindow = await getRootWindow();
      if (browserWindow.isDestroyed()) {
        return;
      }
      this.activeVoiceNotifications.add(notificationId);
      if (process.platform === 'darwin') {
        if (this.bounceId === null) {
          var _app$dock$bounce, _app$dock;
          this.bounceId = (_app$dock$bounce = (_app$dock = electron.app.dock) === null || _app$dock === void 0 ? void 0 : _app$dock.bounce('critical')) !== null && _app$dock$bounce !== void 0 ? _app$dock$bounce : null;
        }
      } else {
        browserWindow.flashFrame(true);
      }
    } catch (error) {
    }
  }
  async stopAttention(notificationId) {
    if (!this.activeVoiceNotifications.has(notificationId)) {
      return;
    }
    this.activeVoiceNotifications.delete(notificationId);
    if (this.activeVoiceNotifications.size > 0) {
      return;
    }
    if (process.platform === 'darwin' && this.bounceId !== null) {
      var _app$dock2;
      (_app$dock2 = electron.app.dock) === null || _app$dock2 === undefined || _app$dock2.cancelBounce(this.bounceId);
      this.bounceId = null;
    }
    try {
      const browserWindow = await getRootWindow();
      if (browserWindow.isDestroyed()) {
        return;
      }
      if (process.platform !== 'darwin') {
        browserWindow.flashFrame(false);
      }
    } catch (error) {
    }
  }
  destroy() {
    if (process.platform === 'darwin' && this.bounceId !== null) {
      var _app$dock3;
      (_app$dock3 = electron.app.dock) === null || _app$dock3 === undefined || _app$dock3.cancelBounce(this.bounceId);
      this.bounceId = null;
    }
  }
}
var attentionDrawing = new AttentionDrawingService();

const resolveIcon = async iconUrl => {
  if (!iconUrl) {
    return undefined;
  }
  if (/^data:/.test(iconUrl)) {
    return electron.nativeImage.createFromDataURL(iconUrl);
  }
  try {
    const {
      webContents
    } = await getRootWindow();
    const dataUri = await invoke(webContents, 'notifications/fetch-icon', iconUrl);
    return electron.nativeImage.createFromDataURL(dataUri);
  } catch (error) {
    console.error(error);
    return undefined;
  }
};
const notifications = new Map();
const notificationTypes = new Map();
const notificationCategories = new Map();
const createNotification = async (id, {
  title,
  subtitle,
  body,
  icon,
  silent,
  canReply,
  actions,
  requireInteraction,
  category
}, ipcMeta) => {
  const notification = new electron.Notification({
    title,
    subtitle,
    body: body !== null && body !== undefined ? body : '',
    icon: await resolveIcon(icon),
    silent: silent !== null && silent !== undefined ? silent : undefined,
    hasReply: canReply,
    actions: actions === null || actions === undefined ? undefined : actions.map(action => ({
      type: 'button',
      text: action.title
    })),
    ...(requireInteraction !== undefined && {
      requireInteraction
    })
  });
  notification.addListener('show', () => {
    dispatchSingle({
      type: NOTIFICATIONS_NOTIFICATION_SHOWN,
      payload: {
        id
      },
      ipcMeta
    });
    const notificationType = notificationTypes.get(id);
    if (notificationType === 'voice') {
      attentionDrawing.drawAttention(id);
    }
  });
  notification.addListener('close', () => {
    dispatchSingle({
      type: NOTIFICATIONS_NOTIFICATION_CLOSED,
      payload: {
        id
      },
      ipcMeta
    });
    notifications.delete(id);
    const notificationType = notificationTypes.get(id);
    if (notificationType === 'voice') {
      attentionDrawing.stopAttention(id);
    }
    notificationTypes.delete(id);
    notificationCategories.delete(id);
  });
  notification.addListener('click', () => {
    const serverUrl = (ipcMeta === null || ipcMeta === undefined ? undefined : ipcMeta.webContentsId) !== undefined ? getServerUrlByWebContentsId(ipcMeta.webContentsId) : undefined;
    const notificationCategory = notificationCategories.get(id);
    dispatchSingle({
      type: NOTIFICATIONS_NOTIFICATION_CLICKED,
      payload: {
        id,
        title,
        ...(serverUrl && {
          serverUrl
        }),
        ...(notificationCategory && {
          category: notificationCategory
        })
      },
      ipcMeta
    });
  });
  notification.addListener('reply', (_event, reply) => {
    dispatchSingle({
      type: NOTIFICATIONS_NOTIFICATION_REPLIED,
      payload: {
        id,
        reply
      },
      ipcMeta
    });
  });
  notification.addListener('action', (_event, index) => {
    dispatchSingle({
      type: NOTIFICATIONS_NOTIFICATION_ACTIONED,
      payload: {
        id,
        index
      },
      ipcMeta
    });
  });
  notifications.set(id, notification);
  if (category) {
    notificationCategories.set(id, category);
  }
  notification.show();
  return id;
};
const updateNotification = async (id, {
  title,
  body,
  silent,
  renotify: _renotify,
  icon,
  requireInteraction,
  notificationType
}) => {
  const notification = notifications.get(id);
  if (!notification) {
    return id;
  }
  if (title !== undefined) {
    notification.title = title;
  }
  if (body !== undefined) {
    notification.body = body;
  }
  if (silent !== undefined) {
    notification.silent = silent;
  }
  if (icon !== undefined) {
    const resolvedIcon = await resolveIcon(icon);
    if (resolvedIcon) {
      notification.icon = resolvedIcon;
    }
  }
  if (requireInteraction !== undefined) {
    notification.requireInteraction = requireInteraction;
  }
  let changedToVoice = false;
  if (notificationType !== undefined) {
    const previousType = notificationTypes.get(id);
    notificationTypes.set(id, notificationType);
    if (previousType === 'voice' && notificationType !== 'voice') {
      attentionDrawing.stopAttention(id);
    } else if (previousType !== 'voice' && notificationType === 'voice') {
      changedToVoice = true;
    }
  }
  notification.show();
  if (changedToVoice) {
    attentionDrawing.drawAttention(id);
  }
  return id;
};
const handleCreateEvent = async ({
  tag,
  ...options
}, ipcMeta) => {
  if (tag && notifications.has(tag)) {
    return updateNotification(tag, options);
  }
  const id = tag || Math.random().toString(36).slice(2);
  notificationTypes.set(id, options.notificationType || 'text');
  return createNotification(id, options, ipcMeta);
};
const setupNotifications = () => {
  listen(NOTIFICATIONS_CREATE_REQUESTED, async action => {
    if (!hasMeta(action)) {
      return;
    }
    dispatch({
      type: NOTIFICATIONS_CREATE_RESPONDED,
      payload: await handleCreateEvent(action.payload, action.ipcMeta),
      meta: {
        id: action.meta.id,
        response: true
      }
    });
  });
  listen(NOTIFICATIONS_NOTIFICATION_DISMISSED, action => {
    var _notifications$get;
    const notificationId = String(action.payload.id);
    const notificationType = notificationTypes.get(notificationId);
    (_notifications$get = notifications.get(notificationId)) === null || _notifications$get === undefined || _notifications$get.close();
    if (notificationType === 'voice') {
      attentionDrawing.stopAttention(notificationId);
    }
    notificationTypes.delete(notificationId);
  });
};

/**
 * Checks if a version meets or exceeds a minimum required version
 * Safely handles various version string formats by normalizing with semver.coerce
 *
 * @param version The version to check
 * @param minimumVersion The minimum version required
 * @returns boolean Whether the version meets or exceeds the minimum required version
 */
function meetsMinimumVersion(version, minimumVersion) {
  var _coerce;
  if (!version) return false;

  // Coerce version to a valid semver format and use its normalized form
  const normalizedVersion = ((_coerce = semver$1.coerce(version)) === null || _coerce === undefined ? undefined : _coerce.version) || '0.0.0';
  return semver$1.gte(normalizedVersion, minimumVersion);
}

const ERROR_PATTERNS = [{
  pattern: /Cannot read properties of undefined \(reading 'headers'\)/i,
  classification: {
    source: 'exchange',
    severity: 'high',
    code: 'EWS_NTLM_AUTH_FAILED',
    userMessage: 'Unable to connect to Exchange server. The NTLM authentication failed.',
    suggestedActions: ['Verify your Exchange server URL is correct', 'Check if your username and password are valid', 'Ensure the Exchange server supports NTLM authentication', 'Contact your IT administrator if the issue persists']
  }
}, {
  pattern: /The remote server returned an error: \(401\) Unauthorized/i,
  classification: {
    source: 'exchange',
    severity: 'high',
    code: 'EWS_UNAUTHORIZED',
    userMessage: 'Your Exchange credentials are invalid or expired.',
    suggestedActions: ['Verify your username and password are correct', 'Check if your account is locked or disabled', 'Contact your IT administrator to verify Exchange access']
  }
}, {
  pattern: /The remote server returned an error: \(404\) Not Found/i,
  classification: {
    source: 'exchange',
    severity: 'high',
    code: 'EWS_SERVER_NOT_FOUND',
    userMessage: 'Exchange server not found. The server URL appears to be incorrect.',
    suggestedActions: ['Verify the Exchange server URL is correct', 'Check if the server is accessible from your network', 'Try using the full EWS endpoint URL (e.g., https://mail.company.com/ews/exchange.asmx)']
  }
}, {
  pattern: /(getaddrinfo ENOTFOUND|ECONNREFUSED|\btimeout\b)/i,
  classification: {
    source: 'network',
    severity: 'high',
    code: 'NETWORK_CONNECTION_FAILED',
    userMessage: 'Cannot connect to Exchange server. Network connection failed.',
    suggestedActions: ['Check your internet connection', 'Verify the Exchange server URL is accessible', 'Check if there are firewall restrictions', 'Try connecting from a different network']
  }
}, {
  pattern: /SSL_ERROR|UNABLE_TO_VERIFY|CERT_|ERR_TLS|self[.\s-]signed|certificate[.\s]has[.\s]expired/i,
  classification: {
    source: 'network',
    severity: 'medium',
    code: 'SSL_CERTIFICATE_ERROR',
    userMessage: 'SSL/TLS certificate error when connecting to Exchange server.',
    suggestedActions: ['Check if the Exchange server has a valid SSL certificate', 'Contact your IT administrator about certificate issues', 'Verify the server URL uses HTTPS']
  }
}, {
  pattern: /Request failed with status code 401.*rocket.*chat/i,
  classification: {
    source: 'rocket_chat',
    severity: 'high',
    code: 'RC_UNAUTHORIZED',
    userMessage: 'Authentication with Rocket.Chat server failed.',
    suggestedActions: ['Log out and log back into Rocket.Chat', 'Check if your Rocket.Chat session is still valid', 'Contact your administrator if calendar integration is not enabled']
  }
}, {
  pattern: /Request failed with status code 500.*rocket.*chat/i,
  classification: {
    source: 'rocket_chat',
    severity: 'high',
    code: 'RC_SERVER_ERROR',
    userMessage: 'Rocket.Chat server encountered an internal error.',
    suggestedActions: ['Try again in a few minutes', 'Contact your Rocket.Chat administrator', 'Check if the calendar feature is properly configured on the server']
  }
}, {
  pattern: /calendar.*not.*enabled|calendar.*feature.*disabled/i,
  classification: {
    source: 'rocket_chat',
    severity: 'medium',
    code: 'RC_CALENDAR_DISABLED',
    userMessage: 'Calendar integration is not enabled on this Rocket.Chat server.',
    suggestedActions: ['Contact your Rocket.Chat administrator to enable calendar integration', 'Check if you have the necessary permissions for calendar features']
  }
}, {
  pattern: /Missing required Outlook credentials/i,
  classification: {
    source: 'desktop_app',
    severity: 'medium',
    code: 'MISSING_CREDENTIALS',
    userMessage: 'Outlook credentials are not configured.',
    suggestedActions: ['Go to Settings and configure your Outlook credentials', 'Make sure all required fields are filled out']
  }
}, {
  pattern: /Failed to decrypt credentials/i,
  classification: {
    source: 'desktop_app',
    severity: 'medium',
    code: 'CREDENTIAL_DECRYPTION_FAILED',
    userMessage: 'Cannot decrypt your saved Outlook credentials.',
    suggestedActions: ['Try re-entering your Outlook credentials in Settings', 'If the issue persists, clear your credentials and set them up again']
  }
}, {
  pattern: /Invalid Exchange server URL configuration/i,
  classification: {
    source: 'configuration',
    severity: 'medium',
    code: 'INVALID_EXCHANGE_URL',
    userMessage: 'The Exchange server URL is not properly configured.',
    suggestedActions: ['Check the Exchange server URL format (e.g., https://mail.company.com)', 'Contact your IT administrator for the correct Exchange server URL', 'Make sure the URL includes the protocol (https://)']
  }
}, {
  pattern: /NTLM.*authentication.*failed/i,
  classification: {
    source: 'authentication',
    severity: 'high',
    code: 'NTLM_AUTH_FAILED',
    userMessage: 'NTLM authentication with Exchange server failed.',
    suggestedActions: ['Verify your domain username and password', 'Check if your domain is included in the username (DOMAIN\\username)', 'Contact your IT administrator about NTLM authentication requirements']
  }
}];
function classifyError(error, _context = {}) {
  const errorMessage = error instanceof Error ? error.message : String(error);
  const stack = error instanceof Error ? error.stack : '';
  const fullErrorText = `${errorMessage} ${stack}`.toLowerCase();
  for (const {
    pattern,
    classification
  } of ERROR_PATTERNS) {
    if (pattern.test(fullErrorText)) {
      return classification;
    }
  }
  return {
    source: 'desktop_app',
    severity: 'medium',
    code: 'UNKNOWN_ERROR',
    userMessage: 'An unexpected error occurred during calendar synchronization.',
    suggestedActions: ['Try the operation again', 'Check your network connection', 'Restart the application if the issue persists', 'Contact support if the problem continues']
  };
}
function createClassifiedError(error, context = {}) {
  const classification = classifyError(error, context);
  const technicalMessage = error instanceof Error ? error.message : String(error);
  const timestamp = new Date().toISOString();
  return {
    ...classification,
    technicalMessage,
    context: {
      ...context,
      timestamp,
      stack: error instanceof Error ? error.stack : undefined
    },
    timestamp
  };
}
const SENSITIVE_CONTEXT_KEYS = new Set(['token', 'password', 'secret', 'authorization', 'cookie', 'set-cookie']);
const isSensitiveKey = key => {
  const lowerKey = key.toLowerCase();
  return SENSITIVE_CONTEXT_KEYS.has(lowerKey) || lowerKey.includes('token') || lowerKey.includes('password') || lowerKey.includes('secret') || lowerKey.includes('authorization') || lowerKey.includes('cookie');
};
const sanitizeContext = (context, seen = new WeakSet()) => {
  const sanitized = {};
  for (const [key, value] of Object.entries(context)) {
    if (isSensitiveKey(key)) {
      sanitized[key] = '[REDACTED]';
    } else if (Array.isArray(value)) {
      if (seen.has(value)) {
        sanitized[key] = '[Circular]';
      } else {
        seen.add(value);
        sanitized[key] = value.map(item => {
          if (typeof item !== 'object' || item === null) return item;
          if (seen.has(item)) return '[Circular]';
          seen.add(item);
          return sanitizeContext(item, seen);
        });
      }
    } else if (typeof value === 'object' && value !== null) {
      if (seen.has(value)) {
        sanitized[key] = '[Circular]';
      } else {
        seen.add(value);
        sanitized[key] = sanitizeContext(value, seen);
      }
    } else {
      sanitized[key] = value;
    }
  }
  return sanitized;
};
function formatErrorForLogging(classifiedError, operationContext) {
  var _classifiedError$sugg;
  const {
    source,
    severity,
    code,
    userMessage,
    technicalMessage,
    context
  } = classifiedError;
  const safeContext = context ? sanitizeContext(context) : {};
  return `[OutlookCalendar] ${operationContext} failed - ${source.toUpperCase()} ERROR
┌─ Error Classification ─────────────────────────────────────
│ Source: ${source}
│ Severity: ${severity}
│ Code: ${code}
│
│ User Message: ${userMessage}
│
│ Technical Details: ${technicalMessage}
│
│ Context: ${JSON.stringify(safeContext, null, 2).replace(/\n/g, '\n│ ')}
│
│ Suggested Actions:
${((_classifiedError$sugg = classifiedError.suggestedActions) === null || _classifiedError$sugg === undefined ? undefined : _classifiedError$sugg.map(action => `│ • ${action}`).join('\n')) || '│ • Contact support'}
└────────────────────────────────────────────────────────────`;
}
function generateUserFriendlyMessage(classifiedError) {
  const {
    source,
    userMessage,
    suggestedActions
  } = classifiedError;
  let sourceDescription = '';
  switch (source) {
    case 'exchange':
      sourceDescription = 'Exchange Server';
      break;
    case 'rocket_chat':
      sourceDescription = 'Rocket.Chat Server';
      break;
    case 'desktop_app':
      sourceDescription = 'Desktop Application';
      break;
    case 'network':
      sourceDescription = 'Network Connection';
      break;
    case 'authentication':
      sourceDescription = 'Authentication';
      break;
    case 'configuration':
      sourceDescription = 'Configuration';
      break;
    default:
      sourceDescription = 'Unknown Source';
  }
  let message = `${sourceDescription} Error: ${userMessage}`;
  if (suggestedActions && suggestedActions.length > 0) {
    message += '\n\nWhat you can try:\n';
    message += suggestedActions.map(action => `• ${action}`).join('\n');
  }
  return message;
}

global.isVerboseOutlookLoggingEnabled = false;
global.isDetailedEventsLoggingEnabled = false;
const prefix = '[OutlookCalendar]';
const eventDetailPrefix = '[OutlookCalendar:Events]';
const setupOutlookLogger = () => {
  global.isVerboseOutlookLoggingEnabled = select(({
    isVerboseOutlookLoggingEnabled
  }) => isVerboseOutlookLoggingEnabled);
  global.isDetailedEventsLoggingEnabled = select(({
    isDetailedEventsLoggingEnabled
  }) => isDetailedEventsLoggingEnabled);
  watch(({
    isVerboseOutlookLoggingEnabled
  }) => isVerboseOutlookLoggingEnabled, enabled => {
    global.isVerboseOutlookLoggingEnabled = enabled;
  });
  watch(({
    isDetailedEventsLoggingEnabled
  }) => isDetailedEventsLoggingEnabled, enabled => {
    global.isDetailedEventsLoggingEnabled = enabled;
  });
};
const outlookLog = (...args) => {
  if (global.isVerboseOutlookLoggingEnabled) {
    console.info(prefix, ...args);
  }
};
const outlookWarn = (...args) => {
  if (global.isVerboseOutlookLoggingEnabled) {
    console.warn(prefix, ...args);
  }
};
const outlookError = (message, ...details) => {
  if (global.isVerboseOutlookLoggingEnabled) {
    console.error(prefix, message, ...details);
  } else {
    console.error(prefix, message);
  }
};
const outlookEventDetail = (...args) => {
  if (global.isDetailedEventsLoggingEnabled) {
    console.info(eventDetailPrefix, ...args);
  }
};

/* eslint-disable new-cap */

/**
 * Builds the EWS endpoint pathname on a URL object
 * Normalizes path segments and ensures proper /ews/exchange.asmx suffix
 */
const buildEwsPathname = url => {
  const pathSegments = url.pathname.split('/').filter(segment => segment.length > 0);

  // Only strip trailing 'ews' and 'exchange.asmx' segments
  while (pathSegments.length > 0) {
    const last = pathSegments[pathSegments.length - 1].toLowerCase();
    if (last === 'ews' || last === 'exchange.asmx') {
      pathSegments.pop();
    } else {
      break;
    }
  }
  pathSegments.push('ews', 'exchange.asmx');
  url.pathname = `/${pathSegments.join('/')}`;
};

/**
 * Sanitizes and constructs a proper Exchange Web Services (EWS) URL
 * Handles various input formats and edge cases to prevent URL construction errors
 *
 * Features:
 * - Native URL() constructor for robust parsing and validation
 * - Intelligent path segment handling to prevent duplication
 * - Support for various URL formats (base URLs, /ews paths, complete URLs)
 * - Case-insensitive path normalization
 * - Smart fallback for malformed URLs with protocol detection
 * - Automatic connectivity testing for debugging
 * - Extensive logging for troubleshooting
 *
 * @param serverUrl - The server URL from configuration (can be base URL or include EWS path)
 * @returns A properly formatted EWS endpoint URL
 *
 * Examples:
 * - 'https://mail.example.com' → 'https://mail.example.com/ews/exchange.asmx'
 * - 'https://mail.example.com/' → 'https://mail.example.com/ews/exchange.asmx'
 * - 'https://mail.example.com/ews' → 'https://mail.example.com/ews/exchange.asmx'
 * - 'https://mail.example.com/ews/' → 'https://mail.example.com/ews/exchange.asmx'
 * - 'https://mail.example.com/EWS' → 'https://mail.example.com/ews/exchange.asmx'
 * - 'https://mail.example.com/ews/exchange.asmx' → 'https://mail.example.com/ews/exchange.asmx'
 *
 * Debugging:
 * - Connectivity testing runs automatically on this debugging branch
 * - Check console logs for detailed validation information
 * - Use testExchangeServerConnectivity() function for manual connectivity testing
 */
const sanitizeExchangeUrl = serverUrl => {
  if (!serverUrl || typeof serverUrl !== 'string') {
    throw new Error('Invalid server URL: must be a non-empty string');
  }
  outlookLog('Starting URL sanitization for:', serverUrl);
  try {
    // Use URL constructor for parsing and validation
    const url = new URL(serverUrl);

    // Validate protocol
    if (!['http:', 'https:'].includes(url.protocol)) {
      throw new Error(`Invalid protocol "${url.protocol}". Only HTTP and HTTPS are supported`);
    }

    // Validate hostname
    if (!url.hostname) {
      throw new Error('URL must have a valid hostname');
    }

    // Normalize and construct EWS path using URL properties
    buildEwsPathname(url);
    const sanitizedUrl = url.toString();
    outlookLog('URL sanitization completed:', {
      input: serverUrl,
      output: sanitizedUrl,
      protocol: url.protocol,
      hostname: url.hostname,
      pathname: url.pathname
    });
    if (!url.pathname.endsWith('/ews/exchange.asmx')) {
      throw new Error('Failed to construct proper EWS endpoint URL');
    }
    return sanitizedUrl;
  } catch (error) {
    // Enhanced fallback using URL constructor more intelligently
    outlookWarn('URL parsing failed, attempting fallback method:', {
      originalUrl: serverUrl,
      error: error instanceof Error ? error.message : String(error)
    });

    // Try to fix common URL issues
    let fallbackUrl = serverUrl.trim();

    // Add protocol if missing
    if (!fallbackUrl.match(/^https?:\/\//)) {
      fallbackUrl = `https://${fallbackUrl}`;
      outlookLog('Added default HTTPS protocol');
    }

    // Remove multiple slashes (except after protocol)
    fallbackUrl = fallbackUrl.replace(/([^:]\/)\/+/g, '$1');
    try {
      const url = new URL(fallbackUrl);

      // Validate basic requirements
      if (!url.hostname) {
        throw new Error('URL must have a valid hostname');
      }

      // Use the same clean path construction as main logic
      buildEwsPathname(url);
      const finalUrl = url.toString();
      outlookLog('Fallback URL construction successful:', {
        input: serverUrl,
        fallback: fallbackUrl,
        output: finalUrl,
        protocol: url.protocol,
        hostname: url.hostname,
        pathname: url.pathname
      });
      return finalUrl;
    } catch (fallbackError) {
      const errorMessage = `Failed to create valid Exchange URL from "${serverUrl}". 
        Original error: ${error instanceof Error ? error.message : String(error)}. 
        Fallback error: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}. 
        Please ensure the URL includes a valid protocol (http:// or https://) and hostname.`;
      outlookError('URL sanitization failed completely:', {
        input: serverUrl,
        originalError: error instanceof Error ? error.message : String(error),
        fallbackError: fallbackError instanceof Error ? fallbackError.message : String(fallbackError)
      });
      throw new Error(errorMessage);
    }
  }
};
const getOutlookEvents = async (credentials, date, allowInsecure = false) => {
  outlookLog('Starting getOutlookEvents', {
    userId: credentials.userId,
    serverUrl: credentials.serverUrl,
    date: date.toISOString(),
    hasLogin: !!credentials.login,
    hasPassword: !!credentials.password
  });
  try {
    const {
      login,
      password,
      serverUrl
    } = credentials;

    // Validate required credentials
    if (!login || !password || !serverUrl) {
      const error = new Error('Missing required Outlook credentials');
      const classifiedError = createClassifiedError(error, {
        operation: 'credential_validation',
        hasLogin: !!login,
        hasPassword: !!password,
        hasServerUrl: !!serverUrl,
        userId: credentials.userId
      });
      outlookError(formatErrorForLogging(classifiedError, 'Credential validation'));
      throw error;
    }
    outlookLog('Initializing Exchange connection');
    // When allowInsecure is true, bypass SSL certificate validation
    // for air-gapped environments with self-signed/internal CA certificates
    const xhrApi = new xhr.XhrApi({
      decompress: true,
      rejectUnauthorized: !allowInsecure
    });
    if (allowInsecure) {
      outlookWarn('SSL certificate validation disabled (allowInsecure=true)');
    }
    xhrApi.useNtlmAuthentication(login, password);
    ewsJavascriptApi.ConfigurationApi.ConfigureXHR(xhrApi);
    const exchange = new ewsJavascriptApi.ExchangeService(ewsJavascriptApi.ExchangeVersion.Exchange2013);
    // This credentials object isn't used when ntlm is active, but the lib still requires it.
    exchange.Credentials = new ewsJavascriptApi.WebCredentials(login, password);
    try {
      const exchangeUrl = sanitizeExchangeUrl(serverUrl);
      exchange.Url = new ewsJavascriptApi.Uri(exchangeUrl);
      outlookLog('Exchange URL set:', exchangeUrl);
    } catch (error) {
      const classifiedError = createClassifiedError(error, {
        operation: 'exchange_url_configuration',
        serverUrl,
        userId: credentials.userId
      });
      outlookError(formatErrorForLogging(classifiedError, 'Exchange URL configuration'));
      throw new Error(`Invalid Exchange server URL configuration: ${classifiedError.technicalMessage}`);
    }
    const validatedDate = new Date(date);
    outlookLog('Searching for appointments on:', validatedDate.toDateString());
    const folderId = new ewsJavascriptApi.FolderId(ewsJavascriptApi.WellKnownFolderName.Calendar);
    const minTime = new ewsJavascriptApi.DateTime(validatedDate.getFullYear(), validatedDate.getMonth() + 1, validatedDate.getDate());
    const maxTime = new ewsJavascriptApi.DateTime(validatedDate.getFullYear(), validatedDate.getMonth() + 1, validatedDate.getDate(), 23, 59, 59);
    const view = new ewsJavascriptApi.CalendarView(minTime, maxTime);
    let appointments = [];
    try {
      outlookLog('Fetching appointments from Exchange server...');
      const findResult = await exchange.FindAppointments(folderId, view);
      appointments = findResult.Items;
      outlookLog('Found', appointments.length, 'appointments');
      outlookEventDetail('Raw Exchange appointments count:', appointments.length);
    } catch (error) {
      var _exchange$Url;
      const classifiedError = createClassifiedError(error, {
        operation: 'fetch_appointments',
        serverUrl: credentials.serverUrl,
        userId: credentials.userId,
        exchangeUrl: (_exchange$Url = exchange.Url) === null || _exchange$Url === void 0 ? void 0 : _exchange$Url.ToString()
      });
      outlookError(formatErrorForLogging(classifiedError, 'Fetch appointments from Exchange'));
      throw new Error(`Failed to fetch appointments: ${classifiedError.technicalMessage}`);
    }
    // Filter out appointments that end exactly at midnight
    const filtered = appointments.filter(appointment => appointment.End > minTime);
    if (filtered.length === 0) {
      return [];
    }
    const propertySet = new ewsJavascriptApi.PropertySet(ewsJavascriptApi.BasePropertySet.FirstClassProperties);
    try {
      outlookLog('Loading properties for', filtered.length, 'appointments');
      await exchange.LoadPropertiesForItems(filtered, propertySet);
      outlookLog('Successfully loaded appointment properties');
    } catch (error) {
      const classifiedError = createClassifiedError(error, {
        operation: 'load_appointment_properties',
        appointmentCount: filtered.length,
        serverUrl: credentials.serverUrl,
        userId: credentials.userId
      });
      outlookError(formatErrorForLogging(classifiedError, 'Load appointment properties'));
      throw new Error(`Failed to load appointment properties: ${classifiedError.technicalMessage}`);
    }
    outlookLog('Processing', filtered.length, 'appointments');
    return filtered.map((appointment, index) => {
      let description = '';
      try {
        var _appointment$Body;
        if ((_appointment$Body = appointment.Body) !== null && _appointment$Body !== void 0 && _appointment$Body.Text) {
          description = appointment.Body.Text;
        }
      } catch (error) {
        outlookWarn(`Failed to get description for appointment ${index}:`, error);
        // Ignore errors when the appointment body is missing.
      }

      // Check if the busy status is Busy
      // LegacyFreeBusyStatus enum: Free = 0, Tentative = 1, Busy = 2, OOF = 3
      const isBusy = appointment.LegacyFreeBusyStatus === ewsJavascriptApi.LegacyFreeBusyStatus.Busy;
      try {
        var _appointment$IsAllDay, _appointment$IsCancel, _appointment$JoinOnli, _appointment$Reminder;
        const appointmentData = {
          id: appointment.Id.UniqueId,
          subject: appointment.Subject,
          startTime: appointment.Start.ToISOString(),
          endTime: appointment.End.ToISOString(),
          description,
          isAllDay: (_appointment$IsAllDay = appointment.IsAllDayEvent) !== null && _appointment$IsAllDay !== void 0 ? _appointment$IsAllDay : false,
          isCanceled: (_appointment$IsCancel = appointment.IsCancelled) !== null && _appointment$IsCancel !== void 0 ? _appointment$IsCancel : false,
          meetingUrl: (_appointment$JoinOnli = appointment.JoinOnlineMeetingUrl) !== null && _appointment$JoinOnli !== void 0 ? _appointment$JoinOnli : undefined,
          reminderMinutesBeforeStart: (_appointment$Reminder = appointment.ReminderMinutesBeforeStart) !== null && _appointment$Reminder !== void 0 ? _appointment$Reminder : undefined,
          busy: isBusy
        };
        outlookLog(`Processed appointment ${index + 1}/${filtered.length}:`, {
          id: appointmentData.id,
          subject: appointmentData.subject,
          startTime: appointmentData.startTime,
          busy: appointmentData.busy
        });
        outlookEventDetail(`Mapped appointment ${index + 1}:`, appointmentData);
        return appointmentData;
      } catch (error) {
        var _appointment$Id;
        const errorMessage = error instanceof Error ? error.message : String(error);
        outlookError(`Failed to process appointment ${index}:`, {
          error: errorMessage,
          appointmentId: (_appointment$Id = appointment.Id) === null || _appointment$Id === void 0 ? void 0 : _appointment$Id.UniqueId,
          subject: appointment.Subject
        });
        throw new Error(`Failed to process appointment: ${errorMessage}`);
      }
    });
  } catch (error) {
    const classifiedError = createClassifiedError(error, {
      operation: 'get_outlook_events',
      serverUrl: credentials.serverUrl,
      userId: credentials.userId,
      date: date.toISOString()
    });
    outlookError(formatErrorForLogging(classifiedError, 'Get Outlook Events'));
    throw new Error(`Outlook calendar sync failed: ${classifiedError.technicalMessage}`);
  }
};

const AXIOS_TIMEOUT_MS = 10_000;
const INITIAL_SYNC_DEBOUNCE_MS = 100;
const getServerInformationByWebContentsId = webContentsId => {
  const {
    servers
  } = select(selectPersistableValues);
  const server = servers.find(server => server.webContentsId === webContentsId);
  return server || {};
};
function checkIfCredentialsAreNotEmpty(credentials) {
  return credentials.login.trim() !== '' && credentials.password.trim() !== '' && credentials.userId.trim() !== '' && credentials.serverUrl.trim() !== '';
}
function encryptedCredentials(credentials) {
  outlookLog('Encrypting credentials for user:', credentials.userId);
  try {
    if (!electron.safeStorage.isEncryptionAvailable()) {
      outlookWarn('Encryption not available, storing credentials in plain text');
      return credentials;
    }
    const encryptedLogin = electron.safeStorage.encryptString(credentials.login).toString('base64');
    const encryptedPassword = electron.safeStorage.encryptString(credentials.password).toString('base64');
    outlookLog('Successfully encrypted credentials');
    return {
      ...credentials,
      login: encryptedLogin,
      password: encryptedPassword
    };
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    outlookError('Failed to encrypt credentials:', {
      error: errorMessage,
      userId: credentials.userId,
      hasLogin: !!credentials.login,
      hasPassword: !!credentials.password
    });
    throw new Error(`Failed to encrypt credentials: ${errorMessage}`);
  }
}
const isLikelyBase64 = str => {
  if (!str || str.length < 4) return false;
  return /^[A-Za-z0-9+/]+=*$/.test(str) && str.length % 4 === 0;
};
function decryptedCredentials(credentials) {
  outlookLog('Decrypting credentials for user:', credentials.userId);
  if (!electron.safeStorage.isEncryptionAvailable()) {
    outlookWarn('Encryption not available, using credentials as plaintext');
    return credentials;
  }
  const loginLooksEncrypted = isLikelyBase64(credentials.login);
  const passwordLooksEncrypted = isLikelyBase64(credentials.password);
  if (!loginLooksEncrypted || !passwordLooksEncrypted) {
    outlookWarn('Credentials do not appear to be encrypted (legacy/plaintext), using as-is', {
      userId: credentials.userId
    });
    return credentials;
  }
  try {
    const decryptedLogin = electron.safeStorage.decryptString(Buffer.from(credentials.login, 'base64')).toString();
    const decryptedPassword = electron.safeStorage.decryptString(Buffer.from(credentials.password, 'base64')).toString();
    outlookLog('Successfully decrypted credentials');
    return {
      ...credentials,
      login: decryptedLogin,
      password: decryptedPassword
    };
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    outlookWarn('Failed to decrypt credentials, falling back to plaintext (legacy credentials)', {
      userId: credentials.userId,
      error: errorMessage
    });
    return credentials;
  }
}

/**
 * Creates an HTTPS agent that bypasses certificate validation.
 * Used for air-gapped environments with self-signed or internal CA certificates.
 */
function createInsecureHttpsAgent() {
  return new https__default.default.Agent({
    rejectUnauthorized: false
  });
}
async function listEventsFromRocketChatServer(serverUrl, userId, token, httpsAgent) {
  const url = server(serverUrl).calendarEvents.list;
  outlookLog('Fetching events from Rocket.Chat server:', {
    url,
    userId,
    hasToken: !!token
  });
  try {
    var _response$data;
    const response = await axios__default.default.get(url, {
      headers: {
        'Content-Type': 'application/json',
        'X-Auth-Token': token,
        'X-User-Id': userId
      },
      params: {
        date: new Date().toISOString()
      },
      timeout: AXIOS_TIMEOUT_MS,
      httpsAgent
    });
    outlookLog('Successfully fetched events from server:', {
      statusCode: response.status,
      eventCount: ((_response$data = response.data) === null || _response$data === void 0 || (_response$data = _response$data.data) === null || _response$data === void 0 ? void 0 : _response$data.length) || 0
    });
    outlookEventDetail('RC server events response:', response.data);
    return response.data;
  } catch (error) {
    var _error$response, _error$response2, _error$response3;
    const classifiedError = createClassifiedError(error, {
      operation: 'fetch_events_from_rocket_chat',
      url,
      userId,
      isAxiosError: axios__default.default.isAxiosError(error),
      status: axios__default.default.isAxiosError(error) ? (_error$response = error.response) === null || _error$response === undefined ? undefined : _error$response.status : undefined,
      statusText: axios__default.default.isAxiosError(error) ? (_error$response2 = error.response) === null || _error$response2 === undefined ? undefined : _error$response2.statusText : undefined,
      responseData: axios__default.default.isAxiosError(error) ? (_error$response3 = error.response) === null || _error$response3 === undefined ? undefined : _error$response3.data : undefined
    });
    outlookError(formatErrorForLogging(classifiedError, 'Fetch events from Rocket.Chat server'));
    return null;
  }
}
async function createEventOnRocketChatServer(serverUrl, userId, token, event, httpsAgent) {
  const url = server(serverUrl).calendarEvents.import;
  outlookLog('Creating event on Rocket.Chat server:', {
    url,
    eventId: event.id,
    subject: event.subject,
    userId
  });
  try {
    // Get server object to check version
    const {
      servers
    } = select(selectPersistableValues);
    const server = servers.find(server => server.url === serverUrl);
    const payload = {
      externalId: event.id,
      subject: event.subject,
      startTime: event.startTime,
      description: event.description,
      reminderMinutesBeforeStart: event.reminderMinutesBeforeStart
    };

    // Add endTime and busy only for server version 7.5.0 or higher
    if (server !== null && server !== void 0 && server.version && meetsMinimumVersion(server.version, '7.5.0')) {
      payload.endTime = event.endTime;
      payload.busy = event.busy;
      outlookLog('Including endTime and busy status (server version >= 7.5.0)');
    }
    outlookEventDetail('Create event payload:', payload);
    const response = await axios__default.default.post(url, payload, {
      headers: {
        'Content-Type': 'application/json',
        'X-Auth-Token': token,
        'X-User-Id': userId
      },
      timeout: AXIOS_TIMEOUT_MS,
      httpsAgent
    });
    outlookLog('Successfully created event:', {
      eventId: event.id,
      statusCode: response.status,
      responseData: response.data
    });
    outlookEventDetail('Create event response:', response.data);
    return {
      success: true
    };
  } catch (error) {
    if (axios__default.default.isAxiosError(error)) {
      var _error$response4, _error$response5, _error$response6;
      outlookError('Axios error creating event:', {
        url,
        eventId: event.id,
        status: (_error$response4 = error.response) === null || _error$response4 === undefined ? undefined : _error$response4.status,
        statusText: (_error$response5 = error.response) === null || _error$response5 === undefined ? undefined : _error$response5.statusText,
        message: error.message,
        code: error.code,
        userId
      });
      outlookLog('Verbose error details (create):', {
        subject: event.subject,
        responseData: (_error$response6 = error.response) === null || _error$response6 === undefined ? undefined : _error$response6.data
      });
    } else {
      const errorMessage = error instanceof Error ? error.message : String(error);
      outlookError('Non-axios error creating event:', {
        url,
        eventId: event.id,
        error: errorMessage,
        userId
      });
    }
    return {
      success: false,
      error: error
    };
  }
}
async function updateEventOnRocketChatServer(serverUrl, userId, token, rocketChatEventId, event, httpsAgent) {
  const url = server(serverUrl).calendarEvents.update;
  outlookLog('Updating event on Rocket.Chat server:', {
    url,
    rocketChatEventId,
    eventId: event.id,
    subject: event.subject,
    userId
  });
  try {
    // Get server object to check version
    const {
      servers
    } = select(selectPersistableValues);
    const server = servers.find(server => server.url === serverUrl);
    const payload = {
      eventId: rocketChatEventId,
      subject: event.subject,
      startTime: event.startTime,
      description: event.description,
      reminderMinutesBeforeStart: event.reminderMinutesBeforeStart
    };

    // Add endTime and busy only for server version 7.5.0 or higher
    if (server !== null && server !== void 0 && server.version && meetsMinimumVersion(server.version, '7.5.0')) {
      payload.endTime = event.endTime;
      payload.busy = event.busy;
      outlookLog('Including endTime and busy status for update (server version >= 7.5.0)');
    }
    outlookEventDetail('Update event payload:', payload);
    const response = await axios__default.default.post(url, payload, {
      headers: {
        'Content-Type': 'application/json',
        'X-Auth-Token': token,
        'X-User-Id': userId
      },
      timeout: AXIOS_TIMEOUT_MS,
      httpsAgent
    });
    outlookLog('Successfully updated event:', {
      rocketChatEventId,
      eventId: event.id,
      statusCode: response.status,
      responseData: response.data
    });
    outlookEventDetail('Update event response:', response.data);
    return {
      success: true
    };
  } catch (error) {
    if (axios__default.default.isAxiosError(error)) {
      var _error$response7, _error$response8, _error$response9;
      outlookError('Axios error updating event:', {
        url,
        rocketChatEventId,
        eventId: event.id,
        status: (_error$response7 = error.response) === null || _error$response7 === undefined ? undefined : _error$response7.status,
        statusText: (_error$response8 = error.response) === null || _error$response8 === undefined ? undefined : _error$response8.statusText,
        message: error.message,
        code: error.code,
        userId
      });
      outlookLog('Verbose error details (update):', {
        subject: event.subject,
        responseData: (_error$response9 = error.response) === null || _error$response9 === undefined ? undefined : _error$response9.data
      });
    } else {
      const errorMessage = error instanceof Error ? error.message : String(error);
      outlookError('Non-axios error updating event:', {
        url,
        rocketChatEventId,
        eventId: event.id,
        error: errorMessage,
        userId
      });
    }
    return {
      success: false,
      error: error
    };
  }
}
async function deleteEventOnRocketChatServer(serverUrl, userId, token, rocketChatEventId, httpsAgent) {
  const url = server(serverUrl).calendarEvents.delete;
  outlookLog('Deleting event from Rocket.Chat server:', {
    url,
    rocketChatEventId,
    userId
  });
  try {
    const response = await axios__default.default.post(url, {
      eventId: rocketChatEventId
    }, {
      headers: {
        'Content-Type': 'application/json',
        'X-Auth-Token': token,
        'X-User-Id': userId
      },
      timeout: AXIOS_TIMEOUT_MS,
      httpsAgent
    });
    outlookLog('Successfully deleted event:', {
      rocketChatEventId,
      statusCode: response.status,
      responseData: response.data
    });
    outlookEventDetail('Delete event response:', response.data);
    return {
      success: true
    };
  } catch (error) {
    if (axios__default.default.isAxiosError(error)) {
      var _error$response0, _error$response1, _error$response10;
      outlookError('Axios error deleting event:', {
        url,
        rocketChatEventId,
        status: (_error$response0 = error.response) === null || _error$response0 === undefined ? undefined : _error$response0.status,
        statusText: (_error$response1 = error.response) === null || _error$response1 === undefined ? undefined : _error$response1.statusText,
        message: error.message,
        code: error.code,
        userId
      });
      outlookLog('Verbose error details (delete):', {
        responseData: (_error$response10 = error.response) === null || _error$response10 === undefined ? undefined : _error$response10.data
      });
    } else {
      const errorMessage = error instanceof Error ? error.message : String(error);
      outlookError('Non-axios error deleting event:', {
        url,
        rocketChatEventId,
        error: errorMessage,
        userId
      });
    }
    return {
      success: false,
      error: error
    };
  }
}

/**
 * Synchronizes Outlook calendar events with the Rocket.Chat server.
 *
 * This function implements sync coalescing: when a sync is already in progress,
 * subsequent sync requests are queued. Only the LAST queued sync actually runs,
 * as it contains the most recent state. Earlier queued syncs resolve immediately
 * since their changes would be superseded by the final sync.
 *
 * This prevents redundant API calls when multiple sync triggers fire in quick
 * succession (e.g., during app startup or rapid credential updates).
 *
 * @param serverUrl - The Rocket.Chat server URL
 * @param credentials - Outlook Exchange credentials
 * @param token - Rocket.Chat authentication token
 */
async function syncEventsWithRocketChatServer(serverUrl, credentials, token, allowInsecure = false) {
  // Validate token before doing anything else
  if (!token || typeof token !== 'string') {
    throw new Error('Authentication required - please log in to Rocket.Chat first');
  }
  const state = getServerState(serverUrl);

  // Check if sync is already in progress
  if (state.isSyncInProgress) {
    outlookLog('Sync already in progress, queueing this request');

    // Queue this sync request to run after current sync completes
    return new Promise((resolve, reject) => {
      state.syncQueue.push({
        run: async () => {
          if (!token || typeof token !== 'string') {
            throw new Error('Authentication required - please log in to Rocket.Chat first');
          }
          await performSync(serverUrl, credentials, token, allowInsecure);
        },
        resolve,
        reject
      });
    });
  }

  // Set lock
  state.isSyncInProgress = true;
  try {
    await performSync(serverUrl, credentials, token, allowInsecure);

    // Process queued syncs - loop until queue is truly empty
    // (new syncs can be queued while lastSync.run() executes)
    while (state.syncQueue.length > 0) {
      outlookLog(`Processing ${state.syncQueue.length} queued sync requests`);
      // Only process the last sync request (most recent state)
      const lastSync = state.syncQueue[state.syncQueue.length - 1];
      // Resolve all other queued syncs since the last one will cover them
      const skippedSyncs = state.syncQueue.slice(0, -1);
      state.syncQueue = [];
      try {
        // eslint-disable-next-line no-await-in-loop -- Must drain queue sequentially
        await lastSync.run();
        lastSync.resolve();
        for (const skipped of skippedSyncs) {
          skipped.resolve();
        }
      } catch (error) {
        outlookError('Queued sync failed:', error);
        lastSync.reject(error);
        for (const skipped of skippedSyncs) {
          skipped.reject(error);
        }
      }
    }
  } finally {
    // Release lock after all syncs complete
    state.isSyncInProgress = false;
  }
}

// eslint-disable-next-line complexity
async function performSync(serverUrl, credentials, token, allowInsecure = false) {
  var _eventsOnRocketChatSe2, _eventsOnRocketChatSe3, _eventsOnRocketChatSe4;
  outlookLog('Starting Outlook calendar synchronization for server:', serverUrl);
  if (!checkIfCredentialsAreNotEmpty(credentials)) return;
  outlookLog('Starting sync with Rocket.Chat server:', {
    serverUrl,
    userId: credentials.userId,
    hasToken: !!token
  });

  // Validate input parameters
  if (!serverUrl || typeof serverUrl !== 'string') {
    throw new Error('Invalid server URL provided');
  }
  if (!credentials || typeof credentials !== 'object') {
    throw new Error('Invalid credentials provided');
  }

  // Token is already validated in syncEventsWithRocketChatServer

  // Create a single HTTPS agent instance to reuse across all requests in this sync
  const httpsAgent = allowInsecure ? createInsecureHttpsAgent() : undefined;
  let eventsOnOutlookServer;
  let eventsOnRocketChatServer;
  try {
    var _eventsOnRocketChatSe;
    outlookLog('Fetching events from Outlook server...');
    eventsOnOutlookServer = await getOutlookEvents(credentials, new Date(Date.now()), allowInsecure);
    outlookLog('Found', eventsOnOutlookServer.length, 'events on Outlook server');
    outlookLog('Fetching events from Rocket.Chat server...');
    eventsOnRocketChatServer = await listEventsFromRocketChatServer(serverUrl, credentials.userId, token, httpsAgent);

    // If we can't fetch events from the server, propagate the failure
    if (!eventsOnRocketChatServer) {
      throw new Error('Failed to fetch events from Rocket.Chat server - sync cannot proceed');
    }
    outlookLog('Found', ((_eventsOnRocketChatSe = eventsOnRocketChatServer) === null || _eventsOnRocketChatSe === void 0 || (_eventsOnRocketChatSe = _eventsOnRocketChatSe.data) === null || _eventsOnRocketChatSe === void 0 ? void 0 : _eventsOnRocketChatSe.length) || 0, 'events on Rocket.Chat server');
  } catch (error) {
    const classifiedError = createClassifiedError(error, {
      operation: 'fetch_events_during_sync',
      serverUrl,
      userId: credentials.userId,
      outlookServerUrl: credentials.serverUrl
    });
    outlookError(formatErrorForLogging(classifiedError, 'Fetch events during sync'));
    throw new Error(`Sync failed during event fetching: ${classifiedError.technicalMessage}`);
  }

  // Get server object to check version
  const {
    servers
  } = select(selectPersistableValues);
  const server = servers.find(server => server.url === serverUrl);
  const appointmentsFound = eventsOnOutlookServer.map(appointment => appointment.id);
  const externalEventsFromRocketChatServer = ((_eventsOnRocketChatSe2 = eventsOnRocketChatServer) === null || _eventsOnRocketChatSe2 === undefined || (_eventsOnRocketChatSe2 = _eventsOnRocketChatSe2.data) === null || _eventsOnRocketChatSe2 === undefined ? undefined : _eventsOnRocketChatSe2.filter(({
    externalId
  }) => externalId)) || [];

  // Check for and clean up duplicate events
  const eventsByExternalId = new Map();
  for (const event of externalEventsFromRocketChatServer) {
    if (!eventsByExternalId.has(event.externalId)) {
      eventsByExternalId.set(event.externalId, []);
    }
    eventsByExternalId.get(event.externalId).push(event);
  }

  // Collect all duplicate deletion promises
  const allDeletionPromises = [];

  // Remove duplicates - keep only the first occurrence
  for (const [externalId, events] of eventsByExternalId) {
    if (events.length > 1) {
      outlookLog(`Found ${events.length} duplicate events for externalId: ${externalId}`);
      outlookLog('Keeping first event, deleting duplicates');

      // Delete all duplicates except the first one
      const deletionPromises = events.slice(1).map(async duplicateEvent => {
        outlookLog('Deleting duplicate event:', {
          rocketChatId: duplicateEvent._id,
          externalId: duplicateEvent.externalId,
          subject: duplicateEvent.subject
        });
        try {
          await deleteEventOnRocketChatServer(serverUrl, credentials.userId, token, duplicateEvent._id, httpsAgent);
        } catch (error) {
          outlookError('Failed to delete duplicate event:', {
            eventId: duplicateEvent._id,
            error: error instanceof Error ? error.message : String(error)
          });
        }
      });
      allDeletionPromises.push(...deletionPromises);
    }
  }

  // Wait for all duplicate deletions to complete
  if (allDeletionPromises.length > 0) {
    outlookLog('Waiting for duplicate deletion to complete...');
    await Promise.all(allDeletionPromises);
    outlookLog('Duplicate deletion completed');
  }
  outlookLog('Starting sync loop for', eventsOnOutlookServer.length, 'Outlook events');
  let syncCreated = 0;
  let syncUpdated = 0;
  let syncSkipped = 0;
  for await (const appointment of eventsOnOutlookServer) {
    try {
      outlookLog('Processing appointment:', {
        id: appointment.id,
        subject: appointment.subject,
        startTime: appointment.startTime
      });

      // Use the deduplicated map to find the event (only first occurrence kept)
      const eventsForThisAppointment = eventsByExternalId.get(appointment.id) || [];
      const alreadyOnRocketChatServer = eventsForThisAppointment[0];
      const {
        subject,
        startTime,
        description,
        reminderMinutesBeforeStart
      } = appointment;

      // If the appointment is not in the rocket.chat calendar for today, add it.
      if (!alreadyOnRocketChatServer) {
        outlookLog('Creating new event in Rocket.Chat:', appointment.id);
        outlookEventDetail('New event from Exchange:', appointment);
        const createResult = await createEventOnRocketChatServer(serverUrl, credentials.userId, token, appointment, httpsAgent);
        if (createResult.success) syncCreated++;
        continue;
      }

      // If nothing on the event has changed, do nothing.
      const hasChanges = !(alreadyOnRocketChatServer.subject === subject && alreadyOnRocketChatServer.startTime === startTime && alreadyOnRocketChatServer.description === description && alreadyOnRocketChatServer.reminderMinutesBeforeStart === reminderMinutesBeforeStart && (!(server !== null && server !== void 0 && server.version) || !meetsMinimumVersion(server.version, '7.5.0') || alreadyOnRocketChatServer.endTime === appointment.endTime && alreadyOnRocketChatServer.busy === appointment.busy));
      if (!hasChanges) {
        outlookLog('No changes detected for event:', appointment.id);
        outlookEventDetail('Unchanged event comparison:', {
          outlookEvent: appointment,
          rcEvent: alreadyOnRocketChatServer
        });
        syncSkipped++;
        continue;
      }

      // If the appointment is in the rocket.chat calendar for today, but something has changed, update it.
      outlookLog('Updating existing event in Rocket.Chat:', {
        rocketChatId: alreadyOnRocketChatServer._id,
        outlookId: appointment.id
      });
      outlookEventDetail('Event changed — before (RC):', alreadyOnRocketChatServer);
      outlookEventDetail('Event changed — after (Outlook):', appointment);
      const updateResult = await updateEventOnRocketChatServer(serverUrl, credentials.userId, token, alreadyOnRocketChatServer._id, appointment, httpsAgent);
      if (updateResult.success) syncUpdated++;
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      outlookError('Error syncing individual event:', {
        appointmentId: appointment.id,
        error: errorMessage
      });
      outlookLog('Verbose error details (sync event):', {
        subject: appointment.subject
      });
      // Continue with other events even if one fails
    }
  }
  if (!((_eventsOnRocketChatSe3 = eventsOnRocketChatServer) !== null && _eventsOnRocketChatSe3 !== undefined && (_eventsOnRocketChatSe3 = _eventsOnRocketChatSe3.data) !== null && _eventsOnRocketChatSe3 !== undefined && _eventsOnRocketChatSe3.length)) {
    outlookLog('No events on Rocket.Chat server to check for deletion');
    return;
  }
  outlookLog('Checking for events to delete from Rocket.Chat server');
  const eventsToDelete = eventsOnRocketChatServer.data.filter(event => event.externalId && !appointmentsFound.includes(event.externalId));
  if (eventsToDelete.length === 0) {
    outlookLog('No events need to be deleted');
  } else {
    outlookLog('Found', eventsToDelete.length, 'events to delete');
  }
  for await (const event of eventsToDelete) {
    try {
      outlookLog('Deleting event from Rocket.Chat:', {
        rocketChatId: event._id,
        externalId: event.externalId,
        subject: event.subject
      });
      await deleteEventOnRocketChatServer(serverUrl, credentials.userId, token, event._id, httpsAgent);
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      outlookError('Error deleting individual event:', {
        eventId: event._id,
        externalId: event.externalId,
        error: errorMessage
      });
      outlookLog('Verbose error details (delete sync):', {
        subject: event.subject
      });
      // Continue with other deletions even if one fails
    }
  }
  const syncDeleted = eventsToDelete.length;
  outlookLog('Sync completed successfully');
  outlookEventDetail('Sync summary:', {
    created: syncCreated,
    updated: syncUpdated,
    deleted: syncDeleted,
    unchanged: syncSkipped,
    totalOutlookEvents: eventsOnOutlookServer.length,
    totalRcEvents: ((_eventsOnRocketChatSe4 = eventsOnRocketChatServer) === null || _eventsOnRocketChatSe4 === undefined || (_eventsOnRocketChatSe4 = _eventsOnRocketChatSe4.data) === null || _eventsOnRocketChatSe4 === undefined ? undefined : _eventsOnRocketChatSe4.length) || 0
  });
}
const serverSyncStates = new Map();
const getServerState = serverUrl => {
  if (!serverSyncStates.has(serverUrl)) {
    serverSyncStates.set(serverUrl, {
      isSyncInProgress: false,
      syncQueue: []
    });
  }
  return serverSyncStates.get(serverUrl);
};
const clearServerState = serverUrl => {
  const state = serverSyncStates.get(serverUrl);
  if (state) {
    if (state.recurringSyncTaskId) {
      clearInterval(state.recurringSyncTaskId);
    }
    if (state.initialSyncTimeoutId) {
      clearTimeout(state.initialSyncTimeoutId);
    }
    if (state.restartDebounceTimer) {
      clearTimeout(state.restartDebounceTimer);
    }
    serverSyncStates.delete(serverUrl);
  }
};
async function maybeSyncEvents(serverToSync) {
  outlookLog('Starting maybeSyncEvents for server:', serverToSync.url);
  const state = getServerState(serverToSync.url);
  try {
    if (!state.userAPIToken) {
      throw new Error('No user API token available');
    }
    if (!serverToSync.webContentsId) {
      throw new Error('No webContentsId available for server');
    }
    const server = getServerInformationByWebContentsId(serverToSync.webContentsId);
    if (!(server !== null && server !== void 0 && server.url)) {
      throw new Error('Server information not found');
    }
    if (!server.outlookCredentials) {
      throw new Error('No Outlook credentials configured for server');
    }
    outlookLog('Decrypting credentials...');
    const credentials = electron.safeStorage.isEncryptionAvailable() ? decryptedCredentials(server.outlookCredentials) : server.outlookCredentials;
    if (!checkIfCredentialsAreNotEmpty(credentials)) {
      throw new Error('Outlook credentials are empty or invalid');
    }
    const allowInsecureOutlookConnections = select(state => state.allowInsecureOutlookConnections);
    outlookLog('Starting sync with server:', server.url);
    await syncEventsWithRocketChatServer(server.url, credentials, state.userAPIToken, allowInsecureOutlookConnections);
    outlookLog('Sync task completed successfully');
  } catch (error) {
    const classifiedError = createClassifiedError(error, {
      operation: 'maybe_sync_events',
      serverUrl: serverToSync.url,
      hasToken: !!state.userAPIToken,
      webContentsId: serverToSync.webContentsId
    });
    outlookError(formatErrorForLogging(classifiedError, 'Maybe sync events'));
    throw error;
  }
}
async function recurringSyncTask(serverToSync) {
  const state = getServerState(serverToSync.url);
  try {
    outlookLog('Executing recurring sync task for server:', serverToSync.url);
    await maybeSyncEvents(serverToSync);
    outlookLog('Recurring sync task completed successfully');
  } catch (error) {
    const classifiedError = createClassifiedError(error, {
      operation: 'recurring_sync_task',
      serverUrl: serverToSync.url
    });
    outlookError(formatErrorForLogging(classifiedError, 'Recurring sync task'));
    outlookLog('User-friendly error message for recurring sync:');
    outlookLog(generateUserFriendlyMessage(classifiedError));
    outlookLog('Stopping recurring sync due to persistent errors');
    if (state.recurringSyncTaskId) {
      clearInterval(state.recurringSyncTaskId);
      state.recurringSyncTaskId = undefined;
    }
  }
}
function startRecurringSyncTask(server) {
  const state = getServerState(server.url);
  if (!state.userAPIToken) return;

  // Clear any existing recurring sync task to prevent duplicates
  if (state.recurringSyncTaskId) {
    outlookLog('Clearing existing recurring sync task');
    clearInterval(state.recurringSyncTaskId);
  }
  const intervalMinutes = select(state => {
    var _state$outlookCalenda;
    return (_state$outlookCalenda = state.outlookCalendarSyncIntervalOverride) !== null && _state$outlookCalenda !== undefined ? _state$outlookCalenda : state.outlookCalendarSyncInterval;
  });
  state.serverRef = server;
  state.recurringSyncTaskId = setInterval(() => recurringSyncTask(server), intervalMinutes * 60 * 1000);
}
const startOutlookCalendarUrlHandler = () => {
  handle('outlook-calendar/set-user-token', async (event, token, userId) => {
    outlookLog('Setting user token for webContents:', event.id);
    try {
      if (!token || typeof token !== 'string') {
        outlookError('Invalid token provided');
        return;
      }
      if (!userId || typeof userId !== 'string') {
        outlookError('Invalid userId provided');
        return;
      }
      const server = getServerInformationByWebContentsId(event.id);
      if (!(server !== null && server !== void 0 && server.url)) {
        outlookError('Server not found for webContents:', event.id);
        return;
      }
      const state = getServerState(server.url);
      const {
        outlookCredentials
      } = server;
      if (!outlookCredentials) {
        outlookLog('No Outlook credentials configured for server:', server.url);
        return;
      }
      if (outlookCredentials.userId !== userId) {
        outlookLog('User ID mismatch - credentials are for different user');
        return;
      }
      state.userAPIToken = token;
      outlookLog('User API token set successfully');
      if (!state.userAPIToken) {
        outlookError('User API token is empty');
        return;
      }
      if (!checkIfCredentialsAreNotEmpty(outlookCredentials)) {
        outlookLog('Outlook credentials are empty');
        return;
      }
      outlookLog('Starting recurring sync task for server:', server.url);
      startRecurringSyncTask(server);

      // Cancel any pending initial sync to prevent duplicates
      if (state.initialSyncTimeoutId) {
        outlookLog('Cancelling pending initial sync');
        clearTimeout(state.initialSyncTimeoutId);
      }

      // Perform initial sync with debounce to prevent duplicate calls
      state.initialSyncTimeoutId = setTimeout(() => {
        outlookLog('Executing initial sync');
        maybeSyncEvents(server).catch(error => {
          const errorMessage = error instanceof Error ? error.message : String(error);
          outlookError('Failed to sync outlook events on startup:', {
            serverUrl: server.url,
            userId,
            error: errorMessage
          });
        });
      }, INITIAL_SYNC_DEBOUNCE_MS);
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      outlookError('Error in set-user-token handler:', {
        webContentsId: event.id,
        userId,
        error: errorMessage
      });
    }
  });
  handle('outlook-calendar/clear-credentials', async event => {
    const server = getServerInformationByWebContentsId(event.id);
    if (!(server !== null && server !== undefined && server.url)) return;
    const {
      outlookCredentials
    } = server;
    if (!outlookCredentials) return;

    // Clear server sync state (stops recurring sync, clears timers)
    clearServerState(server.url);
    dispatch({
      type: OUTLOOK_CALENDAR_SAVE_CREDENTIALS,
      payload: {
        url: server.url,
        outlookCredentials: {
          userId: outlookCredentials.userId,
          serverUrl: outlookCredentials.serverUrl,
          login: '',
          password: ''
        }
      }
    });
  });
  handle('outlook-calendar/set-exchange-url', async (event, url, userId) => {
    const server = getServerInformationByWebContentsId(event.id);
    if (!server) return;
    const {
      outlookCredentials
    } = server;
    if ((outlookCredentials === null || outlookCredentials === undefined ? undefined : outlookCredentials.userId) !== userId || (outlookCredentials === null || outlookCredentials === undefined ? undefined : outlookCredentials.serverUrl) !== url) {
      dispatch({
        type: OUTLOOK_CALENDAR_SAVE_CREDENTIALS,
        payload: {
          url: server.url,
          outlookCredentials: {
            userId,
            serverUrl: url,
            login: '',
            password: ''
          }
        }
      });
    }
  });
  handle('outlook-calendar/has-credentials', async event => {
    const server = getServerInformationByWebContentsId(event.id);
    if (!server) return false;
    const {
      outlookCredentials
    } = server;
    if (!outlookCredentials) return false;
    return checkIfCredentialsAreNotEmpty(outlookCredentials);
  });
  handle('outlook-calendar/get-events', async (event, _date) => {
    const server = getServerInformationByWebContentsId(event.id);
    const {
      outlookCredentials
    } = server;
    if (
    // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
    !outlookCredentials || !outlookCredentials.userId || !outlookCredentials.serverUrl) {
      return Promise.reject(new Error('No credentials'));
    }
    const state = getServerState(server.url);
    let credentials;
    let saveCredentials = false;
    if (!checkIfCredentialsAreNotEmpty(outlookCredentials)) {
      const response = await request({
        type: OUTLOOK_CALENDAR_ASK_CREDENTIALS,
        payload: {
          server,
          userId: outlookCredentials.userId,
          isEncryptionAvailable: electron.safeStorage.isEncryptionAvailable()
        }
      }, OUTLOOK_CALENDAR_SET_CREDENTIALS, OUTLOOK_CALENDAR_DIALOG_DISMISSED);
      if (response.dismissDialog === true) {
        return {
          status: 'canceled'
        };
      }
      if (!checkIfCredentialsAreNotEmpty(response === null || response === undefined ? undefined : response.outlookCredentials)) {
        return Promise.reject(new Error('Invalid credentials'));
      }
      credentials = response.outlookCredentials;
      saveCredentials = response.saveCredentials || false;
    } else {
      credentials = electron.safeStorage.isEncryptionAvailable() ? decryptedCredentials(outlookCredentials) : outlookCredentials;
    }
    const allowInsecureOutlookConnections = select(state => state.allowInsecureOutlookConnections);
    try {
      // Check if user is logged in before attempting sync
      if (!state.userAPIToken) {
        outlookWarn('Manual sync attempted but token not available', {
          serverUrl: server.url,
          userId: outlookCredentials.userId,
          hint: 'Token is set by injected script when: 1) User is logged in, 2) Outlook is enabled in settings, 3) Exchange URL is configured'
        });

        // Try to get the token from the webContents
        outlookLog('Attempting to fetch token from webContents...');
        const contents = electron.webContents.fromId(event.id);
        if (contents) {
          try {
            const token = await contents.executeJavaScript(`Meteor._localStorage?.getItem('Meteor.loginToken')`);
            if (token) {
              outlookLog('Successfully retrieved token from webContents');
              state.userAPIToken = token; // Store it for future use
              // Continue with sync using the retrieved token
              await syncEventsWithRocketChatServer(server.url, credentials, token, allowInsecureOutlookConnections);
              return {
                status: 'success'
              };
            }
          } catch (error) {
            outlookError('Failed to get token from webContents:', error);
          }
        }
        return Promise.reject(new Error('Authentication token not yet available. Please ensure: 1) You are logged into Rocket.Chat, 2) Outlook Calendar is enabled in settings, 3) Exchange URL is configured.'));
      }
      await syncEventsWithRocketChatServer(server.url, credentials, state.userAPIToken, allowInsecureOutlookConnections);
    } catch (e) {
      const classifiedError = createClassifiedError(e, {
        operation: 'sync_events_with_rocket_chat',
        serverUrl: server.url,
        userId: credentials.userId,
        hasToken: !!state.userAPIToken
      });
      outlookError(formatErrorForLogging(classifiedError, 'Sync events with Rocket.Chat server'));
      outlookLog('User-friendly error message:');
      outlookLog(generateUserFriendlyMessage(classifiedError));
      throw e;
    }
    if (saveCredentials) {
      startRecurringSyncTask(server);
      dispatch({
        type: OUTLOOK_CALENDAR_SAVE_CREDENTIALS,
        payload: {
          url: server.url,
          outlookCredentials: electron.safeStorage.isEncryptionAvailable() ? encryptedCredentials(credentials) : credentials
        }
      });
    }
    return {
      status: 'success'
    };
  });
  watch(state => {
    var _state$outlookCalenda2;
    return (_state$outlookCalenda2 = state.outlookCalendarSyncIntervalOverride) !== null && _state$outlookCalenda2 !== undefined ? _state$outlookCalenda2 : state.outlookCalendarSyncInterval;
  }, (curr, prev) => {
    if (prev === undefined || curr === prev) return;
    outlookLog(`Outlook sync interval changed to ${curr} minutes, rescheduling sync jobs`);
    for (const [, state] of serverSyncStates) {
      if (!state.serverRef || !state.userAPIToken) continue;
      clearTimeout(state.restartDebounceTimer);
      const server = state.serverRef;
      state.restartDebounceTimer = setTimeout(async () => {
        try {
          await maybeSyncEvents(server);
        } catch (e) {
          outlookError('Error syncing after interval change:', e);
        }
        startRecurringSyncTask(server);
      }, 10000);
    }
  });
};
const stopOutlookCalendarSync = () => {
  for (const [serverUrl] of serverSyncStates) {
    clearServerState(serverUrl);
  }
};

const setupScreenSharing = () => {
  listen(WEBVIEW_SCREEN_SHARING_SOURCE_REQUESTED, requestAction => {
    if (!hasMeta(requestAction) || !requestAction.meta.id) {
      return;
    }
    const predicate = action => isResponseTo(requestAction.meta.id, WEBVIEW_SCREEN_SHARING_SOURCE_RESPONDED, SCREEN_SHARING_DIALOG_DISMISSED)(action);
    const unsubscribe = listen(predicate, responseAction => {
      var _requestAction$meta;
      unsubscribe();
      const sourceId = responseAction.type === WEBVIEW_SCREEN_SHARING_SOURCE_RESPONDED ? responseAction.payload : null;
      dispatch({
        type: WEBVIEW_SCREEN_SHARING_SOURCE_RESPONDED,
        payload: sourceId,
        meta: {
          response: true,
          id: (_requestAction$meta = requestAction.meta) === null || _requestAction$meta === undefined ? undefined : _requestAction$meta.id
        }
      });
    });
  });
};

const clearWebviewStorageKeepingLoginData = async guestWebContents => {
  if (!guestWebContents) return;
  await guestWebContents.session.clearCache();
  await guestWebContents.session.clearStorageData({
    storages: ['cookies', 'indexdb', 'filesystem', 'shadercache', 'websql', 'serviceworkers', 'cachestorage']
  });
  guestWebContents === null || guestWebContents === undefined || guestWebContents.reloadIgnoringCache();
};
const clearWebviewStorageDeletingLoginData = async guestWebContents => {
  if (!guestWebContents) return;
  await guestWebContents.session.clearCache();
  await guestWebContents.session.clearStorageData();
  guestWebContents === null || guestWebContents === undefined || guestWebContents.reloadIgnoringCache();
};
const handleClearCacheDialog = () => {
  listen(CLEAR_CACHE_DIALOG_KEEP_LOGIN_DATA_CLICKED, async action => {
    const guestWebContents = electron.webContents.fromId(action.payload);
    if (!guestWebContents) {
      return;
    }
    await clearWebviewStorageKeepingLoginData(guestWebContents);
  });
  listen(CLEAR_CACHE_DIALOG_DELETE_LOGIN_DATA_CLICKED, async action => {
    const guestWebContents = electron.webContents.fromId(action.payload);
    if (!guestWebContents) {
      return;
    }
    await clearWebviewStorageDeletingLoginData(guestWebContents);
  });
};

function getDefaultExportFromCjs (x) {
	return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}

const debug$1 = typeof process === 'object' && process.env && process.env.NODE_DEBUG && /\bsemver\b/i.test(process.env.NODE_DEBUG) ? (...args) => console.error('SEMVER', ...args) : () => {};
var debug_1 = debug$1;

// Note: this is the semver.org version of the spec that it implements
// Not necessarily the package version of this code.
const SEMVER_SPEC_VERSION = '2.0.0';
const MAX_LENGTH$1 = 256;
const MAX_SAFE_INTEGER$1 = Number.MAX_SAFE_INTEGER || /* istanbul ignore next */9007199254740991;

// Max safe segment length for coercion.
const MAX_SAFE_COMPONENT_LENGTH = 16;

// Max safe length for a build identifier. The max length minus 6 characters for
// the shortest version with a build 0.0.0+BUILD.
const MAX_SAFE_BUILD_LENGTH = MAX_LENGTH$1 - 6;
const RELEASE_TYPES = ['major', 'premajor', 'minor', 'preminor', 'patch', 'prepatch', 'prerelease'];
var constants = {
  MAX_LENGTH: MAX_LENGTH$1,
  MAX_SAFE_COMPONENT_LENGTH,
  MAX_SAFE_BUILD_LENGTH,
  MAX_SAFE_INTEGER: MAX_SAFE_INTEGER$1,
  RELEASE_TYPES,
  SEMVER_SPEC_VERSION,
  FLAG_INCLUDE_PRERELEASE: 0b001,
  FLAG_LOOSE: 0b010
};

var re$1 = {exports: {}};

(function (module, exports) {
	const {
	  MAX_SAFE_COMPONENT_LENGTH,
	  MAX_SAFE_BUILD_LENGTH,
	  MAX_LENGTH
	} = constants;
	const debug = debug_1;
	exports = module.exports = {};

	// The actual regexps go on exports.re
	const re = exports.re = [];
	const safeRe = exports.safeRe = [];
	const src = exports.src = [];
	const t = exports.t = {};
	let R = 0;
	const LETTERDASHNUMBER = '[a-zA-Z0-9-]';

	// Replace some greedy regex tokens to prevent regex dos issues. These regex are
	// used internally via the safeRe object since all inputs in this library get
	// normalized first to trim and collapse all extra whitespace. The original
	// regexes are exported for userland consumption and lower level usage. A
	// future breaking change could export the safer regex only with a note that
	// all input should have extra whitespace removed.
	const safeRegexReplacements = [['\\s', 1], ['\\d', MAX_LENGTH], [LETTERDASHNUMBER, MAX_SAFE_BUILD_LENGTH]];
	const makeSafeRegex = value => {
	  for (const [token, max] of safeRegexReplacements) {
	    value = value.split(`${token}*`).join(`${token}{0,${max}}`).split(`${token}+`).join(`${token}{1,${max}}`);
	  }
	  return value;
	};
	const createToken = (name, value, isGlobal) => {
	  const safe = makeSafeRegex(value);
	  const index = R++;
	  debug(name, index, value);
	  t[name] = index;
	  src[index] = value;
	  re[index] = new RegExp(value, isGlobal ? 'g' : undefined);
	  safeRe[index] = new RegExp(safe, isGlobal ? 'g' : undefined);
	};

	// The following Regular Expressions can be used for tokenizing,
	// validating, and parsing SemVer version strings.

	// ## Numeric Identifier
	// A single `0`, or a non-zero digit followed by zero or more digits.

	createToken('NUMERICIDENTIFIER', '0|[1-9]\\d*');
	createToken('NUMERICIDENTIFIERLOOSE', '\\d+');

	// ## Non-numeric Identifier
	// Zero or more digits, followed by a letter or hyphen, and then zero or
	// more letters, digits, or hyphens.

	createToken('NONNUMERICIDENTIFIER', `\\d*[a-zA-Z-]${LETTERDASHNUMBER}*`);

	// ## Main Version
	// Three dot-separated numeric identifiers.

	createToken('MAINVERSION', `(${src[t.NUMERICIDENTIFIER]})\\.` + `(${src[t.NUMERICIDENTIFIER]})\\.` + `(${src[t.NUMERICIDENTIFIER]})`);
	createToken('MAINVERSIONLOOSE', `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + `(${src[t.NUMERICIDENTIFIERLOOSE]})`);

	// ## Pre-release Version Identifier
	// A numeric identifier, or a non-numeric identifier.

	createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NUMERICIDENTIFIER]}|${src[t.NONNUMERICIDENTIFIER]})`);
	createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NUMERICIDENTIFIERLOOSE]}|${src[t.NONNUMERICIDENTIFIER]})`);

	// ## Pre-release Version
	// Hyphen, followed by one or more dot-separated pre-release version
	// identifiers.

	createToken('PRERELEASE', `(?:-(${src[t.PRERELEASEIDENTIFIER]}(?:\\.${src[t.PRERELEASEIDENTIFIER]})*))`);
	createToken('PRERELEASELOOSE', `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE]}(?:\\.${src[t.PRERELEASEIDENTIFIERLOOSE]})*))`);

	// ## Build Metadata Identifier
	// Any combination of digits, letters, or hyphens.

	createToken('BUILDIDENTIFIER', `${LETTERDASHNUMBER}+`);

	// ## Build Metadata
	// Plus sign, followed by one or more period-separated build metadata
	// identifiers.

	createToken('BUILD', `(?:\\+(${src[t.BUILDIDENTIFIER]}(?:\\.${src[t.BUILDIDENTIFIER]})*))`);

	// ## Full Version String
	// A main version, followed optionally by a pre-release version and
	// build metadata.

	// Note that the only major, minor, patch, and pre-release sections of
	// the version string are capturing groups.  The build metadata is not a
	// capturing group, because it should not ever be used in version
	// comparison.

	createToken('FULLPLAIN', `v?${src[t.MAINVERSION]}${src[t.PRERELEASE]}?${src[t.BUILD]}?`);
	createToken('FULL', `^${src[t.FULLPLAIN]}$`);

	// like full, but allows v1.2.3 and =1.2.3, which people do sometimes.
	// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty
	// common in the npm registry.
	createToken('LOOSEPLAIN', `[v=\\s]*${src[t.MAINVERSIONLOOSE]}${src[t.PRERELEASELOOSE]}?${src[t.BUILD]}?`);
	createToken('LOOSE', `^${src[t.LOOSEPLAIN]}$`);
	createToken('GTLT', '((?:<|>)?=?)');

	// Something like "2.*" or "1.2.x".
	// Note that "x.x" is a valid xRange identifer, meaning "any version"
	// Only the first item is strictly required.
	createToken('XRANGEIDENTIFIERLOOSE', `${src[t.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`);
	createToken('XRANGEIDENTIFIER', `${src[t.NUMERICIDENTIFIER]}|x|X|\\*`);
	createToken('XRANGEPLAIN', `[v=\\s]*(${src[t.XRANGEIDENTIFIER]})` + `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + `(?:${src[t.PRERELEASE]})?${src[t.BUILD]}?` + `)?)?`);
	createToken('XRANGEPLAINLOOSE', `[v=\\s]*(${src[t.XRANGEIDENTIFIERLOOSE]})` + `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + `(?:${src[t.PRERELEASELOOSE]})?${src[t.BUILD]}?` + `)?)?`);
	createToken('XRANGE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAIN]}$`);
	createToken('XRANGELOOSE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`);

	// Coercion.
	// Extract anything that could conceivably be a part of a valid semver
	createToken('COERCE', `${'(^|[^\\d])' + '(\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` + `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + `(?:$|[^\\d])`);
	createToken('COERCERTL', src[t.COERCE], true);

	// Tilde ranges.
	// Meaning is "reasonably at or greater than"
	createToken('LONETILDE', '(?:~>?)');
	createToken('TILDETRIM', `(\\s*)${src[t.LONETILDE]}\\s+`, true);
	exports.tildeTrimReplace = '$1~';
	createToken('TILDE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAIN]}$`);
	createToken('TILDELOOSE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAINLOOSE]}$`);

	// Caret ranges.
	// Meaning is "at least and backwards compatible with"
	createToken('LONECARET', '(?:\\^)');
	createToken('CARETTRIM', `(\\s*)${src[t.LONECARET]}\\s+`, true);
	exports.caretTrimReplace = '$1^';
	createToken('CARET', `^${src[t.LONECARET]}${src[t.XRANGEPLAIN]}$`);
	createToken('CARETLOOSE', `^${src[t.LONECARET]}${src[t.XRANGEPLAINLOOSE]}$`);

	// A simple gt/lt/eq thing, or just "" to indicate "any version"
	createToken('COMPARATORLOOSE', `^${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]})$|^$`);
	createToken('COMPARATOR', `^${src[t.GTLT]}\\s*(${src[t.FULLPLAIN]})$|^$`);

	// An expression to strip any whitespace between the gtlt and the thing
	// it modifies, so that `> 1.2.3` ==> `>1.2.3`
	createToken('COMPARATORTRIM', `(\\s*)${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]}|${src[t.XRANGEPLAIN]})`, true);
	exports.comparatorTrimReplace = '$1$2$3';

	// Something like `1.2.3 - 1.2.4`
	// Note that these all use the loose form, because they'll be
	// checked against either the strict or loose comparator form
	// later.
	createToken('HYPHENRANGE', `^\\s*(${src[t.XRANGEPLAIN]})` + `\\s+-\\s+` + `(${src[t.XRANGEPLAIN]})` + `\\s*$`);
	createToken('HYPHENRANGELOOSE', `^\\s*(${src[t.XRANGEPLAINLOOSE]})` + `\\s+-\\s+` + `(${src[t.XRANGEPLAINLOOSE]})` + `\\s*$`);

	// Star ranges basically just allow anything at all.
	createToken('STAR', '(<|>)?=?\\s*\\*');
	// >=0.0.0 is like a star
	createToken('GTE0', '^\\s*>=\\s*0\\.0\\.0\\s*$');
	createToken('GTE0PRE', '^\\s*>=\\s*0\\.0\\.0-0\\s*$'); 
} (re$1, re$1.exports));

var reExports = re$1.exports;

// parse out just the options we care about
const looseOption = Object.freeze({
  loose: true
});
const emptyOpts = Object.freeze({});
const parseOptions$1 = options => {
  if (!options) {
    return emptyOpts;
  }
  if (typeof options !== 'object') {
    return looseOption;
  }
  return options;
};
var parseOptions_1 = parseOptions$1;

const numeric = /^[0-9]+$/;
const compareIdentifiers$1 = (a, b) => {
  const anum = numeric.test(a);
  const bnum = numeric.test(b);
  if (anum && bnum) {
    a = +a;
    b = +b;
  }
  return a === b ? 0 : anum && !bnum ? -1 : bnum && !anum ? 1 : a < b ? -1 : 1;
};
const rcompareIdentifiers = (a, b) => compareIdentifiers$1(b, a);
var identifiers = {
  compareIdentifiers: compareIdentifiers$1,
  rcompareIdentifiers
};

const debug = debug_1;
const {
  MAX_LENGTH,
  MAX_SAFE_INTEGER
} = constants;
const {
  safeRe: re,
  t: t$3
} = reExports;
const parseOptions = parseOptions_1;
const {
  compareIdentifiers
} = identifiers;
let SemVer$1 = class SemVer {
  constructor(version, options) {
    options = parseOptions(options);
    if (version instanceof SemVer) {
      if (version.loose === !!options.loose && version.includePrerelease === !!options.includePrerelease) {
        return version;
      } else {
        version = version.version;
      }
    } else if (typeof version !== 'string') {
      throw new TypeError(`Invalid version. Must be a string. Got type "${typeof version}".`);
    }
    if (version.length > MAX_LENGTH) {
      throw new TypeError(`version is longer than ${MAX_LENGTH} characters`);
    }
    debug('SemVer', version, options);
    this.options = options;
    this.loose = !!options.loose;
    // this isn't actually relevant for versions, but keep it so that we
    // don't run into trouble passing this.options around.
    this.includePrerelease = !!options.includePrerelease;
    const m = version.trim().match(options.loose ? re[t$3.LOOSE] : re[t$3.FULL]);
    if (!m) {
      throw new TypeError(`Invalid Version: ${version}`);
    }
    this.raw = version;

    // these are actually numbers
    this.major = +m[1];
    this.minor = +m[2];
    this.patch = +m[3];
    if (this.major > MAX_SAFE_INTEGER || this.major < 0) {
      throw new TypeError('Invalid major version');
    }
    if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) {
      throw new TypeError('Invalid minor version');
    }
    if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) {
      throw new TypeError('Invalid patch version');
    }

    // numberify any prerelease numeric ids
    if (!m[4]) {
      this.prerelease = [];
    } else {
      this.prerelease = m[4].split('.').map(id => {
        if (/^[0-9]+$/.test(id)) {
          const num = +id;
          if (num >= 0 && num < MAX_SAFE_INTEGER) {
            return num;
          }
        }
        return id;
      });
    }
    this.build = m[5] ? m[5].split('.') : [];
    this.format();
  }
  format() {
    this.version = `${this.major}.${this.minor}.${this.patch}`;
    if (this.prerelease.length) {
      this.version += `-${this.prerelease.join('.')}`;
    }
    return this.version;
  }
  toString() {
    return this.version;
  }
  compare(other) {
    debug('SemVer.compare', this.version, this.options, other);
    if (!(other instanceof SemVer)) {
      if (typeof other === 'string' && other === this.version) {
        return 0;
      }
      other = new SemVer(other, this.options);
    }
    if (other.version === this.version) {
      return 0;
    }
    return this.compareMain(other) || this.comparePre(other);
  }
  compareMain(other) {
    if (!(other instanceof SemVer)) {
      other = new SemVer(other, this.options);
    }
    return compareIdentifiers(this.major, other.major) || compareIdentifiers(this.minor, other.minor) || compareIdentifiers(this.patch, other.patch);
  }
  comparePre(other) {
    if (!(other instanceof SemVer)) {
      other = new SemVer(other, this.options);
    }

    // NOT having a prerelease is > having one
    if (this.prerelease.length && !other.prerelease.length) {
      return -1;
    } else if (!this.prerelease.length && other.prerelease.length) {
      return 1;
    } else if (!this.prerelease.length && !other.prerelease.length) {
      return 0;
    }
    let i = 0;
    do {
      const a = this.prerelease[i];
      const b = other.prerelease[i];
      debug('prerelease compare', i, a, b);
      if (a === undefined && b === undefined) {
        return 0;
      } else if (b === undefined) {
        return 1;
      } else if (a === undefined) {
        return -1;
      } else if (a === b) {
        continue;
      } else {
        return compareIdentifiers(a, b);
      }
    } while (++i);
  }
  compareBuild(other) {
    if (!(other instanceof SemVer)) {
      other = new SemVer(other, this.options);
    }
    let i = 0;
    do {
      const a = this.build[i];
      const b = other.build[i];
      debug('prerelease compare', i, a, b);
      if (a === undefined && b === undefined) {
        return 0;
      } else if (b === undefined) {
        return 1;
      } else if (a === undefined) {
        return -1;
      } else if (a === b) {
        continue;
      } else {
        return compareIdentifiers(a, b);
      }
    } while (++i);
  }

  // preminor will bump the version up to the next minor release, and immediately
  // down to pre-release. premajor and prepatch work the same way.
  inc(release, identifier, identifierBase) {
    switch (release) {
      case 'premajor':
        this.prerelease.length = 0;
        this.patch = 0;
        this.minor = 0;
        this.major++;
        this.inc('pre', identifier, identifierBase);
        break;
      case 'preminor':
        this.prerelease.length = 0;
        this.patch = 0;
        this.minor++;
        this.inc('pre', identifier, identifierBase);
        break;
      case 'prepatch':
        // If this is already a prerelease, it will bump to the next version
        // drop any prereleases that might already exist, since they are not
        // relevant at this point.
        this.prerelease.length = 0;
        this.inc('patch', identifier, identifierBase);
        this.inc('pre', identifier, identifierBase);
        break;
      // If the input is a non-prerelease version, this acts the same as
      // prepatch.
      case 'prerelease':
        if (this.prerelease.length === 0) {
          this.inc('patch', identifier, identifierBase);
        }
        this.inc('pre', identifier, identifierBase);
        break;
      case 'major':
        // If this is a pre-major version, bump up to the same major version.
        // Otherwise increment major.
        // 1.0.0-5 bumps to 1.0.0
        // 1.1.0 bumps to 2.0.0
        if (this.minor !== 0 || this.patch !== 0 || this.prerelease.length === 0) {
          this.major++;
        }
        this.minor = 0;
        this.patch = 0;
        this.prerelease = [];
        break;
      case 'minor':
        // If this is a pre-minor version, bump up to the same minor version.
        // Otherwise increment minor.
        // 1.2.0-5 bumps to 1.2.0
        // 1.2.1 bumps to 1.3.0
        if (this.patch !== 0 || this.prerelease.length === 0) {
          this.minor++;
        }
        this.patch = 0;
        this.prerelease = [];
        break;
      case 'patch':
        // If this is not a pre-release version, it will increment the patch.
        // If it is a pre-release it will bump up to the same patch version.
        // 1.2.0-5 patches to 1.2.0
        // 1.2.0 patches to 1.2.1
        if (this.prerelease.length === 0) {
          this.patch++;
        }
        this.prerelease = [];
        break;
      // This probably shouldn't be used publicly.
      // 1.0.0 'pre' would become 1.0.0-0 which is the wrong direction.
      case 'pre':
        {
          const base = Number(identifierBase) ? 1 : 0;
          if (!identifier && identifierBase === false) {
            throw new Error('invalid increment argument: identifier is empty');
          }
          if (this.prerelease.length === 0) {
            this.prerelease = [base];
          } else {
            let i = this.prerelease.length;
            while (--i >= 0) {
              if (typeof this.prerelease[i] === 'number') {
                this.prerelease[i]++;
                i = -2;
              }
            }
            if (i === -1) {
              // didn't increment anything
              if (identifier === this.prerelease.join('.') && identifierBase === false) {
                throw new Error('invalid increment argument: identifier already exists');
              }
              this.prerelease.push(base);
            }
          }
          if (identifier) {
            // 1.2.0-beta.1 bumps to 1.2.0-beta.2,
            // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0
            let prerelease = [identifier, base];
            if (identifierBase === false) {
              prerelease = [identifier];
            }
            if (compareIdentifiers(this.prerelease[0], identifier) === 0) {
              if (isNaN(this.prerelease[1])) {
                this.prerelease = prerelease;
              }
            } else {
              this.prerelease = prerelease;
            }
          }
          break;
        }
      default:
        throw new Error(`invalid increment argument: ${release}`);
    }
    this.raw = this.format();
    if (this.build.length) {
      this.raw += `+${this.build.join('.')}`;
    }
    return this;
  }
};
var semver = SemVer$1;

const SemVer = semver;
const compare$1 = (a, b, loose) => new SemVer(a, loose).compare(new SemVer(b, loose));
var compare_1 = compare$1;

const compare = compare_1;
const gte = (a, b, loose) => compare(a, b, loose) >= 0;
var gte_1 = gte;

var semverGte = /*@__PURE__*/getDefaultExportFromCjs(gte_1);

const publicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvZ/T/RHOr6+yo/iMLUlf
agMiMLFxQR/5Qtc85ykMBvKZqbBGb9zU68VB9n54alrbZG5FdcHkSJXgJIBXF2bk
TGTfBi58JmltZirSWzvXoXnT4ieGNZv+BqnP9zzj9HXOVhVncbRmJPEIJOZfL9AQ
beix3rPgZx3ZepAaoMQnz11dZKDGzkMN75WkTdf324X3DeFgLVmjsYuAcLl/AJMA
uPKSSt0XOQUsfrT7rEqXIrj8rIJcWxIHICMRrwfjw2Qh+3pfIrh7XSzxlW4zCKBN
RpavrrCnpOFRfkC5T9eMKLgyapjufOtbjuzu25N3urBsg6oRFNzsGXWp1C7DwUO2
kwIDAQAB
-----END PUBLIC KEY-----`;
const decodeSupportedVersions = token => jwt__default.default.verify(token, publicKey, {
  algorithms: ['RS256']
});
const supportedVersionsStore = new ElectronStore__default.default({
  name: 'supportedVersions'
});
let builtinSupportedVersions;
const getBuiltinSupportedVersions = async () => {
  if (builtinSupportedVersions) return builtinSupportedVersions;
  try {
    const filePath = node_path.join(__dirname, 'supportedVersions.jwt');
    const encodedToken = await promises.readFile(filePath, 'utf8');
    builtinSupportedVersions = decodeSupportedVersions(encodedToken);
    return builtinSupportedVersions;
  } catch (e) {
    console.error('Error loading supportedVersions.jwt', e);
    return undefined;
  }
};
const logRequestError = description => error => {
  if (axios__default.default.isAxiosError(error)) {
    if (error.response) {
      console.error(`Couldn't load ${description}: ${error.response.status} ${error.response.data}`);
    } else {
      // Something happened in setting up the request that triggered an Error
      console.error(`Couldn't load ${description}: ${error.message}`);
    }
  } else {
    console.error('Fetching ${description} error:', error);
  }
  return undefined;
};
const getCacheKey = serverUrl => `supportedVersions:${serverUrl}`;
const loadFromCache = serverUrl => {
  try {
    const cached = supportedVersionsStore.get(getCacheKey(serverUrl));
    if (!cached) return undefined;
    return cached;
  } catch (error) {
    console.warn(`Error loading cache for ${serverUrl}:`, error);
    return undefined;
  }
};
const saveToCache = (serverUrl, data) => {
  try {
    supportedVersionsStore.set(getCacheKey(serverUrl), data);
  } catch (error) {
    console.warn(`Error saving cache for ${serverUrl}:`, error);
  }
};
const withRetries = async (fetchFn, maxAttempts = 3, delayMs = 2000) => {
  const attempt = async attemptNumber => {
    try {
      const result = await fetchFn();
      if (result !== undefined) {
        return result;
      }
    } catch (error) {
      // Log but continue to next attempt
      if (attemptNumber === maxAttempts) {
        // Last attempt failed
        return undefined;
      }
    }

    // If we haven't exhausted attempts, wait then try again
    if (attemptNumber < maxAttempts) {
      await new Promise(resolve => setTimeout(resolve, delayMs));
      return attempt(attemptNumber + 1);
    }
    return undefined;
  };
  return attempt(1);
};
const getCloudInfo = async (url, uniqueId) => {
  const domain = new URL(url).hostname;
  const response = await axios__default.default.get(supportedVersions({
    domain,
    uniqueId
  }));
  return response.data;
};
const getServerInfo = async url => {
  const response = await axios__default.default.get(server(url).info);
  return response.data;
};
const getUniqueId = async (url, version) => {
  var _coerce;
  const validVersion = ((_coerce = semver$1.coerce((version === null || version === undefined ? undefined : version.trim()) || '0.0.0')) === null || _coerce === undefined ? undefined : _coerce.version) || '0.0.0';
  const serverUrl = semverGte(validVersion, '7.0.0') ? server(url).uniqueId : server(url).setting('uniqueID');
  try {
    var _response$data;
    const response = await axios__default.default.get(serverUrl);
    const value = (_response$data = response.data) === null || _response$data === void 0 || (_response$data = _response$data.settings) === null || _response$data === void 0 || (_response$data = _response$data[0]) === null || _response$data === void 0 ? void 0 : _response$data.value;
    if (!value) {
      console.warn(`No unique ID found for server ${url}`);
      return null;
    }
    return value;
  } catch (error) {
    console.warn(`Error fetching unique ID for server ${url}:`, error);
    return null;
  }
};
const dispatchVersionUpdated = url => info => {
  dispatch({
    type: WEBVIEW_SERVER_VERSION_UPDATED,
    payload: {
      url,
      version: info.version
    }
  });
  return info;
};
const dispatchUniqueIdUpdated = url => uniqueID => {
  if (uniqueID) {
    dispatch({
      type: WEBVIEW_SERVER_UNIQUE_ID_UPDATED,
      payload: {
        url,
        uniqueID
      }
    });
  }
  return uniqueID;
};
const dispatchSupportedVersionsUpdated = (url, supportedVersions, {
  source
}) => {
  dispatch({
    type: WEBVIEW_SERVER_SUPPORTED_VERSIONS_UPDATED,
    payload: {
      url,
      supportedVersions,
      source
    }
  });
};
const updateSupportedVersionsData = async serverUrl => {
  const server = select(({
    servers
  }) => servers.find(server => server.url === serverUrl));
  if (!server) return;

  // Dispatch loading state
  dispatch({
    type: WEBVIEW_SERVER_SUPPORTED_VERSIONS_LOADING,
    payload: {
      url: serverUrl
    }
  });
  const builtinSupportedVersions = await getBuiltinSupportedVersions();

  // Fetch server info with retries (3x with 2s delays)
  const serverInfoResult = await withRetries(() => getServerInfo(server.url), 3, 2000);
  let serverEncoded;
  if (serverInfoResult) {
    var _serverInfoResult$sup;
    dispatchVersionUpdated(server.url)(serverInfoResult);
    serverEncoded = (_serverInfoResult$sup = serverInfoResult.supportedVersions) === null || _serverInfoResult$sup === undefined ? undefined : _serverInfoResult$sup.signed;

    // Try Server with retries (3x with 2s delays)
    if (serverEncoded) {
      try {
        const serverSupportedVersions = decodeSupportedVersions(serverEncoded);
        saveToCache(serverUrl, serverSupportedVersions);
        dispatchSupportedVersionsUpdated(server.url, serverSupportedVersions, {
          source: 'server'
        });
        return;
      } catch (error) {
        console.error('Error decoding server supported versions:', error);
        // Clear serverEncoded to allow cloud fallback
        serverEncoded = undefined;
      }
    }
  }
  const uniqueID = await getUniqueId(server.url, server.version || '').then(dispatchUniqueIdUpdated(server.url)).catch(logRequestError('unique ID'));

  // Try Cloud with retries (3x with 2s delays) if unique ID available
  if (!serverEncoded && uniqueID) {
    const cloudVersionsWithRetry = await withRetries(() => getCloudInfo(server.url, uniqueID), 3, 2000);
    if (cloudVersionsWithRetry !== null && cloudVersionsWithRetry !== undefined && cloudVersionsWithRetry.signed) {
      try {
        const cloudSupportedVersions = decodeSupportedVersions(cloudVersionsWithRetry.signed);
        saveToCache(serverUrl, cloudSupportedVersions);
        dispatchSupportedVersionsUpdated(server.url, cloudSupportedVersions, {
          source: 'cloud'
        });
        return;
      } catch (error) {
        console.error('Error decoding cloud supported versions:', error);
      }
    }
  }

  // Try to load from cache
  const cachedVersions = loadFromCache(serverUrl);
  if (cachedVersions) {
    dispatchSupportedVersionsUpdated(server.url, cachedVersions, {
      source: 'cloud'
    });
    dispatch({
      type: WEBVIEW_SERVER_SUPPORTED_VERSIONS_ERROR,
      payload: {
        url: serverUrl
      }
    });
    return;
  }

  // Fall back to builtin (always available)
  if (builtinSupportedVersions) {
    saveToCache(serverUrl, builtinSupportedVersions);
    dispatchSupportedVersionsUpdated(server.url, builtinSupportedVersions, {
      source: 'builtin'
    });
    dispatch({
      type: WEBVIEW_SERVER_SUPPORTED_VERSIONS_ERROR,
      payload: {
        url: serverUrl
      }
    });
    return;
  }

  // No data available from any source
  dispatch({
    type: WEBVIEW_SERVER_SUPPORTED_VERSIONS_ERROR,
    payload: {
      url: serverUrl
    }
  });
};
function checkSupportedVersionServers() {
  listen(WEBVIEW_READY, async action => {
    updateSupportedVersionsData(action.payload.url);
  });
  listen(SUPPORTED_VERSION_DIALOG_DISMISS, async action => {
    updateSupportedVersionsData(action.payload.url);
  });
  listen(WEBVIEW_SERVER_RELOADED, async action => {
    updateSupportedVersionsData(action.payload.url);
  });
  electron.ipcMain.handle('refresh-supported-versions', async (_event, serverUrl) => {
    updateSupportedVersionsData(serverUrl);
  });
}

const setSpellCheckerLanguages = async languages => {
  await electron.app.whenReady();
  const filteredLanguages = Array.from(languages).filter(language => electron.session.defaultSession.availableSpellCheckerLanguages.includes(language));
  electron.session.defaultSession.setSpellCheckerLanguages(filteredLanguages);
  electron.webContents.getAllWebContents().forEach(webContents => {
    webContents.session.setSpellCheckerLanguages(filteredLanguages);
  });
};
const setupSpellChecking = async () => {
  setSpellCheckerLanguages(new Set(electron.session.defaultSession.getSpellCheckerLanguages()));
  listen(SPELL_CHECKING_TOGGLED, action => {
    const spellCheckerLanguages = new Set(action.payload ? electron.session.defaultSession.getSpellCheckerLanguages() : []);
    setSpellCheckerLanguages(spellCheckerLanguages);
  });
  listen(SPELL_CHECKING_LANGUAGE_TOGGLED, action => {
    const spellCheckerLanguages = new Set(electron.session.defaultSession.getSpellCheckerLanguages());
    if (action.payload.enabled) {
      spellCheckerLanguages.add(action.payload.name);
    } else {
      spellCheckerLanguages.delete(action.payload.name);
    }
    setSpellCheckerLanguages(spellCheckerLanguages);
  });
};

const handleCertificatesManager = () => {
  handle('certificatesManager/remove', async (_webContent, domain) => {
    const trustedCertificates = select(({
      trustedCertificates
    }) => trustedCertificates);
    const notTrustedCertificates = select(({
      notTrustedCertificates
    }) => notTrustedCertificates);
    if (trustedCertificates.hasOwnProperty(domain)) {
      delete trustedCertificates[domain];
      dispatch({
        type: TRUSTED_CERTIFICATES_UPDATED,
        payload: {
          ...trustedCertificates
        }
      });
    }
    if (notTrustedCertificates.hasOwnProperty(domain)) {
      delete notTrustedCertificates[domain];
      dispatch({
        type: NOT_TRUSTED_CERTIFICATES_UPDATED,
        payload: {
          ...notTrustedCertificates
        }
      });
    }
  });
};

class DockService extends Service {
  initialize() {
    if (process.platform !== 'darwin') {
      return;
    }
    this.watch(selectGlobalBadgeText, globalBadgeText => {
      var _app$dock;
      (_app$dock = electron.app.dock) === null || _app$dock === undefined || _app$dock.setBadge(globalBadgeText);
    });
    this.watch(selectGlobalBadgeCount, (globalBadgeCount, prevGlobalBadgeCount) => {
      const {
        isFlashFrameEnabled
      } = select(({
        isFlashFrameEnabled
      }) => ({
        isFlashFrameEnabled
      }));
      if (globalBadgeCount <= 0 || (prevGlobalBadgeCount !== null && prevGlobalBadgeCount !== undefined ? prevGlobalBadgeCount : 0) > 0) {
        return;
      }
      if (isFlashFrameEnabled) {
        var _app$dock2;
        (_app$dock2 = electron.app.dock) === null || _app$dock2 === undefined || _app$dock2.bounce();
      }
    });
  }
}
var dock = new DockService();

const DESTRUCTION_CHECK_INTERVAL = 50;
const DEVTOOLS_TIMEOUT = 2000;
const WEBVIEW_CHECK_INTERVAL = 100;
let videoCallWindow = null;
let isVideoCallWindowDestroying = false;
let pendingVideoCallUrl = null;
let videoCallCredentials = null;
let videoCallProviderName = null;
const videoCallScreenSharingTracker = new ScreenSharingRequestTracker('video-call-window/screen-sharing-source-responded', 'Video call screen sharing');

// Helper function to log URL changes
const setPendingVideoCallUrl = (url, reason) => {
  const previous = pendingVideoCallUrl;
  pendingVideoCallUrl = url;
  console.log(`Video call window: pendingVideoCallUrl updated - ${reason}`, {
    previous,
    new: url,
    timestamp: new Date().toISOString()
  });
};
let videoCallWindowCreationCount = 0;
let videoCallWindowDestructionCount = 0;
const logVideoCallWindowStats = () => {
  const cacheStatus = getDesktopCapturerCacheStatus();
  console.log('Video call window stats:', {
    created: videoCallWindowCreationCount,
    destroyed: videoCallWindowDestructionCount,
    currentInstance: videoCallWindow ? 'active' : 'none',
    cacheStatus: cacheStatus.cached ? 'cached' : 'empty',
    promiseStatus: cacheStatus.pending ? 'pending' : 'none'
  });
};
const fetchVideoCallWindowState = async browserWindow => {
  return {
    focused: browserWindow.isFocused(),
    visible: browserWindow.isVisible(),
    maximized: false,
    minimized: false,
    fullscreen: false,
    normal: true,
    bounds: browserWindow.getNormalBounds()
  };
};
const cleanupVideoCallWindow = () => {
  if (videoCallWindow && !videoCallWindow.isDestroyed() && !isVideoCallWindowDestroying) {
    console.log('Cleaning up video call window resources');
    isVideoCallWindowDestroying = true;
    try {
      // Set permission handler with additional safety
      if (videoCallWindow.webContents && !videoCallWindow.webContents.isDestroyed()) {
        videoCallWindow.webContents.session.setPermissionRequestHandler(() => false);
        videoCallWindow.webContents.executeJavaScript('void 0').catch(() => {
          // Ignore errors during cleanup
        });
      }
      try {
        const allWebContents = electron.webContents.getAllWebContents();
        const webviewContents = allWebContents.find(wc => {
          var _videoCallWindow;
          return wc.hostWebContents === ((_videoCallWindow = videoCallWindow) === null || _videoCallWindow === void 0 ? void 0 : _videoCallWindow.webContents);
        });
        if (webviewContents && !webviewContents.isDestroyed()) {
          console.log('Stopping webview JavaScript execution before window cleanup');
          webviewContents.session.setPermissionRequestHandler(() => false);
          webviewContents.loadURL('about:blank').catch(() => {});
        }
      } catch (error) {
        console.log('Could not clean webview contents, continuing with window cleanup');
      }

      // Clean up screen sharing listener before removing window listeners
      videoCallScreenSharingTracker.cleanup();
      videoCallWindow.removeAllListeners();

      // Use setImmediate to ensure this happens after current event loop
      // This prevents timing issues during app initialization
      setImmediate(() => {
        if (videoCallWindow && !videoCallWindow.isDestroyed()) {
          videoCallWindow.close();
        }
      });
    } catch (error) {
      console.error('Error during video call window cleanup:', error);
      if (videoCallWindow && !videoCallWindow.isDestroyed()) {
        try {
          videoCallWindow.removeAllListeners();
          setImmediate(() => {
            if (videoCallWindow && !videoCallWindow.isDestroyed()) {
              videoCallWindow.close();
            }
          });
        } catch (fallbackError) {
          console.error('Error in fallback cleanup:', fallbackError);
        }
      }
    }
  }

  // Clear credentials immediately during cleanup
  videoCallCredentials = null;

  // Use setTimeout to ensure this cleanup happens after any window events are processed
  setTimeout(() => {
    videoCallWindow = null;
    isVideoCallWindowDestroying = false;
    videoCallWindowDestructionCount++;
    console.log('Video call window cleanup completed');
    logVideoCallWindowStats();
  }, 10);
};

// Internal picker handler function - uses shared tracker
const createInternalPickerHandler = () => cb => {
  if (!videoCallWindow || videoCallWindow.isDestroyed()) {
    console.warn('Screen sharing request rejected - video call window not available');
    cb({
      video: false
    });
    return;
  }
  const window = videoCallWindow;
  videoCallScreenSharingTracker.createRequest(cb, () => {
    window.webContents.send('video-call-window/open-screen-picker');
  });
};
const setupWebviewHandlers = webContents => {
  // Track attached webviews that need handler setup
  const pendingWebviews = [];
  let screenPickerReady = false;
  let provider = null;

  // Function to set up display media handler for a webview
  const setupDisplayMediaHandler = webviewWebContents => {
    if (!provider) return;
    const currentProvider = provider; // Capture for closure
    try {
      // useSystemPicker is an experimental macOS 15+ option; not available on other platforms.
      // We set it to false unconditionally and use the callback handler on all platforms to
      // enable custom source selection (including PipeWire on Wayland via XDG portal).
      webviewWebContents.session.setDisplayMediaRequestHandler((_request, cb) => {
        try {
          currentProvider.handleDisplayMediaRequest(cb);
        } catch (error) {
          console.error('Error in screen picker handler:', error);
          cb({
            video: false
          });
        }
      }, {
        useSystemPicker: false
      } // Always false - portal handled via callback on Linux
      );
    } catch (error) {
      console.error('Error setting up display media request handler:', error);
    }
  };

  // Register listener SYNCHRONOUSLY before async work
  const handleDidAttachWebview = (_event, webviewWebContents) => {
    if (screenPickerReady && provider) {
      setupDisplayMediaHandler(webviewWebContents);
    } else {
      pendingWebviews.push(webviewWebContents);
    }
  };
  webContents.removeAllListeners('did-attach-webview');
  webContents.on('did-attach-webview', handleDidAttachWebview);

  // Load screen picker module asynchronously
  Promise.resolve().then(function () { return require('./index-DUAIsPdo.js'); }).then(screenPickerModule => {
    const {
      createScreenPicker,
      InternalPickerProvider
    } = screenPickerModule;
    provider = createScreenPicker();
    if (provider instanceof InternalPickerProvider) {
      provider.setHandleRequestHandler(createInternalPickerHandler());
    }
    screenPickerReady = true;

    // Set up handlers for any webviews that attached while loading
    for (const webviewWebContents of pendingWebviews) {
      if (!webviewWebContents.isDestroyed()) {
        setupDisplayMediaHandler(webviewWebContents);
      }
    }
    pendingWebviews.length = 0;
  }).catch(error => {
    console.error('Error loading screen picker module:', error);
    // Don't prevent webview from loading if screen picker setup fails
  });
};
const startVideoCallWindowHandler = () => {
  // Sync IPC handler for provider name - used by jitsiBridge preload
  // to skip initialization for non-Jitsi providers without async delay
  electron.ipcMain.on('video-call-window/get-provider-sync', event => {
    event.returnValue = videoCallProviderName;
  });
  handle('video-call-window/screen-recording-is-permission-granted', async () => checkScreenRecordingPermission());
  handle('video-call-window/open-url', async (_webContents, url) => {
    await openExternal(url);
  });
  handle('video-call-window/open-screen-picker', async callerWebContents => {
    if (!videoCallWindow || videoCallWindow.isDestroyed()) {
      console.warn('Video call window: Cannot open screen picker - window not available');
      return {
        success: false
      };
    }

    // Clean up any stale listener before registering a new one, to ensure only
    // one ipcMain listener is active at a time (same pattern as createInternalPickerHandler).
    videoCallScreenSharingTracker.cleanup();
    videoCallWindow.webContents.send('video-call-window/open-screen-picker');

    // Forward the picker response back to the calling webContents (e.g. the Jitsi webview
    // preload that called ipcRenderer.invoke here). The screenSharePicker renderer sends
    // the result via ipcRenderer.send → ipcMain; we relay it to the caller so that
    // jitsiBridge's ipcRenderer.on listener fires correctly.
    electron.ipcMain.once('video-call-window/screen-sharing-source-responded', (_event, sourceId) => {
      if (!callerWebContents.isDestroyed()) {
        callerWebContents.send('video-call-window/screen-sharing-source-responded', sourceId);
      }
    });
    return {
      success: true
    };
  });

  // eslint-disable-next-line complexity
  handle('video-call-window/open-window', async (_wc, url, options) => {
    var _options$providerName;
    console.log('Video call window: Open-window handler called with URL:', url);

    // Store provider name and credentials
    videoCallProviderName = (_options$providerName = options === null || options === undefined ? undefined : options.providerName) !== null && _options$providerName !== undefined ? _options$providerName : null;
    videoCallCredentials = null;
    if ((options === null || options === undefined ? undefined : options.providerName) === 'pexip' && options !== null && options !== undefined && options.credentials) {
      try {
        const serverOrigin = new URL(_wc.getURL()).origin;
        videoCallCredentials = {
          userId: options.credentials.userId,
          authToken: options.credentials.authToken,
          serverUrl: serverOrigin
        };
      } catch {
        // _wc.getURL() may not be a valid URL in edge cases
        videoCallCredentials = null;
      }
    }
    if (isVideoCallWindowDestroying) {
      console.log('Waiting for video call window destruction to complete...');
      await new Promise(resolve => {
        const checkDestructionComplete = () => {
          if (!isVideoCallWindowDestroying) {
            resolve();
          } else {
            setTimeout(checkDestructionComplete, DESTRUCTION_CHECK_INTERVAL);
          }
        };
        checkDestructionComplete();
      });
    }
    if (videoCallWindow && !videoCallWindow.isDestroyed()) {
      console.log('Closing existing video call window to create fresh one');
      videoCallWindow.close();
      videoCallWindow = null;
      if (isVideoCallWindowDestroying) {
        await new Promise(resolve => {
          const checkClosed = () => {
            if (!isVideoCallWindowDestroying) {
              resolve();
            } else {
              setTimeout(checkClosed, DESTRUCTION_CHECK_INTERVAL);
            }
          };
          checkClosed();
        });
      }
    }
    const validUrl = new URL(url);
    const allowedProtocols = ['http:', 'https:'];
    console.log('Video call window: URL validation - hostname:', validUrl.hostname, 'protocol:', validUrl.protocol);
    if (validUrl.hostname.match(/(\.)?g\.co$/)) {
      console.log('Video call window: Google URL detected, opening externally instead of internal window');
      openExternal(validUrl.toString());
      return;
    }
    if (allowedProtocols.includes(validUrl.protocol)) {
      const mainWindow = await getRootWindow();
      const winBounds = await mainWindow.getNormalBounds();
      const centeredWindowPosition = {
        x: winBounds.x + winBounds.width / 2,
        y: winBounds.y + winBounds.height / 2
      };
      const actualScreen = electron.screen.getDisplayNearestPoint({
        x: centeredWindowPosition.x,
        y: centeredWindowPosition.y
      });
      const state = select(state => ({
        videoCallWindowState: state.videoCallWindowState,
        isVideoCallWindowPersistenceEnabled: state.isVideoCallWindowPersistenceEnabled,
        isAutoOpenEnabled: state.isVideoCallDevtoolsAutoOpenEnabled
      }));
      let {
        x,
        y,
        width,
        height
      } = state.videoCallWindowState.bounds;
      if (!state.isVideoCallWindowPersistenceEnabled || !x || !y || width === 0 || height === 0 || !isInsideSomeScreen({
        x,
        y,
        width,
        height
      })) {
        width = Math.round(actualScreen.workAreaSize.width * 0.8);
        height = Math.round(actualScreen.workAreaSize.height * 0.8);
        x = Math.round((actualScreen.workArea.width - width) / 2 + actualScreen.workArea.x);
        y = Math.round((actualScreen.workArea.height - height) / 2 + actualScreen.workArea.y);
      }
      console.log('Creating new video call window');
      videoCallWindowCreationCount++;
      logVideoCallWindowStats();
      const additionalArgs = [];
      if (process__default.default.platform === 'win32') {
        const sessionName = process__default.default.env.SESSIONNAME;
        const isRdpSession = typeof sessionName === 'string' && sessionName !== 'Console';
        const {
          readSetting
        } = await Promise.resolve().then(function () { return readSetting$1; });
        const isScreenCaptureFallbackEnabled = readSetting('isVideoCallScreenCaptureFallbackEnabled');
        if (isScreenCaptureFallbackEnabled || isRdpSession) {
          additionalArgs.push('--disable-features=WebRtcAllowWgcDesktopCapturer,WebRtcAllowWgcScreenCapturer');
          console.log('Video call window: Explicitly passing WGC disable flags to webview via additionalArguments', {
            isRdpSession,
            isScreenCaptureFallbackEnabled
          });
        }
      }
      videoCallWindow = new electron.BrowserWindow({
        width,
        height,
        x,
        y,
        webPreferences: {
          nodeIntegration: true,
          nodeIntegrationInSubFrames: true,
          contextIsolation: false,
          webviewTag: true,
          experimentalFeatures: false,
          offscreen: false,
          disableHtmlFullscreenWindowResize: true,
          backgroundThrottling: true,
          v8CacheOptions: 'bypassHeatCheck',
          spellcheck: false,
          ...(additionalArgs.length > 0 && {
            additionalArguments: additionalArgs
          })
        },
        show: false,
        frame: true,
        transparent: false,
        skipTaskbar: false
      });
      videoCallWindow.webContents.on('will-navigate', (event, url) => {
        if (url.toLowerCase().startsWith('smb://')) {
          event.preventDefault();
        }
      });
      videoCallWindow.webContents.setWindowOpenHandler(({
        url
      }) => {
        if (url.toLowerCase().startsWith('smb://')) {
          return {
            action: 'deny'
          };
        }
        return {
          action: 'allow'
        };
      });
      if (state.isVideoCallWindowPersistenceEnabled) {
        const fetchAndDispatchWindowState = debounce(async () => {
          if (videoCallWindow && !videoCallWindow.isDestroyed()) {
            dispatchLocal({
              type: VIDEO_CALL_WINDOW_STATE_CHANGED,
              payload: await fetchVideoCallWindowState(videoCallWindow)
            });
          }
        }, 1000);
        videoCallWindow.addListener('show', fetchAndDispatchWindowState);
        videoCallWindow.addListener('hide', fetchAndDispatchWindowState);
        videoCallWindow.addListener('focus', fetchAndDispatchWindowState);
        videoCallWindow.addListener('blur', fetchAndDispatchWindowState);
        videoCallWindow.addListener('maximize', fetchAndDispatchWindowState);
        videoCallWindow.addListener('unmaximize', fetchAndDispatchWindowState);
        videoCallWindow.addListener('minimize', fetchAndDispatchWindowState);
        videoCallWindow.addListener('restore', fetchAndDispatchWindowState);
        videoCallWindow.addListener('resize', fetchAndDispatchWindowState);
        videoCallWindow.addListener('move', fetchAndDispatchWindowState);
      }
      videoCallWindow.on('closed', () => {
        console.log('Video call window closed - destroying completely');

        // Clean up screen sharing listener
        videoCallScreenSharingTracker.cleanup();

        // Clear credentials and provider on close
        videoCallCredentials = null;
        videoCallProviderName = null;

        // Use setTimeout to ensure cleanup happens after any potential app lifecycle events
        // This prevents crashes during first launch when timing is critical
        setTimeout(() => {
          try {
            videoCallWindow = null;
            isVideoCallWindowDestroying = false;
            videoCallWindowDestructionCount++;
            logVideoCallWindowStats();
          } catch (error) {
            console.error('Error during video call window closed event handling:', error);
          }
        }, 50); // Small delay to let app state stabilize
      });
      videoCallWindow.on('close', _event => {
        if (!isVideoCallWindowDestroying) {
          isVideoCallWindowDestroying = true;
          console.log('Video call window close initiated - preventing JS execution');

          // Clean up screen sharing listener
          videoCallScreenSharingTracker.cleanup();
          try {
            if (videoCallWindow && !videoCallWindow.isDestroyed()) {
              videoCallWindow.webContents.session.setPermissionRequestHandler(() => false);
              videoCallWindow.webContents.executeJavaScript('void 0').catch(() => {});
            }
          } catch (error) {
            console.log('Error during close preparation:', error);
          }
        }
      });
      videoCallWindow.webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL, isMainFrame) => {
        console.error('Video call window failed to load:', {
          errorCode,
          errorDescription,
          validatedURL,
          isMainFrame
        });
        if (isMainFrame) {
          console.error('Main frame failed to load, this may indicate issues on low-power devices');
        }
      });
      videoCallWindow.webContents.on('dom-ready', () => {
        var _videoCallWindow2;
        (_videoCallWindow2 = videoCallWindow) === null || _videoCallWindow2 === undefined || _videoCallWindow2.webContents.executeJavaScript(`
          if (typeof process !== 'undefined' && process.env && "production" === 'development') {
            console.log('Video call window: JavaScript execution test successful');
          }
          window.videoCallWindowJSWorking = true;
          setTimeout(() => {
            const rootElement = document.getElementById('root');
            const hasReactContent = rootElement && (
              rootElement.hasChildNodes() || 
              rootElement.innerHTML.trim() !== ''
            );
            
            if (!hasReactContent) {
              console.warn('Video call window: React may not have rendered - possible initialization issue');
            } else if (typeof process !== 'undefined' && process.env && "production" === 'development') {
              console.log('Video call window: React content detected successfully');
            }
          }, 5000);
        `).catch(error => {
          console.error('Video call window: JavaScript execution test failed:', error);
        });
      });
      videoCallWindow.webContents.on('console-message', (_event, level, message, line, sourceId) => {
        const logPrefix = 'Video call window console:';
        switch (level) {
          case 0:
            console.log(`${logPrefix} [INFO]`, message, `(${sourceId}:${line})`);
            break;
          case 1:
            console.warn(`${logPrefix} [WARN]`, message, `(${sourceId}:${line})`);
            break;
          case 2:
            console.error(`${logPrefix} [ERROR]`, message, `(${sourceId}:${line})`);
            break;
          default:
            console.log(`${logPrefix} [${level}]`, message, `(${sourceId}:${line})`);
        }
      });
      const htmlPath = path__default.default.join(electron.app.getAppPath(), 'app/video-call-window.html');
      console.log('Video call window: Loading HTML file from:', htmlPath);
      videoCallWindow.loadFile(htmlPath, {
        query: {
          url,
          autoOpenDevtools: String(state.isAutoOpenEnabled)
        }
      }).catch(error => {
        console.error('Video call window: Failed to load HTML file:', error);
        console.error('This may indicate build issues or file system problems on low-power devices');
      });
      videoCallWindow.once('ready-to-show', () => {
        if (videoCallWindow && !videoCallWindow.isDestroyed()) {
          videoCallWindow.setTitle(packageJsonInformation.productName);
          console.log('Video call window: Window ready, waiting for renderer to signal ready state');
          console.log('Video call window: Current pending URL:', pendingVideoCallUrl);
          videoCallWindow.show();
        }
      });
      const {
        webContents
      } = videoCallWindow;

      // Setup webview handlers (listener registered synchronously, module loads async)
      setupWebviewHandlers(webContents);

      // Set the pending URL after window is created to prevent race condition with cleanup
      setPendingVideoCallUrl(url, 'open-window-after-creation');
      console.log('Video call window: Set pending URL after window creation:', url);
      webContents.setWindowOpenHandler(({
        url
      }) => {
        console.log('Video call window - new window requested:', url);
        if (url.toLowerCase().startsWith('smb://')) {
          return {
            action: 'deny'
          };
        }
        if (url.startsWith('http://') || url.startsWith('https://')) {
          openExternal(url);
          return {
            action: 'deny'
          };
        }
        return {
          action: 'allow'
        };
      });
      webContents.on('will-navigate', (event, url) => {
        console.log('Video call window will-navigate:', url);

        // Check for close pages and handle them specially to prevent crashes
        if (url.includes('/close.html') || url.includes('/close2.html')) {
          console.log('Video call window: Navigation to close page detected, will handle gracefully');
          // Don't prevent navigation, but note it for safer handling
        }
        try {
          const parsedUrl = new URL(url);
          if (!['http:', 'https:', 'file:', 'data:', 'about:'].includes(parsedUrl.protocol)) {
            console.log('External protocol detected in video call window:', parsedUrl.protocol);
            event.preventDefault();
            isProtocolAllowed(url).then(allowed => {
              if (allowed) {
                openExternal(url);
              }
            });
          }
        } catch (e) {
          console.warn('Failed to parse URL in video call window:', url, e);
        }
      });
      webContents.session.setPermissionRequestHandler(async (_webContents, permission, callback, details) => {
        console.log('Video call window permission request', permission, details);
        switch (permission) {
          case 'media':
            {
              const {
                mediaTypes = []
              } = details;
              try {
                await handleMediaPermissionRequest(mediaTypes, videoCallWindow, 'initiateCall', callback);
              } catch (error) {
                console.error('Error handling media permission request in video call window:', error);
                callback(false);
              }
              return;
            }
          case 'geolocation':
          case 'notifications':
          case 'midiSysex':
          case 'pointerLock':
          case 'fullscreen':
          case 'screen-wake-lock':
          case 'system-wake-lock':
            callback(true);
            return;
          case 'openExternal':
            {
              callback(true);
              return;
            }
          default:
            callback(false);
        }
      });
    }
  });
  handle('video-call-window/close-requested', async () => {
    console.log('Video call window: Close requested via navigation to close page');

    // Add safety check and delay to prevent crashes during first launch
    if (videoCallWindow && !videoCallWindow.isDestroyed()) {
      // Use setImmediate to ensure this happens after any pending navigation events
      setImmediate(() => {
        try {
          if (videoCallWindow && !videoCallWindow.isDestroyed() && !isVideoCallWindowDestroying) {
            console.log('Video call window: Proceeding with close after navigation delay');
            videoCallWindow.close();
          } else {
            console.log('Video call window: Already destroyed or being destroyed, skipping close');
          }
        } catch (error) {
          console.error('Error closing video call window after close page navigation:', error);
        }
      });
      return {
        success: true
      };
    }
    console.log('Video call window: Already destroyed, cannot close');
    return {
      success: false
    };
  });
  handle('video-call-window/open-webview-dev-tools', async () => {
    if (!videoCallWindow || videoCallWindow.isDestroyed()) {
      console.warn('Video call window not available for dev tools');
      return false;
    }
    try {
      const webviewWebContents = await new Promise(resolve => {
        const checkForWebview = () => {
          const allWebContents = electron.webContents.getAllWebContents();
          const webviewContents = allWebContents.find(wc => {
            var _videoCallWindow3;
            return wc.hostWebContents === ((_videoCallWindow3 = videoCallWindow) === null || _videoCallWindow3 === void 0 ? void 0 : _videoCallWindow3.webContents);
          });
          if (webviewContents) {
            resolve(webviewContents);
          } else {
            setTimeout(checkForWebview, WEBVIEW_CHECK_INTERVAL);
          }
        };
        checkForWebview();
        setTimeout(() => resolve(null), DEVTOOLS_TIMEOUT);
      });
      if (webviewWebContents && !webviewWebContents.isDestroyed()) {
        console.log('Opening developer tools for video call webview');
        webviewWebContents.openDevTools();
        return true;
      }
      console.warn('Video call webview webContents not found or destroyed');
      return false;
    } catch (error) {
      console.error('Error opening webview developer tools:', error);
      return false;
    }
  });
};
const openVideoCallWebviewDevTools = async () => {
  if (!videoCallWindow || videoCallWindow.isDestroyed()) {
    console.warn('Video call window not available for dev tools');
    return false;
  }
  try {
    const webviewWebContents = await new Promise(resolve => {
      const checkForWebview = () => {
        const allWebContents = electron.webContents.getAllWebContents();
        const webviewContents = allWebContents.find(wc => {
          var _videoCallWindow4;
          return wc.hostWebContents === ((_videoCallWindow4 = videoCallWindow) === null || _videoCallWindow4 === void 0 ? void 0 : _videoCallWindow4.webContents);
        });
        if (webviewContents) {
          resolve(webviewContents);
        } else {
          setTimeout(checkForWebview, WEBVIEW_CHECK_INTERVAL);
        }
      };
      checkForWebview();
      setTimeout(() => resolve(null), DEVTOOLS_TIMEOUT);
    });
    if (webviewWebContents && !webviewWebContents.isDestroyed()) {
      console.log('Opening developer tools for video call webview');
      webviewWebContents.openDevTools();
      return true;
    }
    console.warn('Video call webview webContents not found or destroyed');
    return false;
  } catch (error) {
    console.error('Error opening webview developer tools:', error);
    return false;
  }
};
const cleanupVideoCallResources = () => {
  console.log('Cleaning up all video call resources');
  clearDesktopCapturerCache();
  isVideoCallWindowDestroying = false;
  cleanupVideoCallWindow();
};
handle('video-call-window/test-ipc', async () => {
  console.log('Video call window: IPC test request received');
  return {
    success: true,
    timestamp: Date.now()
  };
});
handle('video-call-window/handshake', async () => {
  console.log('Video call window: Handshake request received');
  return {
    success: true,
    timestamp: Date.now()
  };
});
handle('video-call-window/renderer-ready', async () => {
  console.log('Video call window: Renderer signals ready state');
  if (!videoCallWindow || videoCallWindow.isDestroyed()) {
    console.error('Video call window: Window not available when renderer ready');
    throw new Error('Video call window not available');
  }
  console.log('Video call window: Renderer is ready to request URL');
  return {
    success: true
  };
});
handle('video-call-window/request-url', async () => {
  console.log('Video call window: Renderer requesting pending URL');
  if (!videoCallWindow || videoCallWindow.isDestroyed()) {
    console.error('Video call window: Window not available when requesting URL');
    return {
      success: false,
      url: null,
      autoOpenDevtools: false
    };
  }
  if (!pendingVideoCallUrl) {
    console.error('Video call window: No pending URL available');
    return {
      success: false,
      url: null,
      autoOpenDevtools: false
    };
  }
  const state = select(state => ({
    isAutoOpenEnabled: state.isVideoCallDevtoolsAutoOpenEnabled
  }));
  console.log('Video call window: Providing URL to renderer:', pendingVideoCallUrl);
  return {
    success: true,
    url: pendingVideoCallUrl,
    autoOpenDevtools: state.isAutoOpenEnabled
  };
});
handle('video-call-window/url-received', async () => {
  console.log('Video call window: URL received confirmation from renderer');
  return {
    success: true
  };
});
handle('video-call-window/webview-created', async () => {
  console.log('Video call window: Webview created confirmation');
  return {
    success: true
  };
});
handle('video-call-window/webview-loading', async () => {
  console.log('Video call window: Webview started loading');
  return {
    success: true
  };
});
handle('video-call-window/webview-ready', async () => {
  console.log('Video call window: Webview finished loading');
  return {
    success: true
  };
});
handle('video-call-window/webview-failed', async (_webContents, error) => {
  console.error('Video call window: Webview failed to load:', error);
  return {
    success: true
  };
});
handle('video-call-window/get-credentials', async callerWebContents => {
  var _callerWebContents$ho;
  // Only return credentials to the video call window's webview
  const isAuthorizedCaller = !!videoCallWindow && !videoCallWindow.isDestroyed() && (callerWebContents.id === videoCallWindow.webContents.id || ((_callerWebContents$ho = callerWebContents.hostWebContents) === null || _callerWebContents$ho === undefined ? undefined : _callerWebContents$ho.id) === videoCallWindow.webContents.id);
  if (!isAuthorizedCaller || !videoCallCredentials) {
    return null;
  }
  return videoCallCredentials;
});
handle('video-call-window/get-language', async () => {
  console.log('Video call window: Language request received');

  // Import the i18n service to get the current language
  try {
    const {
      getLanguage
    } = await Promise.resolve().then(function () { return main; });
    console.log('Video call window: Providing language:', getLanguage);
    return {
      success: true,
      language: getLanguage
    };
  } catch (error) {
    console.error('Video call window: Failed to get language:', error);
    return {
      success: true,
      language: fallbackLng
    };
  }
});
handle('video-call-window/prewarm-capturer-cache', async () => {
  prewarmDesktopCapturerCache();
  return {
    success: true
  };
});

const t$2 = i18next__default.default.t.bind(i18next__default.default);
const on = (condition, getMenuItems) => condition ? getMenuItems() : [];
const selectAddServersDeps = reselect.createStructuredSelector({
  isAddNewServersEnabled: ({
    isAddNewServersEnabled
  }) => isAddNewServersEnabled
});
const createAppMenu = reselect.createSelector(selectAddServersDeps, ({
  isAddNewServersEnabled
}) => ({
  id: 'appMenu',
  label: process.platform === 'darwin' ? electron.app.name : t$2('menus.fileMenu'),
  submenu: [...on(process.platform === 'darwin', () => [{
    id: 'about',
    label: t$2('menus.about', {
      appName: electron.app.name
    }),
    click: async () => {
      const browserWindow = await getRootWindow();
      if (!browserWindow.isVisible()) {
        browserWindow.showInactive();
      }
      browserWindow.focus();
      dispatch({
        type: MENU_BAR_ABOUT_CLICKED
      });
    }
  }, {
    type: 'separator'
  }, {
    id: 'services',
    label: t$2('menus.services'),
    role: 'services'
  }, {
    type: 'separator'
  }, {
    id: 'hide',
    label: t$2('menus.hide', {
      appName: electron.app.name
    }),
    role: 'hide'
  }, {
    id: 'hideOthers',
    label: t$2('menus.hideOthers'),
    role: 'hideOthers'
  }, {
    id: 'unhide',
    label: t$2('menus.unhide'),
    role: 'unhide'
  }, {
    type: 'separator'
  }]), ...on(process.platform !== 'darwin' && isAddNewServersEnabled, () => [{
    id: 'addNewServer',
    label: t$2('menus.addNewServer'),
    accelerator: 'CommandOrControl+N',
    click: async () => {
      const browserWindow = await getRootWindow();
      if (!browserWindow.isVisible()) {
        browserWindow.showInactive();
      }
      browserWindow.focus();
      dispatch({
        type: MENU_BAR_ADD_NEW_SERVER_CLICKED
      });
    }
  }, {
    type: 'separator'
  }]), {
    id: 'disableGpu',
    label: t$2('menus.disableGpu'),
    enabled: !electron.app.commandLine.hasSwitch('disable-gpu'),
    click: () => {
      relaunchApp('--disable-gpu');
    }
  }, {
    type: 'separator'
  }, {
    id: 'quit',
    label: t$2('menus.quit', {
      appName: electron.app.name
    }),
    accelerator: 'CommandOrControl+Q',
    click: () => {
      electron.app.quit();
    }
  }]
}));
const createEditMenu = reselect.createSelector(_ => undefined, () => ({
  id: 'editMenu',
  label: t$2('menus.editMenu'),
  submenu: [{
    id: 'undo',
    label: t$2('menus.undo'),
    role: 'undo'
  }, {
    id: 'redo',
    label: t$2('menus.redo'),
    role: 'redo'
  }, {
    type: 'separator'
  }, {
    id: 'cut',
    label: t$2('menus.cut'),
    role: 'cut'
  }, {
    id: 'copy',
    label: t$2('menus.copy'),
    role: 'copy'
  }, {
    id: 'paste',
    label: t$2('menus.paste'),
    role: 'paste'
  }, {
    id: 'selectAll',
    label: t$2('menus.selectAll'),
    role: 'selectAll'
  }]
}));
const selectViewDeps = reselect.createStructuredSelector({
  currentView: ({
    currentView
  }) => currentView,
  isSideBarEnabled: ({
    isSideBarEnabled
  }) => isSideBarEnabled,
  isTrayIconEnabled: ({
    isTrayIconEnabled
  }) => isTrayIconEnabled,
  isMenuBarEnabled: ({
    isMenuBarEnabled
  }) => isMenuBarEnabled,
  rootWindowState: ({
    rootWindowState
  }) => rootWindowState
});
const getCurrentView = async () => {
  const browserWindow = await getRootWindow();
  if (!browserWindow.isVisible()) {
    browserWindow.showInactive();
  }
  browserWindow.focus();
  return select(({
    currentView
  }) => currentView);
};
const getCurrentViewWebcontents = async () => {
  const currentView = await getCurrentView();
  const url = typeof currentView === 'object' ? currentView.url : null;
  if (!url) {
    return null;
  }
  return getWebContentsByServerUrl(url);
};
const createViewMenu = reselect.createSelector(selectViewDeps, ({
  currentView,
  isSideBarEnabled,
  isTrayIconEnabled,
  isMenuBarEnabled,
  rootWindowState
}) => ({
  id: 'viewMenu',
  label: t$2('menus.viewMenu'),
  submenu: [{
    id: 'reload',
    label: t$2('menus.reload'),
    accelerator: 'CommandOrControl+R',
    enabled: typeof currentView === 'object' && !!currentView.url,
    click: async () => {
      const guestWebContents = await getCurrentViewWebcontents();
      guestWebContents === null || guestWebContents === undefined || guestWebContents.reload();
      const currentView = await getCurrentView();
      if (typeof currentView === 'object' && !!currentView.url) {
        dispatch({
          type: WEBVIEW_SERVER_RELOADED,
          payload: {
            url: currentView.url
          }
        });
      }
    }
  }, {
    id: 'reloadClearingCache',
    label: t$2('menus.reloadClearingCache'),
    enabled: typeof currentView === 'object' && !!currentView.url,
    click: async () => {
      const guestWebContents = await getCurrentViewWebcontents();
      if (guestWebContents) dispatch({
        type: CLEAR_CACHE_TRIGGERED,
        payload: guestWebContents.id
      });
      const currentView = await getCurrentView();
      if (typeof currentView === 'object' && !!currentView.url) {
        dispatch({
          type: WEBVIEW_SERVER_RELOADED,
          payload: {
            url: currentView.url
          }
        });
      }
    }
  }, {
    id: 'openDevTools',
    label: t$2('menus.openDevTools'),
    enabled: typeof currentView === 'object' && !!currentView.url,
    accelerator: process.platform === 'darwin' ? 'Command+Alt+I' : 'Ctrl+Shift+I',
    click: async () => {
      const guestWebContents = await getCurrentViewWebcontents();
      guestWebContents === null || guestWebContents === undefined || guestWebContents.toggleDevTools();
    }
  }, {
    id: 'openDevToolsOnAllWindows',
    label: t$2('menus.openDevToolsOnAllWindows'),
    enabled: typeof currentView === 'object' && !!currentView.url,
    accelerator: process.platform === 'darwin' ? 'Command+Alt+G' : 'Ctrl+Shift+G',
    click: async () => {
      const windows = electron.BrowserWindow.getAllWindows();
      windows.forEach(window => {
        window.webContents.toggleDevTools();
      });
    }
  }, {
    type: 'separator'
  }, {
    id: 'back',
    label: t$2('menus.back'),
    enabled: typeof currentView === 'object' && !!currentView.url,
    accelerator: process.platform === 'darwin' ? 'Command+[' : 'Alt+Left',
    click: async () => {
      const guestWebContents = await getCurrentViewWebcontents();
      guestWebContents === null || guestWebContents === undefined || guestWebContents.goBack();
    }
  }, {
    id: 'forward',
    label: t$2('menus.forward'),
    enabled: typeof currentView === 'object' && !!currentView.url,
    accelerator: process.platform === 'darwin' ? 'Command+]' : 'Alt+Right',
    click: async () => {
      const guestWebContents = await getCurrentViewWebcontents();
      guestWebContents === null || guestWebContents === undefined || guestWebContents.goForward();
    }
  }, {
    type: 'separator'
  }, {
    id: 'showTrayIcon',
    label: t$2('menus.showTrayIcon'),
    type: 'checkbox',
    checked: isTrayIconEnabled,
    accelerator: process.platform === 'darwin' ? 'Shift+Command+T' : 'Ctrl+Shift+T',
    click: ({
      checked
    }) => {
      setTimeout(() => {
        dispatch({
          type: MENU_BAR_TOGGLE_IS_TRAY_ICON_ENABLED_CLICKED,
          payload: checked
        });
      }, 10);
    }
  }, ...on(process.platform === 'darwin', () => [{
    id: 'showFullScreen',
    label: t$2('menus.showFullScreen'),
    type: 'checkbox',
    checked: rootWindowState.fullscreen,
    accelerator: 'Control+Command+F',
    click: async ({
      checked: enabled
    }) => {
      const browserWindow = await getRootWindow();
      if (!browserWindow.isVisible()) {
        browserWindow.showInactive();
      }
      browserWindow.focus();
      browserWindow.setFullScreen(enabled);
    }
  }]), ...on(process.platform !== 'darwin', () => [{
    id: 'showMenuBar',
    label: t$2('menus.showMenuBar'),
    type: 'checkbox',
    checked: isMenuBarEnabled,
    enabled: !isMenuBarEnabled || isSideBarEnabled,
    accelerator: process.platform === 'darwin' ? 'Shift+Command+M' : 'Ctrl+Shift+M',
    click: async ({
      checked
    }) => {
      const browserWindow = await getRootWindow();
      if (!browserWindow.isVisible()) {
        browserWindow.showInactive();
      }
      browserWindow.focus();
      dispatch({
        type: MENU_BAR_TOGGLE_IS_MENU_BAR_ENABLED_CLICKED,
        payload: checked
      });
    }
  }]), {
    id: 'showServerList',
    label: t$2('menus.showServerList'),
    type: 'checkbox',
    checked: isSideBarEnabled,
    enabled: !isSideBarEnabled || isMenuBarEnabled,
    accelerator: process.platform === 'darwin' ? 'Shift+Command+S' : 'Ctrl+Shift+S',
    click: async ({
      checked
    }) => {
      const browserWindow = await getRootWindow();
      if (!browserWindow.isVisible()) {
        browserWindow.showInactive();
      }
      browserWindow.focus();
      dispatch({
        type: MENU_BAR_TOGGLE_IS_SIDE_BAR_ENABLED_CLICKED,
        payload: checked
      });
    }
  }, {
    type: 'separator'
  }, {
    id: 'resetZoom',
    label: t$2('menus.resetZoom'),
    accelerator: 'CommandOrControl+0',
    click: async () => {
      const guestWebContents = await getCurrentViewWebcontents();
      guestWebContents === null || guestWebContents === undefined || guestWebContents.setZoomLevel(0);
    }
  }, {
    id: 'zoomIn',
    label: t$2('menus.zoomIn'),
    accelerator: 'CommandOrControl+=',
    click: async () => {
      const guestWebContents = await getCurrentViewWebcontents();
      if (!guestWebContents) {
        return;
      }
      const zoomLevel = guestWebContents === null || guestWebContents === undefined ? undefined : guestWebContents.getZoomLevel();
      if (zoomLevel >= 9) {
        return;
      }
      guestWebContents.setZoomLevel(zoomLevel + 1);
    }
  }, {
    id: 'zoomOut',
    label: t$2('menus.zoomOut'),
    accelerator: 'CommandOrControl+-',
    click: async () => {
      const guestWebContents = await getCurrentViewWebcontents();
      if (!guestWebContents) {
        return;
      }
      const zoomLevel = guestWebContents.getZoomLevel();
      if (zoomLevel <= -9) {
        return;
      }
      guestWebContents.setZoomLevel(zoomLevel - 1);
    }
  }]
}));
const selectWindowDeps = reselect.createStructuredSelector({
  servers: ({
    servers
  }) => servers,
  currentView: ({
    currentView
  }) => currentView,
  isShowWindowOnUnreadChangedEnabled: ({
    isShowWindowOnUnreadChangedEnabled
  }) => isShowWindowOnUnreadChangedEnabled,
  isAddNewServersEnabled: ({
    isAddNewServersEnabled
  }) => isAddNewServersEnabled
});
const createWindowMenu = reselect.createSelector(selectWindowDeps, ({
  servers,
  currentView,
  isShowWindowOnUnreadChangedEnabled,
  isAddNewServersEnabled
}) => ({
  id: 'windowMenu',
  label: t$2('menus.windowMenu'),
  role: 'windowMenu',
  submenu: [...on(process.platform === 'darwin' && isAddNewServersEnabled, () => [{
    id: 'addNewServer',
    label: t$2('menus.addNewServer'),
    accelerator: 'CommandOrControl+N',
    click: async () => {
      const browserWindow = await getRootWindow();
      if (!browserWindow.isVisible()) {
        browserWindow.showInactive();
      }
      browserWindow.focus();
      dispatch({
        type: MENU_BAR_ADD_NEW_SERVER_CLICKED
      });
    }
  }, {
    type: 'separator'
  }]), ...on(servers.length > 0, () => [...servers.map((server, i) => {
    var _server$title;
    return {
      id: server.url,
      type: typeof currentView === 'object' && currentView.url === server.url ? 'checkbox' : 'normal',
      label: (_server$title = server.title) === null || _server$title === undefined ? undefined : _server$title.replace(/&/g, '&&'),
      checked: typeof currentView === 'object' && currentView.url === server.url,
      accelerator: `CommandOrControl+${i + 1}`,
      click: async () => {
        const browserWindow = await getRootWindow();
        if (!browserWindow.isVisible()) {
          browserWindow.showInactive();
        }
        browserWindow.focus();
        setTimeout(() => {
          dispatch({
            type: MENU_BAR_SELECT_SERVER_CLICKED,
            payload: server.url
          });
        }, 100);
      }
    };
  }), {
    type: 'separator'
  }]), {
    id: 'downloads',
    label: t$2('menus.downloads'),
    checked: currentView === 'downloads',
    accelerator: 'CommandOrControl+D',
    click: async () => {
      const browserWindow = await getRootWindow();
      if (!browserWindow.isVisible()) {
        browserWindow.showInactive();
      }
      browserWindow.focus();
      dispatch({
        type: SIDE_BAR_DOWNLOADS_BUTTON_CLICKED
      });
    }
  }, {
    id: 'settings',
    label: t$2('menus.settings'),
    checked: currentView === 'settings',
    click: async () => {
      const browserWindow = await getRootWindow();
      if (!browserWindow.isVisible()) {
        browserWindow.showInactive();
      }
      browserWindow.focus();
      dispatch({
        type: SIDE_BAR_SETTINGS_BUTTON_CLICKED
      });
    }
  }, {
    id: 'showOnUnreadMessage',
    type: 'checkbox',
    label: t$2('menus.showOnUnreadMessage'),
    checked: isShowWindowOnUnreadChangedEnabled,
    click: async ({
      checked
    }) => {
      const browserWindow = await getRootWindow();
      if (!browserWindow.isVisible()) {
        browserWindow.showInactive();
      }
      browserWindow.focus();
      dispatch({
        type: MENU_BAR_TOGGLE_IS_SHOW_WINDOW_ON_UNREAD_CHANGED_ENABLED_CLICKED,
        payload: checked
      });
    }
  }, {
    type: 'separator'
  }, {
    id: 'minimize',
    role: 'minimize',
    label: t$2('menus.minimize'),
    accelerator: 'CommandOrControl+M'
  }, {
    id: 'close',
    role: 'close',
    label: t$2('menus.close'),
    accelerator: 'CommandOrControl+W'
  }]
}));
const selectHelpDeps = reselect.createStructuredSelector({
  isDeveloperModeEnabled: ({
    isDeveloperModeEnabled
  }) => isDeveloperModeEnabled,
  isVideoCallDevtoolsAutoOpenEnabled: ({
    isVideoCallDevtoolsAutoOpenEnabled
  }) => isVideoCallDevtoolsAutoOpenEnabled
});
const createHelpMenu = reselect.createSelector(selectHelpDeps, ({
  isDeveloperModeEnabled,
  isVideoCallDevtoolsAutoOpenEnabled
}) => ({
  id: 'helpMenu',
  label: t$2('menus.helpMenu'),
  role: 'help',
  submenu: [{
    id: 'documentation',
    label: t$2('menus.documentation'),
    click: () => {
      openExternal(docs.index);
    }
  }, {
    id: 'reportIssue',
    label: t$2('menus.reportIssue'),
    click: () => {
      openExternal(docs.newIssue);
    }
  }, {
    type: 'separator'
  }, {
    id: 'reload-window',
    label: t$2('menus.reload'),
    accelerator: 'CommandOrControl+Shift+R',
    click: async () => {
      const guestWebContents = await getCurrentViewWebcontents();
      if (guestWebContents) dispatch({
        type: CLEAR_CACHE_TRIGGERED,
        payload: guestWebContents.id
      });
      const currentView = await getCurrentView();
      if (typeof currentView === 'object' && !!currentView.url) {
        dispatch({
          type: WEBVIEW_SERVER_RELOADED,
          payload: {
            url: currentView.url
          }
        });
      }
    }
  }, {
    id: 'toggleDevTools',
    label: t$2('menus.toggleDevTools'),
    accelerator: 'CommandOrControl+Shift+D',
    click: async () => {
      const browserWindow = await getRootWindow();
      if (!browserWindow.isVisible()) {
        browserWindow.showInactive();
      }
      browserWindow.focus();
      browserWindow.webContents.toggleDevTools();
    }
  }, {
    id: 'openLogViewer',
    label: t$2('menus.openLogViewer'),
    accelerator: 'CommandOrControl+Shift+L',
    click: async () => {
      try {
        const browserWindow = await getRootWindow();
        if (!browserWindow.isVisible()) {
          browserWindow.showInactive();
        }
        browserWindow.focus();
        const {
          openLogViewerWindow
        } = await Promise.resolve().then(function () { return ipc; });
        await openLogViewerWindow();
      } catch (error) {
        console.error('Error opening log viewer:', error);
      }
    }
  }, {
    id: 'developerMode',
    type: 'checkbox',
    label: t$2('menus.developerMode'),
    checked: isDeveloperModeEnabled,
    click: async ({
      checked
    }) => {
      const browserWindow = await getRootWindow();
      if (!browserWindow.isVisible()) {
        browserWindow.showInactive();
      }
      browserWindow.focus();
      dispatch({
        type: MENU_BAR_TOGGLE_IS_DEVELOPER_MODE_ENABLED_CLICKED,
        payload: checked
      });
    }
  }, {
    id: 'videoCallToolsSubmenu',
    label: t$2('menus.videoCallTools'),
    submenu: [{
      id: 'videoCallDevTools',
      label: t$2('menus.videoCallDevTools'),
      click: async () => {
        const browserWindow = await getRootWindow();
        if (!browserWindow.isVisible()) {
          browserWindow.showInactive();
        }
        browserWindow.focus();
        try {
          const success = await openVideoCallWebviewDevTools();
          if (!success) {
            console.log('No video call window available for developer tools');
          }
        } catch (error) {
          console.error('Error opening video call developer tools:', error);
        }
      }
    }, {
      id: 'videoCallDevToolsAutoOpen',
      type: 'checkbox',
      label: t$2('menus.videoCallDevToolsAutoOpen'),
      checked: isVideoCallDevtoolsAutoOpenEnabled,
      click: async ({
        checked
      }) => {
        const browserWindow = await getRootWindow();
        if (!browserWindow.isVisible()) {
          browserWindow.showInactive();
        }
        browserWindow.focus();
        dispatch({
          type: MENU_BAR_TOGGLE_IS_VIDEO_CALL_DEVTOOLS_AUTO_OPEN_ENABLED_CLICKED,
          payload: checked
        });
      }
    }]
  }, {
    id: 'openConfigFolder',
    label: t$2('menus.openConfigFolder'),
    click: async () => {
      electron.shell.showItemInFolder(path__default.default.join(electron.app.getPath('userData'), 'config.json'));
    }
  }, {
    type: 'separator'
  }, {
    id: 'clearTrustedCertificates',
    label: t$2('menus.clearTrustedCertificates'),
    click: async () => {
      const browserWindow = await getRootWindow();
      if (!browserWindow.isVisible()) {
        browserWindow.showInactive();
      }
      browserWindow.focus();
      dispatch({
        type: CERTIFICATES_CLEARED
      });
    }
  }, ...on(!process.mas, () => [{
    id: 'resetAppData',
    label: t$2('menus.resetAppData'),
    click: async () => {
      const permitted = await askForAppDataReset();
      if (permitted) {
        relaunchApp('--reset-app-data');
      }
    }
  }]), {
    type: 'separator'
  }, {
    id: 'learnMore',
    label: t$2('menus.learnMore'),
    click: () => {
      openExternal(rocketchat.site);
    }
  }, ...on(process.platform !== 'darwin', () => [{
    id: 'about',
    label: t$2('menus.about', {
      appName: electron.app.name
    }),
    click: async () => {
      const browserWindow = await getRootWindow();
      if (!browserWindow.isVisible()) {
        browserWindow.showInactive();
      }
      browserWindow.focus();
      dispatch({
        type: MENU_BAR_ABOUT_CLICKED
      });
    }
  }])]
}));
const selectMenuBarTemplate = reselect.createSelector([createAppMenu, createEditMenu, createViewMenu, createWindowMenu, createHelpMenu], (...menus) => menus);
const selectMenuBarTemplateAsJson = reselect.createSelector(_state => selectMenuBarTemplate, template => JSON.stringify(template));
class MenuBarService extends Service {
  initialize() {
    this.watch(selectMenuBarTemplateAsJson, async () => {
      const menuBarTemplate = select(selectMenuBarTemplate);
      const menu = electron.Menu.buildFromTemplate(menuBarTemplate);
      if (process.platform === 'darwin') {
        electron.Menu.setApplicationMenu(menu);
        return;
      }
      electron.Menu.setApplicationMenu(null);
      (await getRootWindow()).setMenu(menu);
    });
  }
}
var menuBar = new MenuBarService();

const t$1 = i18next__default.default.t.bind(i18next__default.default);
const ids = ['bold', 'italic', 'strike', 'inline_code', 'multi_line'];
const createTouchBar = () => {
  const serverSelectionScrubber = new electron.TouchBar.TouchBarScrubber({
    selectedStyle: 'background',
    mode: 'free',
    continuous: false,
    items: [],
    select: async index => {
      const browserWindow = await getRootWindow();
      if (!browserWindow.isVisible()) {
        browserWindow.showInactive();
      }
      browserWindow.focus();
      const url = select(({
        servers
      }) => servers[index].url);
      dispatch({
        type: TOUCH_BAR_SELECT_SERVER_TOUCHED,
        payload: url
      });
    }
  });
  const serverSelectionPopover = new electron.TouchBar.TouchBarPopover({
    label: t$1('touchBar.selectServer'),
    icon: undefined,
    items: new electron.TouchBar({
      items: [serverSelectionScrubber]
    }),
    showCloseButton: true
  });
  const messageBoxFormattingButtons = new electron.TouchBar.TouchBarSegmentedControl({
    mode: 'buttons',
    segments: ids.map(id => ({
      icon: electron.nativeImage.createFromPath(`${electron.app.getAppPath()}/app/images/touch-bar/${id}.png`),
      enabled: false
    })),
    change: async selectedIndex => {
      const browserWindow = await getRootWindow();
      if (!browserWindow.isVisible()) {
        browserWindow.showInactive();
      }
      browserWindow.focus();
      dispatch({
        type: TOUCH_BAR_FORMAT_BUTTON_TOUCHED,
        payload: ids[selectedIndex]
      });
    }
  });
  const touchBar = new electron.TouchBar({
    items: [serverSelectionPopover, new electron.TouchBar.TouchBarSpacer({
      size: 'flexible'
    }), messageBoxFormattingButtons, new electron.TouchBar.TouchBarSpacer({
      size: 'flexible'
    })]
  });
  getRootWindow().then(browserWindow => browserWindow.setTouchBar(touchBar));
  return [touchBar, serverSelectionPopover, serverSelectionScrubber, messageBoxFormattingButtons];
};
const updateServerSelectionPopover = (serverSelectionPopover, currentServer) => {
  var _currentServer$title;
  serverSelectionPopover.label = (_currentServer$title = currentServer === null || currentServer === undefined ? undefined : currentServer.title) !== null && _currentServer$title !== undefined ? _currentServer$title : t$1('touchBar.selectServer');
  serverSelectionPopover.icon = currentServer !== null && currentServer !== undefined && currentServer.favicon ? electron.nativeImage.createFromDataURL(currentServer === null || currentServer === undefined ? undefined : currentServer.favicon) : electron.nativeImage.createEmpty();
};
const updateServerSelectionScrubber = (serverSelectionScrubber, servers) => {
  serverSelectionScrubber.items = servers.map(server => {
    var _server$title;
    return {
      label: (_server$title = server.title) === null || _server$title === undefined ? undefined : _server$title.padEnd(30),
      icon: server.favicon ? electron.nativeImage.createFromDataURL(server.favicon) : undefined
    };
  });
};
const toggleMessageFormattingButtons = (messageBoxFormattingButtons, isEnabled) => {
  messageBoxFormattingButtons.segments.forEach(segment => {
    segment.enabled = isEnabled;
  });
};
const selectCurrentServer = ({
  servers,
  currentView
}) => {
  var _servers$find;
  return typeof currentView === 'object' ? (_servers$find = servers.find(({
    url
  }) => url === currentView.url)) !== null && _servers$find !== undefined ? _servers$find : null : null;
};
class TouchBarService extends Service {
  initialize() {
    if (process.platform !== 'darwin') {
      return;
    }
    const [touchBar, serverSelectionPopover, serverSelectionScrubber, messageBoxFormattingButtons] = createTouchBar();
    this.watch(selectCurrentServer, currentServer => {
      updateServerSelectionPopover(serverSelectionPopover, currentServer);
      getRootWindow().then(browserWindow => browserWindow.setTouchBar(touchBar));
    });
    this.watch(({
      servers
    }) => servers, servers => {
      updateServerSelectionScrubber(serverSelectionScrubber, servers);
      getRootWindow().then(browserWindow => browserWindow.setTouchBar(touchBar));
    });
    this.watch(({
      isMessageBoxFocused
    }) => isMessageBoxFocused !== null && isMessageBoxFocused !== undefined ? isMessageBoxFocused : false, isMessageBoxFocused => {
      toggleMessageFormattingButtons(messageBoxFormattingButtons, isMessageBoxFocused);
      getRootWindow().then(browserWindow => browserWindow.setTouchBar(touchBar));
    });
  }
}
var touchBar = new TouchBarService();

const t = i18next__default.default.t.bind(i18next__default.default);
const selectIsRootWindowVisible = ({
  rootWindowState: {
    visible
  }
}) => visible;
const selectHasHideOnTrayNotificationShown = ({
  hasHideOnTrayNotificationShown
}) => hasHideOnTrayNotificationShown;
const createTrayIcon = () => {
  const image = getTrayIconPath({
    platform: process.platform,
    badge: undefined
  });
  const trayIcon = new electron.Tray(electron.nativeImage.createEmpty());
  if (process.platform !== 'darwin') {
    trayIcon.addListener('click', async () => {
      const isRootWindowVisible = select(selectIsRootWindowVisible);
      const browserWindow = await getRootWindow();
      if (isRootWindowVisible) {
        browserWindow.hide();
        return;
      }
      browserWindow.show();
    });
  }
  trayIcon.addListener('balloon-click', async () => {
    const isRootWindowVisible = select(selectIsRootWindowVisible);
    const browserWindow = await getRootWindow();
    if (isRootWindowVisible) {
      browserWindow.hide();
      return;
    }
    browserWindow.show();
  });
  trayIcon.addListener('right-click', (_event, bounds) => {
    trayIcon.popUpContextMenu(undefined, bounds);
  });
  trayIcon.setImage(electron.nativeImage.createFromPath(image));
  return trayIcon;
};
const updateTrayIconImage = (trayIcon, badge) => {
  const image = getTrayIconPath({
    platform: process.platform,
    badge
  });
  trayIcon.setImage(electron.nativeImage.createFromPath(image));
};
const updateTrayIconTitle = (trayIcon, globalBadge) => {
  const title = Number.isInteger(globalBadge) ? String(globalBadge) : '';
  trayIcon.setTitle(title);
};
const updateTrayIconToolTip = (trayIcon, globalBadge) => {
  if (globalBadge === '•') {
    trayIcon.setToolTip(t('tray.tooltip.unreadMessage', {
      appName: electron.app.name
    }));
    return;
  }
  if (Number.isInteger(globalBadge)) {
    trayIcon.setToolTip(t('tray.tooltip.unreadMention', {
      appName: electron.app.name,
      count: globalBadge
    }));
    return;
  }
  trayIcon.setToolTip(t('tray.tooltip.noUnreadMessage', {
    appName: electron.app.name
  }));
};
const warnStillRunning = trayIcon => {
  if (process.platform !== 'win32') {
    return;
  }
  const hasHideOnTrayNotificationShown = select(selectHasHideOnTrayNotificationShown);
  if (!hasHideOnTrayNotificationShown) {
    trayIcon.displayBalloon({
      icon: getAppIconPath({
        platform: process.platform
      }),
      title: t('tray.balloon.stillRunning.title', {
        appName: electron.app.name
      }),
      content: t('tray.balloon.stillRunning.content', {
        appName: electron.app.name
      })
    });
    dispatch({
      type: SET_HAS_TRAY_MINIMIZE_NOTIFICATION_SHOWN,
      payload: true
    });
  }
};
const manageTrayIcon = async () => {
  const trayIcon = createTrayIcon();
  const unwatchGlobalBadge = watch(selectGlobalBadge, globalBadge => {
    updateTrayIconImage(trayIcon, globalBadge);
    updateTrayIconTitle(trayIcon, globalBadge);
    updateTrayIconToolTip(trayIcon, globalBadge);
  });
  let firstTrayIconBalloonShown = false;
  const unwatchIsRootWindowVisible = watch(selectIsRootWindowVisible, (isRootWindowVisible, prevIsRootWindowVisible) => {
    const menuTemplate = [{
      label: isRootWindowVisible ? t('tray.menu.hide') : t('tray.menu.show'),
      click: async () => {
        const isRootWindowVisible = select(selectIsRootWindowVisible);
        const browserWindow = await getRootWindow();
        if (isRootWindowVisible) {
          browserWindow.hide();
          return;
        }
        browserWindow.show();
      }
    }, {
      label: t('tray.menu.quit'),
      click: () => {
        electron.app.quit();
      }
    }];
    const menu = electron.Menu.buildFromTemplate(menuTemplate);
    trayIcon.setContextMenu(menu);
    if (prevIsRootWindowVisible && !isRootWindowVisible && process.platform === 'win32' && !firstTrayIconBalloonShown) {
      warnStillRunning(trayIcon);
      firstTrayIconBalloonShown = true;
    }
  });
  return () => {
    unwatchGlobalBadge();
    unwatchIsRootWindowVisible();
    trayIcon.destroy();
  };
};
class TrayIconService extends Service {
  constructor(...args) {
    super(...args);
    _defineProperty(this, "tearDownPromise", null);
  }
  initialize() {
    this.watch(({
      isTrayIconEnabled
    }) => isTrayIconEnabled !== null && isTrayIconEnabled !== undefined ? isTrayIconEnabled : true, isTrayIconEnabled => {
      if (!this.tearDownPromise && isTrayIconEnabled) {
        this.tearDownPromise = manageTrayIcon();
      } else if (this.tearDownPromise && !isTrayIconEnabled) {
        this.tearDownPromise.then(cleanUp => cleanUp());
        this.tearDownPromise = null;
      }
    });
  }
  destroy() {
    var _this$tearDownPromise;
    (_this$tearDownPromise = this.tearDownPromise) === null || _this$tearDownPromise === undefined || _this$tearDownPromise.then(cleanUp => cleanUp());
    this.tearDownPromise = null;
  }
}
var trayIcon = new TrayIconService();

const readJsonObject = async filePath => {
  try {
    const content = await fs__namespace.default.promises.readFile(filePath, 'utf8');
    const json = JSON.parse(content);
    return json && typeof json === 'object' && !Array.isArray(json) ? json : {};
  } catch (error) {
    return {};
  }
};
const readAppJsonObject = async basename => {
  const filePath = path__default.default.join(electron.app.getAppPath(), electron.app.getAppPath().endsWith('app.asar') ? '..' : '.', basename);
  return readJsonObject(filePath);
};
const readUserJsonObject = async basename => {
  const filePath = path__default.default.join(electron.app.getPath('userData'), basename);
  return readJsonObject(filePath);
};
const loadAppConfiguration = async () => readAppJsonObject('update.json');
const loadUserConfiguration = async () => readUserJsonObject('update.json');
const mergeConfigurations = (defaultConfiguration, appConfiguration, userConfiguration) => {
  const configuration = {
    ...defaultConfiguration,
    ...(typeof appConfiguration.forced === 'boolean' && {
      isEachUpdatesSettingConfigurable: !appConfiguration.forced
    }),
    ...(typeof appConfiguration.canUpdate === 'boolean' && {
      isUpdatingEnabled: appConfiguration.canUpdate
    }),
    ...(typeof appConfiguration.autoUpdate === 'boolean' && {
      doCheckForUpdatesOnStartup: appConfiguration.autoUpdate
    }),
    ...(typeof appConfiguration.skip === 'string' && {
      skippedUpdateVersion: appConfiguration.skip
    }),
    ...(typeof appConfiguration.channel === 'string' && {
      updateChannel: appConfiguration.channel
    })
  };
  if (typeof userConfiguration.autoUpdate === 'boolean' && (configuration.isEachUpdatesSettingConfigurable || typeof appConfiguration.autoUpdate === 'undefined')) {
    configuration.doCheckForUpdatesOnStartup = userConfiguration.autoUpdate;
  }
  if (typeof userConfiguration.skip === 'string' && (configuration.isEachUpdatesSettingConfigurable || typeof appConfiguration.skip === 'undefined')) {
    configuration.skippedUpdateVersion = userConfiguration.skip;
  }
  if (typeof userConfiguration.channel === 'string' && (configuration.isEachUpdatesSettingConfigurable || typeof appConfiguration.channel === 'undefined')) {
    configuration.updateChannel = userConfiguration.channel;
  }
  return configuration;
};
const loadConfiguration = async () => {
  const defaultConfiguration = select(({
    isUpdatingEnabled,
    doCheckForUpdatesOnStartup,
    skippedUpdateVersion,
    isReportEnabled,
    isFlashFrameEnabled,
    isHardwareAccelerationEnabled,
    isInternalVideoChatWindowEnabled,
    isVideoCallScreenCaptureFallbackEnabled,
    updateChannel
  }) => ({
    isUpdatingAllowed: process.platform === 'linux' && !!process.env.APPIMAGE || process.platform === 'win32' && !process.windowsStore || process.platform === 'darwin' && !process.mas,
    isEachUpdatesSettingConfigurable: true,
    isUpdatingEnabled,
    doCheckForUpdatesOnStartup,
    skippedUpdateVersion,
    isReportEnabled,
    isFlashFrameEnabled,
    isHardwareAccelerationEnabled,
    isInternalVideoChatWindowEnabled,
    isVideoCallScreenCaptureFallbackEnabled,
    updateChannel
  }));
  const appConfiguration = await loadAppConfiguration();
  const userConfiguration = await loadUserConfiguration();
  return mergeConfigurations(defaultConfiguration, appConfiguration, userConfiguration);
};
let isUserInitiatedCheck = false;
const setupUpdates = async () => {
  electronUpdater.autoUpdater.autoDownload = false;
  const {
    isUpdatingAllowed,
    isEachUpdatesSettingConfigurable,
    isUpdatingEnabled,
    doCheckForUpdatesOnStartup,
    skippedUpdateVersion,
    isReportEnabled,
    isFlashFrameEnabled,
    isHardwareAccelerationEnabled,
    isInternalVideoChatWindowEnabled,
    isVideoCallScreenCaptureFallbackEnabled,
    updateChannel
  } = await loadConfiguration();
  dispatch({
    type: UPDATES_READY,
    payload: {
      isUpdatingAllowed,
      isEachUpdatesSettingConfigurable,
      isUpdatingEnabled,
      doCheckForUpdatesOnStartup,
      skippedUpdateVersion,
      isReportEnabled,
      isFlashFrameEnabled,
      isHardwareAccelerationEnabled,
      isInternalVideoChatWindowEnabled,
      isVideoCallScreenCaptureFallbackEnabled,
      updateChannel
    }
  });
  if (!isUpdatingAllowed || !isUpdatingEnabled) {
    return;
  }

  // Set initial channel
  electronUpdater.autoUpdater.channel = updateChannel;

  // Enable prerelease updates for alpha and beta channels
  if (updateChannel === 'alpha' || updateChannel === 'beta') {
    electronUpdater.autoUpdater.allowPrerelease = true;
  }

  // Listen for channel changes
  listen(ABOUT_DIALOG_UPDATE_CHANNEL_CHANGED, async action => {
    const newChannel = action.payload;
    electronUpdater.autoUpdater.channel = newChannel;

    // Enable prerelease updates for alpha and beta channels
    electronUpdater.autoUpdater.allowPrerelease = newChannel === 'alpha' || newChannel === 'beta';
    dispatch({
      type: UPDATES_CHANNEL_CHANGED,
      payload: newChannel
    });
  });
  electronUpdater.autoUpdater.addListener('checking-for-update', () => {
    dispatch({
      type: UPDATES_CHECKING_FOR_UPDATE
    });
  });
  electronUpdater.autoUpdater.addListener('update-available', ({
    version
  }) => {
    const skippedUpdateVersion = select(({
      skippedUpdateVersion
    }) => skippedUpdateVersion);
    if (!isUserInitiatedCheck && skippedUpdateVersion === version) {
      dispatch({
        type: UPDATES_NEW_VERSION_NOT_AVAILABLE
      });
      return;
    }

    // Prevent showing "updates" that are actually downgrades
    // This can happen when generateUpdatesFilesForAllChannels is enabled
    // and the user is running a newer dev/PR build than the published release
    const currentVersion = electron.app.getVersion();
    try {
      if (!semver$1.gt(version, currentVersion)) {
        dispatch({
          type: UPDATES_NEW_VERSION_NOT_AVAILABLE
        });
        return;
      }
    } catch {
      // If version comparison fails, proceed with the update
    }
    dispatch({
      type: UPDATES_NEW_VERSION_AVAILABLE,
      payload: version
    });
  });
  electronUpdater.autoUpdater.addListener('update-not-available', () => {
    dispatch({
      type: UPDATES_NEW_VERSION_NOT_AVAILABLE
    });
  });
  const nativeUpdateDownloadedCallback = () => {
    electron.autoUpdater.removeListener('update-downloaded', nativeUpdateDownloadedCallback);
    electron.autoUpdater.quitAndInstall();
  };
  electronUpdater.autoUpdater.addListener('update-downloaded', async () => {
    const response = await askUpdateInstall();
    if (response === AskUpdateInstallResponse.INSTALL_LATER) {
      await warnAboutInstallUpdateLater();
      return;
    }
    try {
      setImmediate(() => {
        electron.app.removeAllListeners('window-all-closed');
        if (process.platform === 'darwin') {
          const allBrowserWindows = electron.BrowserWindow.getAllWindows();
          allBrowserWindows.forEach(browserWindow => {
            browserWindow.removeAllListeners('close');
            browserWindow.destroy();
          });
          electron.autoUpdater.checkForUpdates();
          electron.autoUpdater.on('update-downloaded', nativeUpdateDownloadedCallback);
        } else {
          electronUpdater.autoUpdater.quitAndInstall(true, true);
        }
      });
    } catch (error) {
      error instanceof Error && dispatch({
        type: UPDATES_ERROR_THROWN,
        payload: {
          message: error.message,
          stack: error.stack,
          name: error.name
        }
      });
    }
  });
  electronUpdater.autoUpdater.addListener('error', error => {
    dispatch({
      type: UPDATES_ERROR_THROWN,
      payload: {
        message: error.message,
        stack: error.stack,
        name: error.name
      }
    });
  });
  if (doCheckForUpdatesOnStartup) {
    try {
      isUserInitiatedCheck = false;
      await electronUpdater.autoUpdater.checkForUpdates();
    } catch (error) {
      error instanceof Error && dispatch({
        type: UPDATES_ERROR_THROWN,
        payload: {
          message: error.message,
          stack: error.stack,
          name: error.name
        }
      });
    }
  }
  listen(UPDATES_CHECK_FOR_UPDATES_REQUESTED, async () => {
    try {
      isUserInitiatedCheck = true;
      setTimeout(() => {
        electronUpdater.autoUpdater.checkForUpdates();
      }, 100);
    } catch (error) {
      error instanceof Error && dispatch({
        type: UPDATES_ERROR_THROWN,
        payload: {
          message: error.message,
          stack: error.stack,
          name: error.name
        }
      });
    }
  });
  listen(UPDATE_DIALOG_SKIP_UPDATE_CLICKED, async action => {
    await warnAboutUpdateSkipped();
    dispatch({
      type: UPDATE_SKIPPED,
      payload: action.payload
    });
  });
  listen(UPDATE_DIALOG_INSTALL_BUTTON_CLICKED, async () => {
    await warnAboutUpdateDownload();
    try {
      electronUpdater.autoUpdater.downloadUpdate();
    } catch (error) {
      error instanceof Error && dispatch({
        type: UPDATES_ERROR_THROWN,
        payload: {
          message: error.message,
          stack: error.stack,
          name: error.name
        }
      });
    }
  });
};

const SYSTEM_LOCKING_SCREEN = 'system/locking-screen';
const SYSTEM_SUSPENDING = 'system/suspending';

const setupPowerMonitor = () => {
  electron.powerMonitor.addListener('suspend', () => {
    dispatch({
      type: SYSTEM_SUSPENDING
    });
  });
  electron.powerMonitor.addListener('lock-screen', () => {
    dispatch({
      type: SYSTEM_LOCKING_SCREEN
    });
  });
  handle('power-monitor/get-system-idle-state', async (_webContents, idleThreshold) => electron.powerMonitor.getSystemIdleState(idleThreshold));
};

const start = async () => {
  applySystemCertificates();
  logger.info('Starting Rocket.Chat Desktop application');
  setupWebContentsLogging();
  performElectronStartup();

  // Set up GPU crash handler BEFORE whenReady to catch early GPU failures
  setupGpuCrashHandler();
  await electron.app.whenReady();
  cleanupOldLogs();
  createMainReduxStore();
  setupOutlookLogger();
  setupDebugLoggingWatch();

  // Initialize screen capture fallback state after store is available
  initializeScreenCaptureFallbackState();

  // Set up electron-dl with our download tracking callbacks
  setupElectronDlWithTracking();
  const localStorage = await exportLocalStorage();
  await mergePersistableValues(localStorage);
  await setupServers(localStorage);
  i18n.setUp();
  await i18n.wait();
  setupApp();
  setupMainErrorHandling();
  createRootWindow();
  startOutlookCalendarUrlHandler();
  attachGuestWebContentsEvents();
  await showRootWindow();
  watchMachineTheme();
  setupNotifications();
  attentionDrawing.setUp();
  setupScreenSharing();
  startServerViewScreenSharingHandler();
  startVideoCallWindowHandler();
  startLogViewerWindowHandler();
  await setupSpellChecking();
  setupDeepLinks();
  await setupNavigation();
  setupPowerMonitor();
  await setupUpdates();
  setupDownloads();
  handleCertificatesManager();
  dock.setUp();
  menuBar.setUp();
  touchBar.setUp();
  trayIcon.setUp();
  electron.app.addListener('before-quit', () => {
    dock.tearDown();
    menuBar.tearDown();
    touchBar.tearDown();
    trayIcon.tearDown();
    attentionDrawing.tearDown();
    stopOutlookCalendarSync();
    cleanupVideoCallResources();
  });
  watchAndPersistChanges();
  handleJitsiDesktopCapturerGetSources();
  handleDesktopCapturerGetSources();
  handleClearCacheDialog();
  startDocumentViewerHandler();
  startBrowserHandler();
  checkSupportedVersionServers();
  await processDeepLinksInArgs();
  console.info('Application initialization completed successfully');
};
start().catch(error => {
  logger.error('Failed to start application', error);
  electron.app.exit(1);
});

exports._defineProperty = _defineProperty;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,[encoded data, 1281480 characters]
