Skip to content

Commit 85616e1

Browse files
authored
Refactor websocket code to $oh namespace & Pass access token through header (#2907)
Depens on openhab/openhab-core#4515. This refactors the WebSocket connection code from #2884 to the `$oh` namespace, same as it is for the SSE logic. It also passes the access token as WebSocket subprotocol so it is sent with the `Sec-WebSocket-Protocol` header. --------- Signed-off-by: Florian Hotze <[email protected]>
1 parent f424e2b commit 85616e1

File tree

3 files changed

+122
-53
lines changed

3 files changed

+122
-53
lines changed

bundles/org.openhab.ui/web/src/js/openhab/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import api from './api'
22
import auth from './auth'
33
import sse from './sse'
4+
import ws from './ws'
45
import media from './media'
56
import speech from './speech'
67
import utils from './utils'
@@ -9,6 +10,7 @@ export default {
910
api,
1011
auth,
1112
sse,
13+
ws,
1214
media,
1315
speech,
1416
utils
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { getAccessToken } from './auth'
2+
3+
const HEARTBEAT_MESSAGE = `{
4+
"type": "WebSocketEvent",
5+
"topic": "openhab/websocket/heartbeat",
6+
"payload": "PING",
7+
"source": "WebSocketTestInstance"
8+
}`
9+
10+
const openWSConnections = []
11+
12+
function newWSConnection (path, messageCallback, readyCallback, errorCallback, heartbeatCallback, heartbeatInterval) {
13+
// Create a new WebSocket connection
14+
const socket = new WebSocket(path, [`org.openhab.ws.accessToken.base64.${btoa(getAccessToken())}`, 'org.openhab.ws.protocol.default'])
15+
16+
// Handle WebSocket connection opened
17+
socket.onopen = (event) => {
18+
socket.setKeepalive(heartbeatInterval)
19+
if (readyCallback) {
20+
readyCallback(event)
21+
}
22+
}
23+
24+
// Handle WebSocket message received
25+
socket.onmessage = (event) => {
26+
let evt = event.data
27+
try {
28+
evt = JSON.parse(event.data)
29+
} catch (e) {
30+
console.error('Error while parsing message', e)
31+
}
32+
messageCallback(evt)
33+
}
34+
35+
// Handle WebSocket error
36+
socket.onerror = (event) => {
37+
console.error('WebSocket error', event)
38+
if (errorCallback) {
39+
errorCallback(event)
40+
}
41+
}
42+
43+
// WebSocket keep alive
44+
socket.setKeepalive = (seconds = 5) => {
45+
console.debug('Setting keepalive interval seconds', seconds)
46+
socket.clearKeepalive()
47+
socket.keepaliveTimer = setInterval(() => {
48+
if (heartbeatCallback) {
49+
heartbeatCallback()
50+
} else {
51+
socket.send(HEARTBEAT_MESSAGE)
52+
}
53+
}, seconds * 1000)
54+
}
55+
56+
socket.clearKeepalive = () => {
57+
if (socket.keepaliveTimer) clearInterval(socket.keepaliveTimer)
58+
delete socket.keepaliveTimer
59+
}
60+
61+
// Add the new WebSocket connection to the list
62+
openWSConnections.push(socket)
63+
console.debug(`new WS connection: ${socket.url}, ${openWSConnections.length} open connections`)
64+
console.debug(openWSConnections)
65+
66+
return socket
67+
}
68+
69+
export default {
70+
/**
71+
* Connect to the websocket at the given path.
72+
*
73+
* @param {string} path path to connect to, e.g. `/ws`
74+
* @param {fn} messageCallback
75+
* @param {fn} [readyCallback=null]
76+
* @param {fn} [errorCallback=null]
77+
* @param {fn} [heartbeatCallback=null] heartbeat callback to use instead of the default PING/PONG
78+
* @param {number} [heartbeatInterval=5] heartbeat interval in seconds
79+
* @return {WebSocket}
80+
*/
81+
connect (path, messageCallback, readyCallback = null, errorCallback = null, heartbeatCallback = null, heartbeatInterval = 5) {
82+
return newWSConnection(path, messageCallback, readyCallback, errorCallback, heartbeatCallback, heartbeatInterval)
83+
},
84+
/**
85+
* Close the given websocket connection.
86+
*
87+
* @param {WebSocket} socket
88+
* @param {fn} [callback=null] callback to execute on connection close
89+
*/
90+
close (socket, callback = null) {
91+
if (!socket) return
92+
if (openWSConnections.indexOf(socket) >= 0) {
93+
openWSConnections.splice(openWSConnections.indexOf(socket), 1)
94+
}
95+
console.debug(`WS connection closed: ${socket.url}, ${openWSConnections.length} open connections`)
96+
console.debug(openWSConnections)
97+
socket.onclose = (event) => {
98+
if (callback) {
99+
callback(event)
100+
}
101+
}
102+
socket.close()
103+
socket.clearKeepalive()
104+
}
105+
}

bundles/org.openhab.ui/web/src/pages/developer/log-viewer.vue

+15-53
Original file line numberDiff line numberDiff line change
@@ -312,24 +312,14 @@
312312
</style>
313313

314314
<script lang="ts">
315-
import Vue from 'vue'
316-
317-
import auth from '@/components/auth-mixin.js'
318-
import { getAccessToken, getTokenInCustomHeader, getBasicCredentials } from '@/js/openhab/auth.js'
319-
320315
export default {
321-
mixins: [auth],
322-
components: {
323-
},
324316
data () {
325317
return {
326-
stateConnecting: false,
327318
stateConnected: false,
328319
stateProcessing: true,
329320
scrollTime: 0,
330321
autoScroll: true,
331-
socket: {},
332-
keepAliveTimer: null,
322+
socket: null,
333323
defaultLogLevel: 'WARN',
334324
logPackageInputText: '',
335325
highlightFilters: [],
@@ -425,44 +415,21 @@ export default {
425415
this.loggerPackages = this.loggerPackages.filter(loggerPackage => loggerPackage.loggerName !== logger.loggerName)
426416
},
427417
socketConnect () {
428-
this.stateConnecting = true
429-
430-
// Create a new WebSocket connection
431-
const wsUrl = '/ws/logs?accessToken=' + getAccessToken()
432-
this.socket = new WebSocket(wsUrl)
433-
434-
const me = this
435-
436-
// Event handler when the WebSocket connection is opened
437-
this.socket.onopen = function () {
438-
me.stateConnected = true
439-
me.stateConnecting = false
440-
me.stateProcessing = true
441-
me.$nextTick(() => me.scrollToBottom())
418+
const readyCallback = () => {
419+
this.stateConnected = true
420+
this.stateProcessing = true
421+
this.$nextTick(() => this.scrollToBottom())
442422
}
443423
444-
// Event handler when a message is received from OpenHAB
445-
this.socket.onmessage = function (event) {
446-
try {
447-
const data = JSON.parse(event.data)
448-
me.addLogEntry(data)
449-
} catch (e) {
450-
console.error('Error parsing event data:', e)
451-
}
424+
const messageCallback = (event) => {
425+
this.addLogEntry(event)
452426
}
453427
454-
// Event handler for WebSocket errors
455-
this.socket.onerror = function (error) {
456-
console.error('WebSocket error:', error)
457-
}
458-
459-
// Event handler when the WebSocket connection is closed
460-
this.socket.onclose = function () {
461-
me.stateConnected = false
462-
me.stateConnecting = false
428+
const keepaliveCallback = () => {
429+
this.socket.send('[]')
463430
}
464431
465-
this.keepAliveTimer = setTimeout(this.keepAlive, 9000)
432+
this.socket = this.$oh.ws.connect('/ws/logs', messageCallback, readyCallback, null, keepaliveCallback, 9)
466433
467434
// TEMP
468435
// for (let i = 0; i < 1980; i++) {
@@ -474,15 +441,10 @@ export default {
474441
// })
475442
// }
476443
},
477-
keepAlive () {
478-
if (this.socket && this.stateConnected) {
479-
this.socket.send('[]')
480-
this.keepAliveTimer = setTimeout(this.keepAlive, 9000)
481-
} else {
482-
if (this.keepAliveTimer) {
483-
clearTimeout(this.keepAliveTimer)
484-
}
485-
}
444+
socketClose () {
445+
this.$oh.ws.close(this.socket, () => {
446+
this.stateConnected = false
447+
})
486448
},
487449
renderEntry (entity) {
488450
let tr = document.createElement('tr')
@@ -574,7 +536,7 @@ export default {
574536
},
575537
loggingStop () {
576538
this.stateConnected = false
577-
this.socket.close()
539+
this.socketClose()
578540
},
579541
clearLog () {
580542
this.tableData.length = 0

0 commit comments

Comments
 (0)