PageSourceSearch

https://app.paranoid.com/photos/_content/Paranoid.Photos.Client.Common/js/webrtc-transfer.js

js paranoid.com collected 2026-09-26 06:13:19 UTC 9,206 bytes, 292 lines download raw bytes

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.