=== modified file 'src/com/goldencode/p2j/main/WebClientSpawner.java' --- src/com/goldencode/p2j/main/WebClientSpawner.java 2016-02-26 19:00:09 +0000 +++ src/com/goldencode/p2j/main/WebClientSpawner.java 2016-02-29 23:57:02 +0000 @@ -143,7 +143,7 @@ if (token != null) { - uri = uri + "?" + PARAM_TOKEN + "=" + token; + uri = uri + "index.html?" + PARAM_TOKEN + "=" + token; } } } === modified file 'src/com/goldencode/p2j/ui/client/chui/driver/web/ChuiWebDriver.java' --- src/com/goldencode/p2j/ui/client/chui/driver/web/ChuiWebDriver.java 2015-08-21 08:03:12 +0000 +++ src/com/goldencode/p2j/ui/client/chui/driver/web/ChuiWebDriver.java 2016-03-01 00:05:35 +0000 @@ -99,10 +99,9 @@ String welcomeUrl = getWelcomePageUrl(MAIN_TARGET, WELCOME_PAGE); // create our handlers - ChuiWebPageHandler[] hdlrs = new ChuiWebPageHandler[2]; + ChuiWebPageHandler[] hdlrs = new ChuiWebPageHandler[1]; - hdlrs[0] = new ChuiWebPageHandler(config, MAIN_TARGET, welcomeUrl); - hdlrs[1] = new ChuiWebPageHandler(config, WELCOME_PAGE, welcomeUrl); + hdlrs[0] = new ChuiWebPageHandler(config, WELCOME_PAGE, welcomeUrl); WebClientProtocol websock = simulator.getWebSocket(); === modified file 'src/com/goldencode/p2j/ui/client/driver/web/WebClientProtocol.java' --- src/com/goldencode/p2j/ui/client/driver/web/WebClientProtocol.java 2016-02-24 22:48:55 +0000 +++ src/com/goldencode/p2j/ui/client/driver/web/WebClientProtocol.java 2016-03-01 00:00:23 +0000 @@ -379,6 +379,11 @@ sendBinaryMessage(MSG_PING_PONG); handled = true; } + else if (message[offset] == MSG_QUIT && length == 1) + { + // exit the client session + System.exit(0); + } return handled; } === modified file 'src/com/goldencode/p2j/ui/client/driver/web/res/p2j.js' --- src/com/goldencode/p2j/ui/client/driver/web/res/p2j.js 2016-01-04 15:02:23 +0000 +++ src/com/goldencode/p2j/ui/client/driver/web/res/p2j.js 2016-02-29 22:32:55 +0000 @@ -221,6 +221,94 @@ return canvas; }; + /** + * Save the target JS object serialized to a string value with the given key. + * + * @param {String} key + * The assigned key. + * @param {Object} obj + * The target JS object. + * @param {Boolean} local + * True value indicates that the object is persisted between opened and new sessions, + * otherwise the object is stored during the current session. + */ + function saveObject(key, obj, local) + { + var sObj = JSON.stringify(obj); + var storage; + if (local) + { + storage = localStorage; + } + else + { + storage = sessionStorage; + } + storage.setItem(key, sObj); + return sObj; + } + + me.saveObject = saveObject; + + /** + * Try to restore a JS object from the retrieved string value for the given key. + * + * @param {String} key + * The provided key. + * @param {Boolean} local + * True value indicates that the local storage is used, otherwise the session + * storage is used. + * + * @return {Object} obj + * The target JS object or null. + */ + function restoreObject(key, local) + { + var storage; + if (local) + { + storage = localStorage; + } + else + { + storage = sessionStorage; + } + var sObj = storage.getItem(key); + if (sObj) + { + var obj = JSON.parse(sObj); + return obj; + } + return null; + } + + me.restoreObject = restoreObject; + + /** + * Delete a JS object by the given key. + * + * @param {String} key + * The provided key. + * @param {Boolean} local + * True value indicates that the local storage is used, otherwise the session + * storage is used. + */ + function deleteObject(key, local) + { + var storage; + if (local) + { + storage = localStorage; + } + else + { + storage = sessionStorage; + } + storage.removeItem(key); + } + + me.deleteObject = deleteObject; + /* exports */ return me; }()); === modified file 'src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js' --- src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js 2016-02-25 05:44:49 +0000 +++ src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js 2016-03-01 00:12:31 +0000 @@ -42,7 +42,9 @@ ** available for the browser. ** CA 20160629 Added a drawing cache (and cache management, when the hash was expired by the ** client-side). -** SBI 20160225 Implemented MSG_PARTIAL, added MSG_PING_PONG and idle/connectivity timers. +** SBI 20160225 Implemented MSG_PARTIAL, added MSG_PING_PONG and idle/connectivity timers, +** added the default "Stay on page or Leave page" dialog if a user tries to close +** or to refresh the web application page. */ "use strict"; @@ -92,6 +94,12 @@ var pingPongWatcher; /** + * True value indicates the web page can be closed or redirected to the login page without + * the appearance of the default "stay on page or leave page" warning dialog. + */ + var exitTheApplication = false; + + /** * The next unique message id. */ var nextMsgId = 0; @@ -666,7 +674,7 @@ break; case 0x85: // quit - window.location.replace(referrer); + doRedirectToLoginPage(); break; case 0x86: // switch mode p2j/vt100 @@ -1241,7 +1249,7 @@ { kill(); } - if (elapsed > period) + else if (elapsed > period) { callback(); } @@ -1296,6 +1304,16 @@ } this.kill = kill; + + /** + * Tests if the timer is running. + * + * @return True if the timer is running, otherwise false. + */ + this.isRunning = function() + { + return running; + } } /** @@ -1321,11 +1339,31 @@ connected = true; ws.binaryType = 'arraybuffer'; - connectivityTimer.kill(); - idleTimer.start(); - - // notify the web socket has opened and the page is loaded - me.sendNotification(0xff); + var exit = p2j.restoreObject("exitTheApplication", false); + var date = (new Date()).getTime(); + // reload use case + if (exit && (date < exit.date + 1000)) + { + // reload use case + doRedirectToLoginPage(); + me.sendNotification(0x85); + } + else + { + if (exit) + { + // reopen use case + p2j.deleteObject("exitTheApplication", false); + } + + exitTheApplication = false; + + connectivityTimer.kill(); + idleTimer.start(); + + // notify the web socket has opened and the page is loaded + me.sendNotification(0xff); + } }; // on web socket message @@ -1380,7 +1418,7 @@ // check the normal close reason if (evt.code === 1000) { - setTimeout(function() { window.location.replace(referrer); }, 1000); + doRedirectToLoginPage(); } else { @@ -1389,20 +1427,48 @@ }; /** - * Web socket errors events handler + * Web socket errors events handler. * * @param {Event} evt * The websocket error event */ ws.onerror = function(evt) { + p2j.screen.error("Web socket error " + evt.detail); connected = false; idleTimer.kill(); - connectivityTimer.start(); + + if (exitTheApplication) + { + if (!connectivityTimer.isRunning()) + { + // the reconnect tries are failed the websocket server is down due to the watchdog. + doRedirectToLoginPage(); + } + } + else + { + if (!connectivityTimer.isRunning()) + { + // try to reconnect within watchdog timeout if it is failed, then + // the websocket server is down + connectivityTimer.start(); + exitTheApplication = true; + } + } }; } /** + * Force the web page to be redirected to the login page given by the referrer url. + */ + function doRedirectToLoginPage() + { + exitTheApplication = true; + setTimeout(function() { window.location.replace(referrer); }, 1000); + } + + /** * Initialize module. * * @param {object} cfg configuration. @@ -1451,6 +1517,10 @@ */ function connect() { + if (pingPongWatcher) + { + clearTimeout(pingPongWatcher); + } // don't reconnect if the websocket state is CONNECTING(0) or OPEN(1) or CLOSING(2) if (ws && ws.readyState !== 3) { @@ -1463,21 +1533,53 @@ connectivityTimer = new ControlTimer(Math.max(watchdogTimeout, maxIdleTime), 5000, connect); - // TODO: offline mode - window.addEventListener( - "offline", - function(e) - { - console.debug("offline"); - connectivityTimer.start(); - }, false); + /** + * If the user is navigating away from the application webpage. + * + * @param {PageTransitionEvent} evt + * + */ + window.onpagehide = function(evt) + { + if (!exitTheApplication) + { + // close or reload cases + exitTheApplication = true; + p2j.saveObject( + "exitTheApplication", + { + exitTheApplication : exitTheApplication, + date: (new Date()).getTime() + }, + false); + } + else + { + // after reload + p2j.deleteObject("exitTheApplication", false); + } + }; - // TODO: refresh - window.onpagehide = function() + /** + * Occurs when the web application page are about to be unloaded. + * + * @param {Event} evt + * The beforeunload event. + * + * @return {String} + * The returned value will be used as a custom message for the default + * "Stay on Page or Leave Page" dialog. + */ + window.onbeforeunload = function(evt) { - if (connected) + if (!exitTheApplication) { - ws.close(); + var confirmationMessage = "Do you confirm to logout the P2J application?"; + + // Gecko + IE + (evt || window.event).returnValue = confirmationMessage; + + return confirmationMessage; } }; === modified file 'src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebDriver.java' --- src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebDriver.java 2016-02-05 08:56:45 +0000 +++ src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebDriver.java 2016-02-29 14:35:25 +0000 @@ -216,10 +216,9 @@ String welcomeUrl = getWelcomePageUrl(MAIN_TARGET, WELCOME_PAGE); // create our handlers - GuiWebPageHandler[] hdlrs = new GuiWebPageHandler[2]; + GuiWebPageHandler[] hdlrs = new GuiWebPageHandler[1]; - hdlrs[0] = new GuiWebPageHandler(config, MAIN_TARGET, welcomeUrl); - hdlrs[1] = new GuiWebPageHandler(config, WELCOME_PAGE, welcomeUrl); + hdlrs[0] = new GuiWebPageHandler(config, WELCOME_PAGE, welcomeUrl); // create our web socket websock = new GuiWebSocket(lock, this, timeout, wdtimeout); === modified file 'src/com/goldencode/p2j/ui/client/gui/driver/web/res/p2j.canvas_renderer.js' --- src/com/goldencode/p2j/ui/client/gui/driver/web/res/p2j.canvas_renderer.js 2016-02-19 18:04:59 +0000 +++ src/com/goldencode/p2j/ui/client/gui/driver/web/res/p2j.canvas_renderer.js 2016-02-28 13:01:11 +0000 @@ -326,7 +326,7 @@ if (cachedDraw == undefined) { - me.error("Trying to draw unknown cached image for md5 " + md5 + "!"); + p2j.screen.error("Trying to draw unknown cached image for md5 " + md5 + "!"); console.trace(); return; }