Documentation Index
Fetch the complete documentation index at: https://docsdrop.erikraft.com/llms.txt
Use this file to discover all available pages before exploring further.
WebRTC Connection Implementation
ErikrafT Drop leverages WebRTC (Web Real-Time Communication) to establish direct peer-to-peer connections for secure file transfers. The implementation handles connection establishment, signaling, and data channel management.
WebRTC Architecture
RTCPeer Connection Class
The core WebRTC functionality is implemented in the RTCPeer class in network.js:
class RTCPeer extends Peer {
constructor(serverConnection, isCaller, peerId, roomType, roomId, rtcConfig) {
super(serverConnection, isCaller, peerId, roomType, roomId);
this.rtcSupported = true;
this.rtcConfig = rtcConfig;
if (!this._isCaller) return; // we will listen for a caller
this._connect();
}
}
Connection Establishment Process
1. Connection Initialization
// From network.js lines 1115-1121
_openConnection() {
this._conn = new RTCPeerConnection(this.rtcConfig);
this._conn.onicecandidate = e => this._onIceCandidate(e);
this._conn.onicecandidateerror = e => this._onError(e);
this._conn.onconnectionstatechange = _ => this._onConnectionStateChange();
this._conn.oniceconnectionstatechange = e => this._onIceConnectionStateChange(e);
}
2. Data Channel Creation
// From network.js lines 1123-1137
_openChannel() {
if (!this._conn) return;
const channel = this._conn.createDataChannel('data-channel', {
ordered: true,
reliable: true // Obsolete. See MDN documentation
});
channel.onopen = e => this._onChannelOpened(e);
channel.onerror = e => this._onError(e);
this._conn
.createOffer()
.then(d => this._onDescription(d))
.catch(e => this._onError(e));
}
Signaling Process
Offer/Answer Pattern
The WebRTC connection follows the standard offer/answer pattern:
1. Offer Creation (Caller)
// From network.js lines 1139-1145
_onDescription(description) {
this._conn
.setLocalDescription(description)
.then(_ => this._sendSignal({ sdp: description }))
.catch(e => this._onError(e));
}
2. Signal Relay
// From network.js lines 1279-1285
_sendSignal(signal) {
signal.type = 'signal';
signal.to = this._peerId;
signal.roomType = this._getRoomTypes()[0];
signal.roomId = this._roomIds[this._getRoomTypes()[0]];
this._server.send(signal);
}
3. Answer Creation (Receiver)
// From network.js lines 1155-1165
if (message.sdp) {
this._conn
.setRemoteDescription(message.sdp)
.then(_ => {
if (message.sdp.type === 'offer') {
return this._conn
.createAnswer()
.then(d => this._onDescription(d));
}
})
.catch(e => this._onError(e));
}
ICE Candidate Exchange
1. ICE Candidate Generation
// From network.js lines 1147-1150
_onIceCandidate(event) {
if (!event.candidate) return;
this._sendSignal({ ice: event.candidate });
}
2. ICE Candidate Processing
// From network.js lines 1167-1171
else if (message.ice) {
this._conn
.addIceCandidate(new RTCIceCandidate(message.ice))
.catch(e => this._onError(e));
}
Data Channel Management
Channel Configuration
The data channel is configured for reliable, ordered delivery:
const channel = this._conn.createDataChannel('data-channel', {
ordered: true, // Maintain message order
reliable: true // Ensure delivery (deprecated but maintained for compatibility)
});
Channel Event Handling
1. Channel Opened
// From network.js lines 1174-1184
_onChannelOpened(event) {
console.log('RTC: channel opened with', this._peerId);
const channel = event.channel || event.target;
channel.binaryType = 'arraybuffer';
channel.onmessage = e => this._onMessage(e.data);
channel.onclose = _ => this._onChannelClosed();
this._channel = channel;
Events.fire('peer-connected', {peerId: this._peerId, connectionHash: this.getConnectionHash()});
}
2. Message Handling
// From network.js lines 1186-1191
_onMessage(message) {
if (typeof message === 'string') {
console.log('RTC:', JSON.parse(message));
}
super._onMessage(message);
}
Connection State Management
Connection States
WebRTC connections progress through several states:
// From network.js lines 1245-1257
_onConnectionStateChange() {
console.log('RTC: state changed:', this._conn.connectionState);
switch (this._conn.connectionState) {
case 'disconnected':
Events.fire('peer-disconnected', this._peerId);
this._onError('rtc connection disconnected');
break;
case 'failed':
Events.fire('peer-disconnected', this._peerId);
this._onError('rtc connection failed');
break;
}
}
ICE Connection States
// From network.js lines 1259-1267
_onIceConnectionStateChange() {
switch (this._conn.iceConnectionState) {
case 'failed':
this._onError('ICE Gathering failed');
break;
default:
console.log('ICE Gathering', this._conn.iceConnectionState);
}
}
Connection Hash Generation
For security and identification, each connection generates a unique hash:
// From network.js lines 1193-1217
getConnectionHash() {
const localDescriptionLines = this._conn.localDescription.sdp.split("\r\n");
const remoteDescriptionLines = this._conn.remoteDescription.sdp.split("\r\n");
let localConnectionFingerprint, remoteConnectionFingerprint;
// Extract fingerprints from SDP
for (let i=0; i<localDescriptionLines.length; i++) {
if (localDescriptionLines[i].startsWith("a=fingerprint:")) {
localConnectionFingerprint = localDescriptionLines[i].substring(14);
break;
}
}
// Combine and hash fingerprints
const combinedFingerprints = this._isCaller
? localConnectionFingerprint + remoteConnectionFingerprint
: remoteConnectionFingerprint + localConnectionFingerprint;
let hash = cyrb53(combinedFingerprints).toString();
while (hash.length < 16) {
hash = "0" + hash;
}
return hash;
}
Error Handling and Recovery
Connection Errors
// From network.js lines 1269-1272
_onError(error) {
console.error(error);
Events.fire('rtc-error', { peerId: this._peerId, error: error });
}
Automatic Reconnection
// From network.js lines 1241-1243
_onChannelClosed() {
console.log('RTC: channel closed', this._peerId);
Events.fire('peer-disconnected', this._peerId);
if (!this._isCaller) return;
this._connect(); // reopen the channel
}
Connection Refresh
// From network.js lines 1287-1295
refresh() {
// check if channel is open. otherwise create one
if (this._isConnected() || this._isConnecting()) return;
// only reconnect if peer is caller
if (!this._isCaller) return;
this._connect();
}
Configuration Options
RTC Configuration
The WebRTC connection is configured with ICE servers:
// From server.js lines 8-11
const rtcConfig = {
sdpSemantics: "unified-plan",
iceServers: []
};
LAN Mode Configuration
In LAN mode, ICE servers are disabled for local-only connections:
// From network.js lines 153-155
if (this._lanMode.enabled && msg.wsConfig) {
msg.wsConfig.rtcConfig = { iceServers: [] };
}
Binary Data Handling
// Set binary type for efficient file transfer
channel.binaryType = 'arraybuffer';
Memory Management
- Streaming: Files are streamed in chunks to prevent memory overload
- Garbage Collection: Proper cleanup of closed connections
- Buffer Management: Efficient buffer handling for large files
Network Optimization
- ICE Candidate Filtering: Prioritize local candidates for LAN connections
- Connection Reuse: Maintain persistent connections when possible
- Adaptive Chunking: Adjust chunk size based on connection quality
Browser Compatibility
Feature Detection
// WebRTC support detection
window.isRtcSupported = !!window.RTCPeerConnection;
Fallback Mechanism
When WebRTC is not supported, the system falls back to WebSocket-based transfers:
// From network.js lines 1425-1434
if (window.isRtcSupported && rtcSupported) {
this.peers[peerId] = new RTCPeer(this._server, isCaller, peerId, roomType, roomId, this._wsConfig.rtcConfig);
}
else if (this._wsConfig.wsFallback) {
this.peers[peerId] = new WSPeer(this._server, isCaller, peerId, roomType, roomId);
}
This WebRTC implementation provides secure, efficient peer-to-peer file transfers with automatic fallback mechanisms for maximum compatibility.