Skip to content

Commit

Permalink
Add feature to reconnect WebSocket from client to server (#295)
Browse files Browse the repository at this point in the history
* Add materialize-css package

* Update WebSocket on client side

* Double reconnect attempt interval every time websocket reconnection attempt fails

* Update package-lock.json

* Move toast to be ~5 pixels from the right side

* Only show 'Successfully Connected' toast if websocket connection has previously failed

* Fix toast display time
  • Loading branch information
ac-61 authored Jan 11, 2023
1 parent 2eac369 commit ba84b7f
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 87 deletions.
31 changes: 31 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@
"jquery": "^3.5.1",
"lithosphere": "^1.5.4",
"mark.js": "^8.11.1",
"materialize-css": "^1.0.0",
"memorystore": "^1.6.2",
"mini-css-extract-plugin": "0.9.0",
"nipplejs": "^0.8.5",
"node-fetch": "^2.6.1",
Expand Down
30 changes: 30 additions & 0 deletions src/css/mmgisUI.css
Original file line number Diff line number Diff line change
Expand Up @@ -1548,3 +1548,33 @@ input::-webkit-inner-spin-button {
border-top-color: #fff;
animation: layerLoadingSpin 1s ease-in-out infinite;
}
#toast-container {
position: fixed !important;
bottom: 40px!important;
right: 5px !important;
}
.mmgisToast {
z-index: 1000;
border-radius: 2px;
top: 35px;
width: auto;
margin-top: 10px;
position: relative;
max-width:100%;
height: auto;
line-height: 1.5em;
background-color: var(--color-mmgis);
padding: 10px 25px;
/*
font-size: 1.1rem;
font-weight: 300;
*/
color: var(--color-f);
display: flex;
align-items: center;
justify-content: space-between;
cursor: default;
}
.mmgisToast.failure {
background-color: #a11717;
}
231 changes: 144 additions & 87 deletions src/essence/essence.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import $ from 'jquery'
import WebSocket from 'isomorphic-ws'
import M from 'materialize-css'
import F_ from './Basics/Formulae_/Formulae_'
import T_ from './Basics/Test_/Test_'
import L_ from './Basics/Layers_/Layers_'
Expand Down Expand Up @@ -125,6 +126,147 @@ $(document.body).keydown(function (e) {
var essence = {
configData: null,
hasSwapped: false,
ws: null,
initialWebSocketRetryInterval: 60000, // 1 minute
webSocketRetryInterval: 60000, // Start with this time and double if disconnected
webSocketPingInterval: null,
connectWebSocket: function(path, initial) {
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState
if (essence.ws === undefined || essence.ws === null
|| (essence.ws && essence.ws.readyState === 3)) {

essence.initWebSocket(path)

// If we're trying to start the WebSocket for the first time, we know we're not connected already
// so we don't need to retry to connect yet
if (!initial) {
clearInterval(essence.webSocketPingInterval)
essence.webSocketRetryInterval = essence.webSocketRetryInterval * 2
essence.webSocketPingInterval = setInterval(essence.connectWebSocket, essence.webSocketRetryInterval, path, false) // 10 seconds
}
}
},
initWebSocket: function(path) {
essence.ws = new WebSocket(path)

essence.ws.onerror = function(e) {
console.log(`Unable to connect to WebSocket at ${path}`)

M.Toast.dismissAll()

M.toast({
html: `Not connected to WebSocket. Will retry in ${(essence.webSocketRetryInterval / 60000).toFixed(2)} minutes...`,
displayLength: 10000,
classes: "mmgisToast failure",
});
}

essence.ws.onopen = function () {
console.log('Websocket connection opened...')

UserInterface_.removeLayerUpdateButton()

M.Toast.dismissAll()

if (essence.webSocketRetryInterval > essence.initialWebSocketRetryInterval) {
M.toast({
html: "Successfully connected to WebSocket",
displayLength: 1600,
classes: "mmgisToast",
});

essence.webSocketRetryInterval = essence.initialWebSocketRetryInterval
clearInterval(essence.webSocketPingInterval)
essence.webSocketPingInterval = setInterval(essence.connectWebSocket, essence.webSocketRetryInterval, path, false) // 1 minute
}
}

essence.ws.onmessage = function (data) {
if (data.data) {
try {
const parsed = JSON.parse(data.data)
const mission = essence.configData.msv.mission

if (
!parsed.body.mission ||
parsed.body.mission !== mission
) {
return
}

if ('info' in parsed) {
const { type, layerName } = parsed.info

if (
type === 'addLayer' ||
type === 'updateLayer' ||
type === 'removeLayer'
) {
calls.api(
'get',
{
mission,
},
async function (data) {
if (parsed.forceClientUpdate) {
// Force update the client side
await L_.autoUpdateLayer(
data,
layerName,
type
)
} else {
L_.addLayerQueue.push({
newLayerName: layerName,
data,
type,
})

UserInterface_.updateLayerUpdateButton(
'ADD_LAYER'
)
}
},
function (e) {
console.warn(
"Warning: Couldn't load: " +
mission +
' configuration.'
)
}
)
}
} else {
if (parsed.body && parsed.body.config) {
UserInterface_.updateLayerUpdateButton('RELOAD')
}
}

// Dispatch `websocketChange` event
let _event = new CustomEvent('websocketChange', {
detail: {
layer:
typeof layerName !== 'undefined'
? layerName
: null,
type: typeof type !== 'undefined' ? type : null,
data: parsed,
},
})
document.dispatchEvent(_event)
} catch (e) {
console.warn(
`Error parsing data from MMGIS websocket: ${e}`
)
}
}
}

essence.ws.onclose = function () {
console.log('Closed websocket connection...', new Date())
UserInterface_.updateLayerUpdateButton('DISCONNECTED')
}
},
init: function (config, missionsList, swapping) {
//Save the config data
this.configData = config
Expand Down Expand Up @@ -217,93 +359,8 @@ var essence = {
? `${protocol}://localhost:${port}/`
: `${protocol}://${window.location.host}/`

const ws = new WebSocket(path)

ws.onmessage = function (data) {
if (data.data) {
try {
const parsed = JSON.parse(data.data)
const mission = essence.configData.msv.mission

if (
!parsed.body.mission ||
parsed.body.mission !== mission
) {
return
}

if ('info' in parsed) {
const { type, layerName } = parsed.info

if (
type === 'addLayer' ||
type === 'updateLayer' ||
type === 'removeLayer'
) {
calls.api(
'get',
{
mission,
},
async function (data) {
if (parsed.forceClientUpdate) {
// Force update the client side
await L_.autoUpdateLayer(
data,
layerName,
type
)
} else {
L_.addLayerQueue.push({
newLayerName: layerName,
data,
type,
})

UserInterface_.updateLayerUpdateButton(
'ADD_LAYER'
)
}
},
function (e) {
console.warn(
"Warning: Couldn't load: " +
mission +
' configuration.'
)
}
)
}
} else {
if (parsed.body && parsed.body.config) {
UserInterface_.updateLayerUpdateButton('RELOAD')
}
}

// Dispatch `websocketChange` event
let _event = new CustomEvent('websocketChange', {
detail: {
layer:
typeof layerName !== 'undefined'
? layerName
: null,
type: typeof type !== 'undefined' ? type : null,
data: parsed,
},
})
document.dispatchEvent(_event)
} catch (e) {
console.warn(
`Error parsing data from MMGIS websocket: ${e}`
)
}
}
}

ws.onclose = function () {
console.log('Closed websocket connection...')
UserInterface_.updateLayerUpdateButton('DISCONNECTED')
}
essence.connectWebSocket(path, true)
essence.webSocketPingInterval = setInterval(essence.connectWebSocket, essence.webSocketRetryInterval, path, false) // 10 seconds
}
},
swapMission(to) {
Expand Down

0 comments on commit ba84b7f

Please sign in to comment.