[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:
@@ -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}",
|
||||
|
@@ -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';
|
||||
|
@@ -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',
|
||||
|
@@ -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(
|
||||
|
@@ -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'));
|
||||
|
@@ -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());
|
||||
|
@@ -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);
|
||||
|
@@ -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';
|
||||
|
@@ -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');
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
|
@@ -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,35 +43,20 @@ 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 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?.();
|
||||
}
|
||||
|
||||
return ipcRenderer.invoke(GET_APP_INFO);
|
||||
},
|
||||
getAppInfo: () => ipcRenderer.invoke(GET_APP_INFO),
|
||||
reactAppInitialized: () => ipcRenderer.send(REACT_APP_INITIALIZED),
|
||||
|
||||
// Session
|
||||
@@ -138,9 +115,8 @@ ipcRenderer.invoke(GET_DEVELOPER_MODE_SETTING, 'forceLegacyAPI').then((force) =>
|
||||
|
||||
// Utility
|
||||
unregister: (channel) => ipcRenderer.removeAllListeners(channel),
|
||||
};
|
||||
contextBridge.exposeInMainWorld('desktopAPI', desktopAPI);
|
||||
});
|
||||
};
|
||||
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');
|
||||
};
|
||||
});
|
||||
|
@@ -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()),
|
||||
|
@@ -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', () => {
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
}
|
||||
};
|
||||
|
@@ -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);
|
||||
|
@@ -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) {
|
||||
|
@@ -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: {
|
||||
|
@@ -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();
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@@ -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>
|
||||
|
@@ -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;
|
||||
}
|
@@ -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");
|
||||
|
@@ -14,6 +14,4 @@ export type DeveloperSettings = {
|
||||
disableNotificationStorage?: boolean;
|
||||
disableUserActivityMonitor?: boolean;
|
||||
disableContextMenu?: boolean;
|
||||
forceLegacyAPI?: boolean;
|
||||
forceNewAPI?: boolean;
|
||||
};
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user