1// WebRTC wrapper for P2P Master Key transfer 2// This handles WebRTC PeerConnection setup and DataChannel communication 3 4class WebRTCTransfer { 5 constructor(dotNetHelper) { 6 this.dotNetHelper = dotNetHelper; 7 this.peerConnection = null; 8 this.dataChannel = null; 9 10 // Google STUN servers for NAT traversal 11 this.configuration = { 12 iceServers: [ 13 { urls: 'stun:stun.l.google.com:19302' }, 14 { urls: 'stun:stun1.l.google.com:19302' }, 15 { urls: 'stun:stun2.l.google.com:19302' }, 16 { urls: 'stun:stun3.l.google.com:19302' }, 17 { urls: 'stun:stun4.l.google.com:19302' } 18 ] 19 }; 20 21 } 22 23 /** 24 * Creates a new RTCPeerConnection with event handlers 25 */ 26 createPeerConnection() { 27 this.peerConnection = new RTCPeerConnection(this.configuration); 28 29 // ICE candidate event 30 this.peerConnection.onicecandidate = (event) => { 31 if (event.candidate) { 32 this.dotNetHelper.invokeMethodAsync('OnIceCandidate', { 33 candidate: event.candidate.candidate, 34 sdpMid: event.candidate.sdpMid, 35 sdpMLineIndex: event.candidate.sdpMLineIndex 36 }); 37 } 38 }; 39 40 // Connection state change 41 this.peerConnection.onconnectionstatechange = () => { 42 this.dotNetHelper.invokeMethodAsync('OnConnectionStateChanged', this.peerConnection.connectionState); 43 }; 44 45 // Data channel from remote peer 46 this.peerConnection.ondatachannel = (event) => { 47 this.setupDataChannel(event.channel); 48 }; 49 50 return true; 51 } 52 53 /** 54 * Creates a data channel (for sender/initiator) 55 */ 56 createDataChannel(channelName) { 57 if (!this.peerConnection) { 58 console.error('[WebRTCTransfer] Cannot create data channel: no peer connection'); 59 return false; 60 } 61 62 const channel = this.peerConnection.createDataChannel(channelName, { 63 ordered: true // Ensure messages arrive in order 64 }); 65 66 this.setupDataChannel(channel); 67 return true; 68 } 69 70 /** 71 * Sets up event handlers for a data channel 72 */ 73 setupDataChannel(channel) { 74 this.dataChannel = channel; 75 76 this.dataChannel.onopen = () => { 77 console.log('[WebRTCTransfer] Data channel opened'); 78 this.dotNetHelper.invokeMethodAsync('OnDataChannelOpened'); 79 }; 80 81 this.dataChannel.onclose = () => { 82 console.log('[WebRTCTransfer] Data channel closed'); 83 this.dotNetHelper.invokeMethodAsync('OnDataChannelClosed'); 84 }; 85 86 this.dataChannel.onerror = (error) => { 87 console.error('[WebRTCTransfer] Data channel error:', error); 88 this.dotNetHelper.invokeMethodAsync('OnDataChannelError', error.toString()); 89 }; 90 91 this.dataChannel.onmessage = (event) => { 92 this.dotNetHelper.invokeMethodAsync('OnDataChannelMessage', event.data); 93 }; 94 } 95 96 /** 97 * Creates an SDP offer 98 */ 99 async createOffer() { 100 if (!this.peerConnection) { 101 console.error('[WebRTCTransfer] Cannot create offer: no peer connection'); 102 return null; 103 } 104 105 try { 106 const offer = await this.peerConnection.createOffer(); 107 await this.peerConnection.setLocalDescription(offer); 108 return offer.sdp; 109 } catch (error) { 110 console.error('[WebRTCTransfer] Error creating offer:', error); 111 return null; 112 } 113 } 114 115 /** 116 * Creates an SDP answer 117 */ 118 async createAnswer() { 119 if (!this.peerConnection) { 120 console.error('[WebRTCTransfer] Cannot create answer: no peer connection'); 121 return null; 122 } 123 124 try { 125 const answer = await this.peerConnection.createAnswer(); 126 await this.peerConnection.setLocalDescription(answer); 127 return answer.sdp; 128 } catch (error) { 129 console.error('[WebRTCTransfer] Error creating answer:', error); 130 return null; 131 } 132 } 133 134 /** 135 * Sets remote description from SDP offer 136 */ 137 async setRemoteOffer(sdp) { 138 if (!this.peerConnection) { 139 console.error('[WebRTCTransfer] Cannot set remote offer: no peer connection'); 140 return false;
141 } 142 143 try { 144 await this.peerConnection.setRemoteDescription({ 145 type: 'offer', 146 sdp: sdp 147 }); 148 return true; 149 } catch (error) { 150 console.error('[WebRTCTransfer] Error setting remote offer:', error); 151 return false; 152 } 153 } 154 155 /** 156 * Sets remote description from SDP answer 157 */ 158 async setRemoteAnswer(sdp) { 159 if (!this.peerConnection) { 160 console.error('[WebRTCTransfer] Cannot set remote answer: no peer connection'); 161 return false; 162 } 163 164 try { 165 await this.peerConnection.setRemoteDescription({ 166 type: 'answer', 167 sdp: sdp 168 }); 169 return true; 170 } catch (error) { 171 console.error('[WebRTCTransfer] Error setting remote answer:', error); 172 return false; 173 } 174 } 175 176 /** 177 * Adds an ICE candidate received from remote peer 178 */ 179 async addIceCandidate(candidate, sdpMid, sdpMLineIndex) { 180 if (!this.peerConnection) { 181 console.error('[WebRTCTransfer] Cannot add ICE candidate: no peer connection'); 182 return false; 183 } 184 185 try { 186 await this.peerConnection.addIceCandidate({ 187 candidate: candidate, 188 sdpMid: sdpMid, 189 sdpMLineIndex: sdpMLineIndex 190 }); 191 return true; 192 } catch (error) { 193 console.error('[WebRTCTransfer] Error adding ICE candidate:', error); 194 return false; 195 } 196 } 197 198 /** 199 * Sends data through the data channel 200 * Waits up to 10 seconds for channel to be open 201 */ 202 async sendData(data) { 203 // Wait for data channel to be open 204 const maxWaitTime = 10000; // 10 seconds 205 const checkInterval = 100; // 100ms 206 let waited = 0; 207 208 while ((!this.dataChannel || this.dataChannel.readyState !== 'open') && waited < maxWaitTime) { 209 await new Promise(resolve => setTimeout(resolve, checkInterval)); 210 waited += checkInterval; 211 } 212 213 if (!this.dataChannel || this.dataChannel.readyState !== 'open') { 214 console.error('[WebRTCTransfer] Cannot send data: channel not open'); 215 return false; 216 } 217 218 try { 219 this.dataChannel.send(data); 220 return true; 221 } catch (error) { 222 console.error('[WebRTCTransfer] Error sending data:', error); 223 return false; 224 } 225 } 226 227 /** 228 * Closes the peer connection and data channel 229 */ 230 close() { 231 if (this.dataChannel) { 232 this.dataChannel.close(); 233 this.dataChannel = null; 234 } 235 236 if (this.peerConnection) { 237 this.peerConnection.close(); 238 this.peerConnection = null; 239 } 240 } 241} 242 243// Global instances map (keyed by instance ID) 244window.webrtcTransferInstances = window.webrtcTransferInstances || {}; 245 246// Factory function called from C# 247window.createWebRTCTransfer = function(instanceId, dotNetHelper) { 248 const instance = new WebRTCTransfer(dotNetHelper); 249 window.webrtcTransferInstances[instanceId] = instance; 250 return true; 251}; 252 253// Wrapper functions that route to the correct instance 254window.webrtcCreatePeerConnection = function(instanceId) { 255 return window.webrtcTransferInstances[instanceId]?.createPeerConnection(); 256}; 257 258window.webrtcCreateDataChannel = function(instanceId, channelName) { 259 return window.webrtcTransferInstances[instanceId]?.createDataChannel(channelName); 260}; 261 262window.webrtcCreateOffer = async function(instanceId) { 263 return await window.webrtcTransferInstances[instanceId]?.createOffer(); 264}; 265 266window.webrtcCreateAnswer = async function(instanceId) { 267 return await window.webrtcTransferInstances[instanceId]?.createAnswer(); 268}; 269 270window.webrtcSetRemoteOffer = async function(instanceId, sdp) { 271 return await window.webrtcTransferInstances[instanceId]?.setRemoteOffer(sdp); 272}; 273 274window.webrtcSetRemoteAnswer = async function(instanceId, sdp) { 275 return await window.webrtcTransferInstances[instanceId]?.setRemoteAnswer(sdp); 276}; 277
278window.webrtcAddIceCandidate = async function(instanceId, candidate, sdpMid, sdpMLineIndex) { 279 return await window.webrtcTransferInstances[instanceId]?.addIceCandidate(candidate, sdpMid, sdpMLineIndex); 280}; 281 282window.webrtcSendData = async function(instanceId, data) { 283 return await window.webrtcTransferInstances[instanceId]?.sendData(data); 284}; 285 286window.webrtcClose = function(instanceId) { 287 const instance = window.webrtcTransferInstances[instanceId]; 288 if (instance) { 289 instance.close(); 290 delete window.webrtcTransferInstances[instanceId]; 291 } 292};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.