Files
mattermostest/src/main/app/utils.ts
Devin Binnie 1fe94eb167 Fixed issues with loading the app from cold when deep linking (#3201)
* Fixed issues with loading the app from cold when deep linking

* Don't call show() before the window is created on Windows
2024-11-13 10:46:32 -05:00

261 lines
8.8 KiB
TypeScript

// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import fs from 'fs';
import path from 'path';
import type {BrowserWindow, Rectangle} from 'electron';
import {app, Menu, session, dialog, nativeImage, screen} from 'electron';
import isDev from 'electron-is-dev';
import {APP_MENU_WILL_CLOSE, MAIN_WINDOW_CREATED} from 'common/communication';
import Config from 'common/config';
import JsonFileManager from 'common/JsonFileManager';
import {Logger} from 'common/log';
import type {MattermostServer} from 'common/servers/MattermostServer';
import ServerManager from 'common/servers/serverManager';
import {isValidURI} from 'common/utils/url';
import updateManager from 'main/autoUpdater';
import {migrationInfoPath, updatePaths} from 'main/constants';
import {localizeMessage} from 'main/i18nManager';
import {createMenu as createAppMenu} from 'main/menus/app';
import {createMenu as createTrayMenu} from 'main/menus/tray';
import {ServerInfo} from 'main/server/serverInfo';
import Tray from 'main/tray/tray';
import ViewManager from 'main/views/viewManager';
import MainWindow from 'main/windows/mainWindow';
import type {MigrationInfo} from 'types/config';
import type {RemoteInfo} from 'types/server';
import type {Boundaries} from 'types/utils';
import {mainProtocol} from './initialize';
const assetsDir = path.resolve(app.getAppPath(), 'assets');
const appIconURL = path.resolve(assetsDir, 'appicon_with_spacing_32.png');
const appIcon = nativeImage.createFromPath(appIconURL);
const log = new Logger('App.Utils');
export function openDeepLink(deeplinkingUrl: string) {
try {
if (MainWindow.get()) {
MainWindow.show();
ViewManager.handleDeepLink(deeplinkingUrl);
} else {
MainWindow.on(MAIN_WINDOW_CREATED, () => ViewManager.handleDeepLink(deeplinkingUrl));
}
} catch (err) {
log.error(`There was an error opening the deeplinking url: ${err}`);
}
}
export function updateSpellCheckerLocales() {
if (Config.spellCheckerLocales.length && app.isReady()) {
session.defaultSession.setSpellCheckerLanguages(Config.spellCheckerLocales);
}
}
export function handleUpdateMenuEvent() {
log.debug('handleUpdateMenuEvent');
const aMenu = createAppMenu(Config, updateManager);
Menu.setApplicationMenu(aMenu);
aMenu.addListener('menu-will-close', () => {
ViewManager.focusCurrentView();
MainWindow.sendToRenderer(APP_MENU_WILL_CLOSE);
});
// set up context menu for tray icon
if (shouldShowTrayIcon()) {
const tMenu = createTrayMenu();
Tray.setMenu(tMenu);
}
}
export function getDeeplinkingURL(args: string[]) {
if (Array.isArray(args) && args.length) {
// deeplink urls should always be the last argument, but may not be the first (i.e. Windows with the app already running)
const url = args[args.length - 1];
const protocol = isDev ? 'mattermost-dev' : mainProtocol;
if (url && protocol && url.startsWith(protocol) && isValidURI(url)) {
return url;
}
}
return undefined;
}
export function shouldShowTrayIcon() {
return Config.showTrayIcon || process.platform === 'win32';
}
export function wasUpdated(lastAppVersion?: string) {
return lastAppVersion !== app.getVersion();
}
export function clearAppCache() {
// TODO: clear cache on browserviews, not in the renderer.
const mainWindow = MainWindow.get();
if (mainWindow) {
mainWindow.webContents.session.clearCache().
then(mainWindow.webContents.reload).
catch((err) => {
log.error('clearAppCache', err);
});
} else {
//Wait for mainWindow
setTimeout(clearAppCache, 100);
}
}
function isWithinDisplay(state: Rectangle, display: Boundaries) {
const startsWithinDisplay = !(state.x > display.maxX || state.y > display.maxY || state.x < display.minX || state.y < display.minY);
if (!startsWithinDisplay) {
return false;
}
// is half the screen within the display?
const midX = state.x + (state.width / 2);
const midY = state.y + (state.height / 2);
return !(midX > display.maxX || midY > display.maxY);
}
function getDisplayBoundaries() {
const displays = screen.getAllDisplays();
return displays.map((display) => {
return {
maxX: display.workArea.x + display.workArea.width,
maxY: display.workArea.y + display.workArea.height,
minX: display.workArea.x,
minY: display.workArea.y,
maxWidth: display.workArea.width,
maxHeight: display.workArea.height,
};
});
}
function getValidWindowPosition(state: Rectangle) {
// Check if the previous position is out of the viewable area
// (e.g. because the screen has been plugged off)
const boundaries = getDisplayBoundaries();
const display = boundaries.find((boundary) => {
return isWithinDisplay(state, boundary);
});
if (typeof display === 'undefined') {
return {};
}
return {x: state.x, y: state.y};
}
function getNewWindowPosition(browserWindow: BrowserWindow) {
const mainWindow = MainWindow.get();
if (!mainWindow) {
return browserWindow.getPosition();
}
const newWindowSize = browserWindow.getSize();
const mainWindowSize = mainWindow.getSize();
const mainWindowPosition = mainWindow.getPosition();
return [
Math.floor(mainWindowPosition[0] + ((mainWindowSize[0] - newWindowSize[0]) / 2)),
Math.floor(mainWindowPosition[1] + ((mainWindowSize[1] - newWindowSize[1]) / 2)),
];
}
export function resizeScreen(browserWindow: BrowserWindow) {
const position = getNewWindowPosition(browserWindow);
const size = browserWindow.getSize();
const validPosition = getValidWindowPosition({
x: position[0],
y: position[1],
width: size[0],
height: size[1],
});
if (typeof validPosition.x !== 'undefined' || typeof validPosition.y !== 'undefined') {
browserWindow.setPosition(validPosition.x || 0, validPosition.y || 0);
} else {
browserWindow.center();
}
}
export function flushCookiesStore() {
log.debug('flushCookiesStore');
session.defaultSession.cookies.flushStore().catch((err) => {
log.error(`There was a problem flushing cookies:\n${err}`);
});
}
export function migrateMacAppStore() {
const migrationPrefs = new JsonFileManager<MigrationInfo>(migrationInfoPath);
const oldPath = path.join(app.getPath('userData'), '../../../../../../../Library/Application Support/Mattermost');
// Check if we've already migrated
if (migrationPrefs.getValue('masConfigs')) {
return;
}
// Check if the files are there to migrate
try {
const exists = fs.existsSync(oldPath);
if (!exists) {
log.info('MAS: No files to migrate, skipping');
return;
}
} catch (e) {
log.error('MAS: Failed to check for existing Mattermost Desktop install, skipping', e);
return;
}
const cancelImport = dialog.showMessageBoxSync({
title: app.name,
message: localizeMessage('main.app.utils.migrateMacAppStore.dialog.message', 'Import Existing Configuration'),
detail: localizeMessage('main.app.utils.migrateMacAppStore.dialog.detail', 'It appears that an existing {appName} configuration exists, would you like to import it? You will be asked to pick the correct configuration directory.', {appName: app.name}),
icon: appIcon,
buttons: [
localizeMessage('main.app.utils.migrateMacAppStore.button.selectAndImport', 'Select Directory and Import'),
localizeMessage('main.app.utils.migrateMacAppStore.button.dontImport', 'Don\'t Import'),
],
type: 'info',
defaultId: 0,
cancelId: 1,
});
if (cancelImport) {
migrationPrefs.setValue('masConfigs', true);
return;
}
const result = dialog.showOpenDialogSync({
defaultPath: oldPath,
properties: ['openDirectory'],
});
if (!(result && result[0])) {
return;
}
try {
fs.cpSync(result[0], app.getPath('userData'), {recursive: true});
updatePaths(true);
migrationPrefs.setValue('masConfigs', true);
} catch (e) {
log.error('MAS: An error occurred importing the existing configuration', e);
}
}
export async function updateServerInfos(servers: MattermostServer[]) {
const map: Map<string, RemoteInfo> = new Map();
await Promise.all(servers.map((srv) => {
const serverInfo = new ServerInfo(srv);
return serverInfo.fetchRemoteInfo().
then((data) => {
map.set(srv.id, data);
}).
catch((error) => {
log.warn('Could not get server info for', srv.name, error);
});
}));
ServerManager.updateRemoteInfos(map);
}