diff --git a/README.md b/README.md index cad2bdba..b5a06ed9 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,12 @@ To install the `jupyter-matlab-proxy` package, follow these steps in your Jupyte python -m pip install https://github.com/mathworks/jupyter-matlab-proxy/archive/0.1.0.tar.gz ``` -If you want to use this integration with JupyterLab®, you must also install `jupyterlab-server-proxy` JupyterLab extension. To install the extension, use the following command: +If you want to use this integration with JupyterLab®, ensure that you have JupyterLab installed on your machine by running the following command: +```bash +python -m pip install jupyterlab +``` + +You should then install `jupyterlab-server-proxy` JupyterLab extension. To install the extension, use the following command: ``` bash jupyter labextension install @jupyterlab/server-proxy diff --git a/gui/src/components/LicensingGatherer/MHLM.js b/gui/src/components/LicensingGatherer/MHLM.js index d4fbdcc8..8d2a0852 100644 --- a/gui/src/components/LicensingGatherer/MHLM.js +++ b/gui/src/components/LicensingGatherer/MHLM.js @@ -1,9 +1,10 @@ // Copyright 2020 The MathWorks, Inc. -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { - selectLicensingMhlmUsername + selectLicensingMhlmUsername, + selectWsEnv } from '../../selectors'; import { fetchSetLicensing @@ -62,15 +63,21 @@ function initLogin(clientNonce, serverNonce, sourceId) { loginFrame.postMessage(JSON.stringify(initPayload), "*"); } -// TODO Receive from serverside -// const mhlmLoginHostname = 'login-integ3'; -const mhlmLoginHostname = 'login'; -const getHostname = () => `https://${mhlmLoginHostname}.mathworks.com`; - function MHLM() { const dispatch = useDispatch(); const [iFrameLoaded, setIFrameLoaded] = useState(false); const username = useSelector(selectLicensingMhlmUsername); + const wsEnv = useSelector(selectWsEnv); + const mhlmLoginHostname = useMemo( + () => { + let subdomain = 'login'; + if (wsEnv.includes('integ')) { + subdomain = `${subdomain}-${wsEnv}`; + } + return `https://${subdomain}.mathworks.com`; + }, + [wsEnv] + ); // Create random sourceId string const sourceId = ( @@ -83,7 +90,7 @@ function MHLM() { const handler = event => { // Only process events that are related to the iframe setup - if (event.origin === getHostname()) { + if (event.origin === mhlmLoginHostname) { const data = JSON.parse(event.data); if (data.event === 'nonce') { @@ -111,7 +118,7 @@ function MHLM() { return () => { window.removeEventListener("message", handler); }; - }, [dispatch, sourceId]); + }, [dispatch, sourceId, mhlmLoginHostname]); useEffect(() => { if (iFrameLoaded === true) { @@ -121,7 +128,7 @@ function MHLM() { const handleIFrameLoaded = () => setIFrameLoaded(true); - const embeddedLoginUrl = `${getHostname()}/embedded-login/v2/login.html`; + const embeddedLoginUrl = `${mhlmLoginHostname}/embedded-login/v2/login.html`; return (
diff --git a/gui/src/reducers/index.js b/gui/src/reducers/index.js index ef5c218d..8e55ad97 100644 --- a/gui/src/reducers/index.js +++ b/gui/src/reducers/index.js @@ -94,6 +94,19 @@ export function matlabVersion(state=null, action) { } } +export function wsEnv(state=null, action) { + switch (action.type) { + case RECEIVE_SERVER_STATUS: + case RECEIVE_SET_LICENSING: + case RECEIVE_TERMINATE_INTEGRATION: + case RECEIVE_STOP_MATLAB: + case RECEIVE_START_MATLAB: + return action.status.wsEnv; + default: + return state; + } +} + export function isFetching(state = false, action) { switch (action.type) { case REQUEST_SERVER_STATUS: @@ -209,6 +222,7 @@ export const serverStatus = combineReducers({ licensingInfo, matlabStatus, matlabVersion, + wsEnv, isFetching, hasFetched, isSubmitting, diff --git a/gui/src/selectors/index.js b/gui/src/selectors/index.js index bc1876fa..14eac064 100644 --- a/gui/src/selectors/index.js +++ b/gui/src/selectors/index.js @@ -6,6 +6,7 @@ export const selectTutorialHidden = state => state.tutorialHidden; export const selectServerStatus = state => state.serverStatus; export const selectMatlabStatus = state => state.serverStatus.matlabStatus; export const selectMatlabVersion = state => state.serverStatus.matlabVersion; +export const selectWsEnv = state => state.serverStatus.wsEnv; export const selectSubmittingServerStatus = state => state.serverStatus.isSubmitting; export const selectHasFetchedServerStatus = state => state.serverStatus.hasFetched; export const selectLicensingInfo = state => state.serverStatus.licensingInfo; diff --git a/jupyter_matlab_proxy/__init__.py b/jupyter_matlab_proxy/__init__.py index c73aa2e3..aaac661c 100644 --- a/jupyter_matlab_proxy/__init__.py +++ b/jupyter_matlab_proxy/__init__.py @@ -8,6 +8,7 @@ def _get_env(port, base_url): "APP_PORT": str(port), "BASE_URL": f"{base_url}matlab", "APP_HOST": "127.0.0.1", + "MHLM_CONTEXT" : "MATLAB_JUPYTER" } diff --git a/jupyter_matlab_proxy/app.py b/jupyter_matlab_proxy/app.py index b9b6c98a..084a35f3 100644 --- a/jupyter_matlab_proxy/app.py +++ b/jupyter_matlab_proxy/app.py @@ -78,6 +78,7 @@ def create_status_response(app, loadUrl=None): "licensing": marshal_licensing_info(state.licensing), "loadUrl": loadUrl, "error": marshal_error(state.error), + "wsEnv": state.settings["ws_env"], } ) @@ -390,7 +391,9 @@ def create_app(): def main(): logger.info("Starting MATLAB proxy-app") + app = create_app() + loop = asyncio.get_event_loop() runner = web.AppRunner(app) loop.run_until_complete(runner.setup()) diff --git a/jupyter_matlab_proxy/app_state.py b/jupyter_matlab_proxy/app_state.py index bcdeaf5f..bcd7bf25 100644 --- a/jupyter_matlab_proxy/app_state.py +++ b/jupyter_matlab_proxy/app_state.py @@ -10,6 +10,7 @@ from pathlib import Path import tempfile import socket +import errno from collections import deque from .util import mw from .util.exceptions import ( @@ -290,7 +291,7 @@ def persist_licensing(self): f.write(json.dumps(config)) def reserve_matlab_port(self): - """ Reserve a free port for MATLAB Embedded Connector. """ + """ Reserve a free port for MATLAB Embedded Connector in the allowed range. """ # FIXME Because of https://github.com/http-party/node-http-proxy/issues/1342 the # node application in development mode always uses port 31515 to bypass the @@ -298,10 +299,23 @@ def reserve_matlab_port(self): if os.getenv("DEV") == "true": self.matlab_port = 31515 else: - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind(("", 0)) - self.matlab_port = s.getsockname()[1] - s.close() + + # TODO If MATLAB Connector is enhanced to allow any port, then the + # following can be used to get an unused port instead of the for loop and + # try-except. + # s.bind(("", 0)) + # self.matlab_port = s.getsockname()[1] + + for port in mw.range_matlab_connector_ports(): + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(("", port)) + self.matlab_port = port + s.close() + break + except socket.error as e: + if e.errno != errno.EADDRINUSE: + raise e async def start_matlab(self, restart=False): """ Start MATLAB. """ @@ -381,6 +395,8 @@ async def start_matlab(self, restart=False): matlab_env["MW_LOGIN_DISPLAY_NAME"] = self.licensing["display_name"] matlab_env["MW_LOGIN_USER_ID"] = self.licensing["user_id"] matlab_env["MW_LOGIN_PROFILE_ID"] = self.licensing["profile_id"] + if os.getenv("MHLM_CONTEXT") is None: + matlab_env["MHLM_CONTEXT"] = "MATLAB_JAVASCRIPT_DESKTOP" elif self.licensing["type"] == "nlm": matlab_env["MLM_LICENSE_FILE"] = self.licensing["conn_str"] diff --git a/jupyter_matlab_proxy/settings.py b/jupyter_matlab_proxy/settings.py index 0c4fbd8a..fab4f80e 100644 --- a/jupyter_matlab_proxy/settings.py +++ b/jupyter_matlab_proxy/settings.py @@ -64,6 +64,7 @@ def get(dev=False): "matlab_config_file": Path(tempfile.gettempdir()) / ".matlab" / "proxy_app_config.json", + "ws_env": ws_env, "mwa_api_endpoint": f"https://login{ws_env_suffix}.mathworks.com/authenticationws/service/v4", "mhlm_api_endpoint": f"https://licensing{ws_env_suffix}.mathworks.com/mls/service/v1/entitlement/list", "mwa_login": f"https://login{ws_env_suffix}.mathworks.com", @@ -104,6 +105,7 @@ def get(dev=False): "matlab_display": ":1", "nlm_conn_str": os.environ.get("MLM_LICENSE_FILE"), "matlab_config_file": Path.home() / ".matlab" / "proxy_app_config.json", + "ws_env": ws_env, "mwa_api_endpoint": f"https://login{ws_env_suffix}.mathworks.com/authenticationws/service/v4", "mhlm_api_endpoint": f"https://licensing{ws_env_suffix}.mathworks.com/mls/service/v1/entitlement/list", "mwa_login": f"https://login{ws_env_suffix}.mathworks.com", diff --git a/jupyter_matlab_proxy/util/mw.py b/jupyter_matlab_proxy/util/mw.py index 2783b058..3cebc763 100644 --- a/jupyter_matlab_proxy/util/mw.py +++ b/jupyter_matlab_proxy/util/mw.py @@ -100,6 +100,20 @@ async def fetch_access_token(mwa_api_endpoint, identity_token, source_id): } +def range_matlab_connector_ports(): + """ Generator of acceptable ports for MATLAB Connector. + + Allowed ports conform to the regex: [3,6]1[5-9][1-9][1-9] + """ + + for p1 in (3, 6): + p2 = 1 + for p3 in range(5, 10): + for p4 in range(1, 10): + for p5 in range(1, 10): + yield int(f"{p1}{p2}{p3}{p4}{p5}") + + def parse_nlm_error(logs, conn_str): nlm_logs = [] start = False