123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- import { MessageType, SocketEvent } from '../interfaces/socket-events';
- export interface SocketMessage {
- type: MessageType;
- data: any;
- }
- export default class WebsocketService {
- websocket: WebSocket;
- accessToken: string;
- host: string;
- path: string;
- websocketReconnectTimer: ReturnType<typeof setTimeout>;
- isShutdown = false;
- backOff = 0;
- handleMessage?: (message: SocketEvent) => void;
- socketConnected: () => void;
- socketDisconnected: () => void;
- constructor(accessToken, path, host) {
- this.accessToken = accessToken;
- this.path = path;
- this.websocketReconnectTimer = null;
- this.isShutdown = false;
- this.host = host;
- this.createAndConnect = this.createAndConnect.bind(this);
- this.shutdown = this.shutdown.bind(this);
- this.createAndConnect();
- }
- createAndConnect() {
- if (!this.host) {
- return;
- }
- if (this.isShutdown) {
- return;
- }
- const url = new URL(this.host);
- url.protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
- url.pathname = '/ws';
- url.port = window.location.port === '3000' ? '8080' : window.location.port;
- url.searchParams.append('accessToken', this.accessToken);
- const ws = new WebSocket(url.toString());
- ws.onopen = this.onOpen.bind(this);
- ws.onerror = this.onError.bind(this);
- ws.onmessage = this.onMessage.bind(this);
- this.websocket = ws;
- }
- onOpen() {
- if (this.websocketReconnectTimer) {
- clearTimeout(this.websocketReconnectTimer);
- }
- this.socketConnected();
- this.backOff = 0;
- }
- // On ws error just close the socket and let it re-connect again for now.
- onError() {
- handleNetworkingError();
- this.socketDisconnected();
- this.websocket.close();
- if (!this.isShutdown) {
- this.scheduleReconnect();
- }
- }
- scheduleReconnect() {
- if (this.isShutdown) {
- return;
- }
- if (this.websocketReconnectTimer) {
- clearTimeout(this.websocketReconnectTimer);
- }
- this.websocketReconnectTimer = setTimeout(
- this.createAndConnect,
- Math.min(this.backOff, 10_000),
- );
- this.backOff += 1000;
- }
- shutdown() {
- this.isShutdown = true;
- this.websocket.close();
- }
- /*
- 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: SocketMessage) {
- // Optimization where multiple events can be sent within a
- // single websocket message. So split them if needed.
- const messages = e.data.split('\n');
- let socketEvent: SocketEvent;
- // eslint-disable-next-line no-plusplus
- for (let i = 0; i < messages.length; i++) {
- try {
- socketEvent = JSON.parse(messages[i]);
- if (this.handleMessage) {
- this.handleMessage(socketEvent);
- }
- } catch (err) {
- console.error(err, err.data);
- return;
- }
- if (!socketEvent.type) {
- console.error('No type provided', socketEvent);
- return;
- }
- // Send PONGs
- if (socketEvent.type === MessageType.PING) {
- this.sendPong();
- return;
- }
- }
- }
- isConnected(): boolean {
- return this.websocket?.readyState === this.websocket?.OPEN;
- }
- // Outbound: Other components can pass an object to `send`.
- send(socketEvent: any) {
- // Sanity check that what we're sending is a valid type.
- if (!socketEvent.type || !MessageType[socketEvent.type]) {
- console.warn(`Outbound message: Unknown socket message type: "${socketEvent.type}" sent.`);
- }
- const messageJSON = JSON.stringify(socketEvent);
- this.websocket.send(messageJSON);
- }
- // Reply to a PING as a keep alive.
- sendPong() {
- const pong = { type: MessageType.PONG };
- this.send(pong);
- }
- }
- function handleNetworkingError() {
- console.error(
- `Chat has been disconnected and is likely not working for you. It's possible you were removed from chat. If this is a server configuration issue, visit troubleshooting steps to resolve. https://owncast.online/docs/troubleshooting/#chat-is-disabled`,
- );
- }
|