988 lines
31 KiB
JavaScript
988 lines
31 KiB
JavaScript
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.getMainWindowId = getMainWindowId;
|
|
exports.webContentsSend = webContentsSend;
|
|
exports.init = init;
|
|
exports.handleOpenUrl = handleOpenUrl;
|
|
exports.setMainWindowVisible = setMainWindowVisible;
|
|
exports.setBlurType = setBlurType
|
|
exports.setVibrancy = setVibrancy
|
|
const events = exports.events = new (require("events").EventEmitter)()
|
|
|
|
const VIBRANCY_TYPES = [
|
|
"titlebar",
|
|
"selection",
|
|
"menu",
|
|
"popover",
|
|
"sidebar",
|
|
"header",
|
|
"sheet",
|
|
"window",
|
|
"hud",
|
|
"fullscreen-ui",
|
|
"tooltip",
|
|
"content",
|
|
"under-window",
|
|
"under-page",
|
|
"none"
|
|
]
|
|
|
|
const BLUR_TYPES = ["blurbehind", "acrylic", "transparent"]
|
|
|
|
var glasstron = require("glasstron")
|
|
|
|
var _electron = require("electron");
|
|
|
|
var _fs = _interopRequireDefault(require("fs"));
|
|
|
|
var _path = _interopRequireDefault(require("path"));
|
|
|
|
var _url = _interopRequireDefault(require("url"));
|
|
|
|
var _Backoff = _interopRequireDefault(require("../common/Backoff"));
|
|
|
|
var _securityUtils = require("../common/securityUtils");
|
|
|
|
var appBadge = _interopRequireWildcard(require("./appBadge"));
|
|
|
|
var appConfig = _interopRequireWildcard(require("./appConfig"));
|
|
|
|
var _appSettings = require("./appSettings");
|
|
|
|
var _buildInfo = _interopRequireDefault(require("./buildInfo"));
|
|
|
|
var _ipcMain = _interopRequireDefault(require("./ipcMain"));
|
|
|
|
var legacyModuleUpdater = _interopRequireWildcard(require("./moduleUpdater"));
|
|
|
|
var _updater = _interopRequireDefault(require("./updater"));
|
|
|
|
var notificationScreen = _interopRequireWildcard(require("./notificationScreen"));
|
|
|
|
var paths = _interopRequireWildcard(require("./paths"));
|
|
|
|
var popoutWindows = _interopRequireWildcard(require("./popoutWindows"));
|
|
|
|
var splashScreen = _interopRequireWildcard(require("./splashScreen"));
|
|
|
|
var systemTray = _interopRequireWildcard(require("./systemTray"));
|
|
|
|
var _Constants = require("./Constants");
|
|
|
|
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
|
|
|
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
|
|
const settings = (0, _appSettings.getSettings)();
|
|
const connectionBackoff = new _Backoff.default(1000, 20000);
|
|
const DISCORD_NAMESPACE = 'DISCORD_';
|
|
|
|
let isTabs = false
|
|
function checkCanMigrate() {
|
|
return _fs.default.existsSync(_path.default.join(paths.getUserData(), 'userDataCache.json'));
|
|
}
|
|
|
|
function checkAlreadyMigrated() {
|
|
return _fs.default.existsSync(_path.default.join(paths.getUserData(), 'domainMigrated'));
|
|
}
|
|
|
|
const getWebappEndpoint = () => {
|
|
isTabs = settings.get("isTabs", false)
|
|
if(!isTabs){
|
|
let endpoint = settings.get('WEBAPP_ENDPOINT');
|
|
if (!endpoint) {
|
|
if (_buildInfo.default.releaseChannel === 'stable') {
|
|
endpoint = 'https://discord.com';
|
|
} else if (_buildInfo.default.releaseChannel === 'development') {
|
|
endpoint = 'https://canary.discord.com';
|
|
} else {
|
|
endpoint = `https://${_buildInfo.default.releaseChannel}.discord.com`;
|
|
}
|
|
}
|
|
return endpoint;
|
|
}else{
|
|
return "file://"+_path.default.join(__dirname, "tabs", "index.html")
|
|
}
|
|
};
|
|
|
|
const WEBAPP_ENDPOINT = getWebappEndpoint();
|
|
|
|
function checkUrlOriginMatches(urlA, urlB) {
|
|
let parsedUrlA;
|
|
let parsedUrlB;
|
|
|
|
try {
|
|
parsedUrlA = _url.default.parse(urlA);
|
|
parsedUrlB = _url.default.parse(urlB);
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
|
|
return parsedUrlA.protocol === parsedUrlB.protocol && parsedUrlA.slashes === parsedUrlB.slashes && parsedUrlA.host === parsedUrlB.host;
|
|
}
|
|
|
|
function getSanitizedPath(path) {
|
|
// using the whatwg URL api, get a sanitized pathname from given path
|
|
// this is because url.parse's `path` may not always have a slash
|
|
// in front of it
|
|
return new _url.default.URL(path, WEBAPP_ENDPOINT).pathname;
|
|
}
|
|
|
|
function getSanitizedProtocolPath(url_) {
|
|
try {
|
|
const parsedURL = _url.default.parse(url_);
|
|
|
|
if (parsedURL.protocol === 'discord:') {
|
|
return getSanitizedPath(parsedURL.path);
|
|
}
|
|
} catch (_) {} // protect against URIError: URI malformed
|
|
|
|
|
|
return null;
|
|
} // TODO: These should probably be thrown in constants.
|
|
|
|
|
|
const WEBAPP_PATH = settings.get('WEBAPP_PATH', `/app?_=${Date.now()}`);
|
|
const URL_TO_LOAD = `${WEBAPP_ENDPOINT}${WEBAPP_PATH}`;
|
|
const MIN_WIDTH = settings.get('MIN_WIDTH', 940);
|
|
const MIN_HEIGHT = settings.get('MIN_HEIGHT', 500);
|
|
const DEFAULT_WIDTH = 1280;
|
|
const DEFAULT_HEIGHT = 720; // TODO: document this var's purpose
|
|
|
|
const MIN_VISIBLE_ON_SCREEN = 32;
|
|
let mainWindow = null;
|
|
let mainWindowId = _Constants.DEFAULT_MAIN_WINDOW_ID;
|
|
let mainWindowInitialPath = null;
|
|
let mainWindowDidFinishLoad = false; // whether we are in an intermediate auth process outside of our normal login screen (for e.g. internal builds)
|
|
|
|
let insideAuthFlow = false; // last time the main app renderer has crashed ('crashed' event)
|
|
|
|
let lastCrashed = 0; // whether we failed to load a page outside of the intermediate auth flow
|
|
// used to reload the page after a delay
|
|
|
|
let lastPageLoadFailed = false;
|
|
|
|
function getMainWindowId() {
|
|
return mainWindowId;
|
|
}
|
|
|
|
function webContentsSend(...args) {
|
|
if (mainWindow != null && mainWindow.webContents != null) {
|
|
const [event, ...options] = args;
|
|
mainWindow.webContents.send(`${DISCORD_NAMESPACE}${event}`, ...options);
|
|
}
|
|
}
|
|
|
|
function saveWindowConfig(browserWindow) {
|
|
try {
|
|
if (!browserWindow) {
|
|
return;
|
|
}
|
|
|
|
if(settings.get("NO_WINDOWS_BOUND"))return
|
|
|
|
settings.set('IS_MAXIMIZED', browserWindow.isMaximized());
|
|
settings.set('IS_MINIMIZED', browserWindow.isMinimized());
|
|
|
|
if (!settings.get('IS_MAXIMIZED') && !settings.get('IS_MINIMIZED')) {
|
|
settings.set('WINDOW_BOUNDS', browserWindow.getBounds());
|
|
}
|
|
|
|
settings.save();
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
}
|
|
|
|
function setBlur(blur){
|
|
if(!mainWindow)return
|
|
if(typeof blur !== "boolean")throw new TypeError("INVALID ARGUMENT: blur")
|
|
mainWindow.setBlur(blur)
|
|
}
|
|
|
|
function setVibrancy(vibrancy){
|
|
if(!mainWindow)return
|
|
if(!VIBRANCY_TYPES.includes(vibrancy))throw new TypeError("INVALID ARGUMENT: vibrancy")
|
|
mainWindow.setVibrancy(vibrancy)
|
|
settings.set("GLASSTRON_VIBRANCY", vibrancy)
|
|
}
|
|
|
|
function setBlurType(blurType){
|
|
if(!mainWindow)return
|
|
if(!BLUR_TYPES.includes(blurType))throw new TypeError("INVALID ARGUMENT: blurType")
|
|
mainWindow.blurType = blurType
|
|
settings.set("GLASSTRON_BLUR", blurType)
|
|
}
|
|
|
|
function setDefaultBlur(){
|
|
if(!mainWindow)return
|
|
|
|
let blurType = settings.get("GLASSTRON_BLUR", "blurbehind")
|
|
if(!BLUR_TYPES.includes(blurType)){
|
|
blurType = "blurbehind"
|
|
settings.set("GLASSTRON_BLUR", blurType)
|
|
}
|
|
setBlurType(blurType)
|
|
|
|
let vibrancy = settings.get("GLASSTRON_VIBRANCY", "fullscreen-ui")
|
|
if(!VIBRANCY_TYPES.includes(vibrancy)){
|
|
vibrancy = "fullscreen-ui"
|
|
settings.set("GLASSTRON_VIBRANCY", vibrancy)
|
|
}
|
|
setVibrancy(vibrancy)
|
|
|
|
setBlur(true)
|
|
}
|
|
|
|
function setWindowVisible(isVisible, andUnminimize) {
|
|
if (mainWindow == null) {
|
|
return;
|
|
}
|
|
|
|
if (isVisible) {
|
|
if (andUnminimize || !mainWindow.isMinimized()) {
|
|
mainWindow.show();
|
|
webContentsSend('MAIN_WINDOW_FOCUS');
|
|
}
|
|
} else {
|
|
webContentsSend('MAIN_WINDOW_BLUR');
|
|
mainWindow.hide();
|
|
|
|
if (systemTray.hasInit) {
|
|
systemTray.displayHowToCloseHint();
|
|
}
|
|
}
|
|
|
|
mainWindow.setSkipTaskbar(!isVisible);
|
|
}
|
|
|
|
function doAABBsOverlap(a, b) {
|
|
const ax1 = a.x + a.width;
|
|
const bx1 = b.x + b.width;
|
|
const ay1 = a.y + a.height;
|
|
const by1 = b.y + b.height; // clamp a to b, see if it is non-empty
|
|
|
|
const cx0 = a.x < b.x ? b.x : a.x;
|
|
const cx1 = ax1 < bx1 ? ax1 : bx1;
|
|
|
|
if (cx1 - cx0 > 0) {
|
|
const cy0 = a.y < b.y ? b.y : a.y;
|
|
const cy1 = ay1 < by1 ? ay1 : by1;
|
|
|
|
if (cy1 - cy0 > 0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function applyWindowBoundsToConfig(mainWindowOptions) {
|
|
if (!settings.get('WINDOW_BOUNDS')) {
|
|
mainWindowOptions.center = true;
|
|
return;
|
|
}
|
|
|
|
const bounds = settings.get('WINDOW_BOUNDS');
|
|
bounds.width = Math.max(MIN_WIDTH, bounds.width);
|
|
bounds.height = Math.max(MIN_HEIGHT, bounds.height);
|
|
let isVisibleOnAnyScreen = false;
|
|
|
|
const displays = _electron.screen.getAllDisplays();
|
|
|
|
displays.forEach(display => {
|
|
if (isVisibleOnAnyScreen) {
|
|
return;
|
|
}
|
|
|
|
const displayBound = display.workArea;
|
|
displayBound.x += MIN_VISIBLE_ON_SCREEN;
|
|
displayBound.y += MIN_VISIBLE_ON_SCREEN;
|
|
displayBound.width -= 2 * MIN_VISIBLE_ON_SCREEN;
|
|
displayBound.height -= 2 * MIN_VISIBLE_ON_SCREEN;
|
|
isVisibleOnAnyScreen = doAABBsOverlap(bounds, displayBound);
|
|
});
|
|
|
|
if (isVisibleOnAnyScreen) {
|
|
mainWindowOptions.width = bounds.width;
|
|
mainWindowOptions.height = bounds.height;
|
|
mainWindowOptions.x = bounds.x;
|
|
mainWindowOptions.y = bounds.y;
|
|
} else {
|
|
mainWindowOptions.center = true;
|
|
}
|
|
} // this can be called multiple times (due to recreating the main app window),
|
|
// so we only want to update existing if we already initialized it
|
|
|
|
|
|
function setupNotificationScreen(mainWindow) {
|
|
if (!notificationScreen.hasInit) {
|
|
notificationScreen.init({
|
|
mainWindow,
|
|
title: 'Lightcord Notifications',
|
|
maxVisible: 5,
|
|
screenPosition: 'bottom'
|
|
});
|
|
notificationScreen.events.on(notificationScreen.NOTIFICATION_CLICK, () => {
|
|
setWindowVisible(true, true);
|
|
});
|
|
} else {
|
|
notificationScreen.setMainWindow(mainWindow);
|
|
}
|
|
} // this can be called multiple times (due to recreating the main app window),
|
|
// so we only want to update existing if we already initialized it
|
|
|
|
|
|
function setupSystemTray() {
|
|
if (!systemTray.hasInit) {
|
|
systemTray.init({
|
|
onCheckForUpdates: () => {
|
|
const updater = _updater.default === null || _updater.default === void 0 ? void 0 : _updater.default.getUpdater();
|
|
|
|
if (updater != null) {
|
|
checkForUpdatesWithUpdater(updater);
|
|
} else {
|
|
legacyModuleUpdater.checkForUpdates();
|
|
}
|
|
},
|
|
onTrayClicked: () => setWindowVisible(true, true),
|
|
onOpenVoiceSettings: openVoiceSettings,
|
|
onToggleMute: toggleMute,
|
|
onToggleDeafen: toggleDeafen,
|
|
onLaunchApplication: launchApplication
|
|
});
|
|
}
|
|
} // this can be called multiple times (due to recreating the main app window),
|
|
// so we only want to update existing if we already initialized it
|
|
|
|
|
|
function setupAppBadge() {
|
|
if (!appBadge.hasInit) {
|
|
appBadge.init();
|
|
}
|
|
} // this can be called multiple times (due to recreating the main app window),
|
|
// so we only want to update existing if we already initialized it
|
|
|
|
|
|
function setupAppConfig() {
|
|
if (!appConfig.hasInit) {
|
|
appConfig.init();
|
|
}
|
|
} // this can be called multiple times (due to recreating the main app window),
|
|
// so we only want to update existing if we already initialized it
|
|
|
|
|
|
function setupPopouts() {
|
|
if (!popoutWindows.hasInit) {
|
|
popoutWindows.init();
|
|
}
|
|
}
|
|
|
|
function openVoiceSettings() {
|
|
setWindowVisible(true, true);
|
|
webContentsSend('SYSTEM_TRAY_OPEN_VOICE_SETTINGS');
|
|
}
|
|
|
|
function toggleMute() {
|
|
webContentsSend('SYSTEM_TRAY_TOGGLE_MUTE');
|
|
}
|
|
|
|
function toggleDeafen() {
|
|
webContentsSend('SYSTEM_TRAY_TOGGLE_DEAFEN');
|
|
}
|
|
|
|
function launchApplication(applicationId) {
|
|
webContentsSend('LAUNCH_APPLICATION', applicationId);
|
|
}
|
|
|
|
const loadMainPage = () => {
|
|
lastPageLoadFailed = false;
|
|
mainWindow.loadURL(URL_TO_LOAD);
|
|
};
|
|
|
|
const DEFAULT_BACKGROUND_COLOR = '#2f3136';
|
|
const BACKGROUND_COLOR_KEY = 'BACKGROUND_COLOR';
|
|
|
|
function getBackgroundColor() {
|
|
return settings.get(BACKGROUND_COLOR_KEY, DEFAULT_BACKGROUND_COLOR);
|
|
}
|
|
|
|
function setBackgroundColor(color) {
|
|
settings.set(BACKGROUND_COLOR_KEY, color);
|
|
//mainWindow.setBackgroundColor(color);
|
|
settings.save();
|
|
} // launch main app window; could be called multiple times for various reasons
|
|
|
|
|
|
function launchMainAppWindow(isVisible) {
|
|
if (mainWindow) {
|
|
// TODO: message here?
|
|
mainWindow.destroy();
|
|
}
|
|
|
|
const mainWindowOptions = {
|
|
title: 'Lightcord',
|
|
width: DEFAULT_WIDTH,
|
|
height: DEFAULT_HEIGHT,
|
|
minWidth: MIN_WIDTH,
|
|
minHeight: MIN_HEIGHT,
|
|
transparent: false,
|
|
frame: false,
|
|
resizable: true,
|
|
show: isVisible,
|
|
webPreferences: {
|
|
blinkFeatures: 'EnumerateDevices,AudioOutputDevices',
|
|
nativeWindowOpen: true,
|
|
enableRemoteModule: true,
|
|
spellcheck: true,
|
|
contextIsolation: false,
|
|
...(isTabs ? {
|
|
nodeIntegration: true,
|
|
webviewTag: true
|
|
} : {
|
|
nodeIntegration: false,
|
|
webviewTag: false,
|
|
contextIsolation: true,
|
|
preload: _path.default.join(__dirname, 'mainScreenPreload.js')
|
|
}),
|
|
// NB: this is required in order to give popouts (or any child window opened via window.open w/ nativeWindowOpen)
|
|
// a chance at a node environment (i.e. they run the preload, have an isolated context, etc.) when
|
|
// `app.allowRendererProcessReuse === false` (default in Electron 7).
|
|
additionalArguments: ['--enable-node-leakage-in-renderers']
|
|
},
|
|
icon: _path.default.join(__dirname, "images", 'discord.png')
|
|
};
|
|
|
|
if (process.platform === 'linux') {
|
|
mainWindowOptions.frame = true;
|
|
}
|
|
|
|
if(!settings.get("NO_WINDOWS_BOUND", false))applyWindowBoundsToConfig(mainWindowOptions);
|
|
|
|
const useGlasstron = settings.get("GLASSTRON", true)
|
|
const BrowserWindow = useGlasstron ? glasstron.BrowserWindow : _electron.BrowserWindow
|
|
mainWindow = new BrowserWindow(mainWindowOptions);
|
|
mainWindowId = mainWindow.id;
|
|
global.mainWindowId = mainWindowId;
|
|
|
|
if(useGlasstron)setDefaultBlur()
|
|
|
|
mainWindow.webContents.session.webRequest.onHeadersReceived(function(details, callback) {
|
|
if (!details.responseHeaders["content-security-policy-report-only"] && !details.responseHeaders["content-security-policy"]) return callback({cancel: false});
|
|
delete details.responseHeaders["content-security-policy-report-only"];
|
|
delete details.responseHeaders["content-security-policy"];
|
|
callback({cancel: false, responseHeaders: details.responseHeaders});
|
|
});
|
|
|
|
mainWindow.setMenuBarVisibility(false);
|
|
|
|
if(!settings.get("NO_WINDOWS_BOUND", false)){
|
|
if (settings.get('IS_MAXIMIZED')) {
|
|
mainWindow.maximize();
|
|
}
|
|
|
|
if (settings.get('IS_MINIMIZED')) {
|
|
mainWindow.minimize();
|
|
}
|
|
}
|
|
|
|
mainWindow.webContents.on('new-window', (e, windowURL, frameName, disposition, options) => {
|
|
e.preventDefault();
|
|
|
|
if (frameNames.tartsWith(DISCORD_NAMESPACE) && checkUrlOriginMatches(windowURL, WEBAPP_ENDPOINT) && getSanitizedPath(windowURL) === '/popout') {
|
|
popoutWindows.openOrFocusWindow(e, windowURL, frameName, options);
|
|
return;
|
|
}
|
|
|
|
(0, _securityUtils.saferShellOpenExternal)(windowURL);
|
|
});
|
|
mainWindow.webContents.on('did-fail-load', (e, errCode, errDesc, validatedUrl) => {
|
|
if (insideAuthFlow) {
|
|
return;
|
|
}
|
|
|
|
if (validatedUrl !== URL_TO_LOAD) {
|
|
return;
|
|
} // -3 (ABORTED) means we are reloading the page before it has finished loading
|
|
// 0 (???) seems to also mean the same thing
|
|
|
|
|
|
if (errCode === -3 || errCode === 0) return;
|
|
lastPageLoadFailed = true;
|
|
console.error('[WebContents] did-fail-load', errCode, errDesc, `retry in ${connectionBackoff.current} ms`);
|
|
connectionBackoff.fail(() => {
|
|
console.log('[WebContents] retrying load', URL_TO_LOAD);
|
|
loadMainPage();
|
|
});
|
|
});
|
|
mainWindow.webContents.on('did-finish-load', () => {
|
|
if (insideAuthFlow && mainWindow.webContents && checkUrlOriginMatches(mainWindow.webContents.getURL(), WEBAPP_ENDPOINT)) {
|
|
insideAuthFlow = false;
|
|
}
|
|
|
|
mainWindowDidFinishLoad = true; // if this is a first open and there's an initial path, direct user to that path
|
|
|
|
if (mainWindowInitialPath != null) {
|
|
webContentsSend('MAIN_WINDOW_PATH', mainWindowInitialPath);
|
|
mainWindowInitialPath = null;
|
|
}
|
|
|
|
webContentsSend(mainWindow != null && mainWindow.isFocused() ? 'MAIN_WINDOW_FOCUS' : 'MAIN_WINDOW_BLUR');
|
|
|
|
if (!lastPageLoadFailed) {
|
|
connectionBackoff.succeed();
|
|
}
|
|
events.emit("ready")
|
|
});
|
|
mainWindow.webContents.on('crashed', (e, killed) => {
|
|
if (killed) {
|
|
_electron.app.quit();
|
|
|
|
return;
|
|
} // if we just crashed under 5 seconds ago, we are probably in a loop, so just die.
|
|
|
|
|
|
const crashTime = Date.now();
|
|
|
|
if (crashTime - lastCrashed < 5 * 1000) {
|
|
console.error('[WebContents] double crashed... RIP =(');
|
|
|
|
_electron.app.quit();
|
|
|
|
return;
|
|
}
|
|
|
|
lastCrashed = crashTime;
|
|
console.error('[WebContents] crashed... reloading');
|
|
launchMainAppWindow(true);
|
|
}); // Prevent navigation when links or files are dropping into the app, turning it into a browser.
|
|
// https://github.com/discord/discord/pull/278
|
|
|
|
mainWindow.webContents.on('will-navigate', (evt, url) => {
|
|
if (!insideAuthFlow && !checkUrlOriginMatches(url, WEBAPP_ENDPOINT)) {
|
|
evt.preventDefault();
|
|
}
|
|
}); // track intermediate auth flow
|
|
|
|
mainWindow.webContents.on('did-get-redirect-request', (event, oldUrl, newUrl) => {
|
|
if (checkUrlOriginMatches(oldUrl, WEBAPP_ENDPOINT) && checkUrlOriginMatches(newUrl, 'https://accounts.google.com/')) {
|
|
insideAuthFlow = true;
|
|
}
|
|
});
|
|
mainWindow.webContents.on('context-menu', (_, params) => {
|
|
webContentsSend('SPELLCHECK_RESULT', params.misspelledWord, params.dictionarySuggestions);
|
|
});
|
|
mainWindow.webContents.on('devtools-opened', () => {
|
|
webContentsSend('WINDOW_DEVTOOLS_OPENED');
|
|
});
|
|
mainWindow.webContents.on('devtools-closed', () => {
|
|
webContentsSend('WINDOW_DEVTOOLS_CLOSED');
|
|
});
|
|
mainWindow.on('focus', () => {
|
|
webContentsSend('MAIN_WINDOW_FOCUS');
|
|
});
|
|
mainWindow.on('blur', () => {
|
|
webContentsSend('MAIN_WINDOW_BLUR');
|
|
});
|
|
mainWindow.on('page-title-updated', (e, title) => {
|
|
if (mainWindow === null) {
|
|
return;
|
|
}
|
|
|
|
e.preventDefault();
|
|
|
|
if (!title.endsWith('Lightcord')) {
|
|
title += ' - Lightcord';
|
|
}
|
|
|
|
mainWindow.setTitle(title);
|
|
});
|
|
mainWindow.on('leave-html-full-screen', () => {
|
|
// fixes a bug wherein embedded videos returning from full screen cause our menu to be visible.
|
|
mainWindow.setMenuBarVisibility(false);
|
|
});
|
|
mainWindow.webContents.on('did-navigate-in-page', (_, eventUrl) => {
|
|
let parsedUrl;
|
|
|
|
try {
|
|
parsedUrl = _url.default.parse(eventUrl);
|
|
} catch (_) {
|
|
return;
|
|
} // Prevent back navigation from revisting the login page after logging in,
|
|
// or being able to navigate back after signing out.
|
|
|
|
|
|
if (parsedUrl && parsedUrl.pathname === '/login') {
|
|
mainWindow.webContents.clearHistory();
|
|
}
|
|
}); // 'swipe' only works if the classic 3 finger swipe style is enabled in
|
|
// 'System Preferences > Trackpad > More Gestures.' The more modern 2 finger
|
|
// gesture should be added when Electron adds support.
|
|
|
|
mainWindow.on('swipe', (_, direction) => {
|
|
switch (direction) {
|
|
case 'left':
|
|
webContentsSend('NAVIGATE_BACK', 'SWIPE');
|
|
break;
|
|
|
|
case 'right':
|
|
webContentsSend('NAVIGATE_FORWARD', 'SWIPE');
|
|
break;
|
|
}
|
|
}); // Windows/Linux media keys and 4th/5th mouse buttons.
|
|
|
|
mainWindow.on('app-command', (_, cmd) => {
|
|
switch (cmd) {
|
|
case 'browser-backward':
|
|
webContentsSend('NAVIGATE_BACK', 'BROWSER');
|
|
break;
|
|
|
|
case 'browser-forward':
|
|
webContentsSend('NAVIGATE_FORWARD', 'BROWSER');
|
|
break;
|
|
}
|
|
});
|
|
|
|
if (process.platform === 'win32') {
|
|
setupNotificationScreen(mainWindow);
|
|
}
|
|
|
|
setupSystemTray();
|
|
setupAppBadge();
|
|
setupAppConfig();
|
|
setupPopouts();
|
|
|
|
if (process.platform === 'linux' || process.platform === 'win32') {
|
|
systemTray.show();
|
|
mainWindow.on('close', e => {
|
|
if (mainWindow === null) {
|
|
// this means we're quitting
|
|
popoutWindows.closePopouts();
|
|
return;
|
|
}
|
|
|
|
webContentsSend('MAIN_WINDOW_BLUR'); // Save our app settings
|
|
|
|
saveWindowConfig(mainWindow); // Quit app if that's the setting
|
|
|
|
if (!settings.get('MINIMIZE_TO_TRAY', true)) {
|
|
_electron.app.quit();
|
|
|
|
return;
|
|
} // Else, minimize to tray
|
|
|
|
|
|
setWindowVisible(false);
|
|
e.preventDefault();
|
|
});
|
|
}
|
|
|
|
loadMainPage();
|
|
}
|
|
|
|
let updaterState = _Constants.UpdaterEvents.UPDATE_NOT_AVAILABLE;
|
|
|
|
function handleModuleUpdateCheckFinished(succeeded, updateCount, manualRequired) {
|
|
if (!succeeded) {
|
|
updaterState = _Constants.UpdaterEvents.UPDATE_NOT_AVAILABLE;
|
|
webContentsSend(_Constants.UpdaterEvents.UPDATE_ERROR);
|
|
return;
|
|
}
|
|
|
|
if (updateCount === 0) {
|
|
updaterState = _Constants.UpdaterEvents.UPDATE_NOT_AVAILABLE;
|
|
} else if (manualRequired) {
|
|
updaterState = _Constants.UpdaterEvents.UPDATE_MANUALLY;
|
|
} else {
|
|
updaterState = _Constants.UpdaterEvents.UPDATE_AVAILABLE;
|
|
}
|
|
|
|
webContentsSend(updaterState);
|
|
}
|
|
|
|
function handleModuleUpdateDownloadProgress(name, progress) {
|
|
if (mainWindow) {
|
|
mainWindow.setProgressBar(progress);
|
|
}
|
|
|
|
webContentsSend(_Constants.UpdaterEvents.MODULE_INSTALL_PROGRESS, name, progress);
|
|
}
|
|
|
|
function handleModuleUpdateDownloadsFinished(succeeded, failed) {
|
|
if (mainWindow) {
|
|
mainWindow.setProgressBar(-1);
|
|
}
|
|
|
|
if (updaterState === _Constants.UpdaterEvents.UPDATE_AVAILABLE) {
|
|
if (failed > 0) {
|
|
updaterState = _Constants.UpdaterEvents.UPDATE_NOT_AVAILABLE;
|
|
webContentsSend(_Constants.UpdaterEvents.UPDATE_ERROR);
|
|
} else {
|
|
updaterState = _Constants.UpdaterEvents.UPDATE_DOWNLOADED;
|
|
webContentsSend(updaterState);
|
|
}
|
|
}
|
|
}
|
|
|
|
function handleModuleUpdateInstalledModule(name, current, total, succeeded) {
|
|
if (mainWindow) {
|
|
mainWindow.setProgressBar(-1);
|
|
}
|
|
|
|
webContentsSend(_Constants.UpdaterEvents.MODULE_INSTALLED, name, succeeded);
|
|
}
|
|
|
|
function setUpdaterState(newUpdaterState) {
|
|
updaterState = newUpdaterState;
|
|
webContentsSend(updaterState);
|
|
}
|
|
|
|
async function checkForUpdatesWithUpdater(updater) {
|
|
if (updaterState === _Constants.UpdaterEvents.UPDATE_NOT_AVAILABLE) {
|
|
setUpdaterState(_Constants.UpdaterEvents.CHECKING_FOR_UPDATES);
|
|
|
|
try {
|
|
let installedAnything = false;
|
|
await updater.updateToLatest(progress => {
|
|
const task = progress.task.HostInstall || progress.task.ModuleInstall;
|
|
|
|
if (task != null && progress.state === 'Complete') {
|
|
if (!installedAnything) {
|
|
installedAnything = true;
|
|
setUpdaterState(_Constants.UpdaterEvents.UPDATE_AVAILABLE);
|
|
}
|
|
}
|
|
});
|
|
setUpdaterState(installedAnything ? _Constants.UpdaterEvents.UPDATE_DOWNLOADED : _Constants.UpdaterEvents.UPDATE_NOT_AVAILABLE);
|
|
} catch (e) {
|
|
console.error('Update to latest failed: ', e);
|
|
updaterState = _Constants.UpdaterEvents.UPDATE_NOT_AVAILABLE;
|
|
webContentsSend(_Constants.UpdaterEvents.UPDATE_FAILED);
|
|
}
|
|
} else {
|
|
webContentsSend(updaterState);
|
|
}
|
|
} // Setup handling of events related to updates using the new updater.
|
|
|
|
|
|
function setupUpdaterEventsWithUpdater(updater) {
|
|
_electron.app.on(_Constants.MenuEvents.CHECK_FOR_UPDATES, () => checkForUpdatesWithUpdater());
|
|
|
|
_ipcMain.default.on(_Constants.UpdaterEvents.CHECK_FOR_UPDATES, () => {
|
|
return checkForUpdatesWithUpdater(updater);
|
|
});
|
|
|
|
_ipcMain.default.on(_Constants.UpdaterEvents.QUIT_AND_INSTALL, () => {
|
|
saveWindowConfig(mainWindow);
|
|
mainWindow = null; // TODO(eiz): This is a workaround for old Linux host versions whose host
|
|
// updater did not have a quitAndInstall() method, which causes the module
|
|
// updater to crash if a host update is available and we try to restart to
|
|
// install modules. Remove when all hosts are updated.
|
|
|
|
try {
|
|
legacyModuleUpdater.quitAndInstallUpdates();
|
|
} catch (e) {
|
|
_electron.app.relaunch();
|
|
|
|
_electron.app.quit();
|
|
}
|
|
});
|
|
|
|
_ipcMain.default.on(_Constants.UpdaterEvents.UPDATER_HISTORY_QUERY_AND_TRUNCATE, () => {
|
|
if (updater.queryAndTruncateHistory != null) {
|
|
webContentsSend(_Constants.UpdaterEvents.UPDATER_HISTORY_RESPONSE, updater.queryAndTruncateHistory());
|
|
} else {
|
|
webContentsSend(_Constants.UpdaterEvents.UPDATER_HISTORY_RESPONSE, []);
|
|
}
|
|
});
|
|
} // Setup events related to updates using the old module updater.
|
|
//
|
|
// sets up event listeners between the browser window and the app to send
|
|
// and listen to update-related events
|
|
|
|
|
|
function setupLegacyUpdaterEvents() {
|
|
_electron.app.on(_Constants.MenuEvents.CHECK_FOR_UPDATES, () => legacyModuleUpdater.checkForUpdates());
|
|
|
|
legacyModuleUpdater.events.on(legacyModuleUpdater.CHECKING_FOR_UPDATES, () => {
|
|
updaterState = _Constants.UpdaterEvents.CHECKING_FOR_UPDATES;
|
|
webContentsSend(updaterState);
|
|
}); // TODO(eiz): We currently still need to handle the old style non-object-based
|
|
// updater events to allow discord_desktop_core to be newer than the host asar,
|
|
// which contains the updater itself.
|
|
//
|
|
// Once all clients have updated to a sufficiently new host, we can delete this.
|
|
|
|
if (legacyModuleUpdater.supportsEventObjects) {
|
|
legacyModuleUpdater.events.on(legacyModuleUpdater.UPDATE_CHECK_FINISHED, ({
|
|
succeeded,
|
|
updateCount,
|
|
manualRequired
|
|
}) => {
|
|
handleModuleUpdateCheckFinished(succeeded, updateCount, manualRequired);
|
|
});
|
|
legacyModuleUpdater.events.on(legacyModuleUpdater.DOWNLOADING_MODULE_PROGRESS, ({
|
|
name,
|
|
progress
|
|
}) => {
|
|
handleModuleUpdateDownloadProgress(name, progress);
|
|
});
|
|
legacyModuleUpdater.events.on(legacyModuleUpdater.DOWNLOADING_MODULES_FINISHED, ({
|
|
succeeded,
|
|
failed
|
|
}) => {
|
|
handleModuleUpdateDownloadsFinished(succeeded, failed);
|
|
});
|
|
legacyModuleUpdater.events.on(legacyModuleUpdater.INSTALLED_MODULE, ({
|
|
name,
|
|
current,
|
|
total,
|
|
succeeded
|
|
}) => {
|
|
handleModuleUpdateInstalledModule(name, current, total, succeeded);
|
|
});
|
|
} else {
|
|
legacyModuleUpdater.events.on(legacyModuleUpdater.UPDATE_CHECK_FINISHED, (succeeded, updateCount, manualRequired) => {
|
|
handleModuleUpdateCheckFinished(succeeded, updateCount, manualRequired);
|
|
});
|
|
legacyModuleUpdater.events.on(legacyModuleUpdater.DOWNLOADING_MODULE_PROGRESS, (name, progress) => {
|
|
handleModuleUpdateDownloadProgress(name, progress);
|
|
});
|
|
legacyModuleUpdater.events.on(legacyModuleUpdater.DOWNLOADING_MODULES_FINISHED, (succeeded, failed) => {
|
|
handleModuleUpdateDownloadsFinished(succeeded, failed);
|
|
});
|
|
legacyModuleUpdater.events.on(legacyModuleUpdater.INSTALLED_MODULE, (name, current, total, succeeded) => {
|
|
handleModuleUpdateInstalledModule(name, current, total, succeeded);
|
|
});
|
|
}
|
|
|
|
_ipcMain.default.on(_Constants.UpdaterEvents.CHECK_FOR_UPDATES, () => {
|
|
if (updaterState === _Constants.UpdaterEvents.UPDATE_NOT_AVAILABLE) {
|
|
legacyModuleUpdater.checkForUpdates();
|
|
} else {
|
|
webContentsSend(updaterState);
|
|
}
|
|
});
|
|
|
|
_ipcMain.default.on(_Constants.UpdaterEvents.QUIT_AND_INSTALL, () => {
|
|
saveWindowConfig(mainWindow);
|
|
mainWindow = null; // TODO(eiz): This is a workaround for old Linux host versions whose host
|
|
// updater did not have a quitAndInstall() method, which causes the module
|
|
// updater to crash if a host update is available and we try to restart to
|
|
// install modules. Remove when all hosts are updated.
|
|
|
|
try {
|
|
legacyModuleUpdater.quitAndInstallUpdates();
|
|
} catch (e) {
|
|
_electron.app.relaunch();
|
|
|
|
_electron.app.quit();
|
|
}
|
|
});
|
|
|
|
_ipcMain.default.on(_Constants.UpdaterEvents.MODULE_INSTALL, (_event, name) => {
|
|
// NOTE: do NOT allow options to be passed in, as this enables a client to downgrade its modules to potentially
|
|
// insecure versions.
|
|
legacyModuleUpdater.install(name, false);
|
|
});
|
|
|
|
_ipcMain.default.on(_Constants.UpdaterEvents.MODULE_QUERY, (_event, name) => {
|
|
webContentsSend(_Constants.UpdaterEvents.MODULE_INSTALLED, name, legacyModuleUpdater.isInstalled(name));
|
|
});
|
|
|
|
_ipcMain.default.on(_Constants.UpdaterEvents.UPDATER_HISTORY_QUERY_AND_TRUNCATE, () => {
|
|
webContentsSend(_Constants.UpdaterEvents.UPDATER_HISTORY_RESPONSE, legacyModuleUpdater.events.history);
|
|
legacyModuleUpdater.events.history = [];
|
|
});
|
|
}
|
|
|
|
function init() {
|
|
// electron default behavior is to app.quit here, so long as there are no other listeners. we handle quitting
|
|
// or minimizing to system tray ourselves via mainWindow.on('closed') so this is simply to disable the electron
|
|
// default behavior.
|
|
_electron.app.on('window-all-closed', () => {});
|
|
|
|
_electron.app.on('before-quit', () => {
|
|
saveWindowConfig(mainWindow);
|
|
mainWindow = null;
|
|
notificationScreen.close();
|
|
}); // TODO: move this to main startup
|
|
|
|
|
|
_electron.app.on('gpu-process-crashed', (e, killed) => {
|
|
if (killed) {
|
|
_electron.app.quit();
|
|
}
|
|
});
|
|
|
|
_electron.app.on('accessibility-support-changed', (_event, accessibilitySupportEnabled) => webContentsSend('ACCESSIBILITY_SUPPORT_CHANGED', accessibilitySupportEnabled));
|
|
|
|
_electron.app.on(_Constants.MenuEvents.OPEN_HELP, () => webContentsSend('HELP_OPEN'));
|
|
|
|
_electron.app.on(_Constants.MenuEvents.OPEN_SETTINGS, () => webContentsSend('USER_SETTINGS_OPEN')); // TODO: this hotpatches an issue with focusing the app from background.
|
|
// delete this after next stable electron release.
|
|
|
|
|
|
_electron.app.on('second-instance', (_event, args) => {
|
|
// if the second instance is the uninstaller, the bootstrap listener will quit the running app
|
|
if (args != null && args.indexOf('--squirrel-uninstall') > -1) {
|
|
return;
|
|
} // if the current instance is multi instance, we want to leave the window alone
|
|
|
|
|
|
if (process.argv != null && process.argv.slice(1).includes('--multi-instance')) {
|
|
return;
|
|
}
|
|
|
|
if (mainWindow == null) {
|
|
return;
|
|
}
|
|
|
|
setWindowVisible(true, false);
|
|
mainWindow.focus();
|
|
});
|
|
|
|
_ipcMain.default.on('SETTINGS_UPDATE_BACKGROUND_COLOR', (_event, backgroundColor) => {
|
|
if (getBackgroundColor() !== backgroundColor) {
|
|
setBackgroundColor(backgroundColor);
|
|
}
|
|
});
|
|
|
|
const updater = _updater.default === null || _updater.default === void 0 ? void 0 : _updater.default.getUpdater();
|
|
|
|
if (updater != null) {
|
|
setupUpdaterEventsWithUpdater(updater);
|
|
} else {
|
|
setupLegacyUpdaterEvents();
|
|
}
|
|
|
|
launchMainAppWindow(false);
|
|
}
|
|
|
|
function handleOpenUrl(url) {
|
|
const path = getSanitizedProtocolPath(url);
|
|
|
|
if (path != null) {
|
|
if (!mainWindowDidFinishLoad) {
|
|
mainWindowInitialPath = path;
|
|
}
|
|
|
|
webContentsSend('MAIN_WINDOW_PATH', path);
|
|
}
|
|
|
|
if (mainWindow == null) {
|
|
return;
|
|
}
|
|
|
|
setWindowVisible(true, false);
|
|
mainWindow.focus();
|
|
}
|
|
|
|
function setMainWindowVisible(visible) {
|
|
setWindowVisible(visible, false);
|
|
} |