[MM-52333] Create Server View State module, move into new app
module (#2739)
* Create Server View State Module * Move currentServerId to serverViewState * Move view state into server view state module * PR feedback/bug fixes
This commit is contained in:
507
src/app/serverViewState.test.js
Normal file
507
src/app/serverViewState.test.js
Normal file
@@ -0,0 +1,507 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {URLValidationStatus} from 'common/utils/constants';
|
||||
import {getDefaultViewsForConfigServer} from 'common/views/View';
|
||||
|
||||
import {ServerInfo} from 'main/server/serverInfo';
|
||||
import ModalManager from 'main/views/modalManager';
|
||||
import {getLocalURLString, getLocalPreload} from 'main/utils';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
|
||||
import {ServerViewState} from './serverViewState';
|
||||
|
||||
jest.mock('electron', () => ({
|
||||
ipcMain: {
|
||||
on: jest.fn(),
|
||||
handle: jest.fn(),
|
||||
emit: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('common/servers/serverManager', () => ({
|
||||
setViewIsOpen: jest.fn(),
|
||||
getAllServers: jest.fn(),
|
||||
hasServers: jest.fn(),
|
||||
addServer: jest.fn(),
|
||||
editServer: jest.fn(),
|
||||
removeServer: jest.fn(),
|
||||
getServer: jest.fn(),
|
||||
getView: jest.fn(),
|
||||
getLastActiveTabForServer: jest.fn(),
|
||||
getServerLog: jest.fn(),
|
||||
lookupViewByURL: jest.fn(),
|
||||
getOrderedServers: jest.fn(),
|
||||
}));
|
||||
jest.mock('common/servers/MattermostServer', () => ({
|
||||
MattermostServer: jest.fn(),
|
||||
}));
|
||||
jest.mock('common/views/View', () => ({
|
||||
getDefaultViewsForConfigServer: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/server/serverInfo', () => ({
|
||||
ServerInfo: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/views/modalManager', () => ({
|
||||
addModal: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/utils', () => ({
|
||||
getLocalPreload: jest.fn(),
|
||||
getLocalURLString: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/windows/mainWindow', () => ({
|
||||
get: jest.fn(),
|
||||
show: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/views/viewManager', () => ({
|
||||
getView: jest.fn(),
|
||||
showById: jest.fn(),
|
||||
}));
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
name: 'view-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'view-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'view-3',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
];
|
||||
const servers = [
|
||||
{
|
||||
id: 'server-1',
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
tabs,
|
||||
},
|
||||
];
|
||||
|
||||
describe('app/serverViewState', () => {
|
||||
describe('switchServer', () => {
|
||||
const serverViewState = new ServerViewState();
|
||||
const views = new Map([
|
||||
['view-1', {id: 'view-1'}],
|
||||
['view-2', {id: 'view-2'}],
|
||||
['view-3', {id: 'view-3'}],
|
||||
]);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
const server1 = {
|
||||
id: 'server-1',
|
||||
};
|
||||
const server2 = {
|
||||
id: 'server-2',
|
||||
};
|
||||
ServerManager.getServer.mockImplementation((name) => {
|
||||
switch (name) {
|
||||
case 'server-1':
|
||||
return server1;
|
||||
case 'server-2':
|
||||
return server2;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
ServerManager.getServerLog.mockReturnValue({debug: jest.fn(), error: jest.fn()});
|
||||
ViewManager.getView.mockImplementation((viewId) => views.get(viewId));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
jest.clearAllTimers();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should do nothing if cannot find the server', () => {
|
||||
serverViewState.switchServer('server-3');
|
||||
expect(ViewManager.showById).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should show first open view in order when last active not defined', () => {
|
||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'view-3'});
|
||||
serverViewState.switchServer('server-1');
|
||||
expect(ViewManager.showById).toHaveBeenCalledWith('view-3');
|
||||
});
|
||||
|
||||
it('should show last active view of chosen server', () => {
|
||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'view-2'});
|
||||
serverViewState.switchServer('server-2');
|
||||
expect(ViewManager.showById).toHaveBeenCalledWith('view-2');
|
||||
});
|
||||
|
||||
it('should wait for view to exist if specified', () => {
|
||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'view-3'});
|
||||
views.delete('view-3');
|
||||
serverViewState.switchServer('server-1', true);
|
||||
expect(ViewManager.showById).not.toBeCalled();
|
||||
|
||||
jest.advanceTimersByTime(200);
|
||||
expect(ViewManager.showById).not.toBeCalled();
|
||||
|
||||
views.set('view-3', {});
|
||||
jest.advanceTimersByTime(200);
|
||||
expect(ViewManager.showById).toBeCalledWith('view-3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('showNewServerModal', () => {
|
||||
const serverViewState = new ServerViewState();
|
||||
let serversCopy;
|
||||
|
||||
beforeEach(() => {
|
||||
getLocalURLString.mockReturnValue('/some/index.html');
|
||||
getLocalPreload.mockReturnValue('/some/preload.js');
|
||||
MainWindow.get.mockReturnValue({});
|
||||
|
||||
serversCopy = JSON.parse(JSON.stringify(servers));
|
||||
ServerManager.getAllServers.mockReturnValue([]);
|
||||
ServerManager.addServer.mockImplementation(() => {
|
||||
const newServer = {
|
||||
id: 'server-1',
|
||||
name: 'new-server',
|
||||
url: 'http://new-server.com',
|
||||
tabs,
|
||||
};
|
||||
serversCopy = [
|
||||
...serversCopy,
|
||||
newServer,
|
||||
];
|
||||
return newServer;
|
||||
});
|
||||
ServerManager.hasServers.mockReturnValue(Boolean(serversCopy.length));
|
||||
ServerManager.getServerLog.mockReturnValue({debug: jest.fn(), error: jest.fn()});
|
||||
|
||||
getDefaultViewsForConfigServer.mockImplementation((server) => ({
|
||||
...server,
|
||||
tabs,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should add new server to the config', async () => {
|
||||
const data = {
|
||||
name: 'new-server',
|
||||
url: 'http://new-server.com',
|
||||
};
|
||||
const promise = Promise.resolve(data);
|
||||
ModalManager.addModal.mockReturnValue(promise);
|
||||
|
||||
serverViewState.showNewServerModal();
|
||||
await promise;
|
||||
|
||||
expect(ServerManager.addServer).toHaveBeenCalledWith(data);
|
||||
expect(serversCopy).toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'new-server',
|
||||
url: 'http://new-server.com',
|
||||
tabs,
|
||||
}));
|
||||
|
||||
// TODO: For some reason jest won't recognize this as being called
|
||||
//expect(spy).toHaveBeenCalledWith('server-1', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleEditServerModal', () => {
|
||||
const serverViewState = new ServerViewState();
|
||||
let serversCopy;
|
||||
|
||||
beforeEach(() => {
|
||||
getLocalURLString.mockReturnValue('/some/index.html');
|
||||
getLocalPreload.mockReturnValue('/some/preload.js');
|
||||
MainWindow.get.mockReturnValue({});
|
||||
|
||||
serversCopy = JSON.parse(JSON.stringify(servers));
|
||||
ServerManager.getServer.mockImplementation((id) => {
|
||||
if (id !== serversCopy[0].id) {
|
||||
return undefined;
|
||||
}
|
||||
return {...serversCopy[0], toUniqueServer: jest.fn()};
|
||||
});
|
||||
ServerManager.editServer.mockImplementation((id, server) => {
|
||||
if (id !== serversCopy[0].id) {
|
||||
return;
|
||||
}
|
||||
const newServer = {
|
||||
...serversCopy[0],
|
||||
...server,
|
||||
};
|
||||
serversCopy = [newServer];
|
||||
});
|
||||
ServerManager.getAllServers.mockReturnValue(serversCopy.map((server) => ({...server, toUniqueServer: jest.fn()})));
|
||||
});
|
||||
|
||||
it('should do nothing when the server cannot be found', () => {
|
||||
serverViewState.showEditServerModal(null, 'bad-server');
|
||||
expect(ModalManager.addModal).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should edit the existing server', async () => {
|
||||
const promise = Promise.resolve({
|
||||
name: 'new-server',
|
||||
url: 'http://new-server.com',
|
||||
});
|
||||
ModalManager.addModal.mockReturnValue(promise);
|
||||
|
||||
serverViewState.showEditServerModal(null, 'server-1');
|
||||
await promise;
|
||||
expect(serversCopy).not.toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
tabs,
|
||||
}));
|
||||
expect(serversCopy).toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'new-server',
|
||||
url: 'http://new-server.com',
|
||||
tabs,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleRemoveServerModal', () => {
|
||||
const serverViewState = new ServerViewState();
|
||||
let serversCopy;
|
||||
|
||||
beforeEach(() => {
|
||||
getLocalURLString.mockReturnValue('/some/index.html');
|
||||
getLocalPreload.mockReturnValue('/some/preload.js');
|
||||
MainWindow.get.mockReturnValue({});
|
||||
|
||||
serversCopy = JSON.parse(JSON.stringify(servers));
|
||||
ServerManager.getServer.mockImplementation(() => {
|
||||
return serversCopy[0];
|
||||
});
|
||||
ServerManager.removeServer.mockImplementation(() => {
|
||||
serversCopy = [];
|
||||
});
|
||||
ServerManager.getOrderedServers.mockReturnValue(serversCopy);
|
||||
});
|
||||
|
||||
it('should remove the existing server', async () => {
|
||||
const promise = Promise.resolve(true);
|
||||
ModalManager.addModal.mockReturnValue(promise);
|
||||
|
||||
serverViewState.showRemoveServerModal(null, 'server-1');
|
||||
await promise;
|
||||
expect(serversCopy).not.toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
tabs,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should not remove the existing server when clicking Cancel', async () => {
|
||||
const promise = Promise.resolve(false);
|
||||
ModalManager.addModal.mockReturnValue(promise);
|
||||
|
||||
expect(serversCopy).toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
tabs,
|
||||
}));
|
||||
|
||||
serverViewState.showRemoveServerModal(null, 'server-1');
|
||||
await promise;
|
||||
expect(serversCopy).toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
tabs,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleServerURLValidation', () => {
|
||||
const serverViewState = new ServerViewState();
|
||||
|
||||
beforeEach(() => {
|
||||
MattermostServer.mockImplementation(({url}) => ({url}));
|
||||
ServerInfo.mockImplementation(({url}) => ({
|
||||
fetchRemoteInfo: jest.fn().mockImplementation(() => ({
|
||||
serverVersion: '7.8.0',
|
||||
siteName: 'Mattermost',
|
||||
siteURL: url,
|
||||
})),
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should return Missing when you get no URL', async () => {
|
||||
const result = await serverViewState.handleServerURLValidation({});
|
||||
expect(result.status).toBe(URLValidationStatus.Missing);
|
||||
});
|
||||
|
||||
it('should return Invalid when you pass in invalid characters', async () => {
|
||||
const result = await serverViewState.handleServerURLValidation({}, '!@#$%^&*()!@#$%^&*()');
|
||||
expect(result.status).toBe(URLValidationStatus.Invalid);
|
||||
});
|
||||
|
||||
it('should include HTTPS when missing', async () => {
|
||||
const result = await serverViewState.handleServerURLValidation({}, 'server.com');
|
||||
expect(result.status).toBe(URLValidationStatus.OK);
|
||||
expect(result.validatedURL).toBe('https://server.com/');
|
||||
});
|
||||
|
||||
it('should correct typos in the protocol', async () => {
|
||||
const result = await serverViewState.handleServerURLValidation({}, 'htpst://server.com');
|
||||
expect(result.status).toBe(URLValidationStatus.OK);
|
||||
expect(result.validatedURL).toBe('https://server.com/');
|
||||
});
|
||||
|
||||
it('should replace HTTP with HTTPS when applicable', async () => {
|
||||
const result = await serverViewState.handleServerURLValidation({}, 'http://server.com');
|
||||
expect(result.status).toBe(URLValidationStatus.OK);
|
||||
expect(result.validatedURL).toBe('https://server.com/');
|
||||
});
|
||||
|
||||
it('should generate a warning when the server already exists', async () => {
|
||||
ServerManager.lookupViewByURL.mockReturnValue({server: {id: 'server-1', url: new URL('https://server.com')}});
|
||||
const result = await serverViewState.handleServerURLValidation({}, 'https://server.com');
|
||||
expect(result.status).toBe(URLValidationStatus.URLExists);
|
||||
expect(result.validatedURL).toBe('https://server.com/');
|
||||
});
|
||||
|
||||
it('should generate a warning if the server exists when editing', async () => {
|
||||
ServerManager.lookupViewByURL.mockReturnValue({server: {name: 'Server 1', id: 'server-1', url: new URL('https://server.com')}});
|
||||
const result = await serverViewState.handleServerURLValidation({}, 'https://server.com', 'server-2');
|
||||
expect(result.status).toBe(URLValidationStatus.URLExists);
|
||||
expect(result.validatedURL).toBe('https://server.com/');
|
||||
expect(result.existingServerName).toBe('Server 1');
|
||||
});
|
||||
|
||||
it('should not generate a warning if editing the same server', async () => {
|
||||
ServerManager.lookupViewByURL.mockReturnValue({server: {name: 'Server 1', id: 'server-1', url: new URL('https://server.com')}});
|
||||
const result = await serverViewState.handleServerURLValidation({}, 'https://server.com', 'server-1');
|
||||
expect(result.status).toBe(URLValidationStatus.OK);
|
||||
expect(result.validatedURL).toBe('https://server.com/');
|
||||
});
|
||||
|
||||
it('should attempt HTTP when HTTPS fails, and generate a warning', async () => {
|
||||
ServerInfo.mockImplementation(({url}) => ({
|
||||
fetchRemoteInfo: jest.fn().mockImplementation(() => {
|
||||
if (url.startsWith('https:')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
serverVersion: '7.8.0',
|
||||
siteName: 'Mattermost',
|
||||
siteURL: url,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
const result = await serverViewState.handleServerURLValidation({}, 'http://server.com');
|
||||
expect(result.status).toBe(URLValidationStatus.Insecure);
|
||||
expect(result.validatedURL).toBe('http://server.com/');
|
||||
});
|
||||
|
||||
it('should show a warning when the ping request times out', async () => {
|
||||
ServerInfo.mockImplementation(() => ({
|
||||
fetchRemoteInfo: jest.fn().mockImplementation(() => {
|
||||
throw new Error();
|
||||
}),
|
||||
}));
|
||||
|
||||
const result = await serverViewState.handleServerURLValidation({}, 'https://not-server.com');
|
||||
expect(result.status).toBe(URLValidationStatus.NotMattermost);
|
||||
expect(result.validatedURL).toBe('https://not-server.com/');
|
||||
});
|
||||
|
||||
it('should update the users URL when the Site URL is different', async () => {
|
||||
ServerInfo.mockImplementation(() => ({
|
||||
fetchRemoteInfo: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
serverVersion: '7.8.0',
|
||||
siteName: 'Mattermost',
|
||||
siteURL: 'https://mainserver.com/',
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
const result = await serverViewState.handleServerURLValidation({}, 'https://server.com');
|
||||
expect(result.status).toBe(URLValidationStatus.URLUpdated);
|
||||
expect(result.validatedURL).toBe('https://mainserver.com/');
|
||||
});
|
||||
|
||||
it('should warn the user when the Site URL is different but unreachable', async () => {
|
||||
ServerInfo.mockImplementation(({url}) => ({
|
||||
fetchRemoteInfo: jest.fn().mockImplementation(() => {
|
||||
if (url === 'https://mainserver.com/') {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
serverVersion: '7.8.0',
|
||||
siteName: 'Mattermost',
|
||||
siteURL: 'https://mainserver.com/',
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
const result = await serverViewState.handleServerURLValidation({}, 'https://server.com');
|
||||
expect(result.status).toBe(URLValidationStatus.URLNotMatched);
|
||||
expect(result.validatedURL).toBe('https://server.com/');
|
||||
});
|
||||
|
||||
it('should warn the user when the Site URL already exists as another server', async () => {
|
||||
ServerManager.lookupViewByURL.mockReturnValue({server: {name: 'Server 1', id: 'server-1', url: new URL('https://mainserver.com')}});
|
||||
ServerInfo.mockImplementation(() => ({
|
||||
fetchRemoteInfo: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
serverVersion: '7.8.0',
|
||||
siteName: 'Mattermost',
|
||||
siteURL: 'https://mainserver.com',
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
const result = await serverViewState.handleServerURLValidation({}, 'https://server.com');
|
||||
expect(result.status).toBe(URLValidationStatus.URLExists);
|
||||
expect(result.validatedURL).toBe('https://mainserver.com/');
|
||||
expect(result.existingServerName).toBe('Server 1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleCloseView', () => {
|
||||
const serverViewState = new ServerViewState();
|
||||
|
||||
it('should close the specified view and switch to the next open view', () => {
|
||||
ServerManager.getView.mockReturnValue({server: {id: 'server-1'}});
|
||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'view-2'});
|
||||
serverViewState.handleCloseView(null, 'view-3');
|
||||
expect(ServerManager.setViewIsOpen).toBeCalledWith('view-3', false);
|
||||
expect(ViewManager.showById).toBeCalledWith('view-2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleOpenView', () => {
|
||||
const serverViewState = new ServerViewState();
|
||||
|
||||
it('should open the specified view', () => {
|
||||
serverViewState.handleOpenView(null, 'view-1');
|
||||
expect(ViewManager.showById).toBeCalledWith('view-1');
|
||||
});
|
||||
});
|
||||
});
|
380
src/app/serverViewState.ts
Normal file
380
src/app/serverViewState.ts
Normal file
@@ -0,0 +1,380 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {IpcMainEvent, IpcMainInvokeEvent, ipcMain} from 'electron';
|
||||
|
||||
import {UniqueServer, Server} from 'types/config';
|
||||
import {URLValidationResult} from 'types/server';
|
||||
|
||||
import {
|
||||
CLOSE_VIEW,
|
||||
GET_LAST_ACTIVE,
|
||||
GET_ORDERED_SERVERS,
|
||||
GET_ORDERED_TABS_FOR_SERVER,
|
||||
OPEN_VIEW,
|
||||
SHOW_EDIT_SERVER_MODAL,
|
||||
SHOW_NEW_SERVER_MODAL,
|
||||
SHOW_REMOVE_SERVER_MODAL,
|
||||
SWITCH_SERVER,
|
||||
UPDATE_SERVER_ORDER,
|
||||
UPDATE_SHORTCUT_MENU,
|
||||
UPDATE_TAB_ORDER,
|
||||
VALIDATE_SERVER_URL,
|
||||
} from 'common/communication';
|
||||
import {Logger} from 'common/log';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import {isValidURI, isValidURL, parseURL} from 'common/utils/url';
|
||||
import {URLValidationStatus} from 'common/utils/constants';
|
||||
import Config from 'common/config';
|
||||
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import ModalManager from 'main/views/modalManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||
import {ServerInfo} from 'main/server/serverInfo';
|
||||
|
||||
const log = new Logger('App', 'ServerViewState');
|
||||
|
||||
export class ServerViewState {
|
||||
private currentServerId?: string;
|
||||
|
||||
constructor() {
|
||||
ipcMain.on(SWITCH_SERVER, (event, serverId) => this.switchServer(serverId));
|
||||
ipcMain.on(SHOW_NEW_SERVER_MODAL, this.showNewServerModal);
|
||||
ipcMain.on(SHOW_EDIT_SERVER_MODAL, this.showEditServerModal);
|
||||
ipcMain.on(SHOW_REMOVE_SERVER_MODAL, this.showRemoveServerModal);
|
||||
ipcMain.handle(VALIDATE_SERVER_URL, this.handleServerURLValidation);
|
||||
ipcMain.handle(GET_ORDERED_SERVERS, this.handleGetOrderedServers);
|
||||
ipcMain.on(UPDATE_SERVER_ORDER, this.updateServerOrder);
|
||||
|
||||
ipcMain.on(CLOSE_VIEW, this.handleCloseView);
|
||||
ipcMain.on(OPEN_VIEW, this.handleOpenView);
|
||||
ipcMain.handle(GET_LAST_ACTIVE, this.handleGetLastActive);
|
||||
ipcMain.handle(GET_ORDERED_TABS_FOR_SERVER, this.handleGetOrderedViewsForServer);
|
||||
ipcMain.on(UPDATE_TAB_ORDER, this.updateTabOrder);
|
||||
}
|
||||
|
||||
init = () => {
|
||||
const orderedServers = ServerManager.getOrderedServers();
|
||||
if (orderedServers.length) {
|
||||
if (Config.lastActiveServer && orderedServers[Config.lastActiveServer]) {
|
||||
this.currentServerId = orderedServers[Config.lastActiveServer].id;
|
||||
} else {
|
||||
this.currentServerId = orderedServers[0].id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentServer = () => {
|
||||
log.debug('getCurrentServer');
|
||||
|
||||
if (!this.currentServerId) {
|
||||
throw new Error('No server set as current');
|
||||
}
|
||||
const server = ServerManager.getServer(this.currentServerId);
|
||||
if (!server) {
|
||||
throw new Error('Current server does not exist');
|
||||
}
|
||||
return server;
|
||||
}
|
||||
|
||||
switchServer = (serverId: string, waitForViewToExist = false) => {
|
||||
ServerManager.getServerLog(serverId, 'WindowManager').debug('switchServer');
|
||||
MainWindow.show();
|
||||
const server = ServerManager.getServer(serverId);
|
||||
if (!server) {
|
||||
ServerManager.getServerLog(serverId, 'WindowManager').error('Cannot find server in config');
|
||||
return;
|
||||
}
|
||||
this.currentServerId = serverId;
|
||||
const nextView = ServerManager.getLastActiveTabForServer(serverId);
|
||||
if (waitForViewToExist) {
|
||||
const timeout = setInterval(() => {
|
||||
if (ViewManager.getView(nextView.id)) {
|
||||
ViewManager.showById(nextView.id);
|
||||
clearInterval(timeout);
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
ViewManager.showById(nextView.id);
|
||||
}
|
||||
ipcMain.emit(UPDATE_SHORTCUT_MENU);
|
||||
}
|
||||
|
||||
selectNextView = () => {
|
||||
this.selectView((order) => order + 1);
|
||||
};
|
||||
|
||||
selectPreviousView = () => {
|
||||
this.selectView((order, length) => (length + (order - 1)));
|
||||
};
|
||||
|
||||
updateCurrentView = (serverId: string, viewId: string) => {
|
||||
this.currentServerId = serverId;
|
||||
ServerManager.updateLastActive(viewId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Server Modals
|
||||
*/
|
||||
|
||||
showNewServerModal = () => {
|
||||
log.debug('showNewServerModal');
|
||||
|
||||
const mainWindow = MainWindow.get();
|
||||
if (!mainWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modalPromise = ModalManager.addModal<null, Server>(
|
||||
'newServer',
|
||||
getLocalURLString('newServer.html'),
|
||||
getLocalPreload('desktopAPI.js'),
|
||||
null,
|
||||
mainWindow,
|
||||
!ServerManager.hasServers(),
|
||||
);
|
||||
|
||||
modalPromise.then((data) => {
|
||||
const newServer = ServerManager.addServer(data);
|
||||
this.switchServer(newServer.id, true);
|
||||
}).catch((e) => {
|
||||
// e is undefined for user cancellation
|
||||
if (e) {
|
||||
log.error(`there was an error in the new server modal: ${e}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
private showEditServerModal = (e: IpcMainEvent, id: string) => {
|
||||
log.debug('showEditServerModal', id);
|
||||
|
||||
const mainWindow = MainWindow.get();
|
||||
if (!mainWindow) {
|
||||
return;
|
||||
}
|
||||
const server = ServerManager.getServer(id);
|
||||
if (!server) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modalPromise = ModalManager.addModal<UniqueServer, Server>(
|
||||
'editServer',
|
||||
getLocalURLString('editServer.html'),
|
||||
getLocalPreload('desktopAPI.js'),
|
||||
server.toUniqueServer(),
|
||||
mainWindow);
|
||||
|
||||
modalPromise.then((data) => ServerManager.editServer(id, data)).catch((e) => {
|
||||
// e is undefined for user cancellation
|
||||
if (e) {
|
||||
log.error(`there was an error in the edit server modal: ${e}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
private showRemoveServerModal = (e: IpcMainEvent, id: string) => {
|
||||
log.debug('handleRemoveServerModal', id);
|
||||
|
||||
const mainWindow = MainWindow.get();
|
||||
if (!mainWindow) {
|
||||
return;
|
||||
}
|
||||
const server = ServerManager.getServer(id);
|
||||
if (!server) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modalPromise = ModalManager.addModal<string, boolean>(
|
||||
'removeServer',
|
||||
getLocalURLString('removeServer.html'),
|
||||
getLocalPreload('desktopAPI.js'),
|
||||
server.name,
|
||||
mainWindow,
|
||||
);
|
||||
|
||||
modalPromise.then((remove) => {
|
||||
if (remove) {
|
||||
const remainingServers = ServerManager.getOrderedServers().filter((orderedServer) => server.id !== orderedServer.id);
|
||||
if (this.currentServerId === server.id && remainingServers.length) {
|
||||
this.currentServerId = remainingServers[0].id;
|
||||
}
|
||||
|
||||
if (!remainingServers.length) {
|
||||
delete this.currentServerId;
|
||||
}
|
||||
|
||||
ServerManager.removeServer(server.id);
|
||||
}
|
||||
}).catch((e) => {
|
||||
// e is undefined for user cancellation
|
||||
if (e) {
|
||||
log.error(`there was an error in the edit server modal: ${e}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* IPC Handlers
|
||||
*/
|
||||
|
||||
private handleServerURLValidation = async (e: IpcMainInvokeEvent, url?: string, currentId?: string): Promise<URLValidationResult> => {
|
||||
log.debug('handleServerURLValidation', url, currentId);
|
||||
|
||||
// If the URL is missing or null, reject
|
||||
if (!url) {
|
||||
return {status: URLValidationStatus.Missing};
|
||||
}
|
||||
|
||||
let httpUrl = url;
|
||||
if (!isValidURL(url)) {
|
||||
// If it already includes the protocol, tell them it's invalid
|
||||
if (isValidURI(url)) {
|
||||
httpUrl = url.replace(/^(.+):/, 'https:');
|
||||
} else {
|
||||
// Otherwise add HTTPS for them
|
||||
httpUrl = `https://${url}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the final URL is valid
|
||||
const parsedURL = parseURL(httpUrl);
|
||||
if (!parsedURL) {
|
||||
return {status: URLValidationStatus.Invalid};
|
||||
}
|
||||
|
||||
// Try and add HTTPS to see if we can get a more secure URL
|
||||
let secureURL = parsedURL;
|
||||
if (parsedURL.protocol === 'http:') {
|
||||
secureURL = parseURL(parsedURL.toString().replace(/^http:/, 'https:')) ?? parsedURL;
|
||||
}
|
||||
|
||||
// Tell the user if they already have a server for this URL
|
||||
const existingServer = ServerManager.lookupViewByURL(secureURL, true);
|
||||
if (existingServer && existingServer.server.id !== currentId) {
|
||||
return {status: URLValidationStatus.URLExists, existingServerName: existingServer.server.name, validatedURL: existingServer.server.url.toString()};
|
||||
}
|
||||
|
||||
// Try and get remote info from the most secure URL, otherwise use the insecure one
|
||||
let remoteURL = secureURL;
|
||||
let remoteInfo = await this.testRemoteServer(secureURL);
|
||||
if (!remoteInfo) {
|
||||
if (secureURL.toString() !== parsedURL.toString()) {
|
||||
remoteURL = parsedURL;
|
||||
remoteInfo = await this.testRemoteServer(parsedURL);
|
||||
}
|
||||
}
|
||||
|
||||
// If we can't get the remote info, warn the user that this might not be the right URL
|
||||
// If the original URL was invalid, don't replace that as they probably have a typo somewhere
|
||||
if (!remoteInfo) {
|
||||
return {status: URLValidationStatus.NotMattermost, validatedURL: parsedURL.toString()};
|
||||
}
|
||||
|
||||
// If we were only able to connect via HTTP, warn the user that the connection is not secure
|
||||
if (remoteURL.protocol === 'http:') {
|
||||
return {status: URLValidationStatus.Insecure, serverVersion: remoteInfo.serverVersion, validatedURL: remoteURL.toString()};
|
||||
}
|
||||
|
||||
// If the URL doesn't match the Site URL, set the URL to the correct one
|
||||
if (remoteInfo.siteURL && remoteURL.toString() !== new URL(remoteInfo.siteURL).toString()) {
|
||||
const parsedSiteURL = parseURL(remoteInfo.siteURL);
|
||||
if (parsedSiteURL) {
|
||||
// Check the Site URL as well to see if it's already pre-configured
|
||||
const existingServer = ServerManager.lookupViewByURL(parsedSiteURL, true);
|
||||
if (existingServer && existingServer.server.id !== currentId) {
|
||||
return {status: URLValidationStatus.URLExists, existingServerName: existingServer.server.name, validatedURL: existingServer.server.url.toString()};
|
||||
}
|
||||
|
||||
// If we can't reach the remote Site URL, there's probably a configuration issue
|
||||
const remoteSiteURLInfo = await this.testRemoteServer(parsedSiteURL);
|
||||
if (!remoteSiteURLInfo) {
|
||||
return {status: URLValidationStatus.URLNotMatched, serverVersion: remoteInfo.serverVersion, serverName: remoteInfo.siteName, validatedURL: remoteURL.toString()};
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise fix it for them and return
|
||||
return {status: URLValidationStatus.URLUpdated, serverVersion: remoteInfo.serverVersion, serverName: remoteInfo.siteName, validatedURL: remoteInfo.siteURL};
|
||||
}
|
||||
|
||||
return {status: URLValidationStatus.OK, serverVersion: remoteInfo.serverVersion, serverName: remoteInfo.siteName, validatedURL: remoteInfo.siteURL};
|
||||
};
|
||||
|
||||
private handleCloseView = (event: IpcMainEvent, viewId: string) => {
|
||||
log.debug('handleCloseView', {viewId});
|
||||
|
||||
const view = ServerManager.getView(viewId);
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
ServerManager.setViewIsOpen(viewId, false);
|
||||
const nextView = ServerManager.getLastActiveTabForServer(view.server.id);
|
||||
ViewManager.showById(nextView.id);
|
||||
};
|
||||
|
||||
private handleOpenView = (event: IpcMainEvent, viewId: string) => {
|
||||
log.debug('handleOpenView', {viewId});
|
||||
|
||||
ServerManager.setViewIsOpen(viewId, true);
|
||||
ViewManager.showById(viewId);
|
||||
};
|
||||
|
||||
private handleGetOrderedViewsForServer = (event: IpcMainInvokeEvent, serverId: string) => {
|
||||
return ServerManager.getOrderedTabsForServer(serverId).map((view) => view.toUniqueView());
|
||||
};
|
||||
|
||||
private handleGetLastActive = () => {
|
||||
const server = this.getCurrentServer();
|
||||
const view = ServerManager.getLastActiveTabForServer(server.id);
|
||||
return {server: server.id, view: view.id};
|
||||
};
|
||||
|
||||
private updateServerOrder = (event: IpcMainEvent, serverOrder: string[]) => ServerManager.updateServerOrder(serverOrder);
|
||||
private updateTabOrder = (event: IpcMainEvent, serverId: string, viewOrder: string[]) => ServerManager.updateTabOrder(serverId, viewOrder);
|
||||
|
||||
private handleGetOrderedServers = () => ServerManager.getOrderedServers().map((srv) => srv.toUniqueServer());
|
||||
|
||||
/**
|
||||
* Helper functions
|
||||
*/
|
||||
|
||||
private testRemoteServer = async (parsedURL: URL) => {
|
||||
const server = new MattermostServer({name: 'temp', url: parsedURL.toString()}, false);
|
||||
const serverInfo = new ServerInfo(server);
|
||||
try {
|
||||
const remoteInfo = await serverInfo.fetchRemoteInfo();
|
||||
return remoteInfo;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
private selectView = (fn: (order: number, length: number) => number) => {
|
||||
const currentView = ViewManager.getCurrentView();
|
||||
if (!currentView) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentServerViews = ServerManager.getOrderedTabsForServer(currentView.view.server.id).map((view, index) => ({view, index}));
|
||||
const filteredViews = currentServerViews?.filter((view) => view.view.isOpen);
|
||||
const currentServerView = currentServerViews?.find((view) => view.view.type === currentView.view.type);
|
||||
if (!currentServerViews || !currentServerView || !filteredViews) {
|
||||
return;
|
||||
}
|
||||
|
||||
let currentOrder = currentServerView.index;
|
||||
let nextIndex = -1;
|
||||
while (nextIndex === -1) {
|
||||
const nextOrder = (fn(currentOrder, currentServerViews.length) % currentServerViews.length);
|
||||
nextIndex = filteredViews.findIndex((view) => view.index === nextOrder);
|
||||
currentOrder = nextOrder;
|
||||
}
|
||||
|
||||
const newView = filteredViews[nextIndex].view;
|
||||
ViewManager.showById(newView.id);
|
||||
};
|
||||
}
|
||||
|
||||
const serverViewState = new ServerViewState();
|
||||
export default serverViewState;
|
Reference in New Issue
Block a user