mirror of
https://github.com/schlagmichdoch/PairDrop.git
synced 2026-04-06 18:03:48 +00:00
Compare commits
2 Commits
send_large
...
add_turn_i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
880e8bd195 | ||
|
|
5a56251ee3 |
@@ -737,6 +737,10 @@
|
||||
<!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.-->
|
||||
<path d="M256 0c4.6 0 9.2 1 13.4 2.9L457.7 82.8c22 9.3 38.4 31 38.3 57.2c-.5 99.2-41.3 280.7-213.6 363.2c-16.7 8-36.1 8-52.8 0C57.3 420.7 16.5 239.2 16 140c-.1-26.2 16.3-47.9 38.3-57.2L242.7 2.9C246.8 1 251.4 0 256 0zm0 66.8V444.8C394 378 431.1 230.1 432 141.4L256 66.8l0 0z"></path>
|
||||
</symbol>
|
||||
<symbol id="turn-indicator" viewBox="0 0 576 512">
|
||||
<!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
|
||||
<path d="M0 80C0 53.5 21.5 32 48 32h96c26.5 0 48 21.5 48 48V96H384V80c0-26.5 21.5-48 48-48h96c26.5 0 48 21.5 48 48v96c0 26.5-21.5 48-48 48H432c-26.5 0-48-21.5-48-48V160H192v16c0 1.7-.1 3.4-.3 5L272 288h96c26.5 0 48 21.5 48 48v96c0 26.5-21.5 48-48 48H272c-26.5 0-48-21.5-48-48V336c0-1.7 .1-3.4 .3-5L144 224H48c-26.5 0-48-21.5-48-48V80z"></path>
|
||||
</symbol>
|
||||
</svg>
|
||||
<!-- Scripts -->
|
||||
<script src="scripts/localization.js" defer></script>
|
||||
|
||||
@@ -93,7 +93,6 @@
|
||||
"file-other-description-file": "and 1 other file",
|
||||
"file-other-description-image-plural": "and {{count}} other images",
|
||||
"file-other-description-file-plural": "and {{count}} other files",
|
||||
"text-message-description": "A large text message",
|
||||
"title-image": "Image",
|
||||
"title-file": "File",
|
||||
"title-image-plural": "Images",
|
||||
@@ -172,7 +171,6 @@
|
||||
"file-received-plural": "{{count}} Files Received",
|
||||
"file-transfer-requested": "File Transfer Requested",
|
||||
"image-transfer-requested": "Image Transfer Requested",
|
||||
"message-transfer-requested": "Message Transfer Requested",
|
||||
"message-received": "Message Received",
|
||||
"message-received-plural": "{{count}} Messages Received"
|
||||
},
|
||||
@@ -180,6 +178,7 @@
|
||||
"click-to-send-share-mode": "Click to send {{descriptor}}",
|
||||
"click-to-send": "Click to send files or right click to send a message",
|
||||
"connection-hash": "To verify the security of the end-to-end encryption, compare this security number on both devices",
|
||||
"turn-indicator": "Transfer over the Internet",
|
||||
"connecting": "Connecting…",
|
||||
"preparing": "Preparing…",
|
||||
"waiting": "Waiting…",
|
||||
|
||||
@@ -331,8 +331,6 @@ class Peer {
|
||||
this._isCaller = isCaller;
|
||||
this._peerId = peerId;
|
||||
|
||||
this._maxMessageSize = 65536; // 64 KB
|
||||
|
||||
this._roomIds = {};
|
||||
this._updateRoomIds(roomType, roomId);
|
||||
|
||||
@@ -354,15 +352,14 @@ class Peer {
|
||||
|
||||
// tidy up sender
|
||||
this._filesRequested = null;
|
||||
this._requestSent = null;
|
||||
this._chunker = null;
|
||||
|
||||
// tidy up receiver
|
||||
this._requestPending = null;
|
||||
this._requestAccepted = null;
|
||||
this._pendingRequest = null;
|
||||
this._acceptedRequest = null;
|
||||
this._totalBytesReceived = 0;
|
||||
this._digester = null;
|
||||
this._filesReceived = null;
|
||||
this._filesReceived = [];
|
||||
|
||||
// disable NoSleep if idle
|
||||
Events.fire('evaluate-no-sleep');
|
||||
@@ -500,7 +497,7 @@ class Peer {
|
||||
await this._onState(message.state);
|
||||
break;
|
||||
case 'transfer-request':
|
||||
await this._onTransferRequest(message.request);
|
||||
await this._onTransferRequest(message);
|
||||
break;
|
||||
case 'transfer-request-response':
|
||||
this._onTransferRequestResponse(message);
|
||||
@@ -627,7 +624,7 @@ class Peer {
|
||||
}
|
||||
|
||||
// File Sender Only
|
||||
async _sendFileTransferRequest(files, fileIsMessage = false) {
|
||||
async _sendFileTransferRequest(files) {
|
||||
this._state = Peer.STATE_PREPARE;
|
||||
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'prepare'});
|
||||
|
||||
@@ -645,13 +642,6 @@ class Peer {
|
||||
if (files[i].type.split('/')[0] !== 'image') imagesOnly = false;
|
||||
}
|
||||
|
||||
// request type 'images', 'files' or 'message
|
||||
const filesType = fileIsMessage
|
||||
? 'message'
|
||||
: imagesOnly
|
||||
? 'images'
|
||||
: 'files';
|
||||
|
||||
let dataUrl = "";
|
||||
if (files[0].type.split('/')[0] === 'image') {
|
||||
try {
|
||||
@@ -664,18 +654,13 @@ class Peer {
|
||||
this._state = Peer.STATE_TRANSFER_REQUEST_SENT;
|
||||
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'wait'});
|
||||
|
||||
const request = {
|
||||
this._filesRequested = files;
|
||||
|
||||
this._sendMessage({type: 'transfer-request',
|
||||
header: header,
|
||||
totalSize: totalSize,
|
||||
filesType: filesType,
|
||||
imagesOnly: imagesOnly,
|
||||
thumbnailDataUrl: dataUrl
|
||||
};
|
||||
|
||||
this._filesRequested = files;
|
||||
this._requestSent = request;
|
||||
|
||||
this._sendMessage({type: 'transfer-request',
|
||||
request: request
|
||||
});
|
||||
|
||||
}
|
||||
@@ -690,7 +675,7 @@ class Peer {
|
||||
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: 'idle'});
|
||||
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: null});
|
||||
this._reset();
|
||||
return;
|
||||
}
|
||||
@@ -763,7 +748,7 @@ class Peer {
|
||||
|
||||
if (!message.success) {
|
||||
Logger.warn('File could not be sent');
|
||||
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'idle'});
|
||||
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: null});
|
||||
this._reset();
|
||||
return;
|
||||
}
|
||||
@@ -775,9 +760,6 @@ class Peer {
|
||||
return;
|
||||
}
|
||||
|
||||
// If files sent was message -> abort and wait for text-received message
|
||||
if (this._requestSent.filesType === 'message') return;
|
||||
|
||||
// No more files in queue. Transfer is complete
|
||||
this._reset();
|
||||
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'transfer-complete'});
|
||||
@@ -788,7 +770,7 @@ class Peer {
|
||||
// File Receiver Only
|
||||
async _onTransferRequest(request) {
|
||||
// Only accept one request at a time per peer
|
||||
if (this._requestPending) {
|
||||
if (this._pendingRequest) {
|
||||
this._sendTransferRequestResponse(false);
|
||||
return;
|
||||
}
|
||||
@@ -808,7 +790,7 @@ class Peer {
|
||||
}
|
||||
|
||||
this._state = Peer.STATE_TRANSFER_REQUEST_RECEIVED;
|
||||
this._requestPending = request;
|
||||
this._pendingRequest = request;
|
||||
|
||||
// Automatically accept request if auto-accept is set to true via the Edit Paired Devices Dialog
|
||||
if (this._autoAccept) {
|
||||
@@ -845,7 +827,7 @@ class Peer {
|
||||
if (accepted) {
|
||||
this._state = Peer.STATE_RECEIVE_PROCEEDING;
|
||||
this._busy = true;
|
||||
this._requestAccepted = this._requestPending;
|
||||
this._acceptedRequest = this._pendingRequest;
|
||||
this._lastProgress = 0;
|
||||
this._totalBytesReceived = 0;
|
||||
this._filesReceived = [];
|
||||
@@ -910,7 +892,7 @@ class Peer {
|
||||
|
||||
// While transferring -> round progress to 4th digit. After transferring, set it to 1.
|
||||
let progress = this._digester
|
||||
? Math.floor(1e4 * (this._totalBytesReceived + this._digester._bytesReceived) / this._requestAccepted.totalSize) / 1e4
|
||||
? Math.floor(1e4 * (this._totalBytesReceived + this._digester._bytesReceived) / this._acceptedRequest.totalSize) / 1e4
|
||||
: 1;
|
||||
|
||||
Events.fire('set-progress', {peerId: this._peerId, progress: progress, status: 'receive'});
|
||||
@@ -931,34 +913,26 @@ class Peer {
|
||||
this._singleFileReceiveComplete(file);
|
||||
|
||||
// If less files received than header accepted -> wait for next file
|
||||
if (this._filesReceived.length < this._requestAccepted.header.length) return;
|
||||
if (this._filesReceived.length < this._acceptedRequest.header.length) return;
|
||||
|
||||
// We are done receiving
|
||||
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'receive'});
|
||||
|
||||
// If filesType is 'message' evaluate files as text
|
||||
if (this._requestAccepted.filesType === 'message') {
|
||||
this._textReceivedAsFile();
|
||||
return;
|
||||
}
|
||||
|
||||
// fileType is 'images' or 'files'
|
||||
this._allFilesReceiveComplete();
|
||||
}
|
||||
|
||||
_fitsAcceptedHeader(header) {
|
||||
if (!this._requestAccepted) {
|
||||
if (!this._acceptedRequest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const positionFile = this._filesReceived.length;
|
||||
|
||||
if (positionFile > this._requestAccepted.header.length - 1) {
|
||||
if (positionFile > this._acceptedRequest.header.length - 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if file header fits
|
||||
const acceptedHeader = this._requestAccepted.header[positionFile];
|
||||
const acceptedHeader = this._acceptedRequest.header[positionFile];
|
||||
|
||||
const sameSize = header.size === acceptedHeader.size;
|
||||
const sameType = header.mime === acceptedHeader.mime;
|
||||
@@ -981,11 +955,8 @@ class Peer {
|
||||
// Log speed from request to receive
|
||||
Logger.log(`File received.\n\nSize: ${size} MB\tDuration: ${duration} s\tSpeed: ${speed} MB/s`);
|
||||
|
||||
// Prevent App from downloading message txt file
|
||||
if (this._requestAccepted.filesType !== 'message') {
|
||||
// include for compatibility with 'Snapdrop & PairDrop for Android' app
|
||||
Events.fire('file-received', file);
|
||||
}
|
||||
// include for compatibility with 'Snapdrop & PairDrop for Android' app
|
||||
Events.fire('file-received', file);
|
||||
|
||||
this._filesReceived.push(file);
|
||||
|
||||
@@ -996,88 +967,39 @@ class Peer {
|
||||
Events.fire('files-received', {
|
||||
peerId: this._peerId,
|
||||
files: this._filesReceived,
|
||||
filesType: this._requestAccepted.filesType,
|
||||
totalSize: this._requestAccepted.totalSize
|
||||
imagesOnly: this._acceptedRequest.imagesOnly,
|
||||
totalSize: this._acceptedRequest.totalSize
|
||||
});
|
||||
this._reset();
|
||||
}
|
||||
|
||||
// Message Sender Only
|
||||
|
||||
_base64encode(text) {
|
||||
return btoa(unescape(encodeURIComponent(text)));
|
||||
}
|
||||
|
||||
async _sendText(text) {
|
||||
_sendText(text) {
|
||||
this._state = Peer.STATE_TEXT_SENT;
|
||||
|
||||
// Send text base64 encoded
|
||||
const base64encoded = this._base64encode(text);
|
||||
const message = {type: 'text', text: base64encoded};
|
||||
|
||||
// If text too big for connection -> send as file instead
|
||||
if (JSON.stringify(message).length > this._maxMessageSize) {
|
||||
await this._sendTextAsFile(text);
|
||||
return;
|
||||
}
|
||||
|
||||
this._sendMessage(message);
|
||||
}
|
||||
|
||||
async _sendTextAsFile(text) {
|
||||
// send text in chunks by using the file transfer api
|
||||
const file = new File([text], "pairdrop-message.txt", { type: 'text/plain' });
|
||||
await this._sendFileTransferRequest([file], true);
|
||||
const unescaped = btoa(unescape(encodeURIComponent(text)));
|
||||
this._sendMessage({ type: 'text', text: unescaped });
|
||||
}
|
||||
|
||||
_onTextReceiveComplete() {
|
||||
if (this._state !== Peer.STATE_TEXT_SENT && this._state !== Peer.STATE_TRANSFER_PROCEEDING) {
|
||||
if (this._state !== Peer.STATE_TEXT_SENT) {
|
||||
this._sendState();
|
||||
return;
|
||||
}
|
||||
this._reset();
|
||||
Events.fire('set-progress', { peerId: this._peerId, progress: 0, status: 'idle' });
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.message-transfer-completed"));
|
||||
}
|
||||
|
||||
// Message Receiver Only
|
||||
_base64decodeMessage(base64encoded){
|
||||
let decoded = "";
|
||||
_onText(message) {
|
||||
if (!message.text) return;
|
||||
try {
|
||||
decoded = decodeURIComponent(escape(atob(base64encoded)));
|
||||
const escaped = decodeURIComponent(escape(atob(message.text)));
|
||||
Events.fire('text-received', { text: escaped, peerId: this._peerId });
|
||||
this._sendMessage({ type: 'text-receive-complete' });
|
||||
}
|
||||
catch (e) {
|
||||
Logger.error(e);
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
_onText(message) {
|
||||
if (this._state !== Peer.STATE_IDLE) {
|
||||
this._abortTransfer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!message.text) return;
|
||||
|
||||
const text = this._base64decodeMessage(message.text);
|
||||
|
||||
Events.fire('text-received', { text: text, peerId: this._peerId });
|
||||
this._sendMessage({ type: 'text-receive-complete' });
|
||||
}
|
||||
|
||||
_textReceivedAsFile() {
|
||||
// Use FileReader to unpack text from file
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener("load", _ => {
|
||||
Events.fire('text-received', { text: reader.result, peerId: this._peerId });
|
||||
});
|
||||
|
||||
reader.readAsText(this._filesReceived[0]);
|
||||
|
||||
Events.fire('set-progress', { peerId: this._peerId, progress: 1, status: 'idle' });
|
||||
this._sendMessage({ type: 'text-receive-complete' });
|
||||
this._reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1107,6 +1029,33 @@ class RTCPeer extends Peer {
|
||||
);
|
||||
}
|
||||
|
||||
async _connectionType() {
|
||||
if (!this._conn) return "";
|
||||
|
||||
const stats = await this._conn.getStats(null);
|
||||
|
||||
let id;
|
||||
stats.forEach((report) => {
|
||||
if (report.type === "candidate-pair" && report.state === "succeeded") {
|
||||
id = report.localCandidateId;
|
||||
}
|
||||
});
|
||||
|
||||
if (!id) return "";
|
||||
|
||||
let connectionType;
|
||||
stats.forEach((report) => {
|
||||
if (report.id === id) {
|
||||
connectionType = report.candidateType;
|
||||
}
|
||||
});
|
||||
return connectionType;
|
||||
}
|
||||
|
||||
async _isTurn() {
|
||||
return await this._connectionType() === "relay";
|
||||
}
|
||||
|
||||
_messageChannelOpen() {
|
||||
return this._messageChannel && this._messageChannel.readyState === 'open';
|
||||
}
|
||||
@@ -1192,9 +1141,6 @@ class RTCPeer extends Peer {
|
||||
_onConnectionStateChange() {
|
||||
Logger.debug('RTC: Connection state changed:', this._conn.connectionState);
|
||||
switch (this._conn.connectionState) {
|
||||
case 'connected':
|
||||
this._setMaxMessageSize();
|
||||
break;
|
||||
case 'disconnected':
|
||||
this._refresh();
|
||||
break;
|
||||
@@ -1239,13 +1185,13 @@ class RTCPeer extends Peer {
|
||||
return channel;
|
||||
}
|
||||
|
||||
_onChannelOpened(e) {
|
||||
async _onChannelOpened(e) {
|
||||
Logger.debug(`RTC: Channel ${e.target.label} opened with`, this._peerId);
|
||||
|
||||
// wait until all channels are open
|
||||
if (!this._stable()) return;
|
||||
|
||||
Events.fire('peer-connected', {peerId: this._peerId, connectionHash: this.getConnectionHash()});
|
||||
Events.fire('peer-connected', {peerId: this._peerId, connectionHash: this.getConnectionHash(), connectionType: await this._connectionType()});
|
||||
super._onPeerConnected();
|
||||
|
||||
this._sendPendingOutboundMessaged();
|
||||
@@ -1412,12 +1358,6 @@ class RTCPeer extends Peer {
|
||||
this._server.send(message);
|
||||
}
|
||||
|
||||
_setMaxMessageSize() {
|
||||
this._maxMessageSize = this._conn && this._conn.sctp
|
||||
? this._conn.sctp.maxMessageSize
|
||||
: 262144; // 256 kB
|
||||
}
|
||||
|
||||
async _sendFile(file) {
|
||||
this._chunker = new FileChunkerRTC(
|
||||
file,
|
||||
@@ -1476,8 +1416,6 @@ class WSPeer extends Peer {
|
||||
this.rtcSupported = false;
|
||||
this.signalSuccessful = false;
|
||||
|
||||
this._maxMessageSize = 65536; // 64 KB
|
||||
|
||||
if (!this._isCaller) return; // we will listen for a caller
|
||||
|
||||
this._sendSignal();
|
||||
@@ -1696,8 +1634,8 @@ class PeersManager {
|
||||
await this.peers[message.to]._sendFileTransferRequest(files);
|
||||
}
|
||||
|
||||
async _onSendText(message) {
|
||||
await this.peers[message.to]._sendText(message.text);
|
||||
_onSendText(message) {
|
||||
this.peers[message.to]._sendText(message.text);
|
||||
}
|
||||
|
||||
_onPeerLeft(message) {
|
||||
@@ -1902,7 +1840,7 @@ class FileChunkerRTC extends FileChunker {
|
||||
|
||||
this._chunkSize = peerConnection && peerConnection.sctp
|
||||
? Math.min(peerConnection.sctp.maxMessageSize, 1048576) // 1 MB max
|
||||
: 262144; // 256 kB
|
||||
: 262144; // 256 KB
|
||||
|
||||
this._peerConnection = peerConnection;
|
||||
this._dataChannel = dataChannel;
|
||||
|
||||
@@ -25,7 +25,7 @@ class PeersUI {
|
||||
this.shareMode.text = "";
|
||||
|
||||
Events.on('peer-joined', e => this._onPeerJoined(e.detail.peer, e.detail.roomType, e.detail.roomId));
|
||||
Events.on('peer-connected', e => this._onPeerConnected(e.detail.peerId, e.detail.connectionHash));
|
||||
Events.on('peer-connected', e => this._onPeerConnected(e.detail.peerId, e.detail.connectionHash, e.detail.connectionType));
|
||||
Events.on('peer-connecting', e => this._onPeerConnecting(e.detail));
|
||||
Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail));
|
||||
Events.on('peers', e => this._onPeers(e.detail));
|
||||
@@ -110,12 +110,12 @@ class PeersUI {
|
||||
this.peerUIs[peer.id] = peerUI;
|
||||
}
|
||||
|
||||
_onPeerConnected(peerId, connectionHash) {
|
||||
_onPeerConnected(peerId, connectionHash, connectionType) {
|
||||
const peerUI = this.peerUIs[peerId];
|
||||
|
||||
if (!peerUI) return;
|
||||
|
||||
peerUI._peerConnected(true, connectionHash);
|
||||
peerUI._peerConnected(true, connectionHash, connectionType);
|
||||
|
||||
this._addPeerUIIfMissing(peerUI);
|
||||
}
|
||||
@@ -423,8 +423,8 @@ class PeerUI {
|
||||
this._connected = false;
|
||||
|
||||
this._currentProgress = 0;
|
||||
this._currentStatus = 'idle';
|
||||
this._oldStatus = 'idle';
|
||||
this._currentStatus = null
|
||||
this._oldStatus = null;
|
||||
|
||||
this._progressQueue = [];
|
||||
|
||||
@@ -459,7 +459,9 @@ class PeerUI {
|
||||
this.$displayName.textContent = this._displayName();
|
||||
this.$deviceName.textContent = this._deviceName();
|
||||
|
||||
this.updateTypesClassList();
|
||||
this._updateRoomTypeClasses();
|
||||
this._updateSameBrowserClass();
|
||||
this._updateWsPeerClass();
|
||||
|
||||
this.setStatus("connect");
|
||||
|
||||
@@ -472,14 +474,18 @@ class PeerUI {
|
||||
}
|
||||
|
||||
html() {
|
||||
let title= Localization.getTranslation("peer-ui.click-to-send");
|
||||
const titleLabelTranslation= Localization.getTranslation("peer-ui.click-to-send");
|
||||
const titleTurnTranslation= Localization.getTranslation("peer-ui.turn-indicator");
|
||||
|
||||
this.$el.innerHTML = `
|
||||
<label class="column center pointer" title="${title}">
|
||||
<label class="column center pointer" title="${titleLabelTranslation}">
|
||||
<input type="file" multiple/>
|
||||
<x-icon>
|
||||
<div class="icon-wrapper" shadow="1">
|
||||
<svg class="icon"><use xlink:href="#"/></svg>
|
||||
<div class="turn-indicator-wrapper center" title="${titleTurnTranslation}">
|
||||
<svg class="turn-indicator"><use xlink:href="#turn-indicator"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="highlight-wrapper center">
|
||||
<div class="highlight highlight-room-ip" shadow="1"></div>
|
||||
@@ -499,30 +505,49 @@ class PeerUI {
|
||||
</label>`;
|
||||
}
|
||||
|
||||
updateTypesClassList() {
|
||||
_updateRoomTypeClasses() {
|
||||
// Remove all classes
|
||||
this.$el.classList.remove('type-ip', 'type-secret', 'type-public-id', 'type-same-browser', 'ws-peer');
|
||||
this.$el.classList.remove('type-ip', 'type-secret', 'type-public-id');
|
||||
|
||||
// Add classes accordingly
|
||||
Object.keys(this._roomIds).forEach(roomType => this.$el.classList.add(`type-${roomType}`));
|
||||
}
|
||||
|
||||
_updateSameBrowserClass() {
|
||||
if (BrowserTabsConnector.peerIsSameBrowser(this._peer.id)) {
|
||||
this.$el.classList.add(`type-same-browser`);
|
||||
this.$el.classList.add('same-browser');
|
||||
}
|
||||
else {
|
||||
this.$el.classList.remove('same-browser');
|
||||
}
|
||||
}
|
||||
|
||||
_updateWsPeerClass() {
|
||||
if (!this._peer.rtcSupported || !window.isRtcSupported) {
|
||||
this.$el.classList.add('ws-peer');
|
||||
}
|
||||
else {
|
||||
this.$el.classList.remove('ws-peer');
|
||||
}
|
||||
}
|
||||
|
||||
_updateTurnClass() {
|
||||
if (this._connectionType === "relay") {
|
||||
this.$el.classList.add('turn');
|
||||
}
|
||||
else {
|
||||
this.$el.classList.remove('turn');
|
||||
}
|
||||
}
|
||||
|
||||
_addRoomId(roomType, roomId) {
|
||||
this._roomIds[roomType] = roomId;
|
||||
this.updateTypesClassList();
|
||||
this._updateRoomTypeClasses();
|
||||
}
|
||||
|
||||
_removeRoomId(roomType) {
|
||||
delete this._roomIds[roomType];
|
||||
this.updateTypesClassList();
|
||||
this._updateRoomTypeClasses();
|
||||
}
|
||||
|
||||
_onShareModeChanged(active = false, descriptor = "") {
|
||||
@@ -598,26 +623,32 @@ class PeerUI {
|
||||
});
|
||||
}
|
||||
|
||||
_peerConnected(connected = true, connectionHash = "") {
|
||||
_peerConnected(connected, connectionHash = "", connectionType = "") {
|
||||
if (connected) {
|
||||
this._connected = true;
|
||||
|
||||
// on reconnect
|
||||
this.setStatus(this._oldStatus);
|
||||
this._oldStatus = 'idle';
|
||||
this._oldStatus = null;
|
||||
|
||||
this._connectionHash = connectionHash;
|
||||
this._connectionType = connectionType;
|
||||
|
||||
this._updateSameBrowserClass();
|
||||
this._updateWsPeerClass();
|
||||
this._updateTurnClass();
|
||||
}
|
||||
else {
|
||||
this._connected = false;
|
||||
|
||||
if (this._oldStatus === 'idle' && this._currentStatus !== "connect") {
|
||||
if (!this._oldStatus && this._currentStatus !== "connect") {
|
||||
// save old status when reconnecting
|
||||
this._oldStatus = this._currentStatus;
|
||||
}
|
||||
this.setStatus("connect");
|
||||
|
||||
this._connectionHash = "";
|
||||
this._connectionType = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -787,10 +818,10 @@ class PeerUI {
|
||||
|
||||
clearTimeout(this.statusTimeout);
|
||||
|
||||
if (status === 'idle') {
|
||||
if (!status) {
|
||||
this.$el.removeAttribute('status');
|
||||
this.$el.querySelector('.status').innerHTML = '';
|
||||
this._currentStatus = 'idle';
|
||||
this._currentStatus = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -812,7 +843,7 @@ class PeerUI {
|
||||
|
||||
if (["transfer-complete", "receive-complete", "error"].includes(status)) {
|
||||
this.statusTimeout = setTimeout(() => {
|
||||
this.setProgress(0, 'idle');
|
||||
this.setProgress(0, null);
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
@@ -1031,35 +1062,30 @@ class ReceiveDialog extends Dialog {
|
||||
}
|
||||
}
|
||||
|
||||
_parseFileData(displayName, connectionHash, files, filesType, totalSize, badgeClassName) {
|
||||
if (filesType === 'message') {
|
||||
this.$fileOther.innerText = Localization.getTranslation("dialogs.text-message-description");
|
||||
_parseFileData(displayName, connectionHash, files, imagesOnly, totalSize, badgeClassName) {
|
||||
let fileOther = "";
|
||||
if (files.length === 2) {
|
||||
fileOther = imagesOnly
|
||||
? Localization.getTranslation("dialogs.file-other-description-image")
|
||||
: Localization.getTranslation("dialogs.file-other-description-file");
|
||||
}
|
||||
else {
|
||||
let fileOther = "";
|
||||
if (files.length === 2) {
|
||||
fileOther = filesType === 'images'
|
||||
? Localization.getTranslation("dialogs.file-other-description-image")
|
||||
: Localization.getTranslation("dialogs.file-other-description-file");
|
||||
}
|
||||
else if (files.length > 2) {
|
||||
fileOther = filesType === 'images'
|
||||
? Localization.getTranslation("dialogs.file-other-description-image-plural", null, {count: files.length - 1})
|
||||
: Localization.getTranslation("dialogs.file-other-description-file-plural", null, {count: files.length - 1});
|
||||
}
|
||||
|
||||
const fileName = files[0].name;
|
||||
const fileNameSplit = fileName.split('.');
|
||||
const fileExtension = '.' + fileNameSplit[fileNameSplit.length - 1];
|
||||
const fileStem = fileName.substring(0, fileName.length - fileExtension.length);
|
||||
|
||||
this.$fileOther.innerText = fileOther;
|
||||
this.$fileStem.innerText = fileStem;
|
||||
this.$fileExtension.innerText = fileExtension;
|
||||
else if (files.length > 2) {
|
||||
fileOther = imagesOnly
|
||||
? Localization.getTranslation("dialogs.file-other-description-image-plural", null, {count: files.length - 1})
|
||||
: Localization.getTranslation("dialogs.file-other-description-file-plural", null, {count: files.length - 1});
|
||||
}
|
||||
|
||||
this.$fileSize.innerText = this._formatFileSize(totalSize);
|
||||
const fileName = files[0].name;
|
||||
const fileNameSplit = fileName.split('.');
|
||||
const fileExtension = '.' + fileNameSplit[fileNameSplit.length - 1];
|
||||
const fileStem = fileName.substring(0, fileName.length - fileExtension.length);
|
||||
|
||||
const fileSize = this._formatFileSize(totalSize);
|
||||
|
||||
this.$fileOther.innerText = fileOther;
|
||||
this.$fileStem.innerText = fileStem;
|
||||
this.$fileExtension.innerText = fileExtension;
|
||||
this.$fileSize.innerText = fileSize;
|
||||
this.$displayName.innerText = displayName;
|
||||
this.$displayName.title = connectionHash;
|
||||
this.$displayName.classList.remove("badge-room-ip", "badge-room-secret", "badge-room-public-id");
|
||||
@@ -1075,12 +1101,12 @@ class ReceiveFileDialog extends ReceiveDialog {
|
||||
this.$downloadBtn = this.$el.querySelector('#download-btn');
|
||||
this.$shareBtn = this.$el.querySelector('#share-btn');
|
||||
|
||||
Events.on('files-received', e => this._onFilesReceived(e.detail.peerId, e.detail.files, e.detail.filesType, e.detail.totalSize));
|
||||
Events.on('files-received', e => this._onFilesReceived(e.detail.peerId, e.detail.files, e.detail.imagesOnly, e.detail.totalSize));
|
||||
this._filesDataQueue = [];
|
||||
}
|
||||
|
||||
async _onFilesReceived(peerId, files, filesType, totalSize) {
|
||||
const descriptor = this._getDescriptor(files, filesType);
|
||||
async _onFilesReceived(peerId, files, imagesOnly, totalSize) {
|
||||
const descriptor = this._getDescriptor(files, imagesOnly);
|
||||
const displayName = $(peerId).ui._displayName();
|
||||
const connectionHash = $(peerId).ui._connectionHash;
|
||||
const badgeClassName = $(peerId).ui._badgeClassName();
|
||||
@@ -1088,7 +1114,7 @@ class ReceiveFileDialog extends ReceiveDialog {
|
||||
this._filesDataQueue.push({
|
||||
peerId: peerId,
|
||||
files: files,
|
||||
filesType: filesType,
|
||||
imagesOnly: imagesOnly,
|
||||
totalSize: totalSize,
|
||||
descriptor: descriptor,
|
||||
displayName: displayName,
|
||||
@@ -1131,7 +1157,7 @@ class ReceiveFileDialog extends ReceiveDialog {
|
||||
this._data.displayName,
|
||||
this._data.connectionHash,
|
||||
this._data.files,
|
||||
this._data.filesType,
|
||||
this._data.imagesOnly,
|
||||
this._data.totalSize,
|
||||
this._data.badgeClassName
|
||||
);
|
||||
@@ -1157,15 +1183,15 @@ class ReceiveFileDialog extends ReceiveDialog {
|
||||
return window.iOS && this._data.totalSize > 250000000;
|
||||
}
|
||||
|
||||
_getDescriptor(files, filesType) {
|
||||
_getDescriptor(files, imagesOnly) {
|
||||
let descriptor;
|
||||
if (files.length === 1) {
|
||||
descriptor = filesType === 'images'
|
||||
descriptor = imagesOnly
|
||||
? Localization.getTranslation("dialogs.title-image")
|
||||
: Localization.getTranslation("dialogs.title-file");
|
||||
}
|
||||
else {
|
||||
descriptor = filesType === 'images'
|
||||
descriptor = imagesOnly
|
||||
? Localization.getTranslation("dialogs.title-image-plural")
|
||||
: Localization.getTranslation("dialogs.title-file-plural");
|
||||
}
|
||||
@@ -1504,17 +1530,15 @@ class ReceiveRequestDialog extends ReceiveDialog {
|
||||
_showRequestDialog(request, peerId) {
|
||||
this.correspondingPeerId = peerId;
|
||||
|
||||
const transferRequestTitleTranslation = request.filesType === 'message'
|
||||
? Localization.getTranslation('document-titles.message-transfer-requested')
|
||||
: request.filesType === 'images'
|
||||
? Localization.getTranslation('document-titles.image-transfer-requested')
|
||||
: Localization.getTranslation('document-titles.file-transfer-requested');
|
||||
const transferRequestTitleTranslation = request.imagesOnly
|
||||
? Localization.getTranslation('document-titles.image-transfer-requested')
|
||||
: Localization.getTranslation('document-titles.file-transfer-requested');
|
||||
|
||||
const displayName = $(peerId).ui._displayName();
|
||||
const connectionHash = $(peerId).ui._connectionHash;
|
||||
const badgeClassName = $(peerId).ui._badgeClassName();
|
||||
|
||||
this._parseFileData(displayName, connectionHash, request.header, request.filesType, request.totalSize, badgeClassName);
|
||||
this._parseFileData(displayName, connectionHash, request.header, request.imagesOnly, request.totalSize, badgeClassName);
|
||||
this._addThumbnailToPreviewBox(request.thumbnailDataUrl);
|
||||
|
||||
this.$receiveTitle.innerText = transferRequestTitleTranslation;
|
||||
@@ -2319,11 +2343,6 @@ class SendTextDialog extends Dialog {
|
||||
text: this.$text.innerText
|
||||
});
|
||||
this.hide();
|
||||
}
|
||||
|
||||
hide() {
|
||||
super.hide();
|
||||
this.$submit.setAttribute('disabled', true);
|
||||
setTimeout(() => this.$text.innerText = "", 300);
|
||||
}
|
||||
}
|
||||
@@ -2751,7 +2770,7 @@ class Notifications {
|
||||
|
||||
|
||||
Events.on('text-received', e => this._messageNotification(e.detail.text, e.detail.peerId));
|
||||
Events.on('files-received', e => this._downloadNotification(e.detail.files, e.detail.filesType));
|
||||
Events.on('files-received', e => this._downloadNotification(e.detail.files, e.detail.imagesOnly));
|
||||
Events.on('files-transfer-request', e => this._requestNotification(e.detail.request, e.detail.peerId));
|
||||
// Todo on 'files-transfer-request-abort' remove notification
|
||||
}
|
||||
@@ -2835,26 +2854,31 @@ class Notifications {
|
||||
}
|
||||
|
||||
_requestNotification(request, peerId) {
|
||||
// Do not notify user if page is visible
|
||||
if (document.visibilityState === 'visible') return;
|
||||
if (document.visibilityState !== 'visible') {
|
||||
let imagesOnly = request.header.every(header => header.mime.split('/')[0] === 'image');
|
||||
let displayName = $(peerId).querySelector('.name').textContent;
|
||||
|
||||
const clickToShowTranslation = Localization.getTranslation("notifications.click-to-show");
|
||||
const displayName = $(peerId).querySelector('.name').textContent;
|
||||
let descriptor;
|
||||
if (request.header.length === 1) {
|
||||
descriptor = imagesOnly
|
||||
? Localization.getTranslation("dialogs.title-image")
|
||||
: Localization.getTranslation("dialogs.title-file");
|
||||
}
|
||||
else {
|
||||
descriptor = imagesOnly
|
||||
? Localization.getTranslation("dialogs.title-image-plural")
|
||||
: Localization.getTranslation("dialogs.title-file-plural");
|
||||
}
|
||||
|
||||
const transferRequestTitleTranslation = request.filesType === 'message'
|
||||
? Localization.getTranslation('document-titles.message-transfer-requested')
|
||||
: request.filesType === 'images'
|
||||
? Localization.getTranslation('document-titles.image-transfer-requested')
|
||||
: Localization.getTranslation('document-titles.file-transfer-requested');
|
||||
let title = Localization
|
||||
.getTranslation("notifications.request-title", null, {
|
||||
name: displayName,
|
||||
count: request.header.length,
|
||||
descriptor: descriptor.toLowerCase()
|
||||
});
|
||||
|
||||
let title = Localization
|
||||
.getTranslation("notifications.request-title", null, {
|
||||
name: displayName,
|
||||
count: request.header.length,
|
||||
descriptor: transferRequestTitleTranslation.toLowerCase()
|
||||
});
|
||||
|
||||
this._notify(title, clickToShowTranslation);
|
||||
const notification = this._notify(title, Localization.getTranslation("notifications.click-to-show"));
|
||||
}
|
||||
}
|
||||
|
||||
_download(notification) {
|
||||
|
||||
@@ -279,6 +279,40 @@ x-peer[drop] x-icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
x-peer:not(.turn) .turn-indicator-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
x-peer .turn-indicator-wrapper {
|
||||
z-index: 0;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 43px;
|
||||
right: 23px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
x-peer .turn-indicator-wrapper:before {
|
||||
z-index: -1;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: white;
|
||||
border-radius: 50%;
|
||||
border: solid 2px var(--primary-color);
|
||||
}
|
||||
|
||||
x-peer .turn-indicator {
|
||||
margin: auto;
|
||||
width: calc(var(--icon-size) / 3);
|
||||
height: calc(var(--icon-size) / 3);
|
||||
fill: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Checkboxes as slider */
|
||||
|
||||
.switch {
|
||||
|
||||
Reference in New Issue
Block a user