From 0e1fdbc0c540820f20be4c751b13a436e493a3fe Mon Sep 17 00:00:00 2001 From: Preston Date: Thu, 4 Jan 2024 09:36:29 -0800 Subject: [PATCH] Create eagswebrtc.js --- astronetclient/eagswebrtc.js | 798 +++++++++++++++++++++++++++++++++++ 1 file changed, 798 insertions(+) create mode 100644 astronetclient/eagswebrtc.js diff --git a/astronetclient/eagswebrtc.js b/astronetclient/eagswebrtc.js new file mode 100644 index 0000000..95f3a70 --- /dev/null +++ b/astronetclient/eagswebrtc.js @@ -0,0 +1,798 @@ +"use strict"; + +/* + +This is the backend for voice channels and LAN servers in eaglercraft + +it links with TeaVM EaglerAdapter at runtime + +Copyright 2022 ayunami2000 & lax1dude. All rights reserved. + +*/ + + +// %%%%%%%%%%%%%%%%%%%%%%%%%%%%% VOICE CODE %%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +window.initializeVoiceClient = (() => { + + const READYSTATE_NONE = 0; + const READYSTATE_ABORTED = -1; + const READYSTATE_DEVICE_INITIALIZED = 1; + + const PEERSTATE_FAILED = 0; + const PEERSTATE_SUCCESS = 1; + const PEERSTATE_LOADING = 2; + + class EaglercraftVoicePeer { + + constructor(client, peerId, peerConnection, offer) { + this.client = client; + this.peerId = peerId; + this.peerConnection = peerConnection; + this.stream = null; + + const self = this; + this.peerConnection.addEventListener("icecandidate", (evt) => { + if (evt.candidate) { + self.client.iceCandidateHandler(self.peerId, JSON.stringify({ + sdpMLineIndex: evt.candidate.sdpMLineIndex, + candidate: evt.candidate.candidate + })); + } + }); + + this.peerConnection.addEventListener("track", (evt) => { + self.rawStream = evt.streams[0]; + const aud = new Audio(); + aud.autoplay = true; + aud.muted = true; + aud.onended = function () { + aud.remove(); + }; + aud.srcObject = self.rawStream; + self.client.peerTrackHandler(self.peerId, self.rawStream); + }); + + this.peerConnection.addStream(this.client.localMediaStream.stream); + if (offer) { + this.peerConnection.createOffer((desc) => { + const selfDesc = desc; + self.peerConnection.setLocalDescription(selfDesc, () => { + self.client.descriptionHandler(self.peerId, JSON.stringify(selfDesc)); + if (self.client.peerStateInitial != PEERSTATE_SUCCESS) self.client.peerStateInitial = PEERSTATE_SUCCESS; + }, (err) => { + console.error("Failed to set local description for \"" + self.peerId + "\"! " + err); + if (self.client.peerStateInitial == PEERSTATE_LOADING) self.client.peerStateInitial = PEERSTATE_FAILED; + self.client.signalDisconnect(self.peerId); + }); + }, (err) => { + console.error("Failed to set create offer for \"" + self.peerId + "\"! " + err); + if (self.client.peerStateInitial == PEERSTATE_LOADING) self.client.peerStateInitial = PEERSTATE_FAILED; + self.client.signalDisconnect(self.peerId); + }); + } + + this.peerConnection.addEventListener("connectionstatechange", (evt) => { + if (self.peerConnection.connectionState === 'disconnected') { + self.client.signalDisconnect(self.peerId); + } else if (self.peerConnection.connectionState === 'connected') { + if (self.client.peerState != PEERSTATE_SUCCESS) self.client.peerState = PEERSTATE_SUCCESS; + } else if (self.peerConnection.connectionState === 'failed') { + if (self.client.peerState == PEERSTATE_LOADING) self.client.peerState = PEERSTATE_FAILED; + self.client.signalDisconnect(self.peerId); + } + }); + + } + + disconnect() { + this.peerConnection.close(); + } + + mute(muted) { + this.rawStream.getAudioTracks()[0].enabled = !muted; + } + + setRemoteDescription(descJSON) { + const self = this; + try { + const remoteDesc = JSON.parse(descJSON); + this.peerConnection.setRemoteDescription(remoteDesc, () => { + if (remoteDesc.type == 'offer') { + self.peerConnection.createAnswer((desc) => { + const selfDesc = desc; + self.peerConnection.setLocalDescription(selfDesc, () => { + self.client.descriptionHandler(self.peerId, JSON.stringify(selfDesc)); + if (self.client.peerStateDesc != PEERSTATE_SUCCESS) self.client.peerStateDesc = PEERSTATE_SUCCESS; + }, (err) => { + console.error("Failed to set local description for \"" + self.peerId + "\"! " + err); + if (self.client.peerStateDesc == PEERSTATE_LOADING) self.client.peerStateDesc = PEERSTATE_FAILED; + self.client.signalDisconnect(self.peerId); + }); + }, (err) => { + console.error("Failed to create answer for \"" + self.peerId + "\"! " + err); + if (self.client.peerStateDesc == PEERSTATE_LOADING) self.client.peerStateDesc = PEERSTATE_FAILED; + self.client.signalDisconnect(self.peerId); + }); + } + }, (err) => { + console.error("Failed to set remote description for \"" + self.peerId + "\"! " + err); + if (self.client.peerStateDesc == PEERSTATE_LOADING) self.client.peerStateDesc = PEERSTATE_FAILED; + self.client.signalDisconnect(self.peerId); + }); + } catch (err) { + console.error("Failed to parse remote description for \"" + self.peerId + "\"! " + err); + if (self.client.peerStateDesc == PEERSTATE_LOADING) self.client.peerStateDesc = PEERSTATE_FAILED; + self.client.signalDisconnect(self.peerId); + } + } + + addICECandidate(candidate) { + try { + this.peerConnection.addIceCandidate(new RTCIceCandidate(JSON.parse(candidate))); + if (this.client.peerStateIce != PEERSTATE_SUCCESS) this.client.peerStateIce = PEERSTATE_SUCCESS; + } catch (err) { + console.error("Failed to parse ice candidate for \"" + this.peerId + "\"! " + err); + if (this.client.peerStateIce == PEERSTATE_LOADING) this.client.peerStateIce = PEERSTATE_FAILED; + this.client.signalDisconnect(this.peerId); + } + } + + } + + class EaglercraftVoiceClient { + + constructor() { + this.ICEServers = []; + this.hasInit = false; + this.peerList = new Map(); + this.readyState = READYSTATE_NONE; + this.peerState = PEERSTATE_LOADING; + this.peerStateConnect = PEERSTATE_LOADING; + this.peerStateInitial = PEERSTATE_LOADING; + this.peerStateDesc = PEERSTATE_LOADING; + this.peerStateIce = PEERSTATE_LOADING; + this.iceCandidateHandler = null; + this.descriptionHandler = null; + this.peerTrackHandler = null; + this.peerDisconnectHandler = null; + this.microphoneVolumeAudioContext = null; + } + + voiceClientSupported() { + return typeof window.RTCPeerConnection !== "undefined" && typeof navigator.mediaDevices !== "undefined" && + typeof navigator.mediaDevices.getUserMedia !== "undefined"; + } + + setICEServers(urls) { + this.ICEServers.length = 0; + for (var i = 0; i < urls.length; ++i) { + var etr = urls[i].split(";"); + if (etr.length == 1) { + this.ICEServers.push({urls: etr[0]}); + } else if (etr.length == 3) { + this.ICEServers.push({urls: etr[0], username: etr[1], credential: etr[2]}); + } + } + } + + setICECandidateHandler(cb) { + this.iceCandidateHandler = cb; + } + + setDescriptionHandler(cb) { + this.descriptionHandler = cb; + } + + setPeerTrackHandler(cb) { + this.peerTrackHandler = cb; + } + + setPeerDisconnectHandler(cb) { + this.peerDisconnectHandler = cb; + } + + activateVoice(tk) { + if (this.hasInit) this.localRawMediaStream.getAudioTracks()[0].enabled = tk; + } + + initializeDevices() { + if (!this.hasInit) { + const self = this; + navigator.mediaDevices.getUserMedia({audio: true, video: false}).then((stream) => { + self.microphoneVolumeAudioContext = new AudioContext(); + self.localRawMediaStream = stream; + self.localRawMediaStream.getAudioTracks()[0].enabled = false; + self.localMediaStream = self.microphoneVolumeAudioContext.createMediaStreamDestination(); + self.localMediaStreamGain = self.microphoneVolumeAudioContext.createGain(); + var localStreamIn = self.microphoneVolumeAudioContext.createMediaStreamSource(stream); + localStreamIn.connect(self.localMediaStreamGain); + self.localMediaStreamGain.connect(self.localMediaStream); + self.localMediaStreamGain.gain.value = 1.0; + self.readyState = READYSTATE_DEVICE_INITIALIZED; + this.hasInit = true; + }).catch((err) => { + self.readyState = READYSTATE_ABORTED; + }); + } else { + this.readyState = READYSTATE_DEVICE_INITIALIZED; + } + } + + setMicVolume(val) { + if (this.hasInit) { + if (val > 0.5) val = 0.5 + (val - 0.5) * 2.0; + if (val > 1.5) val = 1.5; + if (val < 0.0) val = 0.0; + this.localMediaStreamGain.gain.value = val * 2.0; + } + } + + resetPeerStates() { + this.peerState = this.peerStateConnect = this.peerStateInitial = this.peerStateDesc = this.peerStateIce = PEERSTATE_LOADING; + } + + getPeerState() { + return this.peerState; + } + + getPeerStateConnect() { + return this.peerStateConnect; + } + + getPeerStateInitial() { + return this.peerStateInitial; + } + + getPeerStateDesc() { + return this.peerStateDesc; + } + + getPeerStateIce() { + return this.peerStateIce; + } + + getReadyState() { + return this.readyState; + } + + signalConnect(peerId, offer) { + if (!this.hasInit) this.initializeDevices(); + try { + const peerConnection = new RTCPeerConnection({ + iceServers: this.ICEServers, + optional: [{DtlsSrtpKeyAgreement: true}] + }); + const peerInstance = new EaglercraftVoicePeer(this, peerId, peerConnection, offer); + this.peerList.set(peerId, peerInstance); + if (this.peerStateConnect != PEERSTATE_SUCCESS) this.peerStateConnect = PEERSTATE_SUCCESS; + } catch (e) { + if (this.peerStateConnect == PEERSTATE_LOADING) this.peerStateConnect = PEERSTATE_FAILED; + } + } + + signalDescription(peerId, descJSON) { + var thePeer = this.peerList.get(peerId); + if ((typeof thePeer !== "undefined") && thePeer !== null) { + thePeer.setRemoteDescription(descJSON); + } + } + + signalDisconnect(peerId, quiet) { + var thePeer = this.peerList.get(peerId); + if ((typeof thePeer !== "undefined") && thePeer !== null) { + this.peerList.delete(thePeer); + try { + thePeer.disconnect(); + } catch (e) { + } + this.peerDisconnectHandler(peerId, quiet); + } + } + + mutePeer(peerId, muted) { + var thePeer = this.peerList.get(peerId); + if ((typeof thePeer !== "undefined") && thePeer !== null) { + thePeer.mute(muted); + } + } + + signalICECandidate(peerId, candidate) { + var thePeer = this.peerList.get(peerId); + if ((typeof thePeer !== "undefined") && thePeer !== null) { + thePeer.addICECandidate(candidate); + } + } + + } + + window.constructVoiceClient = () => new EaglercraftVoiceClient(); +}); + +window.startVoiceClient = () => { + if (typeof window.constructVoiceClient !== "function") { + window.initializeVoiceClient(); + } + return window.constructVoiceClient(); +}; + + +// %%%%%%%%%%%%%%%%%%%%%%%%%%%%% LAN CLIENT CODE %%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +window.initializeLANClient = (() => { + + const READYSTATE_INIT_FAILED = -2; + const READYSTATE_FAILED = -1; + const READYSTATE_DISCONNECTED = 0; + const READYSTATE_CONNECTING = 1; + const READYSTATE_CONNECTED = 2; + + class EaglercraftLANClient { + + constructor() { + this.ICEServers = []; + this.peerConnection = null; + this.dataChannel = null; + this.readyState = READYSTATE_CONNECTING; + this.iceCandidateHandler = null; + this.descriptionHandler = null; + this.remoteDataChannelHandler = null; + this.remoteDisconnectHandler = null; + this.remotePacketHandler = null; + } + + LANClientSupported() { + return typeof window.RTCPeerConnection !== "undefined"; + } + + initializeClient() { + try { + if (this.dataChannel != null) { + this.dataChannel.close(); + this.dataChannel = null; + } + if (this.peerConnection != null) { + this.peerConnection.close(); + } + this.peerConnection = new RTCPeerConnection({ + iceServers: this.ICEServers, + optional: [{DtlsSrtpKeyAgreement: true}] + }); + this.readyState = READYSTATE_CONNECTING; + } catch (e) { + this.readyState = READYSTATE_INIT_FAILED; + } + } + + setICEServers(urls) { + this.ICEServers.length = 0; + for (var i = 0; i < urls.length; ++i) { + var etr = urls[i].split(";"); + if (etr.length == 1) { + this.ICEServers.push({urls: etr[0]}); + } else if (etr.length == 3) { + this.ICEServers.push({urls: etr[0], username: etr[1], credential: etr[2]}); + } + } + } + + setICECandidateHandler(cb) { + this.iceCandidateHandler = cb; + } + + setDescriptionHandler(cb) { + this.descriptionHandler = cb; + } + + setRemoteDataChannelHandler(cb) { + this.remoteDataChannelHandler = cb; + } + + setRemoteDisconnectHandler(cb) { + this.remoteDisconnectHandler = cb; + } + + setRemotePacketHandler(cb) { + this.remotePacketHandler = cb; + } + + getReadyState() { + return this.readyState; + } + + sendPacketToServer(buffer) { + if (this.dataChannel != null && this.dataChannel.readyState == "open") { + this.dataChannel.send(buffer); + } else { + this.signalRemoteDisconnect(false); + } + } + + signalRemoteConnect() { + const self = this; + + const iceCandidates = []; + + this.peerConnection.addEventListener("icecandidate", (evt) => { + if (evt.candidate) { + if (iceCandidates.length == 0) setTimeout(() => { + if (self.peerConnection != null && self.peerConnection.connectionState != "disconnected") { + self.iceCandidateHandler(JSON.stringify(iceCandidates)); + iceCandidates.length = 0; + } + }, 3000); + iceCandidates.push({ + sdpMLineIndex: evt.candidate.sdpMLineIndex, + candidate: evt.candidate.candidate + }); + } + }); + + this.dataChannel = this.peerConnection.createDataChannel("lan"); + this.dataChannel.binaryType = "arraybuffer"; + + this.dataChannel.addEventListener("open", async (evt) => { + while (iceCandidates.length > 0) { + await new Promise(resolve => setTimeout(resolve, 0)); + } + self.remoteDataChannelHandler(self.dataChannel); + }); + + this.dataChannel.addEventListener("message", (evt) => { + self.remotePacketHandler(evt.data); + }, false); + + this.peerConnection.createOffer((desc) => { + const selfDesc = desc; + self.peerConnection.setLocalDescription(selfDesc, () => { + self.descriptionHandler(JSON.stringify(selfDesc)); + }, (err) => { + console.error("Failed to set local description! " + err); + self.readyState = READYSTATE_FAILED; + self.signalRemoteDisconnect(false); + }); + }, (err) => { + console.error("Failed to set create offer! " + err); + self.readyState = READYSTATE_FAILED; + self.signalRemoteDisconnect(false); + }); + + this.peerConnection.addEventListener("connectionstatechange", (evt) => { + if (self.peerConnection.connectionState === 'disconnected') { + self.signalRemoteDisconnect(false); + } else if (self.peerConnection.connectionState === 'connected') { + self.readyState = READYSTATE_CONNECTED; + } else if (self.peerConnection.connectionState === 'failed') { + self.readyState = READYSTATE_FAILED; + self.signalRemoteDisconnect(false); + } + }); + } + + signalRemoteDescription(descJSON) { + try { + this.peerConnection.setRemoteDescription(JSON.parse(descJSON)); + } catch (e) { + console.error(e); + this.readyState = READYSTATE_FAILED; + this.signalRemoteDisconnect(false); + } + } + + signalRemoteICECandidate(candidates) { + try { + const candidateList = JSON.parse(candidates); + for (let candidate of candidateList) { + this.peerConnection.addIceCandidate(candidate); + } + } catch (e) { + console.error(e); + this.readyState = READYSTATE_FAILED; + this.signalRemoteDisconnect(false); + } + } + + signalRemoteDisconnect(quiet) { + if (this.dataChannel != null) { + this.dataChannel.close(); + this.dataChannel = null; + } + if (this.peerConnection != null) { + this.peerConnection.close(); + } + if (!quiet) this.remoteDisconnectHandler(); + this.readyState = READYSTATE_DISCONNECTED; + } + + }; + + window.constructLANClient = () => new EaglercraftLANClient(); +}); + +window.startLANClient = () => { + if (typeof window.constructLANClient !== "function") { + window.initializeLANClient(); + } + return window.constructLANClient(); +}; + + +// %%%%%%%%%%%%%%%%%%%%%%%%%%%%% LAN SERVER CODE %%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +window.initializeLANServer = (() => { + + const PEERSTATE_FAILED = 0; + const PEERSTATE_SUCCESS = 1; + const PEERSTATE_LOADING = 2; + + class EaglercraftLANPeer { + + constructor(client, peerId, peerConnection) { + this.client = client; + this.peerId = peerId; + this.peerConnection = peerConnection; + this.dataChannel = null; + + const self = this; + + const iceCandidates = []; + + this.peerConnection.addEventListener("icecandidate", (evt) => { + if (evt.candidate) { + if (iceCandidates.length == 0) setTimeout(() => { + if (self.peerConnection != null && self.peerConnection.connectionState != "disconnected") { + self.client.iceCandidateHandler(self.peerId, JSON.stringify(iceCandidates)); + iceCandidates.length = 0; + } + }, 3000); + iceCandidates.push({ + sdpMLineIndex: evt.candidate.sdpMLineIndex, + candidate: evt.candidate.candidate + }); + } + }); + + this.peerConnection.addEventListener("datachannel", async (evt) => { + while (iceCandidates.length > 0) { + await new Promise(resolve => setTimeout(resolve, 0)); + } + self.dataChannel = evt.channel; + self.client.remoteClientDataChannelHandler(self.peerId, self.dataChannel); + self.dataChannel.addEventListener("message", (evt) => { + self.client.remoteClientPacketHandler(self.peerId, evt.data); + }, false); + }, false); + + this.peerConnection.addEventListener("connectionstatechange", (evt) => { + if (self.peerConnection.connectionState === 'disconnected') { + self.client.signalRemoteDisconnect(self.peerId); + } else if (self.peerConnection.connectionState === 'connected') { + if (self.client.peerState != PEERSTATE_SUCCESS) self.client.peerState = PEERSTATE_SUCCESS; + } else if (self.peerConnection.connectionState === 'failed') { + if (self.client.peerState == PEERSTATE_LOADING) self.client.peerState = PEERSTATE_FAILED; + self.client.signalRemoteDisconnect(self.peerId); + } + }); + + } + + disconnect() { + if (this.dataChannel != null) { + this.dataChannel.close(); + this.dataChannel = null; + } + this.peerConnection.close(); + } + + setRemoteDescription(descJSON) { + const self = this; + try { + const remoteDesc = JSON.parse(descJSON); + this.peerConnection.setRemoteDescription(remoteDesc, () => { + if (remoteDesc.type == 'offer') { + self.peerConnection.createAnswer((desc) => { + const selfDesc = desc; + self.peerConnection.setLocalDescription(selfDesc, () => { + self.client.descriptionHandler(self.peerId, JSON.stringify(selfDesc)); + if (self.client.peerStateDesc != PEERSTATE_SUCCESS) self.client.peerStateDesc = PEERSTATE_SUCCESS; + }, (err) => { + console.error("Failed to set local description for \"" + self.peerId + "\"! " + err); + if (self.client.peerStateDesc == PEERSTATE_LOADING) self.client.peerStateDesc = PEERSTATE_FAILED; + self.client.signalRemoteDisconnect(self.peerId); + }); + }, (err) => { + console.error("Failed to create answer for \"" + self.peerId + "\"! " + err); + if (self.client.peerStateDesc == PEERSTATE_LOADING) self.client.peerStateDesc = PEERSTATE_FAILED; + self.client.signalRemoteDisconnect(self.peerId); + }); + } + }, (err) => { + console.error("Failed to set remote description for \"" + self.peerId + "\"! " + err); + if (self.client.peerStateDesc == PEERSTATE_LOADING) self.client.peerStateDesc = PEERSTATE_FAILED; + self.client.signalRemoteDisconnect(self.peerId); + }); + } catch (err) { + console.error("Failed to parse remote description for \"" + self.peerId + "\"! " + err); + if (self.client.peerStateDesc == PEERSTATE_LOADING) self.client.peerStateDesc = PEERSTATE_FAILED; + self.client.signalRemoteDisconnect(self.peerId); + } + } + + addICECandidate(candidates) { + try { + const candidateList = JSON.parse(candidates); + for (let candidate of candidateList) { + this.peerConnection.addIceCandidate(new RTCIceCandidate(candidate)); + } + if (this.client.peerStateIce != PEERSTATE_SUCCESS) this.client.peerStateIce = PEERSTATE_SUCCESS; + } catch (err) { + console.error("Failed to parse ice candidate for \"" + this.peerId + "\"! " + err); + if (this.client.peerStateIce == PEERSTATE_LOADING) this.client.peerStateIce = PEERSTATE_FAILED; + this.client.signalRemoteDisconnect(this.peerId); + } + } + + } + + class EaglercraftLANServer { + + constructor() { + this.ICEServers = []; + this.hasInit = false; + this.peerList = new Map(); + this.peerState = PEERSTATE_LOADING; + this.peerStateConnect = PEERSTATE_LOADING; + this.peerStateInitial = PEERSTATE_LOADING; + this.peerStateDesc = PEERSTATE_LOADING; + this.peerStateIce = PEERSTATE_LOADING; + this.iceCandidateHandler = null; + this.descriptionHandler = null; + this.remoteClientDataChannelHandler = null; + this.remoteClientDisconnectHandler = null; + this.remoteClientPacketHandler = null; + } + + LANServerSupported() { + return typeof window.RTCPeerConnection !== "undefined"; + } + + initializeServer() { + // nothing to do! + } + + setICEServers(urls) { + this.ICEServers.length = 0; + for (var i = 0; i < urls.length; ++i) { + var etr = urls[i].split(";"); + if (etr.length == 1) { + this.ICEServers.push({urls: etr[0]}); + } else if (etr.length == 3) { + this.ICEServers.push({urls: etr[0], username: etr[1], credential: etr[2]}); + } + } + } + + setICECandidateHandler(cb) { + this.iceCandidateHandler = cb; + } + + setDescriptionHandler(cb) { + this.descriptionHandler = cb; + } + + setRemoteClientDataChannelHandler(cb) { + this.remoteClientDataChannelHandler = cb; + } + + setRemoteClientDisconnectHandler(cb) { + this.remoteClientDisconnectHandler = cb; + } + + setRemoteClientPacketHandler(cb) { + this.remoteClientPacketHandler = cb; + } + + sendPacketToRemoteClient(peerId, buffer) { + var thePeer = this.peerList.get(peerId); + if ((typeof thePeer !== "undefined") && thePeer !== null) { + if (thePeer.dataChannel != null && thePeer.dataChannel.readyState == "open") { + thePeer.dataChannel.send(buffer); + } else { + this.signalRemoteDisconnect(peerId); + } + } + } + + resetPeerStates() { + this.peerState = this.peerStateConnect = this.peerStateInitial = this.peerStateDesc = this.peerStateIce = PEERSTATE_LOADING; + } + + getPeerState() { + return this.peerState; + } + + getPeerStateConnect() { + return this.peerStateConnect; + } + + getPeerStateInitial() { + return this.peerStateInitial; + } + + getPeerStateDesc() { + return this.peerStateDesc; + } + + getPeerStateIce() { + return this.peerStateIce; + } + + signalRemoteConnect(peerId) { + try { + const peerConnection = new RTCPeerConnection({ + iceServers: this.ICEServers, + optional: [{DtlsSrtpKeyAgreement: true}] + }); + const peerInstance = new EaglercraftLANPeer(this, peerId, peerConnection); + this.peerList.set(peerId, peerInstance); + if (this.peerStateConnect != PEERSTATE_SUCCESS) this.peerStateConnect = PEERSTATE_SUCCESS; + } catch (e) { + if (this.peerStateConnect == PEERSTATE_LOADING) this.peerStateConnect = PEERSTATE_FAILED; + } + } + + signalRemoteDescription(peerId, descJSON) { + var thePeer = this.peerList.get(peerId); + if ((typeof thePeer !== "undefined") && thePeer !== null) { + thePeer.setRemoteDescription(descJSON); + } + } + + signalRemoteICECandidate(peerId, candidate) { + var thePeer = this.peerList.get(peerId); + if ((typeof thePeer !== "undefined") && thePeer !== null) { + thePeer.addICECandidate(candidate); + } + } + + signalRemoteDisconnect(peerId) { + if (peerId.length == 0) { + for (const thePeer of this.peerList.values()) { + if ((typeof thePeer !== "undefined") && thePeer !== null) { + this.peerList.delete(peerId); + try { + thePeer.disconnect(); + } catch (e) { + } + this.remoteClientDisconnectHandler(peerId); + } + } + this.peerList.clear(); + return; + } + var thePeer = this.peerList.get(peerId); + if ((typeof thePeer !== "undefined") && thePeer !== null) { + this.peerList.delete(peerId); + try { + thePeer.disconnect(); + } catch (e) { + } + this.remoteClientDisconnectHandler(peerId); + } + } + + countPeers() { + return this.peerList.size; + } + + }; + + window.constructLANServer = () => new EaglercraftLANServer(); +}); + +window.startLANServer = () => { + if (typeof window.constructLANServer !== "function") { + window.initializeLANServer(); + } + return window.constructLANServer(); +};