Tidy up Peer classes

This commit is contained in:
schlagmichdoch 2024-02-13 21:01:49 +01:00
parent da558ddceb
commit 7c471910ef
2 changed files with 308 additions and 251 deletions

View file

@ -163,7 +163,8 @@
"rate-limit-join-key": "Rate limit reached. Wait 10 seconds and try again.", "rate-limit-join-key": "Rate limit reached. Wait 10 seconds and try again.",
"selected-peer-left": "Selected peer left", "selected-peer-left": "Selected peer left",
"error-sharing-size": "Files too big to be shared. They can be downloaded instead.", "error-sharing-size": "Files too big to be shared. They can be downloaded instead.",
"error-sharing-default": "Error while sharing. It can be downloaded instead." "error-sharing-default": "Error while sharing. It can be downloaded instead.",
"ram-exceed-ios": "File is bigger than 250 MB and will crash the page on iOS. Use https to prevent this."
}, },
"document-titles": { "document-titles": {
"file-received": "File Received", "file-received": "File Received",

View file

@ -318,6 +318,13 @@ class ServerConnection {
class Peer { class Peer {
static STATE_IDLE = 'idle';
static STATE_PREPARE = 'prepare';
static STATE_TRANSFER_REQUEST_SENT = 'transfer-request-sent';
static STATE_RECEIVE_PROCEEDING = 'receive-proceeding';
static STATE_TRANSFER_PROCEEDING = 'transfer-proceeding';
static STATE_TEXT_SENT = 'text-sent';
constructor(serverConnection, isCaller, peerId, roomType, roomId) { constructor(serverConnection, isCaller, peerId, roomType, roomId) {
this._server = serverConnection; this._server = serverConnection;
this._isCaller = isCaller; this._isCaller = isCaller;
@ -329,7 +336,7 @@ class Peer {
this._filesQueue = []; this._filesQueue = [];
this._busy = false; this._busy = false;
this._state = 'idle'; // 'idle', 'prepare', 'wait', 'receive', 'transfer', 'text-sent' this._state = Peer.STATE_IDLE;
// evaluate auto accept // evaluate auto accept
this._evaluateAutoAccept(); this._evaluateAutoAccept();
@ -339,14 +346,19 @@ class Peer {
} }
_reset() { _reset() {
this._state = 'idle'; this._state = Peer.STATE_IDLE;
this._busy = false; this._busy = false;
// tidy up sender
this._filesRequested = null;
this._chunker = null; this._chunker = null;
// tidy up receiver
this._pendingRequest = null; this._pendingRequest = null;
this._acceptedRequest = null; this._acceptedRequest = null;
this._totalBytesReceived = 0;
this._digester = null; this._digester = null;
this._filesReceived = []; this._filesReceived = [];
this._totalBytesReceived = 0;
} }
_refresh() {} _refresh() {}
@ -376,14 +388,6 @@ class Peer {
this._isCaller = isCaller; this._isCaller = isCaller;
} }
_sendMessage(message) {}
_sendData(data) {}
_sendDisplayName(displayName) {
this._sendMessage({type: 'display-name-changed', displayName: displayName});
}
_isSameBrowser() { _isSameBrowser() {
return BrowserTabsConnector.peerIsSameBrowser(this._peerId); return BrowserTabsConnector.peerIsSameBrowser(this._peerId);
} }
@ -473,37 +477,131 @@ class Peer {
} }
_onPeerConnected() { _onPeerConnected() {
this._sendCurrentState(); this._sendState();
} }
_sendCurrentState() { _sendMessage(message) {}
_sendData(data) {}
_onMessage(message) {
switch (message.type) {
case 'display-name-changed':
this._onDisplayNameChanged(message);
break;
case 'state':
this._onState(message.state);
break;
case 'transfer-request':
this._onTransferRequest(message);
break;
case 'transfer-request-response':
this._onTransferRequestResponse(message);
break;
case 'transfer-header':
this._onTransferHeader(message);
break;
case 'receive-progress':
this._onReceiveProgress(message.progress);
break;
case 'receive-confirmation':
this._onReceiveConfirmation(message.bytesReceived);
break;
case 'resend-request':
this._onResendRequest(message.offset);
break;
case 'file-receive-complete':
this._onFileReceiveComplete(message);
break;
case 'text':
this._onText(message);
break;
case 'text-receive-complete':
this._onTextReceiveComplete();
break;
default:
Logger.warn('RTC: Unknown message type:', message.type);
}
}
_sendDisplayName(displayName) {
this._sendMessage({type: 'display-name-changed', displayName: displayName});
}
_onDisplayNameChanged(message) {
const displayNameHasChanged = message.displayName !== this._displayName;
if (!message.displayName || !displayNameHasChanged) return;
this._displayName = message.displayName;
const roomSecret = this._getPairSecret();
if (roomSecret) {
PersistentStorage
.updateRoomSecretDisplayName(roomSecret, message.displayName)
.then(roomSecretEntry => {
Logger.debug(`Successfully updated DisplayName for roomSecretEntry ${roomSecretEntry.key}`);
})
}
Events.fire('peer-display-name-changed', {peerId: this._peerId, displayName: message.displayName});
Events.fire('notify-peer-display-name-changed', this._peerId);
}
_sendState() {
this._sendMessage({type: 'state', state: this._state}) this._sendMessage({type: 'state', state: this._state})
} }
_onReceiveState(peerState) { _onState(peerState) {
if (this._state === "receive") { if (this._state === Peer.STATE_RECEIVE_PROCEEDING) {
if (peerState !== "transfer" || !this._digester) { this._onStateReceiver(peerState);
this._abortTransfer();
return;
}
// Reconnection during receiving of file. Send request for restart
const offset = this._digester._bytesReceived;
this._sendResendRequest(offset);
return
} }
else if (this._state === Peer.STATE_TRANSFER_PROCEEDING) {
if (this._state === "transfer" && peerState !== "receive") { this._onStateSender(peerState);
this._abortTransfer();
return;
} }
} }
async requestFileTransfer(files) { _onStateSender(peerState) {
// this peer is sender
if (peerState !== Peer.STATE_RECEIVE_PROCEEDING) {
this._abortTransfer();
}
}
_onStateReceiver(peerState) {
// this peer is receiver
switch (peerState) {
case Peer.STATE_TRANSFER_REQUEST_SENT:
// Reconnection during file transfer request. Send acceptance again.
this._sendTransferRequestResponse(true);
break;
case Peer.STATE_TRANSFER_PROCEEDING:
// Reconnection during receiving of file. Send request for resending
if (!this._digester) {
this._abortTransfer();
}
const offset = this._digester._bytesReceived;
this._sendResendRequest(offset);
break;
default:
this._abortTransfer();
}
}
_abortTransfer() {
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: null});
this._reset();
}
// File Sender Only
async _sendFileTransferRequest(files) {
this._state = Peer.STATE_PREPARE;
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'prepare'});
let header = []; let header = [];
let totalSize = 0; let totalSize = 0;
let imagesOnly = true let imagesOnly = true;
this._state = 'prepare';
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'prepare'});
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
header.push({ header.push({
@ -515,7 +613,7 @@ class Peer {
if (files[i].type.split('/')[0] !== 'image') imagesOnly = false; if (files[i].type.split('/')[0] !== 'image') imagesOnly = false;
} }
let dataUrl = ''; let dataUrl = "";
if (files[0].type.split('/')[0] === 'image') { if (files[0].type.split('/')[0] === 'image') {
try { try {
dataUrl = await getThumbnailAsDataUrl(files[0], 400, null, 0.9); dataUrl = await getThumbnailAsDataUrl(files[0], 400, null, 0.9);
@ -534,11 +632,33 @@ class Peer {
imagesOnly: imagesOnly, imagesOnly: imagesOnly,
thumbnailDataUrl: dataUrl thumbnailDataUrl: dataUrl
}); });
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'wait'}) Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'wait'})
this._state = 'wait'; this._state = Peer.STATE_TRANSFER_REQUEST_SENT;
}
_onTransferRequestResponse(message) {
if (this._state !== Peer.STATE_TRANSFER_REQUEST_SENT) {
this._sendState();
return;
}
if (!message.accepted) {
if (message.reason === 'ram-exceed-ios') {
Events.fire('notify-user', Localization.getTranslation('notifications.ram-exceed-ios'));
}
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: null});
this._reset();
return;
}
Events.fire('file-transfer-accepted');
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'transfer'});
this._state = Peer.STATE_TRANSFER_PROCEEDING;
this._sendFiles();
} }
sendFiles() { _sendFiles() {
for (let i = 0; i < this._filesRequested.length; i++) { for (let i = 0; i < this._filesRequested.length; i++) {
this._filesQueue.push(this._filesRequested[i]); this._filesQueue.push(this._filesRequested[i]);
} }
@ -562,20 +682,10 @@ class Peer {
}); });
} }
// Is overwritten in expanding classes
_sendFile(file) {} _sendFile(file) {}
_sendResendRequest(offset) {
this._sendMessage({ type: 'resend-request', offset: offset });
}
_sendTransferAbortion() {
this._sendMessage({type: 'file-transfer-complete', success: false});
}
_onResendRequest(offset) { _onResendRequest(offset) {
if (this._state !== 'transfer' || !this._chunker) { if (this._state !== Peer.STATE_TRANSFER_PROCEEDING || !this._chunker) {
this._sendTransferAbortion(); this._sendTransferAbortion();
return; return;
} }
@ -583,66 +693,77 @@ class Peer {
this._chunker._resendFromOffset(offset); this._chunker._resendFromOffset(offset);
} }
_sendProgress(progress) { _onReceiveProgress(progress) {
this._sendMessage({ type: 'receive-progress', progress: progress }); if (this._state !== Peer.STATE_TRANSFER_PROCEEDING) {
} this._sendState();
return;
_onData(data) {
this._onChunkReceived(data);
}
_onMessage(message) {
switch (message.type) {
case 'state':
this._onReceiveState(message.state);
break;
case 'transfer-request':
this._onTransferRequest(message);
break;
case 'transfer-response':
this._onTransferResponse(message);
break;
case 'transfer-header':
this._onTransferHeader(message);
break;
case 'receive-progress':
this._onReceiveProgress(message.progress);
break;
case 'receive-confirmation':
this._onReceiveConfirmation(message.bytesReceived);
break;
case 'resend-request':
this._onResendRequest(message.offset);
break;
case 'file-transfer-complete':
this._onFileTransferComplete(message);
break;
case 'text':
this._onTextReceived(message);
break;
case 'text-sent':
this._onTextSent();
break;
case 'display-name-changed':
this._onDisplayNameChanged(message);
break;
default:
Logger.warn('RTC: Unknown message type:', message.type);
} }
Events.fire('set-progress', {peerId: this._peerId, progress: progress, status: 'transfer'});
} }
_onReceiveConfirmation(bytesReceived) {
if (!this._chunker || this._state !== Peer.STATE_TRANSFER_PROCEEDING) {
this._sendState();
return;
}
this._chunker._onReceiveConfirmation(bytesReceived);
}
_onFileReceiveComplete(message) {
if (this._state !== Peer.STATE_TRANSFER_PROCEEDING) {
this._sendState();
return;
}
this._chunker = null;
if (!message.success) {
Logger.warn('File could not be sent');
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: null});
this._reset();
return;
}
Logger.log(`File sent.\n\nSize: ${message.size} MB\tDuration: ${message.duration} s\tSpeed: ${message.speed} MB/s`);
if (this._filesQueue.length) {
this._dequeueFile();
return;
}
// No more files in queue. Transfer is complete
this._reset();
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'transfer-complete'});
Events.fire('notify-user', Localization.getTranslation("notifications.file-transfer-completed"));
Events.fire('files-sent'); // used by 'Snapdrop & PairDrop for Android' app
}
// File Receiver Only
_onTransferRequest(request) { _onTransferRequest(request) {
if (this._pendingRequest) { if (this._pendingRequest) {
// Only accept one request at a time per peer // Only accept one request at a time per peer
this._sendMessage({type: 'transfer-response', accepted: false}); this._sendTransferRequestResponse(false);
return; return;
} }
this._pendingRequest = request; this._pendingRequest = request;
if (!window.Worker || !window.isSecureContext) {
// Each file must be loaded into RAM completely which might lead to a page crash (Memory limit iOS Safari: ~380 MB)
Logger.warn('Big file transfers might exceed the RAM of the receiver. Use a secure context (https) to prevent this.');
}
if (window.iOS && this._filesTooBigForIos(request.header)) {
// Page will crash. Decline request
Events.fire('notify-user', Localization.getTranslation('notifications.ram-exceed-ios'));
this._sendTransferRequestResponse(false, 'ram-exceed-ios');
return;
}
if (this._autoAccept) { if (this._autoAccept) {
// auto accept if set via Edit Paired Devices Dialog // auto accept if set via Edit Paired Devices Dialog
this._respondToFileTransferRequest(true); this._sendTransferRequestResponse(true);
return; return;
} }
@ -653,22 +774,40 @@ class Peer {
}); });
} }
_respondToFileTransferRequest(accepted) { _filesTooBigForIos(files) {
this._sendMessage({type: 'transfer-response', accepted: accepted}); if (window.Worker && window.isSecureContext) {
return false;
}
for (let i = 0; i < files.length; i++) {
if (files[i].size > 250000000) {
return true;
}
}
return false;
}
_sendTransferRequestResponse(accepted, reason = null) {
let message = {type: 'transfer-request-response', accepted: accepted};
if (reason) {
message.reason = reason;
}
this._sendMessage(message);
if (accepted) { if (accepted) {
this._state = 'receive'; this._state = Peer.STATE_RECEIVE_PROCEEDING;
this._busy = true; this._busy = true;
this._acceptedRequest = this._pendingRequest; this._acceptedRequest = this._pendingRequest;
this._lastProgress = 0; this._lastProgress = 0;
this._totalBytesReceived = 0; this._totalBytesReceived = 0;
this._filesReceived = []; this._filesReceived = [];
} }
this._pendingRequest = null;
} }
_onTransferHeader(header) { _onTransferHeader(header) {
if (this._state !== "receive") { if (this._state !== Peer.STATE_RECEIVE_PROCEEDING) {
this._sendCurrentState(); this._sendState();
return; return;
} }
this._timeStart = Date.now(); this._timeStart = Date.now();
@ -678,7 +817,6 @@ class Peer {
_addFileDigester(header) { _addFileDigester(header) {
this._digester = new FileDigester({size: header.size, name: header.name, mime: header.mime}, this._digester = new FileDigester({size: header.size, name: header.name, mime: header.mime},
this._acceptedRequest.totalSize,
fileBlob => this._fileReceived(fileBlob), fileBlob => this._fileReceived(fileBlob),
bytesReceived => this._sendReceiveConfirmation(bytesReceived) bytesReceived => this._sendReceiveConfirmation(bytesReceived)
); );
@ -688,14 +826,21 @@ class Peer {
this._sendMessage({type: 'receive-confirmation', bytesReceived: bytesReceived}); this._sendMessage({type: 'receive-confirmation', bytesReceived: bytesReceived});
} }
_abortTransfer() { _sendResendRequest(offset) {
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: null}); this._sendMessage({ type: 'resend-request', offset: offset });
this._reset(); }
_sendTransferAbortion() {
this._sendMessage({type: 'file-receive-complete', success: false});
}
_onData(data) {
this._onChunkReceived(data);
} }
_onChunkReceived(chunk) { _onChunkReceived(chunk) {
if(this._state !== 'receive' || !this._digester || !(chunk.byteLength || chunk.size)) { if(this._state !== Peer.STATE_RECEIVE_PROCEEDING || !this._digester || !(chunk.byteLength || chunk.size)) {
this._sendCurrentState(); this._sendState();
return; return;
} }
@ -720,21 +865,26 @@ class Peer {
} }
} }
_onReceiveProgress(progress) { _sendProgress(progress) {
if (this._state !== 'transfer') { this._sendMessage({ type: 'receive-progress', progress: progress });
this._sendCurrentState();
return;
}
Events.fire('set-progress', {peerId: this._peerId, progress: progress, status: 'transfer'});
} }
_onReceiveConfirmation(bytesReceived) { async _fileReceived(file) {
if (!this._chunker || this._state !== 'transfer') { if (!this._fitsHeader(file)) {
this._sendCurrentState(); this._abortTransfer();
Events.fire('notify-user', Localization.getTranslation("notifications.files-incorrect"));
Logger.error("Received files differ from requested files. Abort!");
return; return;
} }
this._chunker._onReceiveConfirmation(bytesReceived);
// File transfer complete
this._singleFileReceiveComplete(file);
if (this._acceptedRequest.header.length) return;
// We are done receiving
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'receive'});
this._allFilesReceiveComplete();
} }
_fitsHeader(file) { _fitsHeader(file) {
@ -750,7 +900,7 @@ class Peer {
return sameSize && sameName; return sameSize && sameName;
} }
_singleFileTransferComplete(file) { _singleFileReceiveComplete(file) {
this._digester = null; this._digester = null;
this._totalBytesReceived += file.size; this._totalBytesReceived += file.size;
@ -761,142 +911,52 @@ class Peer {
// Log speed from request to receive // Log speed from request to receive
Logger.log(`File received.\n\nSize: ${size} MB\tDuration: ${duration} s\tSpeed: ${speed} MB/s`); Logger.log(`File received.\n\nSize: ${size} MB\tDuration: ${duration} s\tSpeed: ${speed} MB/s`);
this._sendMessage({type: 'file-transfer-complete', success: true, duration: duration, size: size, speed: speed});
// include for compatibility with 'Snapdrop & PairDrop for Android' app // include for compatibility with 'Snapdrop & PairDrop for Android' app
Events.fire('file-received', file); Events.fire('file-received', file);
this._filesReceived.push(file); this._filesReceived.push(file);
this._sendMessage({type: 'file-receive-complete', success: true, duration: duration, size: size, speed: speed});
} }
_allFilesTransferComplete() { _allFilesReceiveComplete() {
this._state = 'idle';
Events.fire('files-received', { Events.fire('files-received', {
peerId: this._peerId, peerId: this._peerId,
files: this._filesReceived, files: this._filesReceived,
imagesOnly: this._acceptedRequest.imagesOnly, imagesOnly: this._acceptedRequest.imagesOnly,
totalSize: this._acceptedRequest.totalSize totalSize: this._acceptedRequest.totalSize
}); });
this._filesReceived = []; this._reset();
this._acceptedRequest = null;
this._busy = false;
} }
async _fileReceived(file) { // Message Sender Only
if (!this._fitsHeader(file)) { _sendText(text) {
this._abortTransfer(); this._state = Peer.STATE_TEXT_SENT;
Events.fire('notify-user', Localization.getTranslation("notifications.files-incorrect"));
Logger.error("Received files differ from requested files. Abort!");
return;
}
// File transfer complete
this._singleFileTransferComplete(file);
if (this._acceptedRequest.header.length) return;
// We are done receiving
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'receive'});
this._allFilesTransferComplete();
}
_onFileTransferComplete(message) {
if (this._state !== "transfer") {
this._sendCurrentState();
return;
}
this._chunker = null;
if (!message.success) {
Logger.warn('File could not be sent');
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: null});
this._state = 'idle';
return;
}
Logger.log(`File sent.\n\nSize: ${message.size} MB\tDuration: ${message.duration} s\tSpeed: ${message.speed} MB/s`);
if (this._filesQueue.length) {
this._dequeueFile();
return;
}
// No more files in queue. Transfer is complete
this._state = 'idle';
this._busy = false;
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'transfer-complete'});
Events.fire('notify-user', Localization.getTranslation("notifications.file-transfer-completed"));
Events.fire('files-sent'); // used by 'Snapdrop & PairDrop for Android' app
}
_onTransferResponse(message) {
if (this._state !== 'wait') {
this._sendCurrentState();
return;
}
if (!message.accepted) {
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: null});
this._state = 'idle';
this._filesRequested = null;
return;
}
Events.fire('file-transfer-accepted');
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'transfer'});
this._state = 'transfer';
this.sendFiles();
}
_onTextSent() {
if (this._state !== 'text-sent') {
this._sendCurrentState();
return;
}
this._state = 'idle';
Events.fire('notify-user', Localization.getTranslation("notifications.message-transfer-completed"));
}
sendText(text) {
this._state = 'text-sent';
const unescaped = btoa(unescape(encodeURIComponent(text))); const unescaped = btoa(unescape(encodeURIComponent(text)));
this._sendMessage({ type: 'text', text: unescaped }); this._sendMessage({ type: 'text', text: unescaped });
} }
_onTextReceived(message) { _onTextReceiveComplete() {
if (this._state !== Peer.STATE_TEXT_SENT) {
this._sendState();
return;
}
this._reset();
Events.fire('notify-user', Localization.getTranslation("notifications.message-transfer-completed"));
}
// Message Receiver Only
_onText(message) {
if (!message.text) return; if (!message.text) return;
try { try {
const escaped = decodeURIComponent(escape(atob(message.text))); const escaped = decodeURIComponent(escape(atob(message.text)));
Events.fire('text-received', { text: escaped, peerId: this._peerId }); Events.fire('text-received', { text: escaped, peerId: this._peerId });
this._sendMessage({ type: 'text-sent' }); this._sendMessage({ type: 'text-receive-complete' });
} }
catch (e) { catch (e) {
Logger.error(e); Logger.error(e);
} }
} }
_onDisplayNameChanged(message) {
const displayNameHasChanged = message.displayName !== this._displayName;
if (!message.displayName || !displayNameHasChanged) return;
this._displayName = message.displayName;
const roomSecret = this._getPairSecret();
if (roomSecret) {
PersistentStorage
.updateRoomSecretDisplayName(roomSecret, message.displayName)
.then(roomSecretEntry => {
Logger.debug(`Successfully updated DisplayName for roomSecretEntry ${roomSecretEntry.key}`);
})
}
Events.fire('peer-display-name-changed', {peerId: this._peerId, displayName: message.displayName});
Events.fire('notify-peer-display-name-changed', this._peerId);
}
} }
class RTCPeer extends Peer { class RTCPeer extends Peer {
@ -907,7 +967,7 @@ class RTCPeer extends Peer {
this.rtcSupported = true; this.rtcSupported = true;
this.rtcConfig = rtcConfig; this.rtcConfig = rtcConfig;
this.pendingInboundMessages = []; this.pendingInboundServerSignalMessages = [];
this.pendingOutboundMessages = []; this.pendingOutboundMessages = [];
this._connect(); this._connect();
@ -963,7 +1023,7 @@ class RTCPeer extends Peer {
this._openMessageChannel(); this._openMessageChannel();
this._openDataChannel(); this._openDataChannel();
this._evaluatePendingInboundMessages() this._evaluatePendingInboundServerMessages()
.then((count) => { .then((count) => {
if (count) { if (count) {
Logger.debug("Pending inbound messages evaluated."); Logger.debug("Pending inbound messages evaluated.");
@ -1082,7 +1142,6 @@ class RTCPeer extends Peer {
Logger.error(e.error); Logger.error(e.error);
} }
async _handleLocalDescription(localDescription) { async _handleLocalDescription(localDescription) {
await this._conn.setLocalDescription(localDescription); await this._conn.setLocalDescription(localDescription);
@ -1128,12 +1187,15 @@ class RTCPeer extends Peer {
await this._conn.addIceCandidate(candidate); await this._conn.addIceCandidate(candidate);
} }
async _evaluatePendingInboundMessages() { async _evaluatePendingInboundServerMessages() {
let inboundMessagesEvaluatedCount = 0; let inboundMessagesEvaluatedCount = 0;
while (this.pendingInboundMessages.length > 0) { while (this.pendingInboundServerSignalMessages.length > 0) {
const message = this.pendingInboundMessages.shift(); const message = this.pendingInboundServerSignalMessages.shift();
Logger.debug("Evaluate pending inbound message:", message); Logger.debug("Evaluate pending inbound message:", message);
await this._onServerSignalMessage(message); await this._onServerSignalMessage(message);
inboundMessagesEvaluatedCount++; inboundMessagesEvaluatedCount++;
} }
return inboundMessagesEvaluatedCount; return inboundMessagesEvaluatedCount;
@ -1141,7 +1203,7 @@ class RTCPeer extends Peer {
async _onServerSignalMessage(message) { async _onServerSignalMessage(message) {
if (this._conn === null) { if (this._conn === null) {
this.pendingInboundMessages.push(message); this.pendingInboundServerSignalMessages.push(message);
return; return;
} }
@ -1242,7 +1304,7 @@ class RTCPeer extends Peer {
); );
this._chunker._readChunk(); this._chunker._readChunk();
this._sendHeader(file); this._sendHeader(file);
this._state = 'transfer'; this._state = Peer.STATE_TRANSFER_PROCEEDING;
} }
_onMessage(message) { _onMessage(message) {
@ -1452,11 +1514,11 @@ class PeersManager {
if (this._peerExists(peerId)) { if (this._peerExists(peerId)) {
this._refreshPeer(isCaller, peerId, roomType, roomId); this._refreshPeer(isCaller, peerId, roomType, roomId);
} else { } else {
this.createPeer(isCaller, peerId, roomType, roomId, rtcSupported); this._createPeer(isCaller, peerId, roomType, roomId, rtcSupported);
} }
} }
createPeer(isCaller, peerId, roomType, roomId, rtcSupported) { _createPeer(isCaller, peerId, roomType, roomId, rtcSupported) {
if (window.isRtcSupported && rtcSupported) { if (window.isRtcSupported && rtcSupported) {
this.peers[peerId] = new RTCPeer(this._server, isCaller, peerId, roomType, roomId, this._wsConfig.rtcConfig); this.peers[peerId] = new RTCPeer(this._server, isCaller, peerId, roomType, roomId, this._wsConfig.rtcConfig);
} }
@ -1490,16 +1552,16 @@ class PeersManager {
} }
_onRespondToFileTransferRequest(detail) { _onRespondToFileTransferRequest(detail) {
this.peers[detail.to]._respondToFileTransferRequest(detail.accepted); this.peers[detail.to]._sendTransferRequestResponse(detail.accepted);
} }
async _onFilesSelected(message) { async _onFilesSelected(message) {
let files = await mime.addMissingMimeTypesToFiles(message.files); let files = await mime.addMissingMimeTypesToFiles(message.files);
await this.peers[message.to].requestFileTransfer(files); await this.peers[message.to]._sendFileTransferRequest(files);
} }
_onSendText(message) { _onSendText(message) {
this.peers[message.to].sendText(message.text); this.peers[message.to]._sendText(message.text);
} }
_onPeerLeft(message) { _onPeerLeft(message) {
@ -1748,7 +1810,7 @@ class FileChunkerWS extends FileChunker {
class FileDigester { class FileDigester {
constructor(meta, totalSize, fileCompleteCallback, receiveConfirmationCallback = null) { constructor(meta, fileCompleteCallback, sendReceiveConfirmationCallback) {
this._buffer = []; this._buffer = [];
this._bytesReceived = 0; this._bytesReceived = 0;
this._bytesReceivedSinceLastTime = 0; this._bytesReceivedSinceLastTime = 0;
@ -1756,9 +1818,8 @@ class FileDigester {
this._size = meta.size; this._size = meta.size;
this._name = meta.name; this._name = meta.name;
this._mime = meta.mime; this._mime = meta.mime;
this._totalSize = totalSize;
this._fileCompleteCallback = fileCompleteCallback; this._fileCompleteCallback = fileCompleteCallback;
this._receiveConfimationCallback = receiveConfirmationCallback; this._sendReceiveConfimationCallback = sendReceiveConfirmationCallback;
} }
unchunk(chunk) { unchunk(chunk) {
@ -1766,15 +1827,15 @@ class FileDigester {
this._bytesReceived += chunk.byteLength || chunk.size; this._bytesReceived += chunk.byteLength || chunk.size;
this._bytesReceivedSinceLastTime += chunk.byteLength || chunk.size; this._bytesReceivedSinceLastTime += chunk.byteLength || chunk.size;
// If more than half of maxBytesWithoutConfirmation received -> request more // If more than half of maxBytesWithoutConfirmation received -> send confirmation
if (this._receiveConfimationCallback && 2 * this._bytesReceivedSinceLastTime > this._maxBytesWithoutConfirmation) { if (2 * this._bytesReceivedSinceLastTime > this._maxBytesWithoutConfirmation) {
this._receiveConfimationCallback(this._bytesReceived); this._sendReceiveConfimationCallback(this._bytesReceived);
this._bytesReceivedSinceLastTime = 0; this._bytesReceivedSinceLastTime = 0;
} }
if (this._bytesReceived < this._size) return; if (this._bytesReceived < this._size) return;
// we are done // We are done receiving. Preferably use a file worker to process the file to prevent exceeding of available RAM
if (!window.Worker && !window.isSecureContext) { if (!window.Worker && !window.isSecureContext) {
this.processFileViaMemory(); this.processFileViaMemory();
return; return;
@ -1785,11 +1846,6 @@ class FileDigester {
processFileViaMemory() { processFileViaMemory() {
// Loads complete file into RAM which might lead to a page crash (Memory limit iOS Safari: ~380 MB) // Loads complete file into RAM which might lead to a page crash (Memory limit iOS Safari: ~380 MB)
if (window.iOS && this._totalSize > 250000000) {
alert('File is bigger than 250 MB and might crash the page on iOS. To be able to use a more efficient method use https and avoid private tabs as they have restricted functionality.')
}
Logger.warn('Big file transfers might exceed the RAM of the receiver. Use a secure context (https) to prevent this.');
const file = new File(this._buffer, this._name, { const file = new File(this._buffer, this._name, {
type: this._mime, type: this._mime,
lastModified: new Date().getTime() lastModified: new Date().getTime()