[MM-59483] Remove legacy preload and custom login code (#3174)

* Remove legacy preload script code

* Remove custom login code

* FIx i18n
This commit is contained in:
Devin Binnie
2024-10-25 10:02:56 -04:00
committed by GitHub
parent 14bb75eaed
commit 6d37cc2dbb
26 changed files with 72 additions and 1040 deletions

View File

@@ -85,8 +85,6 @@
"main.menus.app.view.developerModeDisableContextMenu": "Disable Context Menu",
"main.menus.app.view.developerModeDisableNotificationStorage": "Disable Notification Storage",
"main.menus.app.view.developerModeDisableUserActivityMonitor": "Disable User Activity Monitor",
"main.menus.app.view.developerModeForceLegacyAPI": "Force Legacy API",
"main.menus.app.view.developerModeForceNewAPI": "Force New API",
"main.menus.app.view.devToolsAppWrapper": "Developer Tools for Application Wrapper",
"main.menus.app.view.devToolsCurrentCallWidget": "Developer Tools for Call Widget",
"main.menus.app.view.devToolsCurrentServer": "Developer Tools for Current Server",
@@ -164,7 +162,6 @@
"renderer.components.errorView.troubleshooting.browserView.canReachFromBrowserWindow": "You can reach <link>{url}</link> from a browser window.",
"renderer.components.errorView.troubleshooting.computerIsConnected": "Your computer is connected to the internet.",
"renderer.components.errorView.troubleshooting.urlIsCorrect.appNameIsCorrect": "The {appName} URL <link>{url}</link> is correct",
"renderer.components.extraBar.back": "Back",
"renderer.components.input.required": "This field is required",
"renderer.components.mainPage.contextMenu.ariaLabel": "Context menu",
"renderer.components.mainPage.titleBar": "{appName}",

View File

@@ -55,15 +55,11 @@ export const PLAY_SOUND = 'play_sound';
export const GET_DOWNLOAD_LOCATION = 'get_download_location';
export const UPDATE_MENTIONS = 'update_mentions';
export const IS_UNREAD = 'is_unread';
export const UNREAD_RESULT = 'unread_result';
export const UNREADS_AND_MENTIONS = 'unreads-and-mentions';
export const SESSION_EXPIRED = 'session_expired';
export const REACT_APP_INITIALIZED = 'react-app-initialized';
export const TOGGLE_BACK_BUTTON = 'toggle-back-button';
export const SHOW_SETTINGS_WINDOW = 'show-settings-window';
export const LOADING_SCREEN_ANIMATION_FINISHED = 'loading-screen-animation-finished';
@@ -93,8 +89,6 @@ export const CHECK_FOR_UPDATES = 'check-for-updates';
export const NO_UPDATE_AVAILABLE = 'no-update-available';
export const BROWSER_HISTORY_PUSH = 'browser-history-push';
export const APP_LOGGED_IN = 'app-logged-in';
export const APP_LOGGED_OUT = 'app-logged-out';
export const TAB_LOGIN_CHANGED = 'tab-login-changed';
export const GET_AVAILABLE_SPELL_CHECKER_LANGUAGES = 'get-available-spell-checker-languages';
@@ -118,13 +112,11 @@ export const VIEW_FINISHED_RESIZING = 'view-finished-resizing';
// Calls
export const GET_DESKTOP_SOURCES = 'get-desktop-sources';
export const DESKTOP_SOURCES_RESULT = 'desktop-sources-result';
export const DESKTOP_SOURCES_MODAL_REQUEST = 'desktop-sources-modal-request';
export const CALLS_JOIN_CALL = 'calls-join-call';
export const CALLS_LEAVE_CALL = 'calls-leave-call';
export const CALLS_WIDGET_RESIZE = 'calls-widget-resize';
export const CALLS_WIDGET_SHARE_SCREEN = 'calls-widget-share-screen';
export const CALLS_WIDGET_CHANNEL_LINK_CLICK = 'calls-widget-channel-link-click';
export const CALLS_LINK_CLICK = 'calls-link-click';
export const CALLS_JOINED_CALL = 'calls-joined-call';
export const CALLS_POPOUT_FOCUS = 'calls-popout-focus';
@@ -189,14 +181,10 @@ export const OPEN_WINDOWS_CAMERA_PREFERENCES = 'open-windows-camera-preferences'
export const OPEN_WINDOWS_MICROPHONE_PREFERENCES = 'open-windows-microphone-preferences';
export const GET_MEDIA_ACCESS_STATUS = 'get-media-access-status';
// Legacy code remove signal
export const LEGACY_OFF = 'legacy-off';
export const GET_NONCE = 'get-nonce';
export const DEVELOPER_MODE_UPDATED = 'developer-mode-updated';
export const IS_DEVELOPER_MODE_ENABLED = 'is-developer-mode-enabled';
export const GET_DEVELOPER_MODE_SETTING = 'get-developer-mode-setting';
export const METRICS_SEND = 'metrics-send';
export const METRICS_RECEIVE = 'metrics-receive';

View File

@@ -15,7 +15,6 @@ export const MAX_LOADING_SCREEN_SECONDS = 4 * SECOND;
export const TAB_BAR_HEIGHT = 40;
export const TAB_BAR_PADDING = 4;
export const BACK_BAR_HEIGHT = 36;
export const THREE_DOT_MENU_WIDTH = 40;
export const THREE_DOT_MENU_WIDTH_MAC = 80;
export const MENU_SHADOW_WIDTH = 24;
@@ -55,20 +54,6 @@ export const URLValidationStatus = {
URLUpdated: 'URL_UPDATED',
};
// supported custom login paths (oath, saml)
export const customLoginRegexPaths = [
/^\/oauth\/authorize$/i,
/^\/oauth\/deauthorize$/i,
/^\/oauth\/access_token$/i,
/^\/oauth\/[A-Za-z0-9]+\/complete$/i,
/^\/oauth\/[A-Za-z0-9]+\/login$/i,
/^\/oauth\/[A-Za-z0-9]+\/signup$/i,
/^\/api\/v3\/oauth\/[A-Za-z0-9]+\/complete$/i,
/^\/signup\/[A-Za-z0-9]+\/complete$/i,
/^\/login\/[A-Za-z0-9]+\/complete$/i,
/^\/login\/sso\/saml$/i,
];
export const nonTeamUrlPaths = [
'plugins',
'signup',

View File

@@ -9,7 +9,6 @@ import {
isValidURI,
parseURL,
isInternalURL,
isCustomLoginURL,
isCallsPopOutURL,
isTrustedURL,
} from 'common/utils/url';
@@ -204,45 +203,6 @@ describe('common/utils/url', () => {
});
});
describe('isCustomLoginURL', () => {
it('should match correct URL', () => {
expect(isCustomLoginURL(
new URL('http://server.com/oauth/authorize'),
new URL('http://server.com'),
)).toBe(true);
});
it('should not match incorrect URL', () => {
expect(isCustomLoginURL(
new URL('http://server.com/oauth/notauthorize'),
new URL('http://server.com'),
)).toBe(false);
});
it('should not match base URL', () => {
expect(isCustomLoginURL(
new URL('http://server.com/'),
new URL('http://server.com'),
)).toBe(false);
});
it('should match with subpath', () => {
expect(isCustomLoginURL(
new URL('http://server.com/subpath/oauth/authorize'),
new URL('http://server.com/subpath'),
)).toBe(true);
});
it('should not match with different subpath', () => {
expect(isCustomLoginURL(
new URL('http://server.com/subpath/oauth/authorize'),
new URL('http://server.com/different/subpath'),
)).toBe(false);
});
it('should not match with oauth subpath', () => {
expect(isCustomLoginURL(
new URL('http://server.com/oauth/authorize'),
new URL('http://server.com/oauth/authorize'),
)).toBe(false);
});
});
describe('isCallsPopOutURL', () => {
it('should match correct URL', () => {
expect(isCallsPopOutURL(

View File

@@ -4,7 +4,7 @@
import {isHttpsUri, isHttpUri, isUri} from 'valid-url';
import buildConfig from 'common/config/buildConfig';
import {customLoginRegexPaths, nonTeamUrlPaths, CALLS_PLUGIN_ID} from 'common/utils/constants';
import {nonTeamUrlPaths, CALLS_PLUGIN_ID} from 'common/utils/constants';
export const getFormattedPathName = (pn: string) => (pn.endsWith('/') ? pn : `${pn}/`);
export const parseURL = (inputURL: string | URL) => {
@@ -77,22 +77,6 @@ export const isTeamUrl = (serverURL: URL, inputURL: URL, withApi?: boolean) => {
}
return !(paths.some((testPath) => isUrlType(testPath, serverURL, inputURL)));
};
export const isCustomLoginURL = (inputURL: URL, serverURL: URL) => {
if (!isTrustedURL(inputURL, serverURL)) {
return false;
}
const subpath = serverURL.pathname;
const urlPath = inputURL.pathname;
const replacement = subpath.endsWith('/') ? '/' : '';
const replacedPath = urlPath.replace(subpath, replacement);
for (const regexPath of customLoginRegexPaths) {
if (replacedPath.match(regexPath)) {
return true;
}
}
return false;
};
export const isCallsPopOutURL = (serverURL: URL, inputURL: URL, callID: string) => {
const matches = inputURL.pathname.match(new RegExp(`^${escapeRegExp(getFormattedPathName(serverURL.pathname))}([A-Za-z0-9-_]+)/`, 'i'));

View File

@@ -14,9 +14,6 @@ jest.mock('common/utils/url', () => {
isTrustedURL: (url) => {
return url.toString() === 'http://trustedurl.com/';
},
isCustomLoginURL: (url) => {
return url.toString() === 'http://customloginurl.com/';
},
};
});
@@ -73,13 +70,6 @@ describe('main/authManager', () => {
expect(authManager.popPermissionModal).not.toBeCalled();
});
it('should popLoginModal when isCustomLoginURL', () => {
ViewManager.getViewByWebContentsId.mockReturnValue({view: {server: {url: new URL('http://customloginurl.com/')}}});
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://customloginurl.com/'}, null, jest.fn());
expect(authManager.popLoginModal).toBeCalled();
expect(authManager.popPermissionModal).not.toBeCalled();
});
it('should popLoginModal when has permission', () => {
ViewManager.getViewByWebContentsId.mockReturnValue({view: {server: {url: new URL('http://haspermissionurl.com/')}}});
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://haspermissionurl.com/'}, null, jest.fn());

View File

@@ -4,7 +4,7 @@ import type {AuthenticationResponseDetails, AuthInfo, WebContents, Event} from '
import {Logger} from 'common/log';
import {BASIC_AUTH_PERMISSION} from 'common/permissions';
import {isCustomLoginURL, isTrustedURL, parseURL} from 'common/utils/url';
import {isTrustedURL, parseURL} from 'common/utils/url';
import TrustedOriginsStore from 'main/trustedOrigins';
import {getLocalPreload} from 'main/utils';
import modalManager from 'main/views/modalManager';
@@ -45,7 +45,7 @@ export class AuthManager {
}
this.loginCallbackMap.set(request.url, callback); // if callback is undefined set it to null instead so we know we have set it up with no value
if (isTrustedURL(parsedURL, serverURL) || isCustomLoginURL(parsedURL, serverURL) || TrustedOriginsStore.checkPermission(parsedURL, BASIC_AUTH_PERMISSION)) {
if (isTrustedURL(parsedURL, serverURL) || TrustedOriginsStore.checkPermission(parsedURL, BASIC_AUTH_PERMISSION)) {
this.popLoginModal(request, authInfo);
} else {
this.popPermissionModal(request, authInfo, BASIC_AUTH_PERMISSION);

View File

@@ -4,7 +4,7 @@
import {ipcMain} from 'electron';
import {EventEmitter} from 'events';
import {DEVELOPER_MODE_UPDATED, IS_DEVELOPER_MODE_ENABLED, UPDATE_PATHS, GET_DEVELOPER_MODE_SETTING} from 'common/communication';
import {DEVELOPER_MODE_UPDATED, IS_DEVELOPER_MODE_ENABLED, UPDATE_PATHS} from 'common/communication';
import JsonFileManager from 'common/JsonFileManager';
import {developerModeJson} from 'main/constants';
@@ -18,7 +18,6 @@ export class DeveloperMode extends EventEmitter {
this.json = new JsonFileManager(file);
ipcMain.handle(IS_DEVELOPER_MODE_ENABLED, this.enabled);
ipcMain.handle(GET_DEVELOPER_MODE_SETTING, (_, setting) => this.get(setting));
}
enabled = () => process.env.MM_DESKTOP_DEVELOPER_MODE === 'true';

View File

@@ -214,22 +214,6 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
DeveloperMode.toggle('disableContextMenu');
},
},
{
label: localizeMessage('main.menus.app.view.developerModeForceLegacyAPI', 'Force Legacy API'),
type: 'checkbox' as const,
checked: DeveloperMode.get('forceLegacyAPI'),
click() {
DeveloperMode.toggle('forceLegacyAPI');
},
},
{
label: localizeMessage('main.menus.app.view.developerModeForceNewAPI', 'Force New API'),
type: 'checkbox' as const,
checked: DeveloperMode.get('forceNewAPI'),
click() {
DeveloperMode.toggle('forceNewAPI');
},
},
]);
}

View File

@@ -8,16 +8,11 @@ import type {DesktopAPI} from '@mattermost/desktop-api';
import {
NOTIFY_MENTION,
IS_UNREAD,
UNREAD_RESULT,
SESSION_EXPIRED,
REACT_APP_INITIALIZED,
USER_ACTIVITY_UPDATE,
BROWSER_HISTORY_PUSH,
APP_LOGGED_IN,
APP_LOGGED_OUT,
GET_VIEW_INFO_FOR_TEST,
DESKTOP_SOURCES_RESULT,
VIEW_FINISHED_RESIZING,
CALLS_JOIN_CALL,
CALLS_JOINED_CALL,
@@ -33,7 +28,6 @@ import {
BROWSER_HISTORY_STATUS_UPDATED,
NOTIFICATION_CLICKED,
CALLS_WIDGET_RESIZE,
CALLS_WIDGET_CHANNEL_LINK_CLICK,
CALLS_LINK_CLICK,
CALLS_POPOUT_FOCUS,
CALLS_WIDGET_OPEN_THREAD,
@@ -41,9 +35,7 @@ import {
CALLS_WIDGET_OPEN_USER_SETTINGS,
GET_DESKTOP_SOURCES,
UNREADS_AND_MENTIONS,
LEGACY_OFF,
TAB_LOGIN_CHANGED,
GET_DEVELOPER_MODE_SETTING,
METRICS_SEND,
METRICS_REQUEST,
METRICS_RECEIVE,
@@ -51,96 +43,80 @@ import {
import type {ExternalAPI} from 'types/externalAPI';
let legacyEnabled = false;
let legacyOff: () => void;
ipcRenderer.invoke(GET_DEVELOPER_MODE_SETTING, 'forceLegacyAPI').then((force) => {
if (force) {
return;
}
const createListener: ExternalAPI['createListener'] = (channel: string, listener: (...args: never[]) => void) => {
const listenerWithEvent = (_: IpcRendererEvent, ...args: unknown[]) =>
listener(...args as never[]);
ipcRenderer.on(channel, listenerWithEvent);
return () => {
ipcRenderer.off(channel, listenerWithEvent);
};
const createListener: ExternalAPI['createListener'] = (channel: string, listener: (...args: never[]) => void) => {
const listenerWithEvent = (_: IpcRendererEvent, ...args: unknown[]) =>
listener(...args as never[]);
ipcRenderer.on(channel, listenerWithEvent);
return () => {
ipcRenderer.off(channel, listenerWithEvent);
};
};
const desktopAPI: DesktopAPI = {
const desktopAPI: DesktopAPI = {
// Initialization
isDev: () => ipcRenderer.invoke(GET_IS_DEV_MODE),
getAppInfo: () => {
// Using this signal as the sign to disable the legacy code, since it is run before the app is rendered
if (legacyEnabled) {
legacyOff?.();
}
// Initialization
isDev: () => ipcRenderer.invoke(GET_IS_DEV_MODE),
getAppInfo: () => ipcRenderer.invoke(GET_APP_INFO),
reactAppInitialized: () => ipcRenderer.send(REACT_APP_INITIALIZED),
return ipcRenderer.invoke(GET_APP_INFO);
},
reactAppInitialized: () => ipcRenderer.send(REACT_APP_INITIALIZED),
// Session
setSessionExpired: (isExpired) => ipcRenderer.send(SESSION_EXPIRED, isExpired),
onUserActivityUpdate: (listener) => createListener(USER_ACTIVITY_UPDATE, listener),
// Session
setSessionExpired: (isExpired) => ipcRenderer.send(SESSION_EXPIRED, isExpired),
onUserActivityUpdate: (listener) => createListener(USER_ACTIVITY_UPDATE, listener),
onLogin: () => ipcRenderer.send(TAB_LOGIN_CHANGED, true),
onLogout: () => ipcRenderer.send(TAB_LOGIN_CHANGED, false),
onLogin: () => ipcRenderer.send(TAB_LOGIN_CHANGED, true),
onLogout: () => ipcRenderer.send(TAB_LOGIN_CHANGED, false),
// Unreads/mentions/notifications
sendNotification: (title, body, channelId, teamId, url, silent, soundName) =>
ipcRenderer.invoke(NOTIFY_MENTION, title, body, channelId, teamId, url, silent, soundName),
onNotificationClicked: (listener) => createListener(NOTIFICATION_CLICKED, listener),
setUnreadsAndMentions: (isUnread, mentionCount) => ipcRenderer.send(UNREADS_AND_MENTIONS, isUnread, mentionCount),
// Unreads/mentions/notifications
sendNotification: (title, body, channelId, teamId, url, silent, soundName) =>
ipcRenderer.invoke(NOTIFY_MENTION, title, body, channelId, teamId, url, silent, soundName),
onNotificationClicked: (listener) => createListener(NOTIFICATION_CLICKED, listener),
setUnreadsAndMentions: (isUnread, mentionCount) => ipcRenderer.send(UNREADS_AND_MENTIONS, isUnread, mentionCount),
// Navigation
requestBrowserHistoryStatus: () => ipcRenderer.invoke(REQUEST_BROWSER_HISTORY_STATUS),
onBrowserHistoryStatusUpdated: (listener) => createListener(BROWSER_HISTORY_STATUS_UPDATED, listener),
onBrowserHistoryPush: (listener) => createListener(BROWSER_HISTORY_PUSH, listener),
sendBrowserHistoryPush: (path) => ipcRenderer.send(BROWSER_HISTORY_PUSH, path),
// Navigation
requestBrowserHistoryStatus: () => ipcRenderer.invoke(REQUEST_BROWSER_HISTORY_STATUS),
onBrowserHistoryStatusUpdated: (listener) => createListener(BROWSER_HISTORY_STATUS_UPDATED, listener),
onBrowserHistoryPush: (listener) => createListener(BROWSER_HISTORY_PUSH, listener),
sendBrowserHistoryPush: (path) => ipcRenderer.send(BROWSER_HISTORY_PUSH, path),
// Calls
joinCall: (opts) => ipcRenderer.invoke(CALLS_JOIN_CALL, opts),
leaveCall: () => ipcRenderer.send(CALLS_LEAVE_CALL),
// Calls
joinCall: (opts) => ipcRenderer.invoke(CALLS_JOIN_CALL, opts),
leaveCall: () => ipcRenderer.send(CALLS_LEAVE_CALL),
callsWidgetConnected: (callID, sessionID) => ipcRenderer.send(CALLS_JOINED_CALL, callID, sessionID),
resizeCallsWidget: (width, height) => ipcRenderer.send(CALLS_WIDGET_RESIZE, width, height),
callsWidgetConnected: (callID, sessionID) => ipcRenderer.send(CALLS_JOINED_CALL, callID, sessionID),
resizeCallsWidget: (width, height) => ipcRenderer.send(CALLS_WIDGET_RESIZE, width, height),
sendCallsError: (err, callID, errMsg) => ipcRenderer.send(CALLS_ERROR, err, callID, errMsg),
onCallsError: (listener) => createListener(CALLS_ERROR, listener),
sendCallsError: (err, callID, errMsg) => ipcRenderer.send(CALLS_ERROR, err, callID, errMsg),
onCallsError: (listener) => createListener(CALLS_ERROR, listener),
getDesktopSources: (opts) => ipcRenderer.invoke(GET_DESKTOP_SOURCES, opts),
openScreenShareModal: () => ipcRenderer.send(DESKTOP_SOURCES_MODAL_REQUEST),
onOpenScreenShareModal: (listener) => createListener(DESKTOP_SOURCES_MODAL_REQUEST, listener),
getDesktopSources: (opts) => ipcRenderer.invoke(GET_DESKTOP_SOURCES, opts),
openScreenShareModal: () => ipcRenderer.send(DESKTOP_SOURCES_MODAL_REQUEST),
onOpenScreenShareModal: (listener) => createListener(DESKTOP_SOURCES_MODAL_REQUEST, listener),
shareScreen: (sourceID, withAudio) => ipcRenderer.send(CALLS_WIDGET_SHARE_SCREEN, sourceID, withAudio),
onScreenShared: (listener) => createListener(CALLS_WIDGET_SHARE_SCREEN, listener),
shareScreen: (sourceID, withAudio) => ipcRenderer.send(CALLS_WIDGET_SHARE_SCREEN, sourceID, withAudio),
onScreenShared: (listener) => createListener(CALLS_WIDGET_SHARE_SCREEN, listener),
sendJoinCallRequest: (callId) => ipcRenderer.send(CALLS_JOIN_REQUEST, callId),
onJoinCallRequest: (listener) => createListener(CALLS_JOIN_REQUEST, listener),
sendJoinCallRequest: (callId) => ipcRenderer.send(CALLS_JOIN_REQUEST, callId),
onJoinCallRequest: (listener) => createListener(CALLS_JOIN_REQUEST, listener),
openLinkFromCalls: (url) => ipcRenderer.send(CALLS_LINK_CLICK, url),
openLinkFromCalls: (url) => ipcRenderer.send(CALLS_LINK_CLICK, url),
focusPopout: () => ipcRenderer.send(CALLS_POPOUT_FOCUS),
focusPopout: () => ipcRenderer.send(CALLS_POPOUT_FOCUS),
openThreadForCalls: (threadID) => ipcRenderer.send(CALLS_WIDGET_OPEN_THREAD, threadID),
onOpenThreadForCalls: (listener) => createListener(CALLS_WIDGET_OPEN_THREAD, listener),
openThreadForCalls: (threadID) => ipcRenderer.send(CALLS_WIDGET_OPEN_THREAD, threadID),
onOpenThreadForCalls: (listener) => createListener(CALLS_WIDGET_OPEN_THREAD, listener),
openStopRecordingModal: (channelID) => ipcRenderer.send(CALLS_WIDGET_OPEN_STOP_RECORDING_MODAL, channelID),
onOpenStopRecordingModal: (listener) => createListener(CALLS_WIDGET_OPEN_STOP_RECORDING_MODAL, listener),
openStopRecordingModal: (channelID) => ipcRenderer.send(CALLS_WIDGET_OPEN_STOP_RECORDING_MODAL, channelID),
onOpenStopRecordingModal: (listener) => createListener(CALLS_WIDGET_OPEN_STOP_RECORDING_MODAL, listener),
openCallsUserSettings: () => ipcRenderer.send(CALLS_WIDGET_OPEN_USER_SETTINGS),
onOpenCallsUserSettings: (listener) => createListener(CALLS_WIDGET_OPEN_USER_SETTINGS, listener),
openCallsUserSettings: () => ipcRenderer.send(CALLS_WIDGET_OPEN_USER_SETTINGS),
onOpenCallsUserSettings: (listener) => createListener(CALLS_WIDGET_OPEN_USER_SETTINGS, listener),
onSendMetrics: (listener) => createListener(METRICS_SEND, listener),
onSendMetrics: (listener) => createListener(METRICS_SEND, listener),
// Utility
unregister: (channel) => ipcRenderer.removeAllListeners(channel),
};
contextBridge.exposeInMainWorld('desktopAPI', desktopAPI);
});
// Utility
unregister: (channel) => ipcRenderer.removeAllListeners(channel),
};
contextBridge.exposeInMainWorld('desktopAPI', desktopAPI);
ipcRenderer.on(METRICS_REQUEST, async (_, name, serverId) => {
const memory = await process.getProcessMemoryInfo();
@@ -203,319 +179,3 @@ const CLEAR_CACHE_INTERVAL = 6 * 60 * 60 * 1000; // 6 hours
setInterval(() => {
webFrame.clearCache();
}, CLEAR_CACHE_INTERVAL);
ipcRenderer.invoke(GET_DEVELOPER_MODE_SETTING, 'forceNewAPI').then((force) => {
if (force) {
return;
}
/****************************************************************************
* LEGACY CODE BELOW
* All of this code is deprecated and should be removed eventually
* Current it is there to support older versions of the web app
****************************************************************************
*/
/**
* Legacy helper functions
*/
const onLoad = () => {
if (document.getElementById('root') === null) {
console.warn('The guest is not assumed as mattermost-webapp');
return;
}
watchReactAppUntilInitialized(() => {
console.warn('Legacy preload initialized');
ipcRenderer.send(REACT_APP_INITIALIZED);
ipcRenderer.invoke(REQUEST_BROWSER_HISTORY_STATUS).then(sendHistoryButtonReturn);
});
};
const onStorageChanged = (e: StorageEvent) => {
if (e.key === '__login__' && e.storageArea === localStorage && e.newValue) {
ipcRenderer.send(APP_LOGGED_IN);
}
if (e.key === '__logout__' && e.storageArea === localStorage && e.newValue) {
ipcRenderer.send(APP_LOGGED_OUT);
}
};
const isReactAppInitialized = () => {
const initializedRoot =
document.querySelector('#root.channel-view') || // React 16 webapp
document.querySelector('#root .signup-team__container') || // React 16 login
document.querySelector('div[data-reactroot]'); // Older React apps
if (initializedRoot === null) {
return false;
}
return initializedRoot.children.length !== 0;
};
const watchReactAppUntilInitialized = (callback: () => void) => {
let count = 0;
const interval = 500;
const timeout = 30000;
const timer = setInterval(() => {
count += interval;
if (isReactAppInitialized() || count >= timeout) { // assumed as webapp has been initialized.
clearTimeout(timer);
callback();
}
}, interval);
};
const checkUnread = () => {
if (isReactAppInitialized()) {
findUnread();
} else {
watchReactAppUntilInitialized(() => {
findUnread();
});
}
};
const findUnread = () => {
const classes = ['team-container unread', 'SidebarChannel unread', 'sidebar-item unread-title'];
const isUnread = classes.some((classPair) => {
const result = document.getElementsByClassName(classPair);
return result && result.length > 0;
});
ipcRenderer.send(UNREAD_RESULT, isUnread);
};
let sessionExpired: boolean;
const getUnreadCount = () => {
// LHS not found => Log out => Count should be 0, but session may be expired.
let isExpired;
if (document.getElementById('sidebar-left') === null) {
const extraParam = (new URLSearchParams(window.location.search)).get('extra');
isExpired = extraParam === 'expired';
} else {
isExpired = false;
}
if (isExpired !== sessionExpired) {
sessionExpired = isExpired;
ipcRenderer.send(SESSION_EXPIRED, sessionExpired);
}
};
/**
* Legacy message passing code - can be running alongside the new API stuff
*/
// Disabling no-explicit-any for this legacy code
// eslint-disable-next-line @typescript-eslint/no-explicit-any
window.addEventListener('message', ({origin, data = {}}: {origin?: string; data?: {type?: string; message?: any}} = {}) => {
const {type, message = {}} = data;
if (origin !== window.location.origin) {
return;
}
switch (type) {
case 'webapp-ready':
case 'get-app-version': {
// register with the webapp to enable custom integration functionality
ipcRenderer.invoke(GET_APP_INFO).then((info) => {
console.log(`registering ${info.name} v${info.version} with the server`);
window.postMessage(
{
type: 'register-desktop',
message: info,
},
window.location.origin || '*',
);
});
break;
}
case 'dispatch-notification': {
const {title, body, channel, teamId, url, silent, data: messageData} = message;
channels.set(channel.id, channel);
ipcRenderer.invoke(NOTIFY_MENTION, title, body, channel.id, teamId, url, silent, messageData.soundName);
break;
}
case BROWSER_HISTORY_PUSH: {
const {path} = message as {path: string};
ipcRenderer.send(BROWSER_HISTORY_PUSH, path);
break;
}
case 'history-button': {
ipcRenderer.invoke(REQUEST_BROWSER_HISTORY_STATUS).then(sendHistoryButtonReturn);
break;
}
case CALLS_LINK_CLICK: {
ipcRenderer.send(CALLS_LINK_CLICK, message.link);
break;
}
case GET_DESKTOP_SOURCES: {
ipcRenderer.invoke(GET_DESKTOP_SOURCES, message).then(sendDesktopSourcesResult);
break;
}
case CALLS_WIDGET_SHARE_SCREEN: {
ipcRenderer.send(CALLS_WIDGET_SHARE_SCREEN, message.sourceID, message.withAudio);
break;
}
case CALLS_JOIN_CALL: {
ipcRenderer.invoke(CALLS_JOIN_CALL, message).then(sendCallsJoinedCall);
break;
}
case CALLS_JOINED_CALL: {
ipcRenderer.send(CALLS_JOINED_CALL, message.callID, message.sessionID);
break;
}
case CALLS_JOIN_REQUEST: {
ipcRenderer.send(CALLS_JOIN_REQUEST, message.callID);
break;
}
case CALLS_WIDGET_RESIZE: {
ipcRenderer.send(CALLS_WIDGET_RESIZE, message.width, message.height);
break;
}
case CALLS_ERROR: {
ipcRenderer.send(CALLS_ERROR, message.err, message.callID, message.errMsg);
break;
}
case CALLS_WIDGET_CHANNEL_LINK_CLICK:
case CALLS_LEAVE_CALL:
case DESKTOP_SOURCES_MODAL_REQUEST:
case CALLS_POPOUT_FOCUS: {
ipcRenderer.send(type);
}
}
});
// Legacy support to hold the full channel object so that it can be used for the click event
const channels: Map<string, {id: string}> = new Map();
ipcRenderer.on(NOTIFICATION_CLICKED, (event, channelId, teamId, url) => {
const channel = channels.get(channelId) ?? {id: channelId};
channels.delete(channelId);
window.postMessage(
{
type: NOTIFICATION_CLICKED,
message: {
channel,
teamId,
url,
},
},
window.location.origin,
);
});
ipcRenderer.on(BROWSER_HISTORY_PUSH, (event, pathName) => {
window.postMessage(
{
type: 'browser-history-push-return',
message: {
pathName,
},
},
window.location.origin,
);
});
const sendHistoryButtonReturn = (status: {canGoBack: boolean; canGoForward: boolean}) => {
window.postMessage(
{
type: 'history-button-return',
message: {
enableBack: status.canGoBack,
enableForward: status.canGoForward,
},
},
window.location.origin,
);
};
ipcRenderer.on(BROWSER_HISTORY_STATUS_UPDATED, (event, canGoBack, canGoForward) => sendHistoryButtonReturn({canGoBack, canGoForward}));
const sendDesktopSourcesResult = (sources: Array<{
id: string;
name: string;
thumbnailURL: string;
}>) => {
window.postMessage(
{
type: DESKTOP_SOURCES_RESULT,
message: sources,
},
window.location.origin,
);
};
const sendCallsJoinedCall = (message: {callID: string; sessionID: string}) => {
window.postMessage(
{
type: CALLS_JOINED_CALL,
message,
},
window.location.origin,
);
};
ipcRenderer.on(CALLS_JOIN_REQUEST, (_, callID) => {
window.postMessage(
{
type: CALLS_JOIN_REQUEST,
message: {callID},
},
window.location.origin,
);
});
ipcRenderer.on(DESKTOP_SOURCES_MODAL_REQUEST, () => {
window.postMessage(
{
type: DESKTOP_SOURCES_MODAL_REQUEST,
},
window.location.origin,
);
});
ipcRenderer.on(CALLS_WIDGET_SHARE_SCREEN, (_, sourceID, withAudio) => {
window.postMessage(
{
type: CALLS_WIDGET_SHARE_SCREEN,
message: {sourceID, withAudio},
},
window.location.origin,
);
});
ipcRenderer.on(CALLS_ERROR, (_, err, callID, errMsg) => {
window.postMessage(
{
type: CALLS_ERROR,
message: {err, callID, errMsg},
},
window.location.origin,
);
});
// push user activity updates to the webapp
ipcRenderer.on(USER_ACTIVITY_UPDATE, (event, userIsActive, isSystemEvent) => {
if (window.location.origin !== 'null') {
window.postMessage({type: USER_ACTIVITY_UPDATE, message: {userIsActive, manual: isSystemEvent}}, window.location.origin);
}
});
/**
* Legacy functionality that needs to be disabled with the new API
*/
legacyEnabled = true;
ipcRenderer.on(IS_UNREAD, checkUnread);
const unreadInterval = setInterval(getUnreadCount, 1000);
window.addEventListener('storage', onStorageChanged);
window.addEventListener('load', onLoad);
legacyOff = () => {
ipcRenderer.send(LEGACY_OFF);
ipcRenderer.off(IS_UNREAD, checkUnread);
clearInterval(unreadInterval);
window.removeEventListener('storage', onStorageChanged);
window.removeEventListener('load', onLoad);
legacyEnabled = false;
console.log('New API preload initialized');
};
});

View File

@@ -42,7 +42,6 @@ import {
PLAY_SOUND,
MODAL_OPEN,
MODAL_CLOSE,
TOGGLE_BACK_BUTTON,
UPDATE_MENTIONS,
SHOW_DOWNLOADS_DROPDOWN_BUTTON_BADGE,
HIDE_DOWNLOADS_DROPDOWN_BUTTON_BADGE,
@@ -163,7 +162,6 @@ contextBridge.exposeInMainWorld('desktop', {
onPlaySound: (listener) => ipcRenderer.on(PLAY_SOUND, (_, soundName) => listener(soundName)),
onModalOpen: (listener) => ipcRenderer.on(MODAL_OPEN, () => listener()),
onModalClose: (listener) => ipcRenderer.on(MODAL_CLOSE, () => listener()),
onToggleBackButton: (listener) => ipcRenderer.on(TOGGLE_BACK_BUTTON, (_, showExtraBar) => listener(showExtraBar)),
onUpdateMentions: (listener) => ipcRenderer.on(UPDATE_MENTIONS, (_event, view, mentions, unreads, isExpired) => listener(view, mentions, unreads, isExpired)),
onCloseServersDropdown: (listener) => ipcRenderer.on(CLOSE_SERVERS_DROPDOWN, () => listener()),
onOpenServersDropdown: (listener) => ipcRenderer.on(OPEN_SERVERS_DROPDOWN, () => listener()),

View File

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
'use strict';
import {BACK_BAR_HEIGHT, TAB_BAR_HEIGHT} from 'common/utils/constants';
import {TAB_BAR_HEIGHT} from 'common/utils/constants';
import * as Utils from './utils';
@@ -68,27 +68,6 @@ describe('main/utils', () => {
height: 400 - TAB_BAR_HEIGHT,
});
});
it('should include back bar height when specified', () => {
expect(Utils.getWindowBoundaries({
getContentBounds: () => ({width: 500, height: 400}),
}, true)).toStrictEqual({
x: 0,
y: TAB_BAR_HEIGHT + BACK_BAR_HEIGHT,
width: 500,
height: 400 - TAB_BAR_HEIGHT - BACK_BAR_HEIGHT,
});
});
});
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);
});
it('should not have back bar for regular login', () => {
expect(Utils.shouldHaveBackBar(new URL('https://server-1.com'), new URL('https://server-1.com/login'))).toBe(false);
});
});
describe('isStringWithLength', () => {

View File

@@ -11,8 +11,7 @@ const exec = promisify(execOriginal);
import type {BrowserWindow} from 'electron';
import {app} from 'electron';
import {BACK_BAR_HEIGHT, customLoginRegexPaths, TAB_BAR_HEIGHT} from 'common/utils/constants';
import {isAdminUrl, isPluginUrl, isTeamUrl, isUrlType, parseURL} from 'common/utils/url';
import {TAB_BAR_HEIGHT} from 'common/utils/constants';
import type {Args} from 'types/args';
@@ -48,42 +47,20 @@ export function shouldBeHiddenOnStartup(parsedArgv: Args) {
return false;
}
export function getWindowBoundaries(win: BrowserWindow, hasBackBar = false) {
export function getWindowBoundaries(win: BrowserWindow) {
const {width, height} = win.getContentBounds();
return getAdjustedWindowBoundaries(width, height, hasBackBar);
return getAdjustedWindowBoundaries(width, height);
}
export function getAdjustedWindowBoundaries(width: number, height: number, hasBackBar = false) {
export function getAdjustedWindowBoundaries(width: number, height: number) {
return {
x: 0,
y: TAB_BAR_HEIGHT + (hasBackBar ? BACK_BAR_HEIGHT : 0),
y: TAB_BAR_HEIGHT,
width,
height: height - TAB_BAR_HEIGHT - (hasBackBar ? BACK_BAR_HEIGHT : 0),
height: height - TAB_BAR_HEIGHT,
};
}
export function shouldHaveBackBar(serverUrl: URL, inputURL: URL) {
if (isUrlType('login', serverUrl, inputURL)) {
const serverURL = parseURL(serverUrl);
const subpath = serverURL ? serverURL.pathname : '';
const parsedURL = parseURL(inputURL);
if (!parsedURL) {
return false;
}
const urlPath = parsedURL.pathname;
const replacement = subpath.endsWith('/') ? '/' : '';
const replacedPath = urlPath.replace(subpath, replacement);
for (const regexPath of customLoginRegexPaths) {
if (replacedPath.match(regexPath)) {
return true;
}
}
return false;
}
return !isTeamUrl(serverUrl, inputURL) && !isAdminUrl(serverUrl, inputURL) && !isPluginUrl(serverUrl, inputURL);
}
export function getLocalPreload(file: string) {
return path.join(app.getAppPath(), file);
}

View File

@@ -4,14 +4,13 @@
'use strict';
import AppState from 'common/appState';
import {LOAD_FAILED, TOGGLE_BACK_BUTTON, UPDATE_TARGET_URL} from 'common/communication';
import {LOAD_FAILED, UPDATE_TARGET_URL} from 'common/communication';
import {MattermostServer} from 'common/servers/MattermostServer';
import MessagingView from 'common/views/MessagingView';
import {MattermostBrowserView} from './MattermostBrowserView';
import ContextMenu from '../contextMenu';
import Utils from '../utils';
import MainWindow from '../windows/mainWindow';
jest.mock('electron', () => ({
@@ -423,28 +422,6 @@ describe('main/views/MattermostBrowserView', () => {
});
});
describe('handleDidNavigate', () => {
const window = {on: jest.fn()};
const mattermostView = new MattermostBrowserView(view, {}, {});
beforeEach(() => {
MainWindow.get.mockReturnValue(window);
mattermostView.setBounds = jest.fn();
});
it('should hide back button on internal url', () => {
Utils.shouldHaveBackBar.mockReturnValue(false);
mattermostView.handleDidNavigate(null, 'http://server-1.com/path/to/channels');
expect(MainWindow.sendToRenderer).toHaveBeenCalledWith(TOGGLE_BACK_BUTTON, false);
});
it('should show back button on external url', () => {
Utils.shouldHaveBackBar.mockReturnValue(true);
mattermostView.handleDidNavigate(null, 'http://server-2.com/some/other/path');
expect(MainWindow.sendToRenderer).toHaveBeenCalledWith(TOGGLE_BACK_BUTTON, true);
});
});
describe('handleUpdateTarget', () => {
const window = {on: jest.fn()};
const mattermostView = new MattermostBrowserView(view, {}, {});
@@ -476,18 +453,4 @@ describe('main/views/MattermostBrowserView', () => {
expect(mattermostView.emit).toHaveBeenCalled();
});
});
describe('updateMentionsFromTitle', () => {
const mattermostView = new MattermostBrowserView(view, {}, {});
it('should parse mentions from title', () => {
mattermostView.updateMentionsFromTitle('(7) Mattermost');
expect(AppState.updateMentions).toHaveBeenCalledWith(mattermostView.view.id, 7);
});
it('should parse unreads from title', () => {
mattermostView.updateMentionsFromTitle('* Mattermost');
expect(AppState.updateMentions).toHaveBeenCalledWith(mattermostView.view.id, 0);
});
});
});

View File

@@ -11,8 +11,6 @@ import {
LOAD_SUCCESS,
LOAD_FAILED,
UPDATE_TARGET_URL,
IS_UNREAD,
TOGGLE_BACK_BUTTON,
LOADSCREEN_END,
SERVERS_URL_MODIFIED,
BROWSER_HISTORY_STATUS_UPDATED,
@@ -32,7 +30,7 @@ import MainWindow from 'main/windows/mainWindow';
import WebContentsEventManager from './webContentEvents';
import ContextMenu from '../contextMenu';
import {getWindowBoundaries, getLocalPreload, composeUserAgent, shouldHaveBackBar} from '../utils';
import {getWindowBoundaries, getLocalPreload, composeUserAgent} from '../utils';
enum Status {
LOADING,
@@ -41,9 +39,6 @@ enum Status {
ERROR = -1,
}
const MENTIONS_GROUP = 2;
const titleParser = /(\((\d+)\) )?(\* )?/g;
export class MattermostBrowserView extends EventEmitter {
view: MattermostView;
isVisible: boolean;
@@ -84,7 +79,6 @@ export class MattermostBrowserView extends EventEmitter {
this.log.verbose('View created');
this.browserView.webContents.on('update-target-url', this.handleUpdateTarget);
this.browserView.webContents.on('did-navigate', this.handleDidNavigate);
if (process.platform !== 'darwin') {
this.browserView.webContents.on('before-input-event', this.handleInputEvents);
}
@@ -95,10 +89,6 @@ export class MattermostBrowserView extends EventEmitter {
}
});
// Legacy handlers using the title/favicon
this.browserView.webContents.on('page-title-updated', this.handleTitleUpdate);
this.browserView.webContents.on('page-favicon-updated', this.handleFaviconUpdate);
WebContentsEventManager.addWebContentsEventListeners(this.browserView.webContents);
if (!DeveloperMode.get('disableContextMenu')) {
@@ -233,7 +223,7 @@ export class MattermostBrowserView extends EventEmitter {
this.isVisible = true;
mainWindow.addBrowserView(this.browserView);
mainWindow.setTopBrowserView(this.browserView);
this.setBounds(getWindowBoundaries(mainWindow, shouldHaveBackBar(this.view.url || '', this.currentURL)));
this.setBounds(getWindowBoundaries(mainWindow));
if (this.status === Status.READY) {
this.focus();
}
@@ -280,15 +270,6 @@ export class MattermostBrowserView extends EventEmitter {
}
};
/**
* Code to turn off the old method of getting unreads
* Newer web apps will send the mentions/unreads directly
*/
offLegacyUnreads = () => {
this.browserView.webContents.off('page-title-updated', this.handleTitleUpdate);
this.browserView.webContents.off('page-favicon-updated', this.handleFaviconUpdate);
};
/**
* Status hooks
*/
@@ -393,41 +374,6 @@ export class MattermostBrowserView extends EventEmitter {
}
};
/**
* Unreads/mentions handlers
*/
private updateMentionsFromTitle = (title: string) => {
const resultsIterator = title.matchAll(titleParser);
const results = resultsIterator.next(); // we are only interested in the first set
const mentions = (results && results.value && parseInt(results.value[MENTIONS_GROUP], 10)) || 0;
AppState.updateMentions(this.id, mentions);
};
// if favicon is null, it will affect appState, but won't be memoized
private findUnreadState = (favicon: string | null) => {
try {
this.browserView.webContents.send(IS_UNREAD, favicon, this.id);
} catch (err: any) {
this.log.error('There was an error trying to request the unread state', err);
}
};
private handleTitleUpdate = (e: Event, title: string) => {
this.log.debug('handleTitleUpdate', title);
this.updateMentionsFromTitle(title);
};
private handleFaviconUpdate = (e: Event, favicons: string[]) => {
this.log.silly('handleFaviconUpdate', favicons);
// if unread state is stored for that favicon, retrieve value.
// if not, get related info from preload and store it for future changes
this.findUnreadState(favicons[0]);
};
/**
* Loading/retry logic
*/
@@ -486,16 +432,12 @@ export class MattermostBrowserView extends EventEmitter {
this.log.verbose(`finished loading ${loadURL}`);
MainWindow.sendToRenderer(LOAD_SUCCESS, this.id);
this.maxRetries = MAX_SERVER_RETRIES;
if (this.status === Status.LOADING) {
this.updateMentionsFromTitle(this.browserView.webContents.getTitle());
this.findUnreadState(null);
}
this.status = Status.WAITING_MM;
this.removeLoading = setTimeout(this.setInitialized, MAX_LOADING_SCREEN_SECONDS, true);
this.emit(LOAD_SUCCESS, this.id, loadURL);
const mainWindow = MainWindow.get();
if (mainWindow && this.currentURL) {
this.setBounds(getWindowBoundaries(mainWindow, shouldHaveBackBar(this.view.url || '', this.currentURL)));
this.setBounds(getWindowBoundaries(mainWindow));
}
};
};
@@ -504,29 +446,6 @@ export class MattermostBrowserView extends EventEmitter {
* WebContents event handlers
*/
private handleDidNavigate = (event: Event, url: string) => {
this.log.debug('handleDidNavigate', url);
const mainWindow = MainWindow.get();
if (!mainWindow) {
return;
}
const parsedURL = parseURL(url);
if (!parsedURL) {
return;
}
if (shouldHaveBackBar(this.view.url || '', parsedURL)) {
this.setBounds(getWindowBoundaries(mainWindow, true));
MainWindow.sendToRenderer(TOGGLE_BACK_BUTTON, true);
this.log.debug('show back button');
} else {
this.setBounds(getWindowBoundaries(mainWindow));
MainWindow.sendToRenderer(TOGGLE_BACK_BUTTON, false);
this.log.debug('hide back button');
}
};
private handleUpdateTarget = (e: Event, url: string) => {
this.log.silly('handleUpdateTarget', e, url);
const parsedURL = parseURL(url);

View File

@@ -18,10 +18,7 @@ import {
UPDATE_URL_VIEW_WIDTH,
SERVERS_UPDATE,
REACT_APP_INITIALIZED,
APP_LOGGED_OUT,
APP_LOGGED_IN,
RELOAD_CURRENT_VIEW,
UNREAD_RESULT,
HISTORY,
GET_VIEW_INFO_FOR_TEST,
SESSION_EXPIRED,
@@ -31,7 +28,6 @@ import {
SWITCH_TAB,
GET_IS_DEV_MODE,
REQUEST_BROWSER_HISTORY_STATUS,
LEGACY_OFF,
UNREADS_AND_MENTIONS,
TAB_LOGIN_CHANGED,
DEVELOPER_MODE_UPDATED,
@@ -58,7 +54,7 @@ import LoadingScreen from './loadingScreen';
import {MattermostBrowserView} from './MattermostBrowserView';
import modalManager from './modalManager';
import {getLocalPreload, getAdjustedWindowBoundaries, shouldHaveBackBar} from '../utils';
import {getLocalPreload, getAdjustedWindowBoundaries} from '../utils';
const log = new Logger('ViewManager');
const URL_VIEW_DURATION = 10 * SECOND;
@@ -84,14 +80,10 @@ export class ViewManager {
ipcMain.on(HISTORY, this.handleHistory);
ipcMain.on(REACT_APP_INITIALIZED, this.handleReactAppInitialized);
ipcMain.on(BROWSER_HISTORY_PUSH, this.handleBrowserHistoryPush);
ipcMain.on(APP_LOGGED_IN, this.handleAppLoggedIn);
ipcMain.on(APP_LOGGED_OUT, this.handleAppLoggedOut);
ipcMain.on(TAB_LOGIN_CHANGED, this.handleTabLoginChanged);
ipcMain.on(RELOAD_CURRENT_VIEW, this.handleReloadCurrentView);
ipcMain.on(UNREAD_RESULT, this.handleUnreadChanged);
ipcMain.on(UNREADS_AND_MENTIONS, this.handleUnreadsAndMentionsChanged);
ipcMain.on(SESSION_EXPIRED, this.handleSessionExpired);
ipcMain.on(LEGACY_OFF, this.handleLegacyOff);
ipcMain.on(SWITCH_TAB, (event, viewId) => this.showById(viewId));
@@ -108,7 +100,7 @@ export class ViewManager {
private handleDeveloperModeUpdated = (json: DeveloperSettings) => {
log.debug('handleDeveloperModeUpdated', json);
if (['browserOnly', 'disableContextMenu', 'forceLegacyAPI', 'forceNewAPI'].some((key) => Object.hasOwn(json, key))) {
if (['browserOnly', 'disableContextMenu'].some((key) => Object.hasOwn(json, key))) {
this.views.forEach((view) => view.destroy());
this.views = new Map();
this.closedViews = new Map();
@@ -504,27 +496,6 @@ export class ViewManager {
this.getCurrentView()?.goToOffset(offset);
};
private handleAppLoggedIn = (event: IpcMainEvent) => {
log.debug('handleAppLoggedIn', event.sender.id);
const view = this.getViewByWebContentsId(event.sender.id);
if (!view) {
return;
}
view.onLogin(true);
flushCookiesStore();
};
private handleAppLoggedOut = (event: IpcMainEvent) => {
log.debug('handleAppLoggedOut', event.sender.id);
const view = this.getViewByWebContentsId(event.sender.id);
if (!view) {
return;
}
view.onLogin(false);
AppState.clear(view.id);
flushCookiesStore();
};
private handleTabLoginChanged = (event: IpcMainEvent, loggedIn: boolean) => {
log.debug('handleTabLoggedIn', event.sender.id);
const view = this.getViewByWebContentsId(event.sender.id);
@@ -605,28 +576,6 @@ export class ViewManager {
this.showById(view?.id);
};
private handleLegacyOff = (e: IpcMainEvent) => {
log.silly('handleLegacyOff', {webContentsId: e.sender.id});
const view = this.getViewByWebContentsId(e.sender.id);
if (!view) {
return;
}
view.offLegacyUnreads();
};
// if favicon is null, it means it is the initial load,
// so don't memoize as we don't have the favicons and there is no rush to find out.
private handleUnreadChanged = (e: IpcMainEvent, result: boolean) => {
log.silly('handleUnreadChanged', {webContentsId: e.sender.id, result});
const view = this.getViewByWebContentsId(e.sender.id);
if (!view) {
return;
}
AppState.updateUnreads(view.id, result);
};
private handleUnreadsAndMentionsChanged = (e: IpcMainEvent, isUnread: boolean, mentionCount: number) => {
log.silly('handleUnreadsAndMentionsChanged', {webContentsId: e.sender.id, isUnread, mentionCount});
@@ -653,7 +602,7 @@ export class ViewManager {
const currentView = this.getCurrentView();
if (currentView && currentView.currentURL) {
const adjustedBounds = getAdjustedWindowBoundaries(newBounds.width, newBounds.height, shouldHaveBackBar(currentView.view.url, currentView.currentURL));
const adjustedBounds = getAdjustedWindowBoundaries(newBounds.width, newBounds.height);
currentView.setBounds(adjustedBounds);
}
};

View File

@@ -95,27 +95,11 @@ describe('main/views/webContentsEvents', () => {
expect(event.preventDefault).not.toBeCalled();
});
it('should allow navigation when isCustomLoginURL', () => {
willNavigate(event, 'http://server-1.com/oauth/authorize');
expect(event.preventDefault).not.toBeCalled();
});
it('should not allow navigation when isCustomLoginURL is external', () => {
willNavigate(event, 'http://loginurl.com/oauth/authorize');
expect(event.preventDefault).toBeCalled();
});
it('should allow navigation when protocol is mailto', () => {
willNavigate(event, 'mailto:test@mattermost.com');
expect(event.preventDefault).not.toBeCalled();
});
it('should allow navigation when a custom login is in progress', () => {
webContentsEventManager.customLogins[1] = {inProgress: true};
willNavigate(event, 'http://anyoldurl.com');
expect(event.preventDefault).not.toBeCalled();
});
it('should allow navigation when it isChannelExportUrl', () => {
willNavigate(event, 'http://server-1.com/plugins/com.mattermost.plugin-channel-export/api/v1/export');
expect(event.preventDefault).not.toBeCalled();
@@ -127,32 +111,6 @@ describe('main/views/webContentsEvents', () => {
});
});
describe('didStartNavigation', () => {
const webContentsEventManager = new WebContentsEventManager();
const didStartNavigation = webContentsEventManager.generateDidStartNavigation(1);
beforeEach(() => {
webContentsEventManager.getServerURLFromWebContentsId = jest.fn().mockImplementation(() => new URL('http://server-1.com'));
});
afterEach(() => {
jest.clearAllMocks();
webContentsEventManager.customLogins = {};
});
it('should add custom login entry on custom login URL', () => {
webContentsEventManager.customLogins[1] = {inProgress: false};
didStartNavigation(event, 'http://server-1.com/oauth/authorize');
expect(webContentsEventManager.customLogins[1]).toStrictEqual({inProgress: true});
});
it('should remove custom login entry once navigating back to internal URL', () => {
webContentsEventManager.customLogins[1] = {inProgress: true};
didStartNavigation(event, 'http://server-1.com/subpath');
expect(webContentsEventManager.customLogins[1]).toStrictEqual({inProgress: false});
});
});
describe('newWindow', () => {
const webContentsEventManager = new WebContentsEventManager();
const newWindow = webContentsEventManager.generateNewWindowListener(1, true);

View File

@@ -11,7 +11,6 @@ import {
isAdminUrl,
isCallsPopOutURL,
isChannelExportUrl,
isCustomLoginURL,
isHelpUrl,
isImageProxyUrl,
isInternalURL,
@@ -20,11 +19,9 @@ import {
isPluginUrl,
isPublicFilesUrl,
isTeamUrl,
isTrustedURL,
isValidURI,
parseURL,
} from 'common/utils/url';
import {flushCookiesStore} from 'main/app/utils';
import ContextMenu from 'main/contextMenu';
import PluginsPopUpsManager from 'main/views/pluginsPopUps';
import ViewManager from 'main/views/viewManager';
@@ -36,19 +33,13 @@ import {generateHandleConsoleMessage, isCustomProtocol} from './webContentEvents
import allowProtocolDialog from '../allowProtocolDialog';
import {composeUserAgent} from '../utils';
type CustomLogin = {
inProgress: boolean;
}
const log = new Logger('WebContentsEventManager');
export class WebContentsEventManager {
customLogins: Record<number, CustomLogin>;
listeners: Record<number, () => void>;
popupWindow?: {win: BrowserWindow; serverURL?: URL};
constructor() {
this.customLogins = {};
this.listeners = {};
}
@@ -101,16 +92,9 @@ export class WebContentsEventManager {
return;
}
if (serverURL && isCustomLoginURL(parsedURL, serverURL)) {
return;
}
if (parsedURL.protocol === 'mailto:') {
return;
}
if (this.customLogins[webContentsId]?.inProgress) {
flushCookiesStore();
return;
}
const callID = CallsWidgetWindow.callID;
if (serverURL && callID && isCallsPopOutURL(serverURL, parsedURL, callID)) {
@@ -122,25 +106,6 @@ export class WebContentsEventManager {
};
};
private generateDidStartNavigation = (webContentsId: number) => {
return (event: Event, url: string) => {
this.log(webContentsId).debug('did-start-navigation', url);
const parsedURL = parseURL(url)!;
const serverURL = this.getServerURLFromWebContentsId(webContentsId);
if (!serverURL || !isTrustedURL(parsedURL, serverURL)) {
return;
}
if (serverURL && isCustomLoginURL(parsedURL, serverURL)) {
this.customLogins[webContentsId].inProgress = true;
} else if (serverURL && this.customLogins[webContentsId].inProgress && isInternalURL(serverURL || new URL(''), parsedURL)) {
this.customLogins[webContentsId].inProgress = false;
}
};
};
private denyNewWindow = (details: Electron.HandlerDetails): {action: 'deny' | 'allow'} => {
this.log().warn(`Prevented popup window to open a new window to ${details.url}.`);
return {action: 'deny'};
@@ -239,9 +204,6 @@ export class WebContentsEventManager {
}),
serverURL,
};
this.customLogins[this.popupWindow.win.webContents.id] = {
inProgress: false,
};
popup = this.popupWindow.win;
popup.webContents.on('will-redirect', (event, url) => {
@@ -256,7 +218,6 @@ export class WebContentsEventManager {
}
});
popup.webContents.on('will-navigate', this.generateWillNavigate(popup.webContents.id));
popup.webContents.on('did-start-navigation', this.generateDidStartNavigation(popup.webContents.id));
popup.webContents.setWindowOpenHandler(this.denyNewWindow);
popup.once('closed', () => {
this.popupWindow = undefined;
@@ -304,11 +265,6 @@ export class WebContentsEventManager {
addListeners?: (contents: WebContents) => void,
removeListeners?: (contents: WebContents) => void,
) => {
// initialize custom login tracking
this.customLogins[contents.id] = {
inProgress: false,
};
if (this.listeners[contents.id]) {
this.removeWebContentsListeners(contents.id);
}
@@ -316,14 +272,6 @@ export class WebContentsEventManager {
const willNavigate = this.generateWillNavigate(contents.id);
contents.on('will-navigate', willNavigate);
// handle custom login requests (oath, saml):
// 1. are we navigating to a supported local custom login path from the `/login` page?
// - indicate custom login is in progress
// 2. are we finished with the custom login process?
// - indicate custom login is NOT in progress
const didStartNavigation = this.generateDidStartNavigation(contents.id);
contents.on('did-start-navigation', didStartNavigation);
const spellcheck = Config.useSpellChecker;
const newWindow = this.generateNewWindowListener(contents.id, spellcheck);
contents.setWindowOpenHandler(newWindow);
@@ -340,7 +288,6 @@ export class WebContentsEventManager {
const removeWebContentsListeners = () => {
try {
contents.removeListener('will-navigate', willNavigate);
contents.removeListener('did-start-navigation', didStartNavigation);
contents.removeListener('console-message', consoleMessage);
removeListeners?.(contents);
} catch (e) {

View File

@@ -784,74 +784,6 @@ describe('main/windows/callsWidgetWindow', () => {
});
});
describe('handleCallsWidgetChannelLinkClick', () => {
const callsWidgetWindow = new CallsWidgetWindow();
callsWidgetWindow.win = {webContents: {id: 1}};
callsWidgetWindow.mainView = {
view: {
server: {
id: 'server-2',
},
},
sendToRenderer: jest.fn(),
};
callsWidgetWindow.getChannelURL = jest.fn();
const servers = [
{
name: 'server-1',
order: 1,
views: [
{
name: 'view-1',
order: 0,
isOpen: false,
},
{
name: 'view-2',
order: 2,
isOpen: true,
},
],
}, {
name: 'server-2',
order: 0,
views: [
{
name: 'view-1',
order: 0,
isOpen: false,
},
{
name: 'view-2',
order: 2,
isOpen: true,
},
],
lastActiveView: 2,
},
];
const map = servers.reduce((arr, item) => {
item.views.forEach((view) => {
arr.push([`${item.name}_${view.name}`, {}]);
});
return arr;
}, []);
const views = new Map(map);
beforeEach(() => {
ViewManager.getView.mockImplementation((viewId) => views.get(viewId));
});
afterEach(() => {
jest.resetAllMocks();
});
it('should switch server', () => {
callsWidgetWindow.handleCallsWidgetChannelLinkClick({sender: {id: 1}});
expect(ServerViewState.switchServer).toHaveBeenCalledWith('server-2');
});
});
describe('forwardToMainApp', () => {
const view = {
view: {

View File

@@ -14,7 +14,6 @@ import {
CALLS_LEAVE_CALL,
CALLS_LINK_CLICK,
CALLS_POPOUT_FOCUS,
CALLS_WIDGET_CHANNEL_LINK_CLICK,
CALLS_WIDGET_RESIZE,
CALLS_WIDGET_SHARE_SCREEN,
CALLS_WIDGET_OPEN_THREAD,
@@ -80,9 +79,6 @@ export class CallsWidgetWindow {
ipcMain.on(CALLS_WIDGET_OPEN_THREAD, this.handleCallsOpenThread);
ipcMain.on(CALLS_WIDGET_OPEN_STOP_RECORDING_MODAL, this.handleCallsOpenStopRecordingModal);
ipcMain.on(CALLS_WIDGET_OPEN_USER_SETTINGS, this.forwardToMainApp(CALLS_WIDGET_OPEN_USER_SETTINGS));
// deprecated in favour of CALLS_LINK_CLICK
ipcMain.on(CALLS_WIDGET_CHANNEL_LINK_CLICK, this.handleCallsWidgetChannelLinkClick);
}
/**
@@ -555,25 +551,6 @@ export class CallsWidgetWindow {
MainWindow.get()?.focus();
this.mainView?.sendToRenderer(BROWSER_HISTORY_PUSH, url);
};
/**
* @deprecated
*/
private handleCallsWidgetChannelLinkClick = (event: IpcMainEvent) => {
log.debug('handleCallsWidgetChannelLinkClick');
if (!this.isCallsWidget(event.sender.id)) {
return;
}
if (!this.serverID) {
return;
}
ServerViewState.switchServer(this.serverID);
MainWindow.get()?.focus();
this.mainView?.sendToRenderer(BROWSER_HISTORY_PUSH, this.options?.channelURL);
};
}
const callsWidgetWindow = new CallsWidgetWindow();

View File

@@ -1,53 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {Row, Button} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
type Props = {
darkMode?: boolean;
goBack?: () => void;
show?: boolean;
};
export default class ExtraBar extends React.PureComponent<Props> {
handleBack = () => {
if (this.props.goBack) {
this.props.goBack();
}
};
render() {
let barClass = 'clear-mode';
if (!this.props.show) {
barClass = 'hidden';
} else if (this.props.darkMode) {
barClass = 'dark-mode';
}
return (
<Row
id={'extra-bar'}
className={barClass}
>
<div
className={'container-fluid'}
onClick={this.handleBack}
>
<Button
variant={'link'}
size={'sm'}
>
<span className={'backIcon icon-arrow-left'}/>
<span className={'backLabel'}>
<FormattedMessage
id='renderer.components.extraBar.back'
defaultMessage='Back'
/>
</span>
</Button>
</div>
</Row>
);
}
}

View File

@@ -15,7 +15,6 @@ import type {DownloadedItems} from 'types/downloads';
import DeveloperModeIndicator from './DeveloperModeIndicator';
import DownloadsDropdownButton from './DownloadsDropdown/DownloadsDropdownButton';
import ErrorView from './ErrorView';
import ExtraBar from './ExtraBar';
import ServerDropdownButton from './ServerDropdownButton';
import TabBar from './TabBar';
@@ -50,7 +49,6 @@ type State = {
tabViewStatus: Map<string, TabViewStatus>;
modalOpen?: boolean;
fullScreen?: boolean;
showExtraBar?: boolean;
isMenuOpen: boolean;
isDownloadsDropdownOpen: boolean;
showDownloadsBadge: boolean;
@@ -210,10 +208,6 @@ class MainPage extends React.PureComponent<Props, State> {
this.setState({modalOpen: false});
});
window.desktop.onToggleBackButton((showExtraBar) => {
this.setState({showExtraBar});
});
window.desktop.onUpdateMentions((view, mentions, unreads, isExpired) => {
const {unreadCounts, mentionCounts, sessionsExpired} = this.state;
@@ -532,13 +526,6 @@ class MainPage extends React.PureComponent<Props, State> {
const viewsRow = (
<Fragment>
<ExtraBar
darkMode={this.props.darkMode}
show={this.state.showExtraBar}
goBack={() => {
window.desktop.goBack();
}}
/>
<Row>
{views()}
</Row>

View File

@@ -1,44 +0,0 @@
#extra-bar {
max-height: 76px;
transition: max-height 0.25s ease;
background-color: #f5f5f5;
-webkit-font-smoothing: antialiased;
}
#extra-bar div {
padding: 0 0.93em 0.2em;
}
#extra-bar.hidden {
display: none;
}
#extra-bar.dark-mode {
background: #1F1F1F;
}
#extra-bar.dark-mode .btn-link {
color: rgba(243,243,243,0.7);
}
#extra-bar.dark-mode span.backLabel {
color: rgba(243,243,243,0.7);
}
span.backLabel {
font-family: "Open Sans", sans-serif;
font-weight: normal;
line-height: 30px;
font-size: 14px;
padding-top: 0px;
padding-bottom: 0px;
color: #166de0;
}
span.backIcon {
margin-right: 4px;
}
.container-fluid button:first-child {
padding-left: 0px;
}

View File

@@ -6,6 +6,5 @@
@import url("TabBar.css");
@import url("UpdaterPage.css");
@import url("CertificateModal.css");
@import url("ExtraBar.css");
@import url("LoadingScreen.css");
@import url("LoadingAnimation.css");

View File

@@ -14,6 +14,4 @@ export type DeveloperSettings = {
disableNotificationStorage?: boolean;
disableUserActivityMonitor?: boolean;
disableContextMenu?: boolean;
forceLegacyAPI?: boolean;
forceNewAPI?: boolean;
};

View File

@@ -79,7 +79,6 @@ declare global {
onPlaySound: (listener: (soundName: string) => void) => void;
onModalOpen: (listener: () => void) => void;
onModalClose: (listener: () => void) => void;
onToggleBackButton: (listener: (showExtraBar: boolean) => void) => void;
onUpdateMentions: (listener: (view: string, mentions: number, unreads: boolean, isExpired: boolean) => void) => void;
onCloseServersDropdown: (listener: () => void) => void;
onOpenServersDropdown: (listener: () => void) => void;