[MM-59543] Disallow use of file: protocol in the app, remove all references to it, add mattermost-desktop: protocol to read local files (#3095)

This commit is contained in:
Devin Binnie
2024-07-18 16:01:44 -04:00
committed by GitHub
parent 87b2f12663
commit 080e4bf727
41 changed files with 99 additions and 152 deletions

View File

@@ -69,6 +69,10 @@ jest.mock('electron', () => ({
on: jest.fn(),
},
},
protocol: {
registerSchemesAsPrivileged: jest.fn(),
handle: jest.fn(),
},
}));
jest.mock('main/i18nManager', () => ({

View File

@@ -2,8 +2,9 @@
// See LICENSE.txt for license information.
import path from 'path';
import {pathToFileURL} from 'url';
import {app, ipcMain, nativeTheme, session} from 'electron';
import {app, ipcMain, nativeTheme, net, protocol, session} from 'electron';
import installExtension, {REACT_DEVELOPER_TOOLS} from 'electron-extension-installer';
import isDev from 'electron-is-dev';
@@ -36,6 +37,7 @@ import {
import Config from 'common/config';
import {Logger} from 'common/log';
import ServerManager from 'common/servers/serverManager';
import {parseURL} from 'common/utils/url';
import AllowProtocolDialog from 'main/allowProtocolDialog';
import AppVersionManager from 'main/AppVersionManager';
import AuthManager from 'main/authManager';
@@ -254,6 +256,10 @@ function initializeBeforeAppReady() {
nativeTheme.on('updated', handleUpdateTheme);
handleUpdateTheme();
}
protocol.registerSchemesAsPrivileged([
{scheme: 'mattermost-desktop', privileges: {standard: true}},
]);
}
function initializeInterCommunicationEventListeners() {
@@ -291,6 +297,24 @@ function initializeInterCommunicationEventListeners() {
}
async function initializeAfterAppReady() {
protocol.handle('mattermost-desktop', (request: Request) => {
const url = parseURL(request.url);
if (!url) {
return new Response('bad', {status: 400});
}
// Including this snippet from the handler docs to check for path traversal
// https://www.electronjs.org/docs/latest/api/protocol#protocolhandlescheme-handler
const pathToServe = path.join(app.getAppPath(), 'renderer', url.pathname);
const relativePath = path.relative(app.getAppPath(), pathToServe);
const isSafe = relativePath && !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
if (!isSafe) {
return new Response('bad', {status: 400});
}
return net.fetch(pathToFileURL(pathToServe).toString());
});
ServerManager.reloadFromConfig();
updateServerInfos(ServerManager.getAllServers());
ServerManager.on(SERVERS_URL_MODIFIED, (serverIds?: string[]) => {

View File

@@ -4,7 +4,7 @@
import {app} from 'electron';
import ServerManager from 'common/servers/serverManager';
import {getLocalURLString, getLocalPreload} from 'main/utils';
import {getLocalPreload} from 'main/utils';
import ModalManager from 'main/views/modalManager';
import MainWindow from 'main/windows/mainWindow';
@@ -38,7 +38,6 @@ jest.mock('common/servers/serverManager', () => ({
}));
jest.mock('main/utils', () => ({
getLocalPreload: jest.fn(),
getLocalURLString: jest.fn(),
}));
jest.mock('main/views/viewManager', () => ({}));
jest.mock('main/views/modalManager', () => ({
@@ -53,7 +52,6 @@ jest.mock('./app', () => ({}));
describe('main/app/intercom', () => {
describe('handleWelcomeScreenModal', () => {
beforeEach(() => {
getLocalURLString.mockReturnValue('/some/index.html');
getLocalPreload.mockReturnValue('/some/preload.js');
MainWindow.get.mockReturnValue({});
@@ -65,13 +63,12 @@ describe('main/app/intercom', () => {
ModalManager.addModal.mockReturnValue(promise);
handleWelcomeScreenModal();
expect(ModalManager.addModal).toHaveBeenCalledWith('welcomeScreen', '/some/index.html', '/some/preload.js', null, {}, true);
expect(ModalManager.addModal).toHaveBeenCalledWith('welcomeScreen', 'mattermost-desktop://renderer/welcomeScreen.html', '/some/preload.js', null, {}, true);
});
});
describe('handleMainWindowIsShown', () => {
it('MM-48079 should not show onboarding screen or server screen if GPO server is pre-configured', () => {
getLocalURLString.mockReturnValue('/some/index.html');
getLocalPreload.mockReturnValue('/some/preload.js');
MainWindow.get.mockReturnValue({
isVisible: () => true,

View File

@@ -9,7 +9,7 @@ import {Logger} from 'common/log';
import ServerManager from 'common/servers/serverManager';
import {ping} from 'common/utils/requests';
import NotificationManager from 'main/notifications';
import {getLocalPreload, getLocalURLString} from 'main/utils';
import {getLocalPreload} from 'main/utils';
import ModalManager from 'main/views/modalManager';
import MainWindow from 'main/windows/mainWindow';
@@ -88,7 +88,7 @@ export function handleMainWindowIsShown() {
export function handleWelcomeScreenModal() {
log.debug('handleWelcomeScreenModal');
const html = getLocalURLString('welcomeScreen.html');
const html = 'mattermost-desktop://renderer/welcomeScreen.html';
const preload = getLocalPreload('internalAPI.js');
@@ -169,7 +169,7 @@ export function handleShowSettingsModal() {
ModalManager.addModal(
'settingsModal',
getLocalURLString('settings.html'),
'mattermost-desktop://renderer/settings.html',
getLocalPreload('internalAPI.js'),
null,
mainWindow,

View File

@@ -51,7 +51,6 @@ jest.mock('main/views/modalManager', () => ({
jest.mock('main/utils', () => ({
getLocalPreload: (file) => file,
getLocalURLString: (file) => file,
}));
describe('main/authManager', () => {

View File

@@ -6,7 +6,7 @@ import {Logger} from 'common/log';
import {BASIC_AUTH_PERMISSION} from 'common/permissions';
import {isCustomLoginURL, isTrustedURL, parseURL} from 'common/utils/url';
import TrustedOriginsStore from 'main/trustedOrigins';
import {getLocalURLString, getLocalPreload} from 'main/utils';
import {getLocalPreload} from 'main/utils';
import modalManager from 'main/views/modalManager';
import ViewManager from 'main/views/viewManager';
import MainWindow from 'main/windows/mainWindow';
@@ -16,8 +16,8 @@ import type {PermissionType} from 'types/trustedOrigin';
const log = new Logger('AuthManager');
const preload = getLocalPreload('internalAPI.js');
const loginModalHtml = getLocalURLString('loginModal.html');
const permissionModalHtml = getLocalURLString('permissionModal.html');
const loginModalHtml = 'mattermost-desktop://renderer/loginModal.html';
const permissionModalHtml = 'mattermost-desktop://renderer/permissionModal.html';
type LoginModalResult = {
username: string;

View File

@@ -16,7 +16,6 @@ jest.mock('main/views/modalManager', () => ({
jest.mock('main/utils', () => ({
getLocalPreload: (file) => file,
getLocalURLString: (file) => file,
}));
describe('main/certificateManager', () => {

View File

@@ -7,13 +7,13 @@ import {Logger} from 'common/log';
import type {CertificateModalData} from 'types/certificate';
import {getLocalURLString, getLocalPreload} from './utils';
import {getLocalPreload} from './utils';
import modalManager from './views/modalManager';
import MainWindow from './windows/mainWindow';
const log = new Logger('CertificateManager');
const preload = getLocalPreload('internalAPI.js');
const html = getLocalURLString('certificateModal.html');
const html = 'mattermost-desktop://renderer/certificateModal.html';
type CertificateModalResult = {
cert: Certificate;

View File

@@ -14,7 +14,7 @@ const defaultMenuOptions = {
let isInternalSrc;
try {
const srcurl = parseURL(p.srcURL);
isInternalSrc = srcurl?.protocol === 'file:';
isInternalSrc = srcurl?.protocol === 'mattermost-desktop:';
} catch (err) {
isInternalSrc = false;
}

View File

@@ -148,11 +148,11 @@ describe('main/downloadsManager', () => {
it('should mark "completed" files that were deleted as "deleted"', () => {
expect(new DownloadsManager(JSON.stringify(downloadsJson))).toHaveProperty('downloads', {...downloadsJson, 'file1.txt': {...downloadsJson['file1.txt'], state: 'deleted'}});
});
it('should handle a new download', () => {
it('should handle a new download', async () => {
const dl = new DownloadsManager({});
path.parse.mockImplementation(() => ({base: 'file.txt'}));
dl.willDownloadURLs.set('http://some-url.com/some-text.txt', {filePath: locationMock});
dl.handleNewDownload({preventDefault: jest.fn()}, item, {id: 0, getURL: jest.fn(), downloadURL: jest.fn()});
await dl.handleNewDownload({preventDefault: jest.fn()}, item, {id: 0, getURL: jest.fn(), downloadURL: jest.fn()});
expect(dl).toHaveProperty('downloads', {'file.txt': {
addedAt: nowSeconds * 1000,
filename: 'file.txt',

View File

@@ -4,7 +4,7 @@ import fs from 'fs';
import path from 'path';
import type {DownloadItem, Event, WebContents, FileFilter, IpcMainInvokeEvent} from 'electron';
import {ipcMain, dialog, shell, Menu, app} from 'electron';
import {ipcMain, dialog, shell, Menu, app, nativeImage} from 'electron';
import type {ProgressInfo, UpdateInfo} from 'electron-updater';
import {
@@ -123,7 +123,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
item.setSavePath(info.filePath);
}
this.upsertFileToDownloads(item, 'progressing');
await this.upsertFileToDownloads(item, 'progressing');
this.progressingItems.set(this.getFileId(item), item);
this.handleDownloadItemEvents(item, webContents);
this.openDownloadsDropdown();
@@ -501,10 +501,10 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
}
};
private upsertFileToDownloads = (item: DownloadItem, state: DownloadItemState, overridePath?: string) => {
private upsertFileToDownloads = async (item: DownloadItem, state: DownloadItemState, overridePath?: string) => {
const fileId = this.getFileId(item);
log.debug('upsertFileToDownloads', {fileId});
const formattedItem = this.formatDownloadItem(item, state, overridePath);
const formattedItem = await this.formatDownloadItem(item, state, overridePath);
this.save(fileId, formattedItem);
this.checkIfMaxFilesReached();
};
@@ -545,10 +545,10 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
/**
* DownloadItem event handlers
*/
private updatedEventController = (updatedEvent: Event, state: DownloadItemUpdatedEventState, item: DownloadItem) => {
private updatedEventController = async (updatedEvent: Event, state: DownloadItemUpdatedEventState, item: DownloadItem) => {
log.debug('updatedEventController', {state});
this.upsertFileToDownloads(item, state);
await this.upsertFileToDownloads(item, state);
if (state === 'interrupted') {
this.fileSizes.delete(item.getFilename());
@@ -557,7 +557,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
this.shouldShowBadge();
};
private doneEventController = (doneEvent: Event, state: DownloadItemDoneEventState, item: DownloadItem, webContents: WebContents) => {
private doneEventController = async (doneEvent: Event, state: DownloadItemDoneEventState, item: DownloadItem, webContents: WebContents) => {
log.debug('doneEventController', {state});
if (state === 'completed' && !this.open) {
@@ -571,7 +571,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
func();
}
this.upsertFileToDownloads(item, state, bookmark?.originalPath);
await this.upsertFileToDownloads(item, state, bookmark?.originalPath);
this.fileSizes.delete(item.getFilename());
this.progressingItems.delete(this.getFileId(item));
this.shouldAutoClose();
@@ -628,11 +628,16 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
/**
* Internal utils
*/
private formatDownloadItem = (item: DownloadItem, state: DownloadItemState, overridePath?: string): DownloadedItem => {
private formatDownloadItem = async (item: DownloadItem, state: DownloadItemState, overridePath?: string): Promise<DownloadedItem> => {
const totalBytes = this.getFileSize(item);
const receivedBytes = item.getReceivedBytes();
const progress = getPercentage(receivedBytes, totalBytes);
let thumbnailData;
if (state === 'completed' && item.getMimeType().toLowerCase().startsWith('image/')) {
thumbnailData = (await nativeImage.createThumbnailFromPath(overridePath ?? item.getSavePath(), {height: 32, width: 32})).toDataURL();
}
return {
addedAt: doubleSecToMs(item.getStartTime()),
filename: this.getFileId(item),
@@ -644,6 +649,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
totalBytes,
type: DownloadItemTypeEnum.FILE,
bookmark: this.getBookmark(item),
thumbnailData,
};
};

View File

@@ -18,7 +18,7 @@ import type {UpdateManager} from 'main/autoUpdater';
import Diagnostics from 'main/diagnostics';
import downloadsManager from 'main/downloadsManager';
import {localizeMessage} from 'main/i18nManager';
import {getLocalPreload, getLocalURLString} from 'main/utils';
import {getLocalPreload} from 'main/utils';
import ModalManager from 'main/views/modalManager';
import ViewManager from 'main/views/viewManager';
import CallsWidgetWindow from 'main/windows/callsWidgetWindow';
@@ -57,7 +57,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
ModalManager.addModal(
'settingsModal',
getLocalURLString('settings.html'),
'mattermost-desktop://renderer/settings.html',
getLocalPreload('internalAPI.js'),
null,
mainWindow,

View File

@@ -9,7 +9,7 @@ import {Menu} from 'electron';
import ServerViewState from 'app/serverViewState';
import ServerManager from 'common/servers/serverManager';
import {localizeMessage} from 'main/i18nManager';
import {getLocalPreload, getLocalURLString} from 'main/utils';
import {getLocalPreload} from 'main/utils';
import ModalManager from 'main/views/modalManager';
import MainWindow from 'main/windows/mainWindow';
@@ -35,7 +35,7 @@ export function createTemplate() {
ModalManager.addModal(
'settingsModal',
getLocalURLString('settings.html'),
'mattermost-desktop://renderer/settings.html',
getLocalPreload('internalAPI.js'),
null,
mainWindow,

View File

@@ -72,7 +72,6 @@ import {
START_UPDATE_DOWNLOAD,
START_UPGRADE,
TOGGLE_DOWNLOADS_DROPDOWN_MENU,
GET_DOWNLOADED_IMAGE_THUMBNAIL_LOCATION,
DOWNLOADS_DROPDOWN_OPEN_FILE,
MODAL_CANCEL,
MODAL_RESULT,
@@ -110,10 +109,6 @@ contextBridge.exposeInMainWorld('timers', {
setImmediate,
});
contextBridge.exposeInMainWorld('mas', {
getThumbnailLocation: (location) => ipcRenderer.invoke(GET_DOWNLOADED_IMAGE_THUMBNAIL_LOCATION, location),
});
contextBridge.exposeInMainWorld('desktop', {
quit: (reason, stack) => ipcRenderer.send(QUIT, reason, stack),
openAppMenu: () => ipcRenderer.send(OPEN_APP_MENU),

View File

@@ -3,7 +3,6 @@
'use strict';
import {BACK_BAR_HEIGHT, TAB_BAR_HEIGHT} from 'common/utils/constants';
import {runMode} from 'common/utils/util';
import * as Utils from './utils';
@@ -82,29 +81,6 @@ describe('main/utils', () => {
});
});
describe('getLocalURLString', () => {
it('should return URL relative to current run directory', () => {
runMode.mockImplementation(() => 'development');
expect(Utils.getLocalURLString('index.html')).toStrictEqual('file:///path/to/app/dist/renderer/index.html');
});
it('should return URL relative to current run directory in production', () => {
runMode.mockImplementation(() => 'production');
expect(Utils.getLocalURLString('index.html')).toStrictEqual('file:///path/to/app/renderer/index.html');
});
it('should include query string when specified', () => {
const queryMap = new Map([['key', 'value']]);
runMode.mockImplementation(() => 'development');
expect(Utils.getLocalURLString('index.html', queryMap)).toStrictEqual('file:///path/to/app/dist/renderer/index.html?key=value');
});
it('should return URL relative to current run directory when using main process', () => {
runMode.mockImplementation(() => 'development');
expect(Utils.getLocalURLString('index.html', null, true)).toStrictEqual('file:///path/to/app/dist/index.html');
});
});
describe('shouldHaveBackBar', () => {
it('should have back bar for custom logins', () => {
expect(Utils.shouldHaveBackBar(new URL('https://server-1.com'), new URL('https://server-1.com/login/sso/saml'))).toBe(true);

View File

@@ -11,9 +11,8 @@ const exec = promisify(execOriginal);
import type {BrowserWindow} from 'electron';
import {app} from 'electron';
import {BACK_BAR_HEIGHT, customLoginRegexPaths, PRODUCTION, TAB_BAR_HEIGHT} from 'common/utils/constants';
import {BACK_BAR_HEIGHT, customLoginRegexPaths, TAB_BAR_HEIGHT} from 'common/utils/constants';
import {isAdminUrl, isPluginUrl, isTeamUrl, isUrlType, parseURL} from 'common/utils/url';
import Utils from 'common/utils/util';
import type {Args} from 'types/args';
@@ -85,34 +84,8 @@ export function shouldHaveBackBar(serverUrl: URL, inputURL: URL) {
return !isTeamUrl(serverUrl, inputURL) && !isAdminUrl(serverUrl, inputURL) && !isPluginUrl(serverUrl, inputURL);
}
export function getLocalURLString(urlPath: string, query?: Map<string, string>, isMain?: boolean) {
let pathname;
const processPath = isMain ? '' : '/renderer';
const mode = Utils.runMode();
const protocol = 'file';
const hostname = '';
const port = '';
if (mode === PRODUCTION) {
pathname = path.join(app.getAppPath(), `${processPath}/${urlPath}`);
} else {
pathname = path.resolve(__dirname, `../../dist/${processPath}/${urlPath}`); // TODO: find a better way to work with webpack on this
}
const localUrl = new URL(`${protocol}://${hostname}${port}`);
localUrl.pathname = pathname;
if (query) {
query.forEach((value: string, key: string) => {
localUrl.searchParams.append(encodeURIComponent(key), encodeURIComponent(value));
});
}
return localUrl.href;
}
export function getLocalPreload(file: string) {
if (Utils.runMode() === PRODUCTION) {
return path.join(app.getAppPath(), `${file}`);
}
return path.resolve(__dirname, `../../dist/${file}`);
return path.join(app.getAppPath(), file);
}
export function composeUserAgent() {

View File

@@ -12,7 +12,6 @@ import {DownloadsDropdownMenuView} from './downloadsDropdownMenuView';
jest.mock('main/utils', () => ({
getLocalPreload: (file) => file,
getLocalURLString: (file) => file,
}));
jest.mock('electron', () => {
class NotificationMock {

View File

@@ -27,7 +27,7 @@ import {
TAB_BAR_HEIGHT,
} from 'common/utils/constants';
import downloadsManager from 'main/downloadsManager';
import {getLocalPreload, getLocalURLString} from 'main/utils';
import {getLocalPreload} from 'main/utils';
import MainWindow from 'main/windows/mainWindow';
import type {CoordinatesToJsonType, DownloadedItem, DownloadsMenuOpenEventPayload} from 'types/downloads';
@@ -75,7 +75,7 @@ export class DownloadsDropdownMenuView {
// @ts-ignore
transparent: true,
}});
this.view.webContents.loadURL(getLocalURLString('downloadsDropdownMenu.html'));
this.view.webContents.loadURL('mattermost-desktop://renderer/downloadsDropdownMenu.html');
MainWindow.get()?.addBrowserView(this.view);
};

View File

@@ -12,7 +12,6 @@ import {DownloadsDropdownView} from './downloadsDropdownView';
jest.mock('main/utils', () => ({
getLocalPreload: (file) => file,
getLocalURLString: (file) => file,
}));
jest.mock('fs', () => ({
existsSync: jest.fn().mockReturnValue(false),

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {IpcMainEvent, IpcMainInvokeEvent} from 'electron';
import type {IpcMainEvent} from 'electron';
import {BrowserView, ipcMain} from 'electron';
import {
@@ -13,7 +13,6 @@ import {
REQUEST_DOWNLOADS_DROPDOWN_INFO,
UPDATE_DOWNLOADS_DROPDOWN,
UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM,
GET_DOWNLOADED_IMAGE_THUMBNAIL_LOCATION,
DOWNLOADS_DROPDOWN_OPEN_FILE,
MAIN_WINDOW_CREATED,
MAIN_WINDOW_RESIZED,
@@ -22,7 +21,7 @@ import Config from 'common/config';
import {Logger} from 'common/log';
import {TAB_BAR_HEIGHT, DOWNLOADS_DROPDOWN_WIDTH, DOWNLOADS_DROPDOWN_HEIGHT, DOWNLOADS_DROPDOWN_FULL_WIDTH} from 'common/utils/constants';
import downloadsManager from 'main/downloadsManager';
import {getLocalPreload, getLocalURLString} from 'main/utils';
import {getLocalPreload} from 'main/utils';
import MainWindow from 'main/windows/mainWindow';
import type {DownloadedItem} from 'types/downloads';
@@ -47,7 +46,6 @@ export class DownloadsDropdownView {
ipcMain.on(DOWNLOADS_DROPDOWN_OPEN_FILE, this.openFile);
ipcMain.on(UPDATE_DOWNLOADS_DROPDOWN, this.updateDownloadsDropdown);
ipcMain.on(UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM, this.updateDownloadsDropdownMenuItem);
ipcMain.handle(GET_DOWNLOADED_IMAGE_THUMBNAIL_LOCATION, this.getDownloadImageThumbnailLocation);
}
init = () => {
@@ -67,7 +65,7 @@ export class DownloadsDropdownView {
transparent: true,
}});
this.view.webContents.loadURL(getLocalURLString('downloadsDropdown.html'));
this.view.webContents.loadURL('mattermost-desktop://renderer/downloadsDropdown.html');
this.view.webContents.session.webRequest.onHeadersReceived(downloadsManager.webRequestOnHeadersReceivedHandler);
MainWindow.get()?.addBrowserView(this.view);
};
@@ -183,10 +181,6 @@ export class DownloadsDropdownView {
this.view?.setBounds(this.bounds);
}
};
private getDownloadImageThumbnailLocation = (event: IpcMainInvokeEvent, location: string) => {
return location;
};
}
const downloadsDropdownView = new DownloadsDropdownView();

View File

@@ -5,7 +5,7 @@ import {BrowserView, app, ipcMain} from 'electron';
import {DARK_MODE_CHANGE, LOADING_SCREEN_ANIMATION_FINISHED, MAIN_WINDOW_RESIZED, TOGGLE_LOADING_SCREEN_VISIBILITY} from 'common/communication';
import {Logger} from 'common/log';
import {getLocalPreload, getLocalURLString, getWindowBoundaries} from 'main/utils';
import {getLocalPreload, getWindowBoundaries} from 'main/utils';
import MainWindow from 'main/windows/mainWindow';
enum LoadingScreenState {
@@ -85,7 +85,7 @@ export class LoadingScreen {
// @ts-ignore
transparent: true,
}});
const localURL = getLocalURLString('loadingScreen.html');
const localURL = 'mattermost-desktop://renderer/loadingScreen.html';
this.view.webContents.loadURL(localURL);
};

View File

@@ -12,7 +12,6 @@ jest.mock('app/serverViewState', () => ({}));
jest.mock('main/utils', () => ({
getLocalPreload: (file) => file,
getLocalURLString: (file) => file,
}));
jest.mock('electron', () => ({

View File

@@ -22,7 +22,7 @@ import Config from 'common/config';
import {Logger} from 'common/log';
import ServerManager from 'common/servers/serverManager';
import {TAB_BAR_HEIGHT, THREE_DOT_MENU_WIDTH, THREE_DOT_MENU_WIDTH_MAC, MENU_SHADOW_WIDTH} from 'common/utils/constants';
import {getLocalPreload, getLocalURLString} from 'main/utils';
import {getLocalPreload} from 'main/utils';
import type {UniqueServer} from 'types/config';
@@ -83,7 +83,7 @@ export class ServerDropdownView {
// @ts-ignore
transparent: true,
}});
this.view.webContents.loadURL(getLocalURLString('dropdown.html'));
this.view.webContents.loadURL('mattermost-desktop://renderer/dropdown.html');
this.setOrderedServers();
this.windowBounds = MainWindow.getBounds();

View File

@@ -52,7 +52,7 @@ import LoadingScreen from './loadingScreen';
import {MattermostBrowserView} from './MattermostBrowserView';
import modalManager from './modalManager';
import {getLocalURLString, getLocalPreload, getAdjustedWindowBoundaries, shouldHaveBackBar} from '../utils';
import {getLocalPreload, getAdjustedWindowBoundaries, shouldHaveBackBar} from '../utils';
const log = new Logger('ViewManager');
const URL_VIEW_DURATION = 10 * SECOND;
@@ -354,8 +354,7 @@ export class ViewManager {
// @ts-ignore
transparent: true,
}});
const query = new Map([['url', urlString]]);
const localURL = getLocalURLString('urlView.html', query);
const localURL = `mattermost-desktop://renderer/urlView.html?url=${encodeURIComponent(urlString)}`;
urlView.webContents.loadURL(localURL);
MainWindow.get()?.addBrowserView(urlView);
const boundaries = this.views.get(this.currentView || '')?.getBounds() ?? MainWindow.getBounds();

View File

@@ -68,7 +68,6 @@ jest.mock('../contextMenu', () => jest.fn());
jest.mock('../utils', () => ({
isInsideRectangle: jest.fn(),
getLocalPreload: jest.fn(),
getLocalURLString: jest.fn(),
}));
jest.mock('main/i18nManager', () => ({

View File

@@ -37,7 +37,7 @@ import {localizeMessage} from 'main/i18nManager';
import type {SavedWindowState} from 'types/mainWindow';
import ContextMenu from '../contextMenu';
import {getLocalPreload, getLocalURLString, isInsideRectangle} from '../utils';
import {getLocalPreload, isInsideRectangle} from '../utils';
const log = new Logger('MainWindow');
const ALT_MENU_KEYS = ['Alt+F', 'Alt+E', 'Alt+V', 'Alt+H', 'Alt+W', 'Alt+P'];
@@ -152,7 +152,7 @@ export class MainWindow extends EventEmitter {
const contextMenu = new ContextMenu({}, this.win);
contextMenu.reload();
const localURL = getLocalURLString('index.html');
const localURL = 'mattermost-desktop://renderer/index.html';
this.win.loadURL(localURL).catch(
(reason) => {
log.error('failed to load', reason);