// dtp-socket.js // Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; const DTP_COMPONENT = { name: 'Socket', slug: 'socket' }; window.dtp = window.dtp || { }; import DtpWebLog from './dtp-log.js'; export default class DtpWebSocket { constructor ( ) { this.isConnected = false; this.isAuthenticated = false; this.joinedChannels = { }; this.log = new DtpWebLog(DTP_COMPONENT); } async connect (options) { options = Object.assign({ withRetry: true, withError: false, }, options); try { const response = await fetch('/auth/socket-token'); if (!response.ok) { this.retryConnect(); throw new Error('socket failed to connect'); } const json = await response.json(); if (!json.success) { this.retryConnect(); throw new Error(`failed to connect: ${json.message}`); } this.log.debug('connect', 'WebSocket connecting to Digital Telepresence Platform', json); this.socket = io('/', { transports: ['websocket'], reconnection: false, auth: { token: json.token, }, }); this.socket.on('connect', this.onSocketConnect.bind(this)); this.socket.on('authenticated', this.onSocketAuthenticated.bind(this)); this.socket.on('join-result', this.onJoinResult.bind(this)); this.socket.on('disconnect', this.onSocketDisconnect.bind(this)); } catch (error) { this.log.error('connect', 'failed to connect', { error }); if (options.withRetry) { this.retryConnect(); } if (options.withError) { throw error; } } } async onSocketConnect ( ) { this.log.info('onSocketConnect', 'WebSocket connected'); this.isConnected = true; if (this.disconnectDialog) { this.disconnectDialog.hide(); delete this.disconnectDialog; } } async onSocketDisconnect (reason) { this.isConnected = false; this.socket.close(); delete this.socket; if (!this.isAuthenticated) { UIkit.modal.alert(`Failed to authenticate WebSocket session. Please log out, log back in, and try again.`); return; } const REASONS = { 'io server disconnect': 'The server closed the connection', 'io client disconnect': 'The client closed the connection', 'ping timeout': 'The server is unreachable', 'transport close': 'Connection was lost or network changed', 'transport error': 'Server communication error, please try again.', }; this.log.warn('onSocketDisconnect', 'WebSocket disconnected', { reason }); const modal = UIkit.modal.alert(`Disconnected: ${REASONS[reason]}`); this.disconnectDialog = modal.dialog; UIkit.util.on(modal.dialog.$el, 'hidden', ( ) => { this.log.info('onSocketDisconnect', 'disconnect dialog closed'); delete this.disconnectDialog; }); this.retryConnect(); } retryConnect ( ) { if (this.retryTimeout) { return; // a retry is already in progress } this.retryTimeout = setTimeout(async ( ) => { delete this.retryTimeout; await this.connect(); }, 2000); } async onSocketAuthenticated (message) { this.log.info('onSocketAuthenticated', message.message, { user: message.user }); this.isAuthenticated = true; this.user = message.user; this.joinChannel(message.user._id, 'user'); document.dispatchEvent(new Event('socketConnected')); } async joinChannel (channelId, channelType) { this.log.info('joinChannel', 'joining channel', { channelId, channelType }); this.socket.emit('join', { channelId, channelType }); } async onJoinResult (message) { this.log.info('onJoinResult', 'channel joined', { message }); document.dispatchEvent(new Event('socketChannelJoined', { channelId: message.channelId })); } async leaveChannel (channelId) { this.log.info('leaveChannel', 'leaving channel', { channelId }); this.socket.emit('leave', { channelId }); } async emit (messageName, payload) { this.log.info('emit', 'sending message', { messageName, payload }); this.socket.emit(messageName, payload); } }