Skip to content

Commit 634d4be

Browse files
Shushant SinghPrabhakar Kumar
authored andcommitted
Limits the use of MATLAB to one browser session at a time.
1 parent 0925888 commit 634d4be

File tree

13 files changed

+396
-60
lines changed

13 files changed

+396
-60
lines changed

gui/src/actionCreators/actionCreators.spec.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2020-2023 The MathWorks, Inc.
1+
// Copyright 2020-2024 The MathWorks, Inc.
22

33
import configureMockStore from 'redux-mock-store';
44
import thunk from 'redux-thunk';
@@ -94,7 +94,7 @@ describe('Test fetchWithTimeout method', () => {
9494
});
9595

9696
it('should fetch requested data without raising an exception or dispatching any action', async () => {
97-
fetchMock.getOnce('/get_status', {
97+
fetchMock.getOnce('/get_status?IS_DESKTOP=TRUE', {
9898
body: {
9999
matlab: {
100100
status: 'down',
@@ -104,7 +104,7 @@ describe('Test fetchWithTimeout method', () => {
104104
headers: { 'content-type': 'application/json' },
105105
});
106106

107-
const response = await actionCreators.fetchWithTimeout(store.dispatch, '/get_status', {}, 10000);
107+
const response = await actionCreators.fetchWithTimeout(store.dispatch, '/get_status?IS_DESKTOP=TRUE', {}, 10000);
108108
const body = await response.json()
109109

110110
expect(body).not.toBeNull();
@@ -116,7 +116,7 @@ describe('Test fetchWithTimeout method', () => {
116116
];
117117

118118
try {
119-
const response = await actionCreators.fetchWithTimeout(store.dispatch, '/get_status', {}, 100);
119+
const response = await actionCreators.fetchWithTimeout(store.dispatch, '/get_status?IS_DESKTOP=TRUE', {}, 100);
120120
} catch (error) {
121121
expect(error).toBeInstanceOf(TypeError)
122122
const received = store.getActions();
@@ -130,14 +130,14 @@ describe('Test fetchWithTimeout method', () => {
130130

131131
// Send a delayed response, well after the timeout for the request has expired.
132132
// This should trigger the abort() method of the AbortController()
133-
fetchMock.getOnce('/get_status', new Promise(resolve => setTimeout(() => resolve({ body: 'ok' }), 1000 + timeout)));
133+
fetchMock.getOnce('/get_status?IS_DESKTOP=TRUE', new Promise(resolve => setTimeout(() => resolve({ body: 'ok' }), 1000 + timeout)));
134134

135135
const abortSpy = jest.spyOn(global.AbortController.prototype, 'abort');
136136
const expectedActions = [
137137
actions.RECEIVE_ERROR,
138138
];
139139

140-
await actionCreators.fetchWithTimeout(store.dispatch, '/get_status', {}, timeout);
140+
await actionCreators.fetchWithTimeout(store.dispatch, '/get_status?IS_DESKTOP=TRUE', {}, timeout);
141141

142142
expect(abortSpy).toBeCalledTimes(1);
143143
const received = store.getActions();
@@ -194,7 +194,7 @@ describe('Test Async actionCreators', () => {
194194
});
195195

196196
it('dispatches REQUEST_SERVER_STATUS, RECEIVE_SERVER_STATUS when fetching status', () => {
197-
fetchMock.getOnce('/get_status', {
197+
fetchMock.getOnce('/get_status?IS_DESKTOP=TRUE', {
198198
body: {
199199
matlab: {
200200
status: 'down',

gui/src/actionCreators/index.js

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2020-2023 The MathWorks, Inc.
1+
// Copyright 2020-2024 The MathWorks, Inc.
22

33
import {
44
SET_TRIGGER_POSITION,
@@ -19,10 +19,19 @@ import {
1919
RECEIVE_START_MATLAB,
2020
RECEIVE_ERROR,
2121
RECEIVE_ENV_CONFIG,
22+
RECEIVE_CONCURRENCY_CHECK,
2223
SET_AUTH_STATUS,
23-
SET_AUTH_TOKEN
24+
SET_AUTH_TOKEN,
25+
RECEIVE_SESSION_STATUS,
26+
WAS_EVER_ACTIVE,
27+
REQUEST_SESSION_STATUS,
28+
SET_CLIENT_ID,
2429
} from '../actions';
25-
import { selectMatlabPending } from '../selectors';
30+
import {
31+
selectMatlabPending,
32+
selectIsConcurrencyEnabled,
33+
selectClientId,
34+
} from '../selectors';
2635
import sha256 from 'crypto-js/sha256';
2736

2837
export function setAuthStatus(authInfo) {
@@ -61,12 +70,25 @@ export function setOverlayVisibility(visibility) {
6170
};
6271
}
6372

73+
export function setClientId(client_id) {
74+
return {
75+
type: SET_CLIENT_ID,
76+
client_id
77+
};
78+
}
79+
6480
export function requestServerStatus() {
6581
return {
6682
type: REQUEST_SERVER_STATUS,
6783
};
6884
}
6985

86+
export function wasEverActive() {
87+
return {
88+
type: WAS_EVER_ACTIVE,
89+
}
90+
}
91+
7092
export function receiveServerStatus(status) {
7193
return function (dispatch, getState) {
7294
return dispatch({
@@ -76,6 +98,20 @@ export function receiveServerStatus(status) {
7698
});
7799
}
78100
}
101+
export function requestSessionStatus() {
102+
return {
103+
type: REQUEST_SESSION_STATUS,
104+
};
105+
}
106+
107+
export function receiveSessionStatus(status) {
108+
return function (dispatch, getState) {
109+
return dispatch({
110+
type: RECEIVE_SESSION_STATUS,
111+
status,
112+
})
113+
}
114+
}
79115

80116
export function requestEnvConfig() {
81117
return {
@@ -90,6 +126,13 @@ export function receiveEnvConfig(config) {
90126
};
91127
}
92128

129+
export function receiveConcurrencyCheck(config) {
130+
return {
131+
type: RECEIVE_CONCURRENCY_CHECK,
132+
config,
133+
}
134+
}
135+
93136
export function requestSetLicensing() {
94137
return {
95138
type: REQUEST_SET_LICENSING,
@@ -193,14 +236,43 @@ export async function fetchWithTimeout(dispatch, resource, options = {}, timeout
193236
}
194237
}
195238

196-
export function fetchServerStatus() {
239+
export function fetchServerStatus(requestTransferSession = false) {
197240
return async function (dispatch, getState) {
241+
const isConcurrencyEnabled = selectIsConcurrencyEnabled(getState());
242+
const clientIdInState = selectClientId(getState());
243+
const clientId = clientIdInState ? clientIdInState : sessionStorage.getItem("MWI_CLIENT_ID");
198244

199245
dispatch(requestServerStatus());
200-
const response = await fetchWithTimeout(dispatch, './get_status', {}, 10000);
201-
const data = await response.json();
246+
247+
let url = './get_status?IS_DESKTOP=TRUE'
248+
249+
if (isConcurrencyEnabled && clientId) {
250+
let params = new URLSearchParams();
251+
params.append("MWI_CLIENT_ID",encodeURIComponent(clientId))
252+
253+
if (requestTransferSession){
254+
params.append("TRANSFER_SESSION",encodeURIComponent(requestTransferSession))
255+
}
256+
257+
url = url + '&' + params.toString();
258+
259+
}
260+
261+
const response = await fetchWithTimeout(dispatch, url, {}, 10000);
262+
263+
const data = await response.json();
202264
dispatch(receiveServerStatus(data));
203265

266+
if (clientId == null && data["clientId"]) {
267+
sessionStorage.setItem("MWI_CLIENT_ID", data["clientId"]);
268+
dispatch(setClientId(data["clientId"]));
269+
}
270+
if ("isActiveClient" in data) {
271+
dispatch(receiveSessionStatus(data))
272+
if (data["isActiveClient"] === true) {
273+
dispatch(wasEverActive())
274+
}
275+
}
204276
}
205277
}
206278

gui/src/actions/index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
// Copyright (c) 2020-2023 The MathWorks, Inc.
1+
// Copyright 2020-2024 The MathWorks, Inc.
22

33
export const SET_TRIGGER_POSITION = 'SET_TRIGGER_POSITION';
44
export const SET_TUTORIAL_HIDDEN = 'SET_TUTORIAL_HIDDEN';
55
export const SET_OVERLAY_VISIBILITY = 'SET_OVERLAY_VISIBILITY';
6+
export const SET_CLIENT_ID = 'SET_CLIENT_ID';
67
export const REQUEST_SERVER_STATUS = 'REQUEST_SERVER_STATUS';
78
export const RECEIVE_SERVER_STATUS = 'RECEIVE_SERVER_STATUS';
89
export const REQUEST_SET_LICENSING = 'REQUEST_SET_LICENSING';
@@ -18,5 +19,10 @@ export const RECEIVE_START_MATLAB = 'RECEIVE_START_MATLAB';
1819
export const RECEIVE_ERROR = 'RECEIVE_ERROR';
1920
export const REQUEST_ENV_CONFIG = 'REQUEST_ENV_CONFIG';
2021
export const RECEIVE_ENV_CONFIG = 'RECEIVE_ENV_CONFIG';
22+
export const RECEIVE_CONCURRENCY_CHECK = 'RECEIVE_CONCURRENCY_CHECK';
2123
export const SET_AUTH_STATUS = 'SET_AUTH_STATUS';
2224
export const SET_AUTH_TOKEN = 'SET_AUTH_TOKEN';
25+
export const REQUEST_SERVER_INITIALIZATION = 'REQUEST_SERVER_INITIALIZATION';
26+
export const RECEIVE_SESSION_STATUS = 'RECEIVE_SESSION_STATUS';
27+
export const WAS_EVER_ACTIVE = 'WAS_EVER_ACTIVE';
28+
export const REQUEST_SESSION_STATUS = 'REQUEST_SESSION_STATUS';

gui/src/components/App/index.js

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2020-2023 The MathWorks, Inc.
1+
// Copyright 2020-2024 The MathWorks, Inc.
22

33
import React, { useState, useCallback, useEffect, useMemo } from 'react';
44
import { useSelector, useDispatch } from 'react-redux';
@@ -30,6 +30,9 @@ import {
3030
selectLicensingInfo,
3131
selectUseMOS,
3232
selectUseMRE,
33+
selectIsConcurrent,
34+
selectWasEverActive,
35+
selectIsConcurrencyEnabled,
3336
} from "../../selectors";
3437

3538
import {
@@ -55,11 +58,14 @@ function App() {
5558
const error = useSelector(selectError);
5659
const loadUrl = useSelector(selectLoadUrl);
5760
const isConnectionError = useSelector(selectIsConnectionError);
58-
const isAuthenticated = useSelector(selectIsAuthenticated)
61+
const isAuthenticated = useSelector(selectIsAuthenticated);
5962
const authEnabled = useSelector(selectAuthEnabled);
6063
const licensingInfo = useSelector(selectLicensingInfo);
6164
const useMOS = useSelector(selectUseMOS);
6265
const useMRE = useSelector(selectUseMRE);
66+
const isSessionConcurrent = useSelector(selectIsConcurrent);
67+
const isConcurrencyEnabled = useSelector(selectIsConcurrencyEnabled);
68+
const wasEverActive = useSelector(selectWasEverActive);
6369

6470
const baseUrl = useMemo(() => {
6571
const url = document.URL
@@ -92,7 +98,10 @@ function App() {
9298
);
9399

94100
const [dialogModel, setDialogModel] = useState(null);
101+
const [isTerminated, setIsTerminated] = useState(false);
95102

103+
// sessionDialog stores the state of concurrent session based on which either matlab gets rendered or the concurrent session dialog gets rendered
104+
let sessionDialog = null;
96105
let dialog;
97106
if (dialogModel) {
98107
const closeHandler = () => setDialogModel(null);
@@ -135,6 +144,35 @@ function App() {
135144
);
136145
} else if (error && error.type === "MatlabInstallError") {
137146
dialog = <Error message={error.message} />;
147+
}
148+
// check the user authentication before giving them the option to transfer the session.
149+
else if ((!authEnabled || isAuthenticated) && isSessionConcurrent && isConcurrencyEnabled) {
150+
// Transfer the session to this tab
151+
// setting the query parameter of requestTransferSession to true
152+
const transferSessionOnClick= () => {
153+
dispatch(fetchServerStatus(true));
154+
sessionDialog=null;
155+
}
156+
const endSession = () => {
157+
setIsTerminated(true);
158+
}
159+
if(isTerminated) {
160+
sessionDialog = <Error message="Your session has been terminated. Refresh the page to restart the session." />;
161+
}
162+
else {
163+
sessionDialog = (
164+
<Confirmation
165+
confirm={transferSessionOnClick}
166+
cancel={endSession}
167+
title='MATLAB is currently open in another window'
168+
cancelButton={wasEverActive?('Cancel'):('Continue in existing window')}
169+
confirmButton = {wasEverActive?('Confirm'):('Continue in this window')}>
170+
{wasEverActive
171+
? 'You have been disconnected because MATLAB is open in another window. Click on Confirm to continue using MATLAB here.'
172+
: <div>MATLAB is open in another window and cannot be opened in a second window or tab at the same time.<br></br>Would you like to continue in this window?</div> }
173+
</Confirmation>
174+
);
175+
}
138176
}
139177

140178
useEffect(() => {
@@ -147,11 +185,11 @@ function App() {
147185

148186
useEffect(() => {
149187
// Initial fetch server status
150-
if (!hasFetchedServerStatus) {
188+
if (hasFetchedEnvConfig && !hasFetchedServerStatus) {
151189
dispatch(fetchServerStatus());
152190
}
153191

154-
}, [dispatch, hasFetchedServerStatus]);
192+
}, [dispatch, hasFetchedServerStatus, hasFetchedEnvConfig]);
155193

156194
// Periodic fetch server status
157195
useInterval(() => {
@@ -233,11 +271,22 @@ function App() {
233271
const overlayTrigger = overlayVisible ? null : <OverlayTrigger />;
234272

235273
return (
236-
<div data-testid="app" className="main">
237-
{overlayTrigger}
238-
{matlabJsd}
239-
{overlay}
240-
</div>
274+
// If we use div instead of React.Fragment then the editor screen becomes white / doesn't fully render.
275+
// Have noticed this behavior both in windows and Linux.
276+
// If session dialog is not 'null' then render the transfer dialog or error dialog otherwise render the normal MATLAB.
277+
<React.Fragment>
278+
{sessionDialog?(
279+
<Overlay>
280+
{sessionDialog}
281+
</Overlay>
282+
):(
283+
<div data-testid="app" className="main">
284+
{overlayTrigger}
285+
{matlabJsd}
286+
{overlay}
287+
</div>
288+
)}
289+
</React.Fragment>
241290
);
242291
}
243292

gui/src/components/Confirmation/index.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
// Copyright (c) 2020-2022 The MathWorks, Inc.
1+
// Copyright 2020-2024 The MathWorks, Inc.
22

33
import React from 'react';
44
import PropTypes from 'prop-types';
55

6-
function Confirmation({ confirm, cancel, children }) {
6+
// Made the Confirmation component more scalable where one can customize all the messages which are to be displayed.
7+
function Confirmation({ confirm, cancel, title = 'Confirmation', cancelButton = 'Cancel', confirmButton = 'Confirm', children }) {
78
return (
89
<div className="modal show"
910
id="confirmation"
@@ -14,14 +15,14 @@ function Confirmation({ confirm, cancel, children }) {
1415
role="document">
1516
<div className="modal-content">
1617
<div className="modal-header">
17-
<h4 className="modal-title" id="confirmation-dialog-title">Confirmation</h4>
18+
<h4 className="modal-title" id="confirmation-dialog-title">{title}</h4>
1819
</div>
1920
<div className="modal-body">
2021
{children}
2122
</div>
2223
<div className="modal-footer">
23-
<button onClick={cancel} data-testid='cancelButton' className="btn companion_btn btn_color_blue">Cancel</button>
24-
<button onClick={confirm} data-testid='confirmButton' className="btn btn_color_blue">Confirm</button>
24+
<button onClick={cancel} data-testid='cancelButton' className="btn companion_btn btn_color_blue">{cancelButton}</button>
25+
<button onClick={confirm} data-testid='confirmButton' className="btn btn_color_blue">{confirmButton}</button>
2526
</div>
2627
</div>
2728
</div>

0 commit comments

Comments
 (0)