2021-01-05 07:30:18 +01:00
import { URL _WEBSOCKET } from './constants.js' ;
2020-08-24 04:06:58 +02:00
/ * *
* These are the types of messages that we can handle with the websocket .
* Mostly used by ` websocket.js ` but if other components need to handle
* different types then it can import this file .
* /
export const SOCKET _MESSAGE _TYPES = {
CHAT : 'CHAT' ,
PING : 'PING' ,
NAME _CHANGE : 'NAME_CHANGE' ,
2020-10-17 02:36:11 +02:00
PONG : 'PONG' ,
2021-02-19 08:05:52 +01:00
SYSTEM : 'SYSTEM' ,
USER _JOINED : 'USER_JOINED' ,
CHAT _ACTION : 'CHAT_ACTION'
2020-08-24 04:06:58 +02:00
} ;
2020-08-06 19:55:33 +02:00
2021-02-19 08:05:52 +01:00
const IGNORE _CLIENT _FLAG = 'IGNORE_CLIENT' ;
2020-08-13 18:28:47 +02:00
export const CALLBACKS = {
2020-08-06 19:55:33 +02:00
RAW _WEBSOCKET _MESSAGE _RECEIVED : 'rawWebsocketMessageReceived' ,
WEBSOCKET _CONNECTED : 'websocketConnected' ,
WEBSOCKET _DISCONNECTED : 'websocketDisconnected' ,
}
2020-08-24 04:06:58 +02:00
const TIMER _WEBSOCKET _RECONNECT = 5000 ; // ms
2020-08-06 19:55:33 +02:00
2020-08-24 04:06:58 +02:00
export default class Websocket {
2021-02-19 08:05:52 +01:00
constructor ( ignoreClient ) {
2020-08-06 19:55:33 +02:00
this . websocket = null ;
this . websocketReconnectTimer = null ;
this . websocketConnectedListeners = [ ] ;
this . websocketDisconnectListeners = [ ] ;
this . rawMessageListeners = [ ] ;
this . send = this . send . bind ( this ) ;
2020-09-06 23:28:21 +02:00
this . createAndConnect = this . createAndConnect . bind ( this ) ;
this . scheduleReconnect = this . scheduleReconnect . bind ( this ) ;
2020-08-06 19:55:33 +02:00
2021-02-19 08:05:52 +01:00
this . ignoreClient = ignoreClient ;
2020-09-06 23:28:21 +02:00
this . createAndConnect ( ) ;
}
createAndConnect ( ) {
2021-02-19 08:05:52 +01:00
const extraFlags = this . ignoreClient ? [ IGNORE _CLIENT _FLAG ] : [ ] ;
const ws = new WebSocket ( URL _WEBSOCKET , extraFlags ) ;
2020-08-06 19:55:33 +02:00
ws . onopen = this . onOpen . bind ( this ) ;
ws . onclose = this . onClose . bind ( this ) ;
ws . onerror = this . onError . bind ( this ) ;
ws . onmessage = this . onMessage . bind ( this ) ;
this . websocket = ws ;
}
// Other components should register for websocket callbacks.
addListener ( type , callback ) {
if ( type == CALLBACKS . WEBSOCKET _CONNECTED ) {
this . websocketConnectedListeners . push ( callback ) ;
} else if ( type == CALLBACKS . WEBSOCKET _DISCONNECTED ) {
this . websocketDisconnectListeners . push ( callback ) ;
} else if ( type == CALLBACKS . RAW _WEBSOCKET _MESSAGE _RECEIVED ) {
this . rawMessageListeners . push ( callback ) ;
}
}
// Interface with other components
// Outbound: Other components can pass an object to `send`.
send ( message ) {
// Sanity check that what we're sending is a valid type.
if ( ! message . type || ! SOCKET _MESSAGE _TYPES [ message . type ] ) {
2020-09-06 23:28:21 +02:00
console . warn (
` Outbound message: Unknown socket message type: " ${ message . type } " sent. `
) ;
2020-08-06 19:55:33 +02:00
}
2020-08-13 18:28:47 +02:00
2020-08-06 19:55:33 +02:00
const messageJSON = JSON . stringify ( message ) ;
this . websocket . send ( messageJSON ) ;
}
// Private methods
// Fire the callbacks of the listeners.
notifyWebsocketConnectedListeners ( message ) {
this . websocketConnectedListeners . forEach ( function ( callback ) {
callback ( message ) ;
} ) ;
}
notifyWebsocketDisconnectedListeners ( message ) {
this . websocketDisconnectListeners . forEach ( function ( callback ) {
callback ( message ) ;
} ) ;
}
notifyRawMessageListeners ( message ) {
this . rawMessageListeners . forEach ( function ( callback ) {
callback ( message ) ;
} ) ;
}
// Internal websocket callbacks
onOpen ( e ) {
if ( this . websocketReconnectTimer ) {
clearTimeout ( this . websocketReconnectTimer ) ;
}
this . notifyWebsocketConnectedListeners ( ) ;
}
onClose ( e ) {
// connection closed, discard old websocket and create a new one in 5s
this . websocket = null ;
this . notifyWebsocketDisconnectedListeners ( ) ;
this . handleNetworkingError ( 'Websocket closed.' ) ;
2020-09-06 23:28:21 +02:00
this . scheduleReconnect ( ) ;
2020-08-06 19:55:33 +02:00
}
// On ws error just close the socket and let it re-connect again for now.
onError ( e ) {
this . handleNetworkingError ( ` Socket error: ${ JSON . parse ( e ) } ` ) ;
this . websocket . close ( ) ;
2020-09-06 23:28:21 +02:00
this . scheduleReconnect ( ) ;
}
scheduleReconnect ( ) {
this . websocketReconnectTimer = setTimeout (
this . createAndConnect ,
TIMER _WEBSOCKET _RECONNECT
) ;
2020-08-06 19:55:33 +02:00
}
/ *
onMessage is fired when an inbound object comes across the websocket .
If the message is of type ` PING ` we send a ` PONG ` back and do not
pass it along to listeners .
* /
onMessage ( e ) {
try {
var model = JSON . parse ( e . data ) ;
} catch ( e ) {
2020-09-06 23:28:21 +02:00
console . log ( e ) ;
2020-08-06 19:55:33 +02:00
}
2020-08-13 18:28:47 +02:00
2020-08-06 19:55:33 +02:00
// Send PONGs
if ( model . type === SOCKET _MESSAGE _TYPES . PING ) {
this . sendPong ( ) ;
return ;
}
// Notify any of the listeners via the raw socket message callback.
this . notifyRawMessageListeners ( model ) ;
}
// Reply to a PING as a keep alive.
sendPong ( ) {
const pong = { type : SOCKET _MESSAGE _TYPES . PONG } ;
this . send ( pong ) ;
}
handleNetworkingError ( error ) {
2020-12-22 07:33:15 +01:00
console . error ( ` Websocket Error. Chat is likely not working. Visit troubleshooting steps to resolve. https://owncast.online/docs/troubleshooting/#chat-is-disabled: ${ error } ` ) ;
2020-08-24 04:06:58 +02:00
}
2020-08-06 19:55:33 +02:00
}