You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
224 lines
6.7 KiB
224 lines
6.7 KiB
// dtpweb-socket.js
|
|
// Copyright (C) 2022,2023 DTP Technologies, LLC
|
|
// All Rights Reserved
|
|
|
|
'use strict';
|
|
|
|
window.dtp = window.dtp || { };
|
|
|
|
// import UIkit from 'uikit';
|
|
import DtpWebLog from './dtp-log.js';
|
|
|
|
export default class DtpWebSocket {
|
|
|
|
constructor ( ) {
|
|
this.isConnected = false;
|
|
this.isAuthenticated = false;
|
|
this.joinedChannels = { };
|
|
|
|
this.log = new DtpWebLog('DtpWebSocket');
|
|
}
|
|
|
|
async connect (options) {
|
|
this.options = Object.assign({
|
|
mode: 'User',
|
|
withRetry: true,
|
|
withError: false,
|
|
}, options);
|
|
try {
|
|
let buster = Math.random().toString().slice(2);
|
|
let response;
|
|
switch (this.options.mode) {
|
|
case 'User':
|
|
if (!window.dtp.user) {
|
|
return false; // if we don't have a user, just go away and stop trying.
|
|
}
|
|
response = await fetch(`/auth/socket-token?cb=${buster}`);
|
|
break;
|
|
case 'Channel':
|
|
response = await fetch(`/auth/socket-token/obs-widget?cb=${buster}`);
|
|
break;
|
|
}
|
|
|
|
if (!response.ok) {
|
|
if (options.withRetry) {
|
|
return this.retryConnect();
|
|
}
|
|
let json;
|
|
try {
|
|
json = await response.json();
|
|
} catch (jsonError) {
|
|
throw new Error('socket failed to connect');
|
|
}
|
|
throw new Error(`socket failed to connect: ${json.message}`);
|
|
}
|
|
|
|
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('disconnect', this.onSocketDisconnect.bind(this));
|
|
|
|
this.socket.on('user-authenticated', this.onSocketUserAuthenticated.bind(this));
|
|
this.socket.on('widget-authenticated', this.onSocketWidgetAuthenticated.bind(this));
|
|
|
|
this.socket.on('join-result', this.onJoinResult.bind(this));
|
|
|
|
return true;
|
|
} catch (error) {
|
|
this.log.error('connect', 'failed to connect', { error });
|
|
if (this.options.withRetry) {
|
|
this.retryConnect();
|
|
}
|
|
if (this.options.withError) {
|
|
throw error;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
disconnect ( ) {
|
|
// remove disconnect handler since we're being deliberate
|
|
this.socket.off('disconnect', this.onSocketDisconnect.bind(this));
|
|
this.socket.disconnect();
|
|
}
|
|
|
|
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();
|
|
if (this.options.onSocketDisconnect && (typeof this.options.onSocketDisconnect === 'function')) {
|
|
this.options.onSocketDisconnect(this.socket);
|
|
}
|
|
delete this.socket;
|
|
|
|
if (!this.isAuthenticated) {
|
|
UIkit.modal.alert(`Failed to authenticate WebSocket session. Please log out, log back in, and try again.`);
|
|
return;
|
|
}
|
|
|
|
this.log.warn('onSocketDisconnect', 'WebSocket disconnected', { reason });
|
|
this.showDisconnectWarning(reason);
|
|
this.retryConnect();
|
|
}
|
|
|
|
showDisconnectWarning (reason) {
|
|
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.',
|
|
};
|
|
|
|
const modal = UIkit.modal.dialog(`
|
|
<div class="uk-card uk-card-default uk-card-small uk-text-center">
|
|
<div class="uk-card-header">
|
|
<h1>Network Disconnected</h1>
|
|
</div>
|
|
<div class="uk-card-body">
|
|
<div class="uk-text-bold">${REASONS[reason]}.</div>
|
|
<div class="uk-text-small">Reconnecting...</div>
|
|
</div>
|
|
</div>`);
|
|
|
|
this.disconnectDialog = modal;
|
|
|
|
UIkit.util.on(modal.$el, 'hidden', ( ) => {
|
|
delete this.disconnectDialog;
|
|
});
|
|
}
|
|
|
|
retryConnect ( ) {
|
|
if (this.retryTimeout) {
|
|
return; // a retry is already in progress
|
|
}
|
|
this.retryTimeout = setTimeout(async ( ) => {
|
|
delete this.retryTimeout;
|
|
await this.connect(this.options);
|
|
}, 5000);
|
|
}
|
|
|
|
async onSocketUserAuthenticated (message) {
|
|
this.log.info('onSocketUserAuthenticated', message.message, { user: message.user });
|
|
|
|
this.isAuthenticated = true;
|
|
this.user = message.user;
|
|
|
|
if (this.options.onSocketConnect && (typeof this.options.onSocketConnect === 'function')) {
|
|
this.options.onSocketConnect(this.socket);
|
|
}
|
|
|
|
this.joinChannel(message.user._id, 'User');
|
|
this.log.info('onSocketUserAuthenticated', 'dispatching dtp-socket-connected');
|
|
document.dispatchEvent(new Event('dtp-socket-connected'));
|
|
}
|
|
|
|
async onSocketWidgetAuthenticated (message) {
|
|
this.log.info('onSocketWidgetAuthenticated', message.message, { channel: message.channel });
|
|
this.channel = message.channel;
|
|
|
|
if (this.options.onSocketConnect && (typeof this.options.onSocketConnect === 'function')) {
|
|
this.options.onSocketConnect(this.socket);
|
|
}
|
|
|
|
document.dispatchEvent(new Event('dtp-socket-connected'));
|
|
}
|
|
|
|
async joinChannel (channelId, channelType, passcode) {
|
|
this.log.info('joinChannel', 'joining channel', { channelId, channelType });
|
|
this.socket.emit('join', { channelId, channelType, passcode });
|
|
}
|
|
|
|
async isChannelJoined (channelId) {
|
|
return !!this.joinedChannels[channelId];
|
|
}
|
|
|
|
async onJoinResult (message) {
|
|
this.log.info('onJoinResult', 'channel join result received', { message });
|
|
if (message.channelId) {
|
|
this.joinedChannels[message.channelId] = message;
|
|
}
|
|
|
|
const event = new CustomEvent('dtp-channel-joined', { detail: message });
|
|
document.dispatchEvent(event);
|
|
}
|
|
|
|
async leaveChannel (channelId) {
|
|
this.log.info('leaveChannel', 'leaving channel', { channelId });
|
|
this.socket.emit('leave', { channelId });
|
|
if (this.joinedChannels[channelId]) {
|
|
delete this.joinedChannels[channelId];
|
|
}
|
|
}
|
|
|
|
async sendUserChat (channelId, content, action) {
|
|
this.log.info('sendUserChat', 'sending message to channel', { channelId, content, action });
|
|
this.socket.emit('user-chat', { channelId, content, action });
|
|
}
|
|
|
|
async sendUserReaction (channelId, timestamp, reaction) {
|
|
this.log.info('sendUserReaction', 'sending reaction to channel', { channelId, timestamp, reaction });
|
|
this.socket.emit('user-react', { channelId, timestamp, reaction });
|
|
}
|
|
}
|