From b6b7886e296d0c4c663ec15d403111ceeb4f3d44 Mon Sep 17 00:00:00 2001
From: Avaer Kazmer
Date: Mon, 28 Oct 2019 00:12:00 -0400
Subject: [PATCH 1/6] Break out presence code to presence.js
---
presence.js | 293 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 293 insertions(+)
create mode 100644 presence.js
diff --git a/presence.js b/presence.js
new file mode 100644
index 0000000..ac3bc96
--- /dev/null
+++ b/presence.js
@@ -0,0 +1,293 @@
+const defaultIceServers = [
+ {'urls': 'stun:stun.stunprotocol.org:3478'},
+ {'urls': 'stun:stun.l.google.com:19302'},
+];
+
+function _randomString() {
+ return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);
+}
+
+class PeerConnection extends EventTarget {
+ constructor(peerConnectionId) {
+ super();
+
+ this.connectionId = peerConnectionId;
+
+ this.peerConnection = new RTCPeerConnection({
+ iceServers: defaultIceServers,
+ });
+ this.open = false;
+
+ this.peerConnection.ontrack = e => {
+ console.log('got track', e);
+ };
+
+ const sendChannel = this.peerConnection.createDataChannel('sendChannel');
+ this.peerConnection.sendChannel = sendChannel;
+ let pingInterval = 0;
+ sendChannel.onopen = () => {
+ // console.log('data channel local open');
+
+ this.open = true;
+ this.dispatchEvent(new CustomEvent('open'));
+
+ pingInterval = setInterval(() => {
+ sendChannel.send(JSON.stringify({
+ method: 'ping',
+ }));
+ }, 1000);
+ };
+ sendChannel.onclose = () => {
+ // console.log('data channel local close');
+
+ _cleanup();
+ };
+ sendChannel.onerror = err => {
+ // console.log('data channel local error', err);
+ };
+ let watchdogTimeout = 0;
+ const _kick = () => {
+ if (watchdogTimeout) {
+ clearTimeout(watchdogTimeout);
+ watchdogTimeout = 0;
+ }
+ watchdogTimeout = setTimeout(() => {
+ this.peerConnection.close();
+ }, 5000);
+ };
+ _kick();
+ this.peerConnection.ondatachannel = e => {
+ const {channel} = e;
+ // console.log('data channel remote open', channel);
+ channel.onclose = () => {
+ // console.log('data channel remote close');
+ this.peerConnection.close();
+ };
+ channel.onerror = err => {
+ // console.log('data channel remote error', err);
+ };
+ channel.onmessage = e => {
+ // console.log('data channel message', e.data);
+
+ const data = JSON.parse(e.data);
+ const {method} = data;
+ if (method === 'pose') {
+ this.dispatchEvent(new CustomEvent('pose', {
+ detail: data,
+ }))
+ } else if (method === 'ping') {
+ // nothing
+ } else {
+ this.dispatchEvent(new MessageEvent('message', {
+ data,
+ }));
+ }
+
+ _kick();
+ };
+ this.peerConnection.recvChannel = channel;
+ };
+ this.peerConnection.close = (close => function() {
+ _cleanup();
+
+ return close.apply(this, arguments);
+ })(this.peerConnection.close);
+ const _cleanup = () => {
+ if (this.open) {
+ this.open = false;
+ this.dispatchEvent(new CustomEvent('close'));
+ }
+ if (pingInterval) {
+ clearInterval(pingInterval);
+ pingInterval = 0;
+ }
+ };
+ }
+
+ close() {
+ this.peerConnection.close();
+ }
+
+ send(s) {
+ this.peerConnection.sendChannel.send(s);
+ }
+
+ update(hmd, gamepads) {
+ this.send(JSON.stringify({
+ method: 'pose',
+ hmd,
+ gamepads,
+ }));
+ }
+}
+
+class PresenceConnection extends EventTarget {
+ constructor(url) {
+ super();
+
+ this.rtcWs = new WebSocket(url);
+ this.connectionId = _randomString();
+ this.peerConnections = [];
+
+ this.rtcWs.onopen = () => {
+ // console.log('presence socket open');
+
+ this.rtcWs.send(JSON.stringify({
+ method: 'init',
+ connectionId: this.connectionId,
+ }));
+ };
+ const _addPeerConnection = peerConnectionId => {
+ let peerConnection = this.peerConnections.find(peerConnection => peerConnection.connectionId === peerConnectionId);
+ if (peerConnection && !peerConnection.open) {
+ peerConnection.close();
+ peerConnection = null;
+ }
+ if (!peerConnection) {
+ peerConnection = new PeerConnection(peerConnectionId);
+ this.dispatchEvent(new CustomEvent('peerconnection', {
+ detail: peerConnection,
+ }));
+ peerConnection.addEventListener('close', () => {
+ const index = this.peerConnections.indexOf(peerConnection);
+ if (index !== -1) {
+ this.peerConnections.splice(index, 1);
+ }
+ });
+ peerConnection.peerConnection.onicecandidate = e => {
+ // console.log('ice candidate', e.candidate);
+
+ this.rtcWs.send(JSON.stringify({
+ dst: peerConnectionId,
+ src: this.connectionId,
+ method: 'iceCandidate',
+ candidate: e.candidate,
+ }));
+ };
+ this.peerConnections.push(peerConnection);
+
+ if (this.connectionId < peerConnectionId) {
+ peerConnection.peerConnection
+ .createOffer()
+ .then(offer => {
+ peerConnection.peerConnection.setLocalDescription(offer);
+
+ this.rtcWs.send(JSON.stringify({
+ dst: peerConnectionId,
+ src: this.connectionId,
+ method: 'offer',
+ offer,
+ }));
+ });
+ }
+ }
+ };
+ const _removePeerConnection = peerConnectionId => {
+ const index = this.peerConnections.findIndex(peerConnection => peerConnection.connectionId === peerConnectionId);
+ if (index !== -1) {
+ this.peerConnections.splice(index, 1)[0].close();
+ } else {
+ console.warn('no such peer connection', peerConnectionId, this.peerConnections.map(peerConnection => peerConnection.connectionId));
+ }
+ };
+ this.rtcWs.onmessage = e => {
+ // console.log('got message', e.data);
+
+ const data = JSON.parse(e.data);
+ const {method} = data;
+ if (method === 'join') {
+ const {connectionId: peerConnectionId} = data;
+ _addPeerConnection(peerConnectionId);
+ } else if (method === 'offer') {
+ const {src: peerConnectionId, offer} = data;
+
+ const peerConnection = this.peerConnections.find(peerConnection => peerConnection.connectionId === peerConnectionId);
+ if (peerConnection) {
+ peerConnection.peerConnection.setRemoteDescription(offer)
+ .then(() => peerConnection.peerConnection.createAnswer())
+ .then(answer => {
+ peerConnection.peerConnection.setLocalDescription(answer);
+
+ this.rtcWs.send(JSON.stringify({
+ dst: peerConnectionId,
+ src: this.connectionId,
+ method: 'answer',
+ answer,
+ }));
+ });
+ } else {
+ console.warn('no such peer connection', peerConnectionId, this.peerConnections.map(peerConnection => peerConnection.connectionId));
+ }
+ } else if (method === 'answer') {
+ const {src: peerConnectionId, answer} = data;
+
+ const peerConnection = this.peerConnections.find(peerConnection => peerConnection.connectionId === peerConnectionId);
+ if (peerConnection) {
+ peerConnection.peerConnection.setRemoteDescription(answer);
+ } else {
+ console.warn('no such peer connection', peerConnectionId, this.peerConnections.map(peerConnection => peerConnection.connectionId));
+ }
+ } else if (method === 'iceCandidate') {
+ const {src: peerConnectionId, candidate} = data;
+
+ const peerConnection = this.peerConnections.find(peerConnection => peerConnection.connectionId === peerConnectionId);
+ if (peerConnection) {
+ peerConnection.peerConnection.addIceCandidate(candidate)
+ .catch(err => {
+ // console.warn(err);
+ });
+ } else {
+ console.warn('no such peer connection', peerConnectionId, this.peerConnections.map(peerConnection => peerConnection.connectionId));
+ }
+ } else if (method === 'leave') {
+ const {connectionId: peerConnectionId} = data;
+ _removePeerConnection(peerConnectionId);
+ } else {
+ this.dispatchEvent(new MessageEvent('message', {
+ data: e.data,
+ }));
+ }
+ };
+ this.rtcWs.onclose = () => {
+ clearInterval(pingInterval);
+ console.log('rtc closed');
+ };
+ this.rtcWs.onerror = err => {
+ console.warn('rtc error', err);
+ clearInterval(pingInterval);
+ };
+ const pingInterval = setInterval(() => {
+ this.rtcWs.send(JSON.stringify({
+ method: 'ping',
+ }));
+ }, 30*1000);
+ }
+
+ disconect() {
+ this.rtcWs.close();
+ this.rtcWs = null;
+
+ for (let i = 0; i < this.peerConnections[i]; i++) {
+ this.peerConnections[i].close();
+ }
+ this.peerConnections.length = 0;
+ }
+
+ send(s) {
+ this.rtcWs.send(s);
+ }
+
+ update(hmd, gamepads) {
+ for (let i = 0; i < this.peerConnections.length; i++) {
+ const peerConnection = this.peerConnections[i];
+ if (peerConnection.open) {
+ peerConnection.update(hmd, gamepads);
+ }
+ }
+ }
+}
+
+export {
+ PeerConnection,
+ PresenceConnection,
+};
\ No newline at end of file
From da4544dc215fbbc628c8a554dc53ec6d41484294 Mon Sep 17 00:00:00 2001
From: Avaer Kazmer
Date: Mon, 28 Oct 2019 00:13:45 -0400
Subject: [PATCH 2/6] Import presence code from presence.js
---
app.html | 438 ++++++++++++++++---------------------------------------
1 file changed, 128 insertions(+), 310 deletions(-)
diff --git a/app.html b/app.html
index 4241cb1..74eaaf3 100644
--- a/app.html
+++ b/app.html
@@ -25,6 +25,7 @@
+
-
+
diff --git a/presence.js b/multiplayer.js
similarity index 100%
rename from presence.js
rename to multiplayer.js