Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature to reconnect WebSocket from client to server #295

Merged
merged 8 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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