import { createConsumer } from '@rails/actioncable'; import Cookies from 'js-cookie'; const PRESENCE_INTERVAL = 20000; const RECONNECT_INTERVAL = 1000; class BaseActionCableConnector { static isDisconnected = false; constructor( app, pubsubToken, websocketHost = '', presenceInterval = PRESENCE_INTERVAL ) { this.consumer = null; this.subscription = null; this.websocketHost = websocketHost; this.pubsubToken = pubsubToken; this.app = app; this.events = {}; this.reconnectTimer = null; this.isAValidEvent = () => true; this.connect(); this.triggerPresenceInterval = () => { setTimeout(() => { this.subscription?.updatePresence(); this.triggerPresenceInterval(); }, presenceInterval); }; this.triggerPresenceInterval(); } async connect() { const wsOrigin = this.websocketHost || window.location.origin; let websocketURL = `${wsOrigin}/cable`; if (Cookies.get('cw_d_session_state')) { try { const response = await window.axios.post('/api/v1/auth/ws_ticket'); const ticket = response.data?.data?.ticket; if (!ticket) throw new Error('missing websocket ticket'); websocketURL += `?ticket=${encodeURIComponent(ticket)}`; this.usesWSTicket = true; } catch (error) { this.initReconnectTimer(); return; } } else if (this.pubsubToken) { websocketURL += `?pubsub_token=${encodeURIComponent(this.pubsubToken)}`; this.usesWSTicket = false; } this.consumer = createConsumer(websocketURL); this.subscription = this.consumer.subscriptions.create( { channel: 'RoomChannel', pubsub_token: this.pubsubToken, account_id: this.app.$store.getters.getCurrentAccountId, user_id: this.app.$store.getters.getCurrentUserID, }, { updatePresence() { this.perform('update_presence'); }, received: this.onReceived, disconnected: () => { BaseActionCableConnector.isDisconnected = true; if (this.usesWSTicket) { this.consumer?.disconnect(); this.consumer = null; this.subscription = null; } this.onDisconnected(); this.initReconnectTimer(); }, } ); } checkConnection() { if (!this.consumer) { this.connect(); return; } const isConnectionActive = this.consumer.connection.isOpen(); const isReconnected = BaseActionCableConnector.isDisconnected && isConnectionActive; if (isReconnected) { this.clearReconnectTimer(); this.onReconnect(); BaseActionCableConnector.isDisconnected = false; } else { this.initReconnectTimer(); } } clearReconnectTimer = () => { if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; } }; initReconnectTimer = () => { this.clearReconnectTimer(); this.reconnectTimer = setTimeout(() => { this.checkConnection(); }, RECONNECT_INTERVAL); }; // eslint-disable-next-line class-methods-use-this onReconnect = () => {}; // eslint-disable-next-line class-methods-use-this onDisconnected = () => {}; disconnect() { this.consumer?.disconnect(); } onReceived = ({ event, data } = {}) => { if (this.isAValidEvent(data)) { if (this.events[event] && typeof this.events[event] === 'function') { this.events[event](data); } } }; } export default BaseActionCableConnector;