UNPKG

networked-aframe

Version:

A web framework for building multi-user virtual reality experiences.

230 lines (183 loc) 6.42 kB
/* global NAF */ var ReservedDataType = { Update: 'u', UpdateMulti: 'um', Remove: 'r' }; class NetworkConnection { constructor(networkEntities) { this.entities = networkEntities; this.setupDefaultDataSubscriptions(); this.connectedClients = {}; this.activeDataChannels = {}; } setNetworkAdapter(adapter) { this.adapter = adapter; } setupDefaultDataSubscriptions() { this.dataChannelSubs = {}; this.dataChannelSubs[ReservedDataType.Update] = this.entities.updateEntity.bind(this.entities); this.dataChannelSubs[ReservedDataType.UpdateMulti] = this.entities.updateEntityMulti.bind(this.entities); this.dataChannelSubs[ReservedDataType.Remove] = this.entities.removeRemoteEntity.bind(this.entities); } connect(serverUrl, appName, roomName, enableAudio = false, enableVideo = false) { NAF.app = appName; NAF.room = roomName; this.adapter.setServerUrl(serverUrl); this.adapter.setApp(appName); this.adapter.setRoom(roomName); var webrtcOptions = { audio: enableAudio, video: enableVideo, datachannel: true }; this.adapter.setWebRtcOptions(webrtcOptions); this.adapter.setServerConnectListeners( this.connectSuccess.bind(this), this.connectFailure.bind(this) ); this.adapter.setDataChannelListeners( this.dataChannelOpen.bind(this), this.dataChannelClosed.bind(this), this.receivedData.bind(this) ); this.adapter.setRoomOccupantListener(this.occupantsReceived.bind(this)); return this.adapter.connect(); } onConnect(callback) { this.onConnectCallback = callback; if (this.isConnected()) { callback(); } else { document.body.addEventListener('connected', callback, false); } } connectSuccess(clientId) { NAF.log.write('Networked-Aframe Client ID:', clientId); NAF.clientId = clientId; var evt = new CustomEvent('connected', {'detail': { clientId: clientId }}); document.body.dispatchEvent(evt); } connectFailure(errorCode, message) { NAF.log.error(errorCode, "failure to connect"); } occupantsReceived(occupantList) { var prevConnectedClients = Object.assign({}, this.connectedClients); this.connectedClients = occupantList; this.checkForDisconnectingClients(prevConnectedClients, occupantList); this.checkForConnectingClients(occupantList); } checkForDisconnectingClients(oldOccupantList, newOccupantList) { for (var id in oldOccupantList) { var clientFound = newOccupantList[id]; if (!clientFound) { NAF.log.write('Closing stream to', id); this.adapter.closeStreamConnection(id); } } } // Some adapters will handle this internally checkForConnectingClients(occupantList) { for (var id in occupantList) { var startConnection = this.isNewClient(id) && this.adapter.shouldStartConnectionTo(occupantList[id]); if (startConnection) { NAF.log.write('Opening datachannel to', id); this.adapter.startStreamConnection(id); } } } getConnectedClients() { return this.connectedClients; } isConnected() { return !!NAF.clientId; } isMineAndConnected(clientId) { return this.isConnected() && NAF.clientId === clientId; } isNewClient(clientId) { return !this.isConnectedTo(clientId); } isConnectedTo(clientId) { return this.adapter.getConnectStatus(clientId) === NAF.adapters.IS_CONNECTED; } dataChannelOpen(clientId) { NAF.log.write('Opened data channel from ' + clientId); this.activeDataChannels[clientId] = true; this.entities.completeSync(clientId, true); var evt = new CustomEvent('clientConnected', {detail: {clientId: clientId}}); document.body.dispatchEvent(evt); } dataChannelClosed(clientId) { NAF.log.write('Closed data channel from ' + clientId); this.activeDataChannels[clientId] = false; this.entities.removeEntitiesOfClient(clientId); var evt = new CustomEvent('clientDisconnected', {detail: {clientId: clientId}}); document.body.dispatchEvent(evt); } hasActiveDataChannel(clientId) { return !!this.activeDataChannels[clientId]; } broadcastData(dataType, data) { this.adapter.broadcastData(dataType, data); } broadcastDataGuaranteed(dataType, data) { this.adapter.broadcastDataGuaranteed(dataType, data); } sendData(toClientId, dataType, data, guaranteed) { if (this.hasActiveDataChannel(toClientId)) { if (guaranteed) { this.adapter.sendDataGuaranteed(toClientId, dataType, data); } else { this.adapter.sendData(toClientId, dataType, data); } } else { // console.error("NOT-CONNECTED", "not connected to " + toClient); } } sendDataGuaranteed(toClientId, dataType, data) { this.sendData(toClientId, dataType, data, true); } subscribeToDataChannel(dataType, callback) { if (this.isReservedDataType(dataType)) { NAF.log.error('NetworkConnection@subscribeToDataChannel: ' + dataType + ' is a reserved dataType. Choose another'); return; } this.dataChannelSubs[dataType] = callback; } unsubscribeToDataChannel(dataType) { if (this.isReservedDataType(dataType)) { NAF.log.error('NetworkConnection@unsubscribeToDataChannel: ' + dataType + ' is a reserved dataType. Choose another'); return; } delete this.dataChannelSubs[dataType]; } isReservedDataType(dataType) { return dataType == ReservedDataType.Update || dataType == ReservedDataType.Remove; } receivedData(fromClientId, dataType, data, source) { if (this.dataChannelSubs[dataType]) { this.dataChannelSubs[dataType](fromClientId, dataType, data, source); } else { NAF.log.write('NetworkConnection@receivedData: ' + dataType + ' has not been subscribed to yet. Call subscribeToDataChannel()'); } } getServerTime() { return this.adapter.getServerTime(); } disconnect() { this.entities.removeRemoteEntities(); if (this.adapter) { this.adapter.disconnect(); } NAF.app = ''; NAF.room = ''; NAF.clientId = ''; this.connectedClients = {}; this.activeDataChannels = {}; this.adapter = null; this.setupDefaultDataSubscriptions(); document.body.removeEventListener('connected', this.onConnectCallback); } } module.exports = NetworkConnection;