A "multiplayer" HTML5 <canvas> to which people can connect and make changes over time.
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.
 
 
 
 

134 lines
3.8 KiB

// canvas-socket.js
// Copyright (C) 2022 Rob Colbert @[email protected]
// License: Apache-2.0
'use strict';
window.dtp = window.dtp || { };
export default class CanvasSocket {
constructor ( ) {
this.isConnected = false;
this.isAuthenticated = false;
this.joinedChannels = { };
}
async connect (options) {
options = Object.assign({
withRetry: true,
withError: false,
}, options);
try {
console.log('connecting to Multiplayer Canvas server');
this.socket = io('/', {
transports: ['websocket'],
reconnection: false,
auth: {
token: options.connectToken,
},
});
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) {
console.log('connect', 'failed to connect', { error });
if (options.withRetry) {
this.retryConnect();
}
if (options.withError) {
throw error;
}
}
}
async onSocketConnect ( ) {
console.log('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.',
};
console.log('onSocketDisconnect', 'WebSocket disconnected', { reason });
const modal = UIkit.modal.alert(`Disconnected: ${REASONS[reason]}`);
this.disconnectDialog = modal.dialog;
UIkit.util.on(modal.dialog.$el, 'hidden', ( ) => {
console.log('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) {
console.log('onSocketAuthenticated', message.message, { user: message.user });
this.isAuthenticated = true;
this.user = message.user;
this.joinChannel(window.dtp.imageId, 'image');
document.dispatchEvent(new Event('mpcvs:socket-connected'));
}
async joinChannel (channelId, channelType) {
console.log('joinChannel', 'joining channel', { channelId, channelType });
this.socket.emit('join', { channelId, channelType });
}
async isChannelJoined (channelId) {
return !!this.joinedChannels[channelId];
}
async onJoinResult (message) {
console.log('onJoinResult', 'channel joined', { message });
this.joinedChannels[message.channelId] = message;
}
async leaveChannel (channelId) {
console.log('leaveChannel', 'leaving channel', { channelId });
this.socket.emit('leave', { channelId });
if (this.joinedChannels[channelId]) {
delete this.joinedChannels[channelId];
}
}
async sendUserChat (channelId, content) {
console.log('sendUserChat', 'sending message to channel', { channelId, content });
this.socket.emit('user-chat', { channelId, content });
}
}