eaglercraft-1.8/sources/wasm-gc-teavm/js/platformWebRTC.js

652 lines
19 KiB
JavaScript

/*
* Copyright (c) 2024 lax1dude, ayunami2000. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
const READYSTATE_INIT_FAILED = -2;
const READYSTATE_FAILED = -1;
const READYSTATE_DISCONNECTED = 0;
const READYSTATE_CONNECTING = 1;
const READYSTATE_CONNECTED = 2;
const EVENT_WEBRTC_ICE = 0;
const EVENT_WEBRTC_DESC = 1;
const EVENT_WEBRTC_OPEN = 2;
const EVENT_WEBRTC_PACKET = 3;
const EVENT_WEBRTC_CLOSE = 4;
const platfWebRTCName = "platformWebRTC";
/**
* @typedef {{
* peerId:string,
* peerConnection:!RTCPeerConnection,
* dataChannel:RTCDataChannel,
* ipcChannel:(string|null),
* pushEvent:function(number,*),
* disconnect:function()
* }}
*/
let LANPeerInternal;
function initializePlatfWebRTC(webrtcImports) {
const clientLANPacketBuffer = new EaglerLinkedQueue();
let lanClient;
lanClient = {
iceServers: [],
/** @type {RTCPeerConnection|null} */
peerConnection: null,
/** @type {RTCDataChannel|null} */
dataChannel: null,
readyState: READYSTATE_CONNECTING,
/** @type {string|null} */
iceCandidate: null,
/** @type {string|null} */
description: null,
dataChannelOpen: false,
dataChannelClosed: true,
disconnect: function(quiet) {
if (lanClient.dataChannel) {
try {
lanClient.dataChannel.close();
} catch (t) {
}
lanClient.dataChannel = null;
}
if (lanClient.peerConnection) {
try {
lanClient.peerConnection.close();
} catch (t) {
}
lanClient.peerConnection = null;
}
if (!quiet) lanClient.dataChannelClosed = true;
lanClient.readyState = READYSTATE_DISCONNECTED;
}
};
/**
* @return {boolean}
*/
webrtcImports["supported"] = function() {
return typeof RTCPeerConnection !== "undefined";
};
/**
* @return {number}
*/
webrtcImports["clientLANReadyState"] = function() {
return lanClient.readyState;
};
webrtcImports["clientLANCloseConnection"] = function() {
lanClient.disconnect(false);
};
/**
* @param {!Uint8Array} pkt
*/
webrtcImports["clientLANSendPacket"] = function(pkt) {
if (lanClient.dataChannel !== null && "open" === lanClient.dataChannel.readyState) {
try {
lanClient.dataChannel.send(pkt);
} catch (e) {
lanClient.disconnect(false);
}
}else {
lanClient.disconnect(false);
}
};
/**
* @return {Uint8Array}
*/
webrtcImports["clientLANReadPacket"] = function() {
const ret = clientLANPacketBuffer.shift();
return ret ? new Uint8Array(ret["data"]) : null;
};
/**
* @return {number}
*/
webrtcImports["clientLANAvailable"] = function() {
return clientLANPacketBuffer.getLength();
};
/**
* @param {!Array<string>} servers
*/
webrtcImports["clientLANSetICEServersAndConnect"] = function(servers) {
lanClient.iceServers.length = 0;
for (let url of servers) {
let etr = url.split(";");
if(etr.length === 1) {
lanClient.iceServers.push({
urls: etr[0]
});
}else if(etr.length === 3) {
lanClient.iceServers.push({
urls: etr[0],
username: etr[1],
credential: etr[2]
});
}
}
if(lanClient.readyState === READYSTATE_CONNECTED || lanClient.readyState === READYSTATE_CONNECTING) {
lanClient.disconnect(true);
}
try {
if (lanClient.dataChannel) {
try {
lanClient.dataChannel.close();
} catch (t) {
}
lanClient.dataChannel = null;
}
if (lanClient.peerConnection) {
try {
lanClient.peerConnection.close();
} catch (t) {
}
}
lanClient.peerConnection = new RTCPeerConnection({
iceServers: lanClient.iceServers,
optional: [
{
DtlsSrtpKeyAgreement: true
}
]
});
lanClient.readyState = READYSTATE_CONNECTING;
} catch (/** Error */ t) {
eagStackTrace(ERROR, "Could not create LAN client RTCPeerConnection!", t);
lanClient.readyState = READYSTATE_INIT_FAILED;
return;
}
try {
const iceCandidates = [];
lanClient.peerConnection.addEventListener("icecandidate", /** @type {function(Event)} */ ((/** !RTCPeerConnectionIceEvent */ evt) => {
if(evt.candidate) {
if(iceCandidates.length === 0) {
const candidateState = [0, 0];
const runnable = () => {
if(lanClient.peerConnection !== null && lanClient.peerConnection.connectionState !== "disconnected") {
const trial = ++candidateState[1];
if(candidateState[0] !== iceCandidates.length && trial < 3) {
candidateState[0] = iceCandidates.length;
setTimeout(runnable, 2000);
return;
}
lanClient.iceCandidate = JSON.stringify(iceCandidates);
iceCandidates.length = 0;
}
};
setTimeout(runnable, 2000);
}
iceCandidates.push({
"sdpMLineIndex": evt.candidate.sdpMLineIndex,
"candidate": evt.candidate.candidate
});
}
}));
lanClient.dataChannel = lanClient.peerConnection.createDataChannel("lan");
lanClient.dataChannel.binaryType = "arraybuffer";
let evtHandler;
evtHandler = () => {
if (iceCandidates.length > 0) {
setTimeout(evtHandler, 10);
return;
}
lanClient.dataChannelClosed = false;
lanClient.dataChannelOpen = true;
};
lanClient.dataChannel.addEventListener("open", evtHandler);
lanClient.dataChannel.addEventListener("message", /** @type {function(Event)} */ ((/** MessageEvent */ evt) => {
clientLANPacketBuffer.push({ "data": evt.data, "_next": null });
}));
lanClient.peerConnection.createOffer().then((/** !RTCSessionDescription */ desc) => {
lanClient.peerConnection.setLocalDescription(desc).then(() => {
lanClient.description = JSON.stringify(desc);
}).catch((err) => {
eagError("Failed to set local description! {}", /** @type {string} */ (err.message));
lanClient.readyState = READYSTATE_FAILED;
lanClient.disconnect(false);
});
}).catch((err) => {
eagError("Failed to set create offer! {}", /** @type {string} */ (err.message));
lanClient.readyState = READYSTATE_FAILED;
lanClient.disconnect(false);
});
lanClient.peerConnection.addEventListener("connectionstatechange", /** @type {function(Event)} */ ((evt) => {
var connectionState = lanClient.peerConnection.connectionState;
if ("disconnected" === connectionState) {
lanClient.disconnect(false);
} else if ("connected" === connectionState) {
lanClient.readyState = READYSTATE_CONNECTED;
} else if ("failed" === connectionState) {
lanClient.readyState = READYSTATE_FAILED;
lanClient.disconnect(false);
}
}));
} catch (t) {
if (lanClient.dataChannel) {
try {
lanClient.dataChannel.close();
} catch (tt) {
}
lanClient.dataChannel = null;
}
if (lanClient.peerConnection) {
try {
lanClient.peerConnection.close();
} catch (tt) {
}
lanClient.peerConnection = null;
}
eagStackTrace(ERROR, "Could not create LAN client RTCDataChannel!", t);
lanClient.readyState = READYSTATE_INIT_FAILED;
}
};
webrtcImports["clearLANClientState"] = function() {
lanClient.iceCandidate = lanClient.description = null;
lanClient.dataChannelOpen = false;
lanClient.dataChannelClosed = true;
};
/**
* @return {string|null}
*/
webrtcImports["clientLANAwaitICECandidate"] = function() {
if (lanClient.iceCandidate === null) {
return null;
}
const ret = lanClient.iceCandidate;
lanClient.iceCandidate = null;
return ret;
};
/**
* @return {string|null}
*/
webrtcImports["clientLANAwaitDescription"] = function() {
if (lanClient.description === null) {
return null;
}
const ret = lanClient.description;
lanClient.description = null;
return ret;
};
/**
* @return {boolean}
*/
webrtcImports["clientLANAwaitChannel"] = function() {
if (lanClient.dataChannelOpen) {
lanClient.dataChannelOpen = false;
return true;
}
return false;
};
/**
* @return {boolean}
*/
webrtcImports["clientLANClosed"] = function() {
return lanClient.dataChannelClosed;
};
/**
* @param {string} candidate
*/
webrtcImports["clientLANSetICECandidate"] = function(candidate) {
try {
const lst = /** @type {Array<!Object>} */ (JSON.parse(candidate));
for (var i = 0; i < lst.length; ++i) {
lanClient.peerConnection.addIceCandidate(new RTCIceCandidate(lst[i]));
}
}catch(/** Error */ ex) {
eagStackTrace(ERROR, "Uncaught exception setting remote ICE candidates", ex);
lanClient.readyState = READYSTATE_FAILED;
lanClient.disconnect(false);
}
};
/**
* @param {string} description
*/
webrtcImports["clientLANSetDescription"] = function(description) {
try {
lanClient.peerConnection.setRemoteDescription(/** @type {!RTCSessionDescription} */ (JSON.parse(description)));
}catch(/** Error */ ex) {
eagStackTrace(ERROR, "Uncaught exception setting remote description", ex);
lanClient.readyState = READYSTATE_FAILED;
lanClient.disconnect(false);
}
};
let lanServer;
lanServer = {
/** @type {!Array<Object>} */
iceServers: [],
/** @type {!Map<string, !LANPeerInternal>} */
peerList: new Map(),
/** @type {!Map<string, !LANPeerInternal>} */
ipcMapList: new Map(),
disconnect: function(/** string */ peerId) {
const thePeer = lanServer.peerList.get(peerId);
if(thePeer) {
lanServer.peerList.delete(peerId);
if(thePeer.ipcChannel) {
lanServer.ipcMapList.delete(thePeer.ipcChannel);
}
try {
thePeer.disconnect();
} catch (ignored) {}
thePeer.pushEvent(EVENT_WEBRTC_CLOSE, null);
}
}
};
/**
* @param {!Array<string>} servers
*/
webrtcImports["serverLANInitializeServer"] = function(servers) {
lanServer.iceServers.length = 0;
for (let url of servers) {
let etr = url.split(";");
if(etr.length === 1) {
lanServer.iceServers.push({
"urls": etr[0]
});
}else if(etr.length === 3) {
lanServer.iceServers.push({
"urls": etr[0],
"username": etr[1],
"credential": etr[2]
});
}
}
};
webrtcImports["serverLANCloseServer"] = function() {
for (let thePeer of Object.values(lanServer.peerList)) {
if (thePeer) {
try {
thePeer.disconnect();
} catch (e) {}
thePeer.pushEvent(EVENT_WEBRTC_CLOSE, null);
}
}
lanServer.peerList.clear();
};
/**
* @param {string} peer
*/
webrtcImports["serverLANCreatePeer"] = function(peer) {
try {
const events = new EaglerLinkedQueue();
/** @type {!LANPeerInternal} */
let peerInstance;
peerInstance = {
peerId: peer,
peerConnection: new RTCPeerConnection(/** @type {RTCConfiguration} */ ({
"iceServers": lanServer.iceServers,
"optional": [
{
"DtlsSrtpKeyAgreement": true
}
]
})),
/** @type {RTCDataChannel} */
dataChannel: null,
/** @type {string|null} */
ipcChannel: null,
pushEvent: function(type, data) {
events.push({
"type": type,
"data": data,
"_next": null
});
},
disconnect: function() {
if (peerInstance.dataChannel) peerInstance.dataChannel.close();
peerInstance.peerConnection.close();
}
};
lanServer.peerList.set(peerInstance.peerId, peerInstance);
const iceCandidates = [];
peerInstance.peerConnection.addEventListener("icecandidate", /** @type {function(Event)} */ ((/** RTCPeerConnectionIceEvent */ evt) => {
if(evt.candidate) {
if(iceCandidates.length === 0) {
const candidateState = [0, 0];
const runnable = () => {
if(peerInstance.peerConnection !== null && peerInstance.peerConnection.connectionState !== "disconnected") {
const trial = ++candidateState[1];
if(candidateState[0] !== iceCandidates.length && trial < 3) {
candidateState[0] = iceCandidates.length;
setTimeout(runnable, 2000);
return;
}
peerInstance.pushEvent(EVENT_WEBRTC_ICE, JSON.stringify(iceCandidates));
iceCandidates.length = 0;
}
};
setTimeout(runnable, 2000);
}
iceCandidates.push({
"sdpMLineIndex": evt.candidate.sdpMLineIndex,
"candidate": evt.candidate.candidate
});
}
}));
let evtHandler;
evtHandler = (/** RTCDataChannelEvent */ evt) => {
if (iceCandidates.length > 0) {
setTimeout(evtHandler, 10, evt);
return;
}
if (!evt.channel) return;
const newDataChannel = evt.channel;
if(peerInstance.dataChannel !== null) {
newDataChannel.close();
return;
}
peerInstance.dataChannel = newDataChannel;
peerInstance.pushEvent(EVENT_WEBRTC_OPEN, null);
peerInstance.dataChannel.addEventListener("message", (evt2) => {
const data = evt2.data;
if(peerInstance.ipcChannel) {
sendIPCPacketFunc(peerInstance.ipcChannel, data);
}else {
peerInstance.pushEvent(EVENT_WEBRTC_PACKET, new Uint8Array(data));
}
});
};
peerInstance.peerConnection.addEventListener("datachannel", /** @type {function(Event)} */ (evtHandler));
peerInstance.peerConnection.addEventListener("connectionstatechange", (evt) => {
const connectionState = peerInstance.peerConnection.connectionState;
if ("disconnected" === connectionState || "failed" === connectionState) {
lanServer.disconnect(peerInstance.peerId);
}
});
return {
"peerId": peerInstance.peerId,
/**
* @return {number}
*/
"countAvailableEvents": function() {
return events.getLength();
},
/**
* @return {Object}
*/
"nextEvent": function() {
return events.shift();
},
/**
* @param {!Uint8Array} dat
*/
"writePacket": function(dat) {
let b = false;
if (peerInstance.dataChannel !== null && "open" === peerInstance.dataChannel.readyState) {
try {
peerInstance.dataChannel.send(dat);
} catch (e) {
b = true;
}
} else {
b = true;
}
if(b) {
lanServer.disconnect(peerInstance.peerId);
}
},
/**
* @param {string} iceCandidates
*/
"handleRemoteICECandidates": function(iceCandidates) {
try {
const candidateList = /** @type {!Array<!RTCIceCandidateInit>} */ (JSON.parse(iceCandidates));
for (let candidate of candidateList) {
peerInstance.peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
}
} catch (err) {
eagError("Failed to parse ice candidate for \"{}\"! {}", peerInstance.peerId, err.message);
lanServer.disconnect(peerInstance.peerId);
}
},
/**
* @param {string} desc
*/
"handleRemoteDescription": function(desc) {
try {
const remoteDesc = /** @type {!RTCSessionDescription} */ (JSON.parse(desc));
peerInstance.peerConnection.setRemoteDescription(remoteDesc).then(() => {
if (remoteDesc.hasOwnProperty("type") && "offer" === remoteDesc["type"]) {
peerInstance.peerConnection.createAnswer().then((desc) => {
peerInstance.peerConnection.setLocalDescription(desc).then(() => {
peerInstance.pushEvent(EVENT_WEBRTC_DESC, JSON.stringify(desc));
}).catch((err) => {
eagError("Failed to set local description for \"{}\"! {}", peerInstance.peerId, err.message);
lanServer.disconnect(peerInstance.peerId);
});
}).catch((err) => {
eagError("Failed to create answer for \"{}\"! {}", peerInstance.peerId, err.message);
lanServer.disconnect(peerInstance.peerId);
});
}
}).catch((err) => {
eagError("Failed to set remote description for \"{}\"! {}", peerInstance.peerId, err.message);
lanServer.disconnect(peerInstance.peerId);
});
} catch (err) {
eagError("Failed to parse remote description for \"{}\"! {}", peerInstance.peerId, err.message);
lanServer.disconnect(peerInstance.peerId);
}
},
/**
* @param {string|null} ipcChannel
*/
"mapIPC": function(ipcChannel) {
if(!peerInstance.ipcChannel) {
if(ipcChannel) {
peerInstance.ipcChannel = ipcChannel;
lanServer.ipcMapList.set(ipcChannel, peerInstance);
}
}else {
if(!ipcChannel) {
lanServer.ipcMapList.delete(peerInstance.ipcChannel);
peerInstance.ipcChannel = null;
}
}
},
"disconnect": function() {
lanServer.disconnect(peerInstance.peerId);
}
};
}catch(/** Error */ tt) {
eagStackTrace(ERROR, "Failed to create WebRTC LAN peer!", tt);
return null;
}
};
/**
* @param {string} channel
* @param {!ArrayBuffer} arr
*/
serverLANPeerPassIPCFunc = function(channel, arr) {
const peer = lanServer.ipcMapList.get(channel);
if(peer) {
let b = false;
if (peer.dataChannel && "open" === peer.dataChannel.readyState) {
try {
peer.dataChannel.send(arr);
} catch (e) {
b = true;
}
} else {
b = true;
}
if(b) {
lanServer.disconnect(peer.peerId);
}
return true;
}else {
return false;
}
};
}
function initializeNoPlatfWebRTC(webrtcImports) {
setUnsupportedFunc(webrtcImports, platfWebRTCName, "supported");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANReadyState");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANCloseConnection");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANSendPacket");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANReadPacket");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANAvailable");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANSetICEServersAndConnect");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clearLANClientState");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANAwaitICECandidate");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANAwaitDescription");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANAwaitChannel");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANClosed");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANSetICECandidate");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANSetDescription");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANClosed");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "serverLANInitializeServer");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "serverLANCloseServer");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "serverLANCreatePeer");
}