1+ const defaultIceServers = [
2+ { 'urls' : 'stun:stun.stunprotocol.org:3478' } ,
3+ { 'urls' : 'stun:stun.l.google.com:19302' } ,
4+ ] ;
5+
6+ function _randomString ( ) {
7+ return Math . random ( ) . toString ( 36 ) . replace ( / [ ^ a - z ] + / g, '' ) . substr ( 0 , 5 ) ;
8+ }
9+
10+ class XRChannelConnection extends EventTarget {
11+ constructor ( url ) {
12+ super ( ) ;
13+
14+ this . rtcWs = new WebSocket ( url ) ;
15+ this . connectionId = _randomString ( ) ;
16+ this . peerConnections = [ ] ;
17+
18+ this . rtcWs . onopen = ( ) => {
19+ // console.log('presence socket open');
20+
21+ this . rtcWs . send ( JSON . stringify ( {
22+ method : 'init' ,
23+ connectionId : this . connectionId ,
24+ } ) ) ;
25+ } ;
26+ const _addPeerConnection = peerConnectionId => {
27+ let peerConnection = this . peerConnections . find ( peerConnection => peerConnection . connectionId === peerConnectionId ) ;
28+ if ( peerConnection && ! peerConnection . open ) {
29+ peerConnection . close ( ) ;
30+ peerConnection = null ;
31+ }
32+ if ( ! peerConnection ) {
33+ peerConnection = new XRPeerConnection ( peerConnectionId ) ;
34+ this . dispatchEvent ( new CustomEvent ( 'peerconnection' , {
35+ detail : peerConnection ,
36+ } ) ) ;
37+ peerConnection . addEventListener ( 'close' , ( ) => {
38+ const index = this . peerConnections . indexOf ( peerConnection ) ;
39+ if ( index !== - 1 ) {
40+ this . peerConnections . splice ( index , 1 ) ;
41+ }
42+ } ) ;
43+ peerConnection . peerConnection . onicecandidate = e => {
44+ // console.log('ice candidate', e.candidate);
45+
46+ this . rtcWs . send ( JSON . stringify ( {
47+ dst : peerConnectionId ,
48+ src : this . connectionId ,
49+ method : 'iceCandidate' ,
50+ candidate : e . candidate ,
51+ } ) ) ;
52+ } ;
53+ this . peerConnections . push ( peerConnection ) ;
54+
55+ if ( this . connectionId < peerConnectionId ) {
56+ peerConnection . peerConnection
57+ . createOffer ( )
58+ . then ( offer => {
59+ peerConnection . peerConnection . setLocalDescription ( offer ) ;
60+
61+ this . rtcWs . send ( JSON . stringify ( {
62+ dst : peerConnectionId ,
63+ src : this . connectionId ,
64+ method : 'offer' ,
65+ offer,
66+ } ) ) ;
67+ } ) ;
68+ }
69+ }
70+ } ;
71+ const _removePeerConnection = peerConnectionId => {
72+ const index = this . peerConnections . findIndex ( peerConnection => peerConnection . connectionId === peerConnectionId ) ;
73+ if ( index !== - 1 ) {
74+ this . peerConnections . splice ( index , 1 ) [ 0 ] . close ( ) ;
75+ } else {
76+ console . warn ( 'no such peer connection' , peerConnectionId , this . peerConnections . map ( peerConnection => peerConnection . connectionId ) ) ;
77+ }
78+ } ;
79+ this . rtcWs . onmessage = e => {
80+ // console.log('got message', e.data);
81+
82+ const data = JSON . parse ( e . data ) ;
83+ const { method} = data ;
84+ if ( method === 'join' ) {
85+ const { connectionId : peerConnectionId } = data ;
86+ _addPeerConnection ( peerConnectionId ) ;
87+ } else if ( method === 'offer' ) {
88+ const { src : peerConnectionId , offer} = data ;
89+
90+ const peerConnection = this . peerConnections . find ( peerConnection => peerConnection . connectionId === peerConnectionId ) ;
91+ if ( peerConnection ) {
92+ peerConnection . peerConnection . setRemoteDescription ( offer )
93+ . then ( ( ) => peerConnection . peerConnection . createAnswer ( ) )
94+ . then ( answer => {
95+ peerConnection . peerConnection . setLocalDescription ( answer ) ;
96+
97+ this . rtcWs . send ( JSON . stringify ( {
98+ dst : peerConnectionId ,
99+ src : this . connectionId ,
100+ method : 'answer' ,
101+ answer,
102+ } ) ) ;
103+ } ) ;
104+ } else {
105+ console . warn ( 'no such peer connection' , peerConnectionId , this . peerConnections . map ( peerConnection => peerConnection . connectionId ) ) ;
106+ }
107+ } else if ( method === 'answer' ) {
108+ const { src : peerConnectionId , answer} = data ;
109+
110+ const peerConnection = this . peerConnections . find ( peerConnection => peerConnection . connectionId === peerConnectionId ) ;
111+ if ( peerConnection ) {
112+ peerConnection . peerConnection . setRemoteDescription ( answer ) ;
113+ } else {
114+ console . warn ( 'no such peer connection' , peerConnectionId , this . peerConnections . map ( peerConnection => peerConnection . connectionId ) ) ;
115+ }
116+ } else if ( method === 'iceCandidate' ) {
117+ const { src : peerConnectionId , candidate} = data ;
118+
119+ const peerConnection = this . peerConnections . find ( peerConnection => peerConnection . connectionId === peerConnectionId ) ;
120+ if ( peerConnection ) {
121+ peerConnection . peerConnection . addIceCandidate ( candidate )
122+ . catch ( err => {
123+ // console.warn(err);
124+ } ) ;
125+ } else {
126+ console . warn ( 'no such peer connection' , peerConnectionId , this . peerConnections . map ( peerConnection => peerConnection . connectionId ) ) ;
127+ }
128+ } else if ( method === 'leave' ) {
129+ const { connectionId : peerConnectionId } = data ;
130+ _removePeerConnection ( peerConnectionId ) ;
131+ } else {
132+ this . dispatchEvent ( new MessageEvent ( 'message' , {
133+ data : e . data ,
134+ } ) ) ;
135+ }
136+ } ;
137+ this . rtcWs . onclose = ( ) => {
138+ clearInterval ( pingInterval ) ;
139+ console . log ( 'rtc closed' ) ;
140+ } ;
141+ this . rtcWs . onerror = err => {
142+ console . warn ( 'rtc error' , err ) ;
143+ clearInterval ( pingInterval ) ;
144+ } ;
145+ const pingInterval = setInterval ( ( ) => {
146+ this . rtcWs . send ( JSON . stringify ( {
147+ method : 'ping' ,
148+ } ) ) ;
149+ } , 30 * 1000 ) ;
150+ }
151+
152+ disconect ( ) {
153+ this . rtcWs . close ( ) ;
154+ this . rtcWs = null ;
155+
156+ for ( let i = 0 ; i < this . peerConnections [ i ] ; i ++ ) {
157+ this . peerConnections [ i ] . close ( ) ;
158+ }
159+ this . peerConnections . length = 0 ;
160+ }
161+
162+ send ( s ) {
163+ this . rtcWs . send ( s ) ;
164+ }
165+
166+ update ( hmd , gamepads ) {
167+ for ( let i = 0 ; i < this . peerConnections . length ; i ++ ) {
168+ const peerConnection = this . peerConnections [ i ] ;
169+ if ( peerConnection . open ) {
170+ peerConnection . update ( hmd , gamepads ) ;
171+ }
172+ }
173+ }
174+ }
175+ window . XRChannelConnection = XRChannelConnection ;
176+
177+ class XRPeerConnection extends EventTarget {
178+ constructor ( peerConnectionId ) {
179+ super ( ) ;
180+
181+ this . connectionId = peerConnectionId ;
182+
183+ this . peerConnection = new RTCPeerConnection ( {
184+ iceServers : defaultIceServers ,
185+ } ) ;
186+ this . open = false ;
187+
188+ this . peerConnection . ontrack = e => {
189+ console . log ( 'got track' , e ) ;
190+ } ;
191+
192+ const sendChannel = this . peerConnection . createDataChannel ( 'sendChannel' ) ;
193+ this . peerConnection . sendChannel = sendChannel ;
194+ let pingInterval = 0 ;
195+ sendChannel . onopen = ( ) => {
196+ // console.log('data channel local open');
197+
198+ this . open = true ;
199+ this . dispatchEvent ( new CustomEvent ( 'open' ) ) ;
200+
201+ pingInterval = setInterval ( ( ) => {
202+ sendChannel . send ( JSON . stringify ( {
203+ method : 'ping' ,
204+ } ) ) ;
205+ } , 1000 ) ;
206+ } ;
207+ sendChannel . onclose = ( ) => {
208+ // console.log('data channel local close');
209+
210+ _cleanup ( ) ;
211+ } ;
212+ sendChannel . onerror = err => {
213+ // console.log('data channel local error', err);
214+ } ;
215+ let watchdogTimeout = 0 ;
216+ const _kick = ( ) => {
217+ if ( watchdogTimeout ) {
218+ clearTimeout ( watchdogTimeout ) ;
219+ watchdogTimeout = 0 ;
220+ }
221+ watchdogTimeout = setTimeout ( ( ) => {
222+ this . peerConnection . close ( ) ;
223+ } , 5000 ) ;
224+ } ;
225+ _kick ( ) ;
226+ this . peerConnection . ondatachannel = e => {
227+ const { channel} = e ;
228+ // console.log('data channel remote open', channel);
229+ channel . onclose = ( ) => {
230+ // console.log('data channel remote close');
231+ this . peerConnection . close ( ) ;
232+ } ;
233+ channel . onerror = err => {
234+ // console.log('data channel remote error', err);
235+ } ;
236+ channel . onmessage = e => {
237+ // console.log('data channel message', e.data);
238+
239+ const data = JSON . parse ( e . data ) ;
240+ const { method} = data ;
241+ if ( method === 'pose' ) {
242+ this . dispatchEvent ( new CustomEvent ( 'pose' , {
243+ detail : data ,
244+ } ) )
245+ } else if ( method === 'ping' ) {
246+ // nothing
247+ } else {
248+ this . dispatchEvent ( new MessageEvent ( 'message' , {
249+ data,
250+ } ) ) ;
251+ }
252+
253+ _kick ( ) ;
254+ } ;
255+ this . peerConnection . recvChannel = channel ;
256+ } ;
257+ this . peerConnection . close = ( close => function ( ) {
258+ _cleanup ( ) ;
259+
260+ return close . apply ( this , arguments ) ;
261+ } ) ( this . peerConnection . close ) ;
262+ const _cleanup = ( ) => {
263+ if ( this . open ) {
264+ this . open = false ;
265+ this . dispatchEvent ( new CustomEvent ( 'close' ) ) ;
266+ }
267+ if ( pingInterval ) {
268+ clearInterval ( pingInterval ) ;
269+ pingInterval = 0 ;
270+ }
271+ } ;
272+ }
273+
274+ close ( ) {
275+ this . peerConnection . close ( ) ;
276+ }
277+
278+ send ( s ) {
279+ this . peerConnection . sendChannel . send ( s ) ;
280+ }
281+
282+ update ( hmd , gamepads ) {
283+ this . send ( JSON . stringify ( {
284+ method : 'pose' ,
285+ hmd,
286+ gamepads,
287+ } ) ) ;
288+ }
289+ }
290+ window . XRPeerConnection = XRPeerConnection ;
0 commit comments