[MM-60308] Add a set of "Developer Mode" settings that allow the user to turn off systems or force the app to behave a certain way (#3144)

* Add developer mode manager, implement browser-only mode

* Add indicator when developer mode is enabled

* Add switch to disable notification storage

* Add setting to disable the user activity monitor

* Add switchOff method for easily creating switches to disable/enable functionality, added setting to disable context menu

* Add setting to force legacy API

* Add force new API to remove any support for legacy mode, fix i18n

* Fix lint

* Use one call to `push`
This commit is contained in:
Devin Binnie
2024-09-18 10:02:20 -04:00
committed by GitHub
parent 61cf759b23
commit 42a0bc4759
21 changed files with 734 additions and 380 deletions

View File

@@ -58,6 +58,9 @@ jest.mock('../utils', () => ({
composeUserAgent: () => 'Mattermost/5.0.0',
shouldHaveBackBar: jest.fn(),
}));
jest.mock('main/developerMode', () => ({
get: jest.fn(),
}));
const server = new MattermostServer({name: 'server_name', url: 'http://server-1.com'});
const view = new MessagingView(server, true);

View File

@@ -24,6 +24,7 @@ import ServerManager from 'common/servers/serverManager';
import {RELOAD_INTERVAL, MAX_SERVER_RETRIES, SECOND, MAX_LOADING_SCREEN_SECONDS} from 'common/utils/constants';
import {isInternalURL, parseURL} from 'common/utils/url';
import type {MattermostView} from 'common/views/View';
import DeveloperMode from 'main/developerMode';
import {getServerAPI} from 'main/server/serverAPI';
import MainWindow from 'main/windows/mainWindow';
@@ -52,7 +53,7 @@ export class MattermostBrowserView extends EventEmitter {
private atRoot: boolean;
private options: BrowserViewConstructorOptions;
private removeLoading?: NodeJS.Timeout;
private contextMenu: ContextMenu;
private contextMenu?: ContextMenu;
private status?: Status;
private retryLoad?: NodeJS.Timeout;
private maxRetries: number;
@@ -65,7 +66,7 @@ export class MattermostBrowserView extends EventEmitter {
const preload = getLocalPreload('externalAPI.js');
this.options = Object.assign({}, options);
this.options.webPreferences = {
preload,
preload: DeveloperMode.get('browserOnly') ? undefined : preload,
additionalArguments: [
`version=${app.getVersion()}`,
`appName=${app.name}`,
@@ -99,7 +100,10 @@ export class MattermostBrowserView extends EventEmitter {
WebContentsEventManager.addWebContentsEventListeners(this.browserView.webContents);
this.contextMenu = new ContextMenu({}, this.browserView);
if (!DeveloperMode.get('disableContextMenu')) {
this.contextMenu = new ContextMenu({}, this.browserView);
}
this.maxRetries = MAX_SERVER_RETRIES;
this.altPressStatus = false;
@@ -192,7 +196,7 @@ export class MattermostBrowserView extends EventEmitter {
loadURL = this.view.url.toString();
}
this.log.verbose(`Loading ${loadURL}`);
const loading = this.browserView.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
const loading = this.browserView.webContents.loadURL(loadURL, {userAgent: composeUserAgent(DeveloperMode.get('browserOnly'))});
loading.then(this.loadSuccess(loadURL)).catch((err) => {
if (err.code && err.code.startsWith('ERR_CERT')) {
MainWindow.sendToRenderer(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
@@ -427,7 +431,7 @@ export class MattermostBrowserView extends EventEmitter {
if (!this.browserView || !this.browserView.webContents) {
return;
}
const loading = this.browserView.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
const loading = this.browserView.webContents.loadURL(loadURL, {userAgent: composeUserAgent(DeveloperMode.get('browserOnly'))});
loading.then(this.loadSuccess(loadURL)).catch((err) => {
if (this.maxRetries-- > 0) {
this.loadRetry(loadURL, err);

View File

@@ -34,6 +34,7 @@ import {
LEGACY_OFF,
UNREADS_AND_MENTIONS,
TAB_LOGIN_CHANGED,
DEVELOPER_MODE_UPDATED,
} from 'common/communication';
import Config from 'common/config';
import {Logger} from 'common/log';
@@ -45,10 +46,13 @@ import Utils from 'common/utils/util';
import type {MattermostView} from 'common/views/View';
import {TAB_MESSAGING} from 'common/views/View';
import {flushCookiesStore} from 'main/app/utils';
import DeveloperMode from 'main/developerMode';
import {localizeMessage} from 'main/i18nManager';
import PermissionsManager from 'main/permissionsManager';
import MainWindow from 'main/windows/mainWindow';
import type {DeveloperSettings} from 'types/settings';
import LoadingScreen from './loadingScreen';
import {MattermostBrowserView} from './MattermostBrowserView';
import modalManager from './modalManager';
@@ -91,6 +95,7 @@ export class ViewManager {
ipcMain.on(SWITCH_TAB, (event, viewId) => this.showById(viewId));
ServerManager.on(SERVERS_UPDATE, this.handleReloadConfiguration);
DeveloperMode.on(DEVELOPER_MODE_UPDATED, this.handleDeveloperModeUpdated);
}
private init = () => {
@@ -99,6 +104,17 @@ export class ViewManager {
this.showInitial();
};
private handleDeveloperModeUpdated = (json: DeveloperSettings) => {
log.debug('handleDeveloperModeUpdated', json);
if (['browserOnly', 'disableContextMenu', 'forceLegacyAPI', 'forceNewAPI'].some((key) => Object.hasOwn(json, key))) {
this.views.forEach((view) => view.destroy());
this.views = new Map();
this.closedViews = new Map();
this.init();
}
};
getView = (viewId: string) => {
return this.views.get(viewId);
};