mirror of
https://github.com/schlagmichdoch/PairDrop.git
synced 2025-04-20 15:06:15 -04:00
Implement new status 'connecting', automatic reconnect on disconnect and auto resume of transfer + sending of queued messages. (fixes #260 and #247)
This commit is contained in:
parent
b36105b1cf
commit
f22abca783
3 changed files with 589 additions and 316 deletions
|
@ -176,9 +176,11 @@
|
||||||
"click-to-send-share-mode": "Click to send {{descriptor}}",
|
"click-to-send-share-mode": "Click to send {{descriptor}}",
|
||||||
"click-to-send": "Click to send files or right click to send a message",
|
"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",
|
"connection-hash": "To verify the security of the end-to-end encryption, compare this security number on both devices",
|
||||||
|
"connecting": "Connecting…",
|
||||||
"preparing": "Preparing…",
|
"preparing": "Preparing…",
|
||||||
"waiting": "Waiting…",
|
"waiting": "Waiting…",
|
||||||
"processing": "Processing…",
|
"processing": "Processing…",
|
||||||
"transferring": "Transferring…"
|
"transferring": "Transferring…",
|
||||||
|
"receiving": "Receiving…"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,9 @@ class ServerConnection {
|
||||||
_onOpen() {
|
_onOpen() {
|
||||||
console.log('WS: server connected');
|
console.log('WS: server connected');
|
||||||
Events.fire('ws-connected');
|
Events.fire('ws-connected');
|
||||||
if (this._isReconnect) Events.fire('notify-user', Localization.getTranslation("notifications.connected"));
|
if (this._isReconnect) {
|
||||||
|
Events.fire('notify-user', Localization.getTranslation("notifications.connected"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPairDeviceInitiate() {
|
_onPairDeviceInitiate() {
|
||||||
|
@ -101,6 +103,7 @@ class ServerConnection {
|
||||||
|
|
||||||
_onPairDeviceJoin(pairKey) {
|
_onPairDeviceJoin(pairKey) {
|
||||||
if (!this._isConnected()) {
|
if (!this._isConnected()) {
|
||||||
|
// Todo: instead use pending outbound ws queue
|
||||||
setTimeout(() => this._onPairDeviceJoin(pairKey), 1000);
|
setTimeout(() => this._onPairDeviceJoin(pairKey), 1000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -336,6 +339,10 @@ class Peer {
|
||||||
this._evaluateAutoAccept();
|
this._evaluateAutoAccept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_setIsCaller(isCaller) {
|
||||||
|
this._isCaller = isCaller;
|
||||||
|
}
|
||||||
|
|
||||||
sendJSON(message) {
|
sendJSON(message) {
|
||||||
this._send(JSON.stringify(message));
|
this._send(JSON.stringify(message));
|
||||||
}
|
}
|
||||||
|
@ -433,6 +440,14 @@ class Peer {
|
||||||
: false;
|
: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onPeerConnected() {
|
||||||
|
if (this._digester) {
|
||||||
|
// Reconnection during receiving of file. Send request for restart
|
||||||
|
const offset = this._digester._bytesReceived;
|
||||||
|
this._requestResendFromOffset(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async requestFileTransfer(files) {
|
async requestFileTransfer(files) {
|
||||||
let header = [];
|
let header = [];
|
||||||
let totalSize = 0;
|
let totalSize = 0;
|
||||||
|
@ -472,8 +487,8 @@ class Peer {
|
||||||
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'wait'})
|
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'wait'})
|
||||||
}
|
}
|
||||||
|
|
||||||
async 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]);
|
||||||
}
|
}
|
||||||
this._filesRequested = null
|
this._filesRequested = null
|
||||||
|
@ -487,7 +502,7 @@ class Peer {
|
||||||
this._sendFile(file);
|
this._sendFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _sendFile(file) {
|
_sendFile(file) {
|
||||||
this.sendJSON({
|
this.sendJSON({
|
||||||
type: 'header',
|
type: 'header',
|
||||||
size: file.size,
|
size: file.size,
|
||||||
|
@ -508,11 +523,21 @@ class Peer {
|
||||||
this.sendJSON({ type: 'partition-received', offset: offset });
|
this.sendJSON({ type: 'partition-received', offset: offset });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_requestResendFromOffset(offset) {
|
||||||
|
this.sendJSON({ type: 'request-resend-from-offset', offset: offset });
|
||||||
|
}
|
||||||
|
|
||||||
_sendNextPartition() {
|
_sendNextPartition() {
|
||||||
if (!this._chunker || this._chunker.isFileEnd()) return;
|
if (!this._chunker || this._chunker.isFileEnd()) return;
|
||||||
this._chunker.nextPartition();
|
this._chunker.nextPartition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onRequestResendFromOffset(offset) {
|
||||||
|
console.log("Restart requested from offset:", offset)
|
||||||
|
if (!this._chunker) return;
|
||||||
|
this._chunker._restartFromOffset(offset);
|
||||||
|
}
|
||||||
|
|
||||||
_sendProgress(progress) {
|
_sendProgress(progress) {
|
||||||
this.sendJSON({ type: 'progress', progress: progress });
|
this.sendJSON({ type: 'progress', progress: progress });
|
||||||
}
|
}
|
||||||
|
@ -522,25 +547,35 @@ class Peer {
|
||||||
this._onChunkReceived(message);
|
this._onChunkReceived(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const messageJSON = JSON.parse(message);
|
|
||||||
switch (messageJSON.type) {
|
try {
|
||||||
|
message = JSON.parse(message);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Peer: Received JSON is malformed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (message.type) {
|
||||||
case 'request':
|
case 'request':
|
||||||
this._onFilesTransferRequest(messageJSON);
|
this._onFilesTransferRequest(message);
|
||||||
break;
|
break;
|
||||||
case 'header':
|
case 'header':
|
||||||
this._onFileHeader(messageJSON);
|
this._onFileHeader(message);
|
||||||
break;
|
break;
|
||||||
case 'partition':
|
case 'partition':
|
||||||
this._onReceivedPartitionEnd(messageJSON);
|
this._onReceivedPartitionEnd(message);
|
||||||
break;
|
break;
|
||||||
case 'partition-received':
|
case 'partition-received':
|
||||||
this._sendNextPartition();
|
this._sendNextPartition();
|
||||||
break;
|
break;
|
||||||
case 'progress':
|
case 'progress':
|
||||||
this._onDownloadProgress(messageJSON.progress);
|
this._onProgress(message.progress);
|
||||||
|
break;
|
||||||
|
case 'request-resend-from-offset':
|
||||||
|
this._onRequestResendFromOffset(message.offset);
|
||||||
break;
|
break;
|
||||||
case 'files-transfer-response':
|
case 'files-transfer-response':
|
||||||
this._onFileTransferRequestResponded(messageJSON);
|
this._onFileTransferRequestResponded(message);
|
||||||
break;
|
break;
|
||||||
case 'file-transfer-complete':
|
case 'file-transfer-complete':
|
||||||
this._onFileTransferCompleted();
|
this._onFileTransferCompleted();
|
||||||
|
@ -549,10 +584,10 @@ class Peer {
|
||||||
this._onMessageTransferCompleted();
|
this._onMessageTransferCompleted();
|
||||||
break;
|
break;
|
||||||
case 'text':
|
case 'text':
|
||||||
this._onTextReceived(messageJSON);
|
this._onTextReceived(message);
|
||||||
break;
|
break;
|
||||||
case 'display-name-changed':
|
case 'display-name-changed':
|
||||||
this._onDisplayNameChanged(messageJSON);
|
this._onDisplayNameChanged(message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -620,21 +655,28 @@ class Peer {
|
||||||
if(!this._digester || !(chunk.byteLength || chunk.size)) return;
|
if(!this._digester || !(chunk.byteLength || chunk.size)) return;
|
||||||
|
|
||||||
this._digester.unchunk(chunk);
|
this._digester.unchunk(chunk);
|
||||||
|
|
||||||
const progress = this._digester.progress;
|
const progress = this._digester.progress;
|
||||||
|
|
||||||
if (progress > 1) {
|
if (progress > 1) {
|
||||||
this._abortTransfer();
|
this._abortTransfer();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._onDownloadProgress(progress);
|
if (progress === 1) {
|
||||||
|
this._digester = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Events.fire('set-progress', {peerId: this._peerId, progress: progress, status: 'receive'});
|
||||||
|
|
||||||
// occasionally notify sender about our progress
|
// occasionally notify sender about our progress
|
||||||
if (progress - this._lastProgress < 0.005 && progress !== 1) return;
|
if (progress - this._lastProgress >= 0.005 || progress === 1) {
|
||||||
this._lastProgress = progress;
|
this._lastProgress = progress;
|
||||||
this._sendProgress(progress);
|
this._sendProgress(progress);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDownloadProgress(progress) {
|
_onProgress(progress) {
|
||||||
Events.fire('set-progress', {peerId: this._peerId, progress: progress, status: 'transfer'});
|
Events.fire('set-progress', {peerId: this._peerId, progress: progress, status: 'transfer'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -654,25 +696,33 @@ class Peer {
|
||||||
Events.fire('file-received', fileBlob);
|
Events.fire('file-received', fileBlob);
|
||||||
|
|
||||||
this._filesReceived.push(fileBlob);
|
this._filesReceived.push(fileBlob);
|
||||||
if (!this._requestAccepted.header.length) {
|
|
||||||
this._busy = false;
|
if (this._requestAccepted.header.length) return;
|
||||||
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'process'});
|
|
||||||
Events.fire('files-received', {peerId: this._peerId, files: this._filesReceived, imagesOnly: this._requestAccepted.imagesOnly, totalSize: this._requestAccepted.totalSize});
|
// We are done receiving
|
||||||
this._filesReceived = [];
|
this._busy = false;
|
||||||
this._requestAccepted = null;
|
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'process'});
|
||||||
}
|
Events.fire('files-received', {
|
||||||
|
peerId: this._peerId,
|
||||||
|
files: this._filesReceived,
|
||||||
|
imagesOnly: this._requestAccepted.imagesOnly,
|
||||||
|
totalSize: this._requestAccepted.totalSize
|
||||||
|
});
|
||||||
|
this._filesReceived = [];
|
||||||
|
this._requestAccepted = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onFileTransferCompleted() {
|
_onFileTransferCompleted() {
|
||||||
this._chunker = null;
|
this._chunker = null;
|
||||||
if (!this._filesQueue.length) {
|
if (this._filesQueue.length) {
|
||||||
this._busy = false;
|
|
||||||
Events.fire('notify-user', Localization.getTranslation("notifications.file-transfer-completed"));
|
|
||||||
Events.fire('files-sent'); // used by 'Snapdrop & PairDrop for Android' app
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._dequeueFile();
|
this._dequeueFile();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No more files in queue. Transfer is complete
|
||||||
|
this._busy = false;
|
||||||
|
Events.fire('notify-user', Localization.getTranslation("notifications.file-transfer-completed"));
|
||||||
|
Events.fire('files-sent'); // used by 'Snapdrop & PairDrop for Android' app
|
||||||
}
|
}
|
||||||
|
|
||||||
_onFileTransferRequestResponded(message) {
|
_onFileTransferRequestResponded(message) {
|
||||||
|
@ -725,99 +775,293 @@ class RTCPeer extends Peer {
|
||||||
super(serverConnection, isCaller, peerId, roomType, roomId);
|
super(serverConnection, isCaller, peerId, roomType, roomId);
|
||||||
|
|
||||||
this.rtcSupported = true;
|
this.rtcSupported = true;
|
||||||
this.rtcConfig = rtcConfig
|
this.rtcConfig = rtcConfig;
|
||||||
|
|
||||||
|
this.pendingInboundMessages = [];
|
||||||
|
this.pendingOutboundMessages = [];
|
||||||
|
|
||||||
|
Events.on('beforeunload', e => this._onBeforeUnload(e));
|
||||||
|
Events.on('pagehide', _ => this._onPageHide());
|
||||||
|
|
||||||
if (!this._isCaller) return; // we will listen for a caller
|
|
||||||
this._connect();
|
this._connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
_connect() {
|
_isConnected() {
|
||||||
if (!this._conn || this._conn.signalingState === "closed") this._openConnection();
|
return this._conn && this._conn.connectionState === 'connected';
|
||||||
|
}
|
||||||
|
|
||||||
if (this._isCaller) {
|
_isConnecting() {
|
||||||
this._openChannel();
|
return this._conn
|
||||||
}
|
&& (
|
||||||
else {
|
this._conn.connectionState === 'new'
|
||||||
this._conn.ondatachannel = e => this._onChannelOpened(e);
|
|| this._conn.connectionState === 'connecting'
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_isChannelOpen() {
|
||||||
|
return this._channel && this._channel.readyState === 'open';
|
||||||
|
}
|
||||||
|
|
||||||
|
_isChannelConnecting() {
|
||||||
|
return this._channel && this._channel.readyState === 'connecting';
|
||||||
|
}
|
||||||
|
|
||||||
|
_isStable() {
|
||||||
|
return this._isChannelOpen() && this._isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
_connect() {
|
||||||
|
if (this._isStable()) return;
|
||||||
|
|
||||||
|
Events.fire('peer-connecting', this._peerId);
|
||||||
|
|
||||||
|
this._openConnection();
|
||||||
|
// TOdo: one channel for messages - one for data?
|
||||||
|
this._openChannel();
|
||||||
}
|
}
|
||||||
|
|
||||||
_openConnection() {
|
_openConnection() {
|
||||||
this._conn = new RTCPeerConnection(this.rtcConfig);
|
const conn = new RTCPeerConnection(this.rtcConfig);
|
||||||
this._conn.onicecandidate = e => this._onIceCandidate(e);
|
conn.onnegotiationneeded = _ => this._onNegotiationNeeded();
|
||||||
this._conn.onicecandidateerror = e => this._onError(e);
|
conn.onsignalingstatechange = _ => this._onSignalingStateChanged();
|
||||||
this._conn.onconnectionstatechange = _ => this._onConnectionStateChange();
|
conn.oniceconnectionstatechange = _ => this._onIceConnectionStateChange();
|
||||||
this._conn.oniceconnectionstatechange = e => this._onIceConnectionStateChange(e);
|
conn.onicegatheringstatechange = _ => this._onIceGatheringStateChanged();
|
||||||
|
conn.onconnectionstatechange = _ => this._onConnectionStateChange();
|
||||||
|
conn.onicecandidate = e => this._onIceCandidate(e);
|
||||||
|
conn.onicecandidateerror = e => this._onIceCandidateError(e);
|
||||||
|
|
||||||
|
this._conn = conn;
|
||||||
|
|
||||||
|
this._evaluatePendingInboundMessages()
|
||||||
|
.then((count) => {
|
||||||
|
if (count) {
|
||||||
|
console.log("Pending inbound messages evaluated.");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_openChannel() {
|
async _onNegotiationNeeded() {
|
||||||
if (!this._conn) return;
|
console.log('RTC: Negotiation needed');
|
||||||
|
|
||||||
const channel = this._conn.createDataChannel('data-channel', {
|
if (this._isCaller) {
|
||||||
ordered: true,
|
// Creating offer if required
|
||||||
reliable: true // Obsolete. See https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/reliable
|
console.log('RTC: Creating offer');
|
||||||
});
|
const description = await this._conn.createOffer();
|
||||||
channel.onopen = e => this._onChannelOpened(e);
|
await this._handleLocalDescription(description);
|
||||||
channel.onerror = e => this._onError(e);
|
}
|
||||||
|
|
||||||
this._conn
|
|
||||||
.createOffer()
|
|
||||||
.then(d => this._onDescription(d))
|
|
||||||
.catch(e => this._onError(e));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDescription(description) {
|
_onSignalingStateChanged() {
|
||||||
// description.sdp = description.sdp.replace('b=AS:30', 'b=AS:1638400');
|
console.log('RTC: Signaling state changed:', this._conn.signalingState);
|
||||||
this._conn
|
}
|
||||||
.setLocalDescription(description)
|
|
||||||
.then(_ => this._sendSignal({ sdp: description }))
|
_onIceConnectionStateChange() {
|
||||||
.catch(e => this._onError(e));
|
console.log('RTC: ICE connection state changed:', this._conn.iceConnectionState);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onIceGatheringStateChanged() {
|
||||||
|
console.log('RTC: ICE gathering state changed:', this._conn.iceConnectionState);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onConnectionStateChange() {
|
||||||
|
console.log('RTC: Connection state changed:', this._conn.connectionState);
|
||||||
|
switch (this._conn.connectionState) {
|
||||||
|
case 'disconnected':
|
||||||
|
this._refresh();
|
||||||
|
break;
|
||||||
|
case 'failed':
|
||||||
|
console.warn('RTC connection failed');
|
||||||
|
// TOdo: implement ws fallback as real fallback
|
||||||
|
this._refresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onIceCandidate(event) {
|
_onIceCandidate(event) {
|
||||||
if (!event.candidate) return;
|
this._handleLocalCandidate(event.candidate);
|
||||||
this._sendSignal({ ice: event.candidate });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onServerMessage(message) {
|
_onIceCandidateError(error) {
|
||||||
if (!this._conn) this._connect();
|
console.error(error);
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
else if (message.ice) {
|
|
||||||
this._conn
|
|
||||||
.addIceCandidate(new RTCIceCandidate(message.ice))
|
|
||||||
.catch(e => this._onError(e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onChannelOpened(event) {
|
_openChannel() {
|
||||||
console.log('RTC: channel opened with', this._peerId);
|
const channel = this._conn.createDataChannel('data-channel', {
|
||||||
const channel = event.channel || event.target;
|
ordered: true,
|
||||||
|
negotiated: true,
|
||||||
|
id: 0
|
||||||
|
});
|
||||||
channel.binaryType = 'arraybuffer';
|
channel.binaryType = 'arraybuffer';
|
||||||
channel.onmessage = e => this._onMessage(e.data);
|
channel.onopen = _ => this._onChannelOpened();
|
||||||
channel.onclose = _ => this._onChannelClosed();
|
channel.onclose = _ => this._onChannelClosed();
|
||||||
|
channel.onerror = e => this._onChannelError(e);
|
||||||
|
channel.onmessage = e => this._onMessage(e.data);
|
||||||
|
|
||||||
this._channel = channel;
|
this._channel = channel;
|
||||||
Events.on('beforeunload', e => this._onBeforeUnload(e));
|
|
||||||
Events.on('pagehide', _ => this._onPageHide());
|
|
||||||
Events.fire('peer-connected', {peerId: this._peerId, connectionHash: this.getConnectionHash()});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onMessage(message) {
|
_onChannelOpened() {
|
||||||
if (typeof message === 'string') {
|
console.log('RTC: Channel opened with', this._peerId);
|
||||||
console.log('RTC:', JSON.parse(message));
|
console.debug(this.getConnectionHash())
|
||||||
|
console.debug(this._conn)
|
||||||
|
console.debug(this._channel)
|
||||||
|
Events.fire('peer-connected', {peerId: this._peerId, connectionHash: this.getConnectionHash()});
|
||||||
|
super._onPeerConnected();
|
||||||
|
while (this._isChannelOpen() && this.pendingOutboundMessages.length > 0) {
|
||||||
|
this._sendViaChannel(this.pendingOutboundMessages.shift());
|
||||||
}
|
}
|
||||||
super._onMessage(message);
|
}
|
||||||
|
|
||||||
|
_onChannelClosed() {
|
||||||
|
console.log('RTC: Channel closed', this._peerId);
|
||||||
|
this._refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onChannelError(error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async _handleLocalDescription(localDescription) {
|
||||||
|
await this._conn.setLocalDescription(localDescription);
|
||||||
|
|
||||||
|
console.log("RTC: Sending local description");
|
||||||
|
this._sendSignal({ signalType: 'description', description: localDescription });
|
||||||
|
}
|
||||||
|
|
||||||
|
async _handleRemoteDescription(remoteDescription) {
|
||||||
|
console.log("RTC: Received remote description");
|
||||||
|
await this._conn.setRemoteDescription(remoteDescription);
|
||||||
|
|
||||||
|
if (!this._isCaller) {
|
||||||
|
// Creating answer if required
|
||||||
|
console.log('RTC: Creating answer');
|
||||||
|
const localDescription = await this._conn.createAnswer();
|
||||||
|
await this._handleLocalDescription(localDescription);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleLocalCandidate(candidate) {
|
||||||
|
console.log("RTC: Sending local candidate");
|
||||||
|
this._sendSignal({ signalType: 'candidate', candidate: candidate });
|
||||||
|
|
||||||
|
if (candidate === null) {
|
||||||
|
this.localIceCandidatesSent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _handleRemoteCandidate(candidate) {
|
||||||
|
console.log("RTC: Received remote candidate");
|
||||||
|
if (candidate !== null) {
|
||||||
|
await this._conn.addIceCandidate(candidate);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.remoteIceCandidatesReceived = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _evaluatePendingInboundMessages() {
|
||||||
|
let inboundMessagesEvaluatedCount = 0;
|
||||||
|
while (this.pendingInboundMessages.length > 0) {
|
||||||
|
const message = this.pendingInboundMessages.shift();
|
||||||
|
console.log("Evaluate pending inbound message:", message);
|
||||||
|
await this.onServerMessage(message);
|
||||||
|
inboundMessagesEvaluatedCount++;
|
||||||
|
}
|
||||||
|
return inboundMessagesEvaluatedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onServerMessage(message) {
|
||||||
|
if (this._conn === null) {
|
||||||
|
console.debug("Not ready yet. Pending needed indeed?")
|
||||||
|
this.pendingInboundMessages.push(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (message.signalType) {
|
||||||
|
case 'description':
|
||||||
|
await this._handleRemoteDescription(message.description);
|
||||||
|
break;
|
||||||
|
case 'candidate':
|
||||||
|
await this._handleRemoteCandidate(message.candidate);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn(this.name, 'Unknown message type:', message.type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_disconnect() {
|
||||||
|
Events.fire('peer-disconnected', this._peerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
_refresh() {
|
||||||
|
Events.fire('peer-connecting', this._peerId);
|
||||||
|
this._closeChannelAndConnection();
|
||||||
|
|
||||||
|
this._connect(); // reopen the channel
|
||||||
|
}
|
||||||
|
|
||||||
|
_closeChannelAndConnection() {
|
||||||
|
if (this._channel) {
|
||||||
|
this._channel.onopen = null;
|
||||||
|
this._channel.onclose = null;
|
||||||
|
this._channel.onerror = null;
|
||||||
|
this._channel.onmessage = null;
|
||||||
|
this._channel.close();
|
||||||
|
this._channel = null;
|
||||||
|
}
|
||||||
|
if (this._conn) {
|
||||||
|
this._conn.onnegotiationneeded = null;
|
||||||
|
this._conn.onsignalingstatechange = null;
|
||||||
|
this._conn.oniceconnectionstatechange = null;
|
||||||
|
this._conn.onicegatheringstatechange = null;
|
||||||
|
this._conn.onconnectionstatechange = null;
|
||||||
|
this._conn.onicecandidate = null;
|
||||||
|
this._conn.onicecandidateerror = null;
|
||||||
|
this._conn.close();
|
||||||
|
this._conn = null;
|
||||||
|
}
|
||||||
|
this.localIceCandidatesSent = false;
|
||||||
|
this.remoteIceCandidatesReceived = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onBeforeUnload(e) {
|
||||||
|
if (this._busy) {
|
||||||
|
e.preventDefault();
|
||||||
|
return Localization.getTranslation("notifications.unfinished-transfers-warning");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onPageHide() {
|
||||||
|
this._disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
_send(message) {
|
||||||
|
// Todo: if channel or connection is closed or disconnected: do not send
|
||||||
|
// put messages in queue and send after reconnection.
|
||||||
|
// this._pendingMessages[];
|
||||||
|
if (!this._isStable() || this.pendingOutboundMessages.length > 0) {
|
||||||
|
// queue messages if not connected OR if connected AND queue is not empty
|
||||||
|
this.pendingOutboundMessages.push(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._sendViaChannel(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendViaChannel(message) {
|
||||||
|
this._channel.send(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendSignal(message) {
|
||||||
|
message.type = 'signal';
|
||||||
|
message.to = this._peerId;
|
||||||
|
message.roomType = this._getRoomTypes()[0];
|
||||||
|
message.roomId = this._roomIds[this._getRoomTypes()[0]];
|
||||||
|
this._server.send(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendDisplayName(displayName) {
|
||||||
|
super.sendDisplayName(displayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
getConnectionHash() {
|
getConnectionHash() {
|
||||||
|
@ -886,54 +1130,12 @@ class RTCPeer extends Peer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onIceConnectionStateChange() {
|
_onMessage(message) {
|
||||||
switch (this._conn.iceConnectionState) {
|
if (typeof message === 'string') {
|
||||||
case 'failed':
|
// Todo: Test speed increase without prints? --> print only on debug mode via URL argument `?debug_mode=true`
|
||||||
this._onError('ICE Gathering failed');
|
console.log('RTC:', JSON.parse(message));
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log('ICE Gathering', this._conn.iceConnectionState);
|
|
||||||
}
|
}
|
||||||
}
|
super._onMessage(message);
|
||||||
|
|
||||||
_onError(error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
_send(message) {
|
|
||||||
if (!this._channel) this.refresh();
|
|
||||||
this._channel.send(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
_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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
_isConnected() {
|
|
||||||
return this._channel && this._channel.readyState === 'open';
|
|
||||||
}
|
|
||||||
|
|
||||||
_isConnecting() {
|
|
||||||
return this._channel && this._channel.readyState === 'connecting';
|
|
||||||
}
|
|
||||||
|
|
||||||
sendDisplayName(displayName) {
|
|
||||||
if (!this._isConnected()) return;
|
|
||||||
super.sendDisplayName(displayName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1020,9 +1222,7 @@ class PeersManager {
|
||||||
this.peers[peerId].onServerMessage(message);
|
this.peers[peerId].onServerMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
_refreshPeer(peerId, roomType, roomId) {
|
_refreshPeer(isCaller, peerId, roomType, roomId) {
|
||||||
if (!this._peerExists(peerId)) return false;
|
|
||||||
|
|
||||||
const peer = this.peers[peerId];
|
const peer = this.peers[peerId];
|
||||||
const roomTypesDiffer = Object.keys(peer._roomIds)[0] !== roomType;
|
const roomTypesDiffer = Object.keys(peer._roomIds)[0] !== roomType;
|
||||||
const roomIdsDiffer = peer._roomIds[roomType] !== roomId;
|
const roomIdsDiffer = peer._roomIds[roomType] !== roomId;
|
||||||
|
@ -1036,17 +1236,22 @@ class PeersManager {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
peer.refresh();
|
// reconnect peer - caller/waiter might be switched
|
||||||
|
peer._setIsCaller(isCaller);
|
||||||
|
peer._refresh();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_createOrRefreshPeer(isCaller, peerId, roomType, roomId, rtcSupported) {
|
_createOrRefreshPeer(isCaller, peerId, roomType, roomId, rtcSupported) {
|
||||||
if (this._peerExists(peerId)) {
|
if (this._peerExists(peerId)) {
|
||||||
this._refreshPeer(peerId, roomType, roomId);
|
this._refreshPeer(isCaller, peerId, roomType, roomId);
|
||||||
return;
|
} else {
|
||||||
|
this.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);
|
||||||
}
|
}
|
||||||
|
@ -1091,7 +1296,7 @@ class PeersManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPeerLeft(message) {
|
_onPeerLeft(message) {
|
||||||
if (this._peerExists(message.peerId) && this._webRtcSupported(message.peerId)) {
|
if (this._peerExists(message.peerId) && !this._webRtcSupported(message.peerId)) {
|
||||||
console.log('WSPeer left:', message.peerId);
|
console.log('WSPeer left:', message.peerId);
|
||||||
}
|
}
|
||||||
if (message.disconnect === true) {
|
if (message.disconnect === true) {
|
||||||
|
@ -1136,11 +1341,10 @@ class PeersManager {
|
||||||
_onPeerDisconnected(peerId) {
|
_onPeerDisconnected(peerId) {
|
||||||
const peer = this.peers[peerId];
|
const peer = this.peers[peerId];
|
||||||
delete this.peers[peerId];
|
delete this.peers[peerId];
|
||||||
if (!peer || !peer._conn) return;
|
|
||||||
if (peer._channel) peer._channel.onclose = null;
|
if (!peer) return;
|
||||||
peer._conn.close();
|
|
||||||
peer._busy = false;
|
peer._closeChannelAndConnection();
|
||||||
peer._roomIds = {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onRoomSecretsDeleted(roomSecrets) {
|
_onRoomSecretsDeleted(roomSecrets) {
|
||||||
|
@ -1268,6 +1472,11 @@ class FileChunker {
|
||||||
this._readChunk();
|
this._readChunk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_restartFromOffset(offset) {
|
||||||
|
this._offset = offset;
|
||||||
|
this.nextPartition();
|
||||||
|
}
|
||||||
|
|
||||||
repeatPartition() {
|
repeatPartition() {
|
||||||
this._offset -= this._partitionSize;
|
this._offset -= this._partitionSize;
|
||||||
this.nextPartition();
|
this.nextPartition();
|
||||||
|
|
|
@ -16,7 +16,7 @@ class PeersUI {
|
||||||
this.$shareModeCancelBtn = $$('.shr-panel .cancel-btn');
|
this.$shareModeCancelBtn = $$('.shr-panel .cancel-btn');
|
||||||
this.$shareModeEditBtn = $$('.shr-panel .edit-btn');
|
this.$shareModeEditBtn = $$('.shr-panel .edit-btn');
|
||||||
|
|
||||||
this.peers = {};
|
this.peerUIs = {};
|
||||||
|
|
||||||
this.shareMode = {};
|
this.shareMode = {};
|
||||||
this.shareMode.active = false;
|
this.shareMode.active = false;
|
||||||
|
@ -24,9 +24,9 @@ class PeersUI {
|
||||||
this.shareMode.files = [];
|
this.shareMode.files = [];
|
||||||
this.shareMode.text = "";
|
this.shareMode.text = "";
|
||||||
|
|
||||||
Events.on('peer-joined', e => this._onPeerJoined(e.detail));
|
Events.on('peer-joined', e => this._onPeerJoined(e.detail.peer, e.detail.roomType, e.detail.roomId));
|
||||||
Events.on('peer-added', _ => this._evaluateOverflowingPeers());
|
|
||||||
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));
|
||||||
|
Events.on('peer-connecting', e => this._onPeerConnecting(e.detail));
|
||||||
Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail));
|
Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail));
|
||||||
Events.on('peers', e => this._onPeers(e.detail));
|
Events.on('peers', e => this._onPeers(e.detail));
|
||||||
Events.on('set-progress', e => this._onSetProgress(e.detail));
|
Events.on('set-progress', e => this._onSetProgress(e.detail));
|
||||||
|
@ -47,17 +47,17 @@ class PeersUI {
|
||||||
|
|
||||||
this.$shareModeCancelBtn.addEventListener('click', _ => this._deactivateShareMode());
|
this.$shareModeCancelBtn.addEventListener('click', _ => this._deactivateShareMode());
|
||||||
|
|
||||||
Events.on('peer-display-name-changed', e => this._onPeerDisplayNameChanged(e));
|
Events.on('peer-display-name-changed', e => this._onPeerDisplayNameChanged(e.detail.peerId, e.detail.displayName));
|
||||||
|
|
||||||
Events.on('ws-config', e => this._evaluateRtcSupport(e.detail))
|
Events.on('ws-config', e => this._evaluateRtcSupport(e.detail))
|
||||||
}
|
}
|
||||||
|
|
||||||
_evaluateRtcSupport(wsConfig) {
|
_evaluateRtcSupport(wsConfig) {
|
||||||
if (wsConfig.wsFallback) {
|
if (wsConfig.wsFallback) {
|
||||||
this.$wsFallbackWarning.hidden = false;
|
this.$wsFallbackWarning.removeAttribute("hidden");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.$wsFallbackWarning.hidden = true;
|
this.$wsFallbackWarning.setAttribute("hidden", true);
|
||||||
if (!window.isRtcSupported) {
|
if (!window.isRtcSupported) {
|
||||||
alert(Localization.getTranslation("instructions.webrtc-requirement"));
|
alert(Localization.getTranslation("instructions.webrtc-requirement"));
|
||||||
}
|
}
|
||||||
|
@ -65,15 +65,17 @@ class PeersUI {
|
||||||
}
|
}
|
||||||
|
|
||||||
_changePeerDisplayName(peerId, displayName) {
|
_changePeerDisplayName(peerId, displayName) {
|
||||||
this.peers[peerId].name.displayName = displayName;
|
const peerUI = this.peerUIs[peerId];
|
||||||
const peerIdNode = $(peerId);
|
|
||||||
if (peerIdNode && displayName) peerIdNode.querySelector('.name').textContent = displayName;
|
if (!peerUI) return;
|
||||||
this._redrawPeerRoomTypes(peerId);
|
|
||||||
|
peerUI._setDisplayName(displayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPeerDisplayNameChanged(e) {
|
_onPeerDisplayNameChanged(peerId, displayName) {
|
||||||
if (!e.detail.displayName) return;
|
if (!peerId || !displayName) return;
|
||||||
this._changePeerDisplayName(e.detail.peerId, e.detail.displayName);
|
|
||||||
|
this._changePeerDisplayName(peerId, displayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onKeyDown(e) {
|
async _onKeyDown(e) {
|
||||||
|
@ -89,50 +91,48 @@ class PeersUI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPeerJoined(msg) {
|
_onPeerJoined(peer, roomType, roomId) {
|
||||||
this._joinPeer(msg.peer, msg.roomType, msg.roomId);
|
this._joinPeer(peer, roomType, roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
_joinPeer(peer, roomType, roomId) {
|
_joinPeer(peer, roomType, roomId) {
|
||||||
const existingPeer = this.peers[peer.id];
|
const existingPeerUI = this.peerUIs[peer.id];
|
||||||
if (existingPeer) {
|
if (existingPeerUI) {
|
||||||
// peer already exists. Abort but add roomType to GUI
|
// peerUI already exists. Abort but add roomType to GUI
|
||||||
existingPeer._roomIds[roomType] = roomId;
|
existingPeerUI._addRoomId(roomType, roomId);
|
||||||
this._redrawPeerRoomTypes(peer.id);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
peer._isSameBrowser = () => BrowserTabsConnector.peerIsSameBrowser(peer.id);
|
const peerUI = new PeerUI(peer, roomType, roomId, {
|
||||||
peer._roomIds = {};
|
|
||||||
|
|
||||||
peer._roomIds[roomType] = roomId;
|
|
||||||
this.peers[peer.id] = peer;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onPeerConnected(peerId, connectionHash) {
|
|
||||||
if (!this.peers[peerId] || $(peerId)) return;
|
|
||||||
|
|
||||||
const peer = this.peers[peerId];
|
|
||||||
|
|
||||||
new PeerUI(peer, connectionHash, {
|
|
||||||
active: this.shareMode.active,
|
active: this.shareMode.active,
|
||||||
descriptor: this.shareMode.descriptor,
|
descriptor: this.shareMode.descriptor,
|
||||||
});
|
});
|
||||||
|
this.peerUIs[peer.id] = peerUI;
|
||||||
}
|
}
|
||||||
|
|
||||||
_redrawPeerRoomTypes(peerId) {
|
_onPeerConnected(peerId, connectionHash) {
|
||||||
const peer = this.peers[peerId];
|
const peerUI = this.peerUIs[peerId];
|
||||||
const peerNode = $(peerId);
|
|
||||||
|
|
||||||
if (!peer || !peerNode) return;
|
if (!peerUI) return;
|
||||||
|
|
||||||
peerNode.classList.remove('type-ip', 'type-secret', 'type-public-id', 'type-same-browser');
|
peerUI._peerConnected(true, connectionHash);
|
||||||
|
|
||||||
if (peer._isSameBrowser()) {
|
this._addPeerUIIfMissing(peerUI);
|
||||||
peerNode.classList.add(`type-same-browser`);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(peer._roomIds).forEach(roomType => peerNode.classList.add(`type-${roomType}`));
|
_addPeerUIIfMissing(peerUI) {
|
||||||
|
if (this.$xPeers.contains(peerUI.$el)) return;
|
||||||
|
|
||||||
|
this.$xPeers.appendChild(peerUI.$el);
|
||||||
|
this._evaluateOverflowingPeers();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onPeerConnecting(peerId) {
|
||||||
|
const peerUI = this.peerUIs[peerId];
|
||||||
|
|
||||||
|
if (!peerUI) return;
|
||||||
|
|
||||||
|
peerUI._peerConnected(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_evaluateOverflowingPeers() {
|
_evaluateOverflowingPeers() {
|
||||||
|
@ -149,26 +149,31 @@ class PeersUI {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPeerDisconnected(peerId) {
|
_onPeerDisconnected(peerId) {
|
||||||
const $peer = $(peerId);
|
const peerUI = this.peerUIs[peerId];
|
||||||
if (!$peer) return;
|
|
||||||
$peer.remove();
|
if (!peerUI) return;
|
||||||
|
|
||||||
|
peerUI._removeDom();
|
||||||
|
|
||||||
|
delete this.peerUIs[peerId];
|
||||||
|
|
||||||
this._evaluateOverflowingPeers();
|
this._evaluateOverflowingPeers();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onRoomTypeRemoved(peerId, roomType) {
|
_onRoomTypeRemoved(peerId, roomType) {
|
||||||
const peer = this.peers[peerId];
|
const peerUI = this.peerUIs[peerId];
|
||||||
|
|
||||||
if (!peer) return;
|
if (!peerUI) return;
|
||||||
|
|
||||||
delete peer._roomIds[roomType];
|
peerUI._removeRoomId(roomType);
|
||||||
|
|
||||||
this._redrawPeerRoomTypes(peerId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSetProgress(progress) {
|
_onSetProgress(progress) {
|
||||||
const $peer = $(progress.peerId);
|
const peerUI = this.peerUIs[progress.peerId];
|
||||||
if (!$peer) return;
|
|
||||||
$peer.ui.setProgress(progress.progress, progress.status)
|
if (!peerUI) return;
|
||||||
|
|
||||||
|
peerUI.setProgress(progress.progress, progress.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDrop(e) {
|
_onDrop(e) {
|
||||||
|
@ -392,35 +397,52 @@ class PeersUI {
|
||||||
class PeerUI {
|
class PeerUI {
|
||||||
|
|
||||||
static _badgeClassNames = ["badge-room-ip", "badge-room-secret", "badge-room-public-id"];
|
static _badgeClassNames = ["badge-room-ip", "badge-room-secret", "badge-room-public-id"];
|
||||||
static _shareMode = {
|
|
||||||
active: false,
|
|
||||||
descriptor: ""
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(peer, connectionHash, shareMode) {
|
constructor(peer, roomType, roomId, shareMode = {active: false, descriptor: ""}) {
|
||||||
this.$xInstructions = $$('x-instructions');
|
this.$xInstructions = $$('x-instructions');
|
||||||
this.$xPeers = $$('x-peers');
|
|
||||||
|
|
||||||
this._peer = peer;
|
this._peer = peer;
|
||||||
this._connectionHash =
|
this._connectionHash = "";
|
||||||
`${connectionHash.substring(0, 4)} ${connectionHash.substring(4, 8)} ${connectionHash.substring(8, 12)} ${connectionHash.substring(12, 16)}`;
|
this._connected = false;
|
||||||
|
|
||||||
// This is needed if the ShareMode is started BEFORE the PeerUI is drawn.
|
this._roomIds = {}
|
||||||
PeerUI._shareMode = shareMode;
|
this._roomIds[roomType] = roomId;
|
||||||
|
|
||||||
|
this._shareMode = shareMode;
|
||||||
|
|
||||||
|
this._createCallbacks();
|
||||||
this._initDom();
|
this._initDom();
|
||||||
|
|
||||||
this.$xPeers.appendChild(this.$el);
|
|
||||||
Events.fire('peer-added');
|
|
||||||
|
|
||||||
// ShareMode
|
// ShareMode
|
||||||
Events.on('share-mode-changed', e => this._onShareModeChanged(e.detail.active, e.detail.descriptor));
|
Events.on('share-mode-changed', e => this._onShareModeChanged(e.detail.active, e.detail.descriptor));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_initDom() {
|
||||||
|
this.$el = document.createElement('x-peer');
|
||||||
|
this.$el.id = this._peer.id;
|
||||||
|
this.$el.ui = this;
|
||||||
|
this.$el.classList.add('center');
|
||||||
|
|
||||||
|
this.html();
|
||||||
|
|
||||||
|
this.$label = this.$el.querySelector('label');
|
||||||
|
this.$input = this.$el.querySelector('input');
|
||||||
|
this.$displayName = this.$el.querySelector('.name');
|
||||||
|
|
||||||
|
this.updateTypesClassList();
|
||||||
|
|
||||||
|
this.setStatus("connect");
|
||||||
|
|
||||||
|
this._evaluateShareMode();
|
||||||
|
this._bindListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
_removeDom() {
|
||||||
|
this.$el.remove();
|
||||||
|
}
|
||||||
|
|
||||||
html() {
|
html() {
|
||||||
let title= PeerUI._shareMode.active
|
let title= Localization.getTranslation("peer-ui.click-to-send");
|
||||||
? Localization.getTranslation("peer-ui.click-to-send-share-mode", null, {descriptor: PeerUI._shareMode.descriptor})
|
|
||||||
: Localization.getTranslation("peer-ui.click-to-send");
|
|
||||||
|
|
||||||
this.$el.innerHTML = `
|
this.$el.innerHTML = `
|
||||||
<label class="column center pointer" title="${title}">
|
<label class="column center pointer" title="${title}">
|
||||||
|
@ -449,41 +471,37 @@ class PeerUI {
|
||||||
this.$el.querySelector('svg use').setAttribute('xlink:href', this._icon());
|
this.$el.querySelector('svg use').setAttribute('xlink:href', this._icon());
|
||||||
this.$el.querySelector('.name').textContent = this._displayName();
|
this.$el.querySelector('.name').textContent = this._displayName();
|
||||||
this.$el.querySelector('.device-name').textContent = this._deviceName();
|
this.$el.querySelector('.device-name').textContent = this._deviceName();
|
||||||
|
|
||||||
this.$label = this.$el.querySelector('label');
|
|
||||||
this.$input = this.$el.querySelector('input');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addTypesToClassList() {
|
updateTypesClassList() {
|
||||||
if (this._peer._isSameBrowser()) {
|
// Remove all classes
|
||||||
|
this.$el.classList.remove('type-ip', 'type-secret', 'type-public-id', 'type-same-browser', 'ws-peer');
|
||||||
|
|
||||||
|
// Add classes accordingly
|
||||||
|
Object.keys(this._roomIds).forEach(roomType => this.$el.classList.add(`type-${roomType}`));
|
||||||
|
|
||||||
|
if (BrowserTabsConnector.peerIsSameBrowser(this._peer.id)) {
|
||||||
this.$el.classList.add(`type-same-browser`);
|
this.$el.classList.add(`type-same-browser`);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(this._peer._roomIds).forEach(roomType => this.$el.classList.add(`type-${roomType}`));
|
if (!this._peer.rtcSupported || !window.isRtcSupported) {
|
||||||
|
this.$el.classList.add('ws-peer');
|
||||||
if (!this._peer.rtcSupported || !window.isRtcSupported) this.$el.classList.add('ws-peer');
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_initDom() {
|
_addRoomId(roomType, roomId) {
|
||||||
this.$el = document.createElement('x-peer');
|
this._roomIds[roomType] = roomId;
|
||||||
this.$el.id = this._peer.id;
|
this.updateTypesClassList();
|
||||||
this.$el.ui = this;
|
}
|
||||||
this.$el.classList.add('center');
|
|
||||||
|
|
||||||
this.addTypesToClassList();
|
_removeRoomId(roomType) {
|
||||||
|
delete this._roomIds[roomType];
|
||||||
this.html();
|
this.updateTypesClassList();
|
||||||
|
|
||||||
this._createCallbacks();
|
|
||||||
|
|
||||||
this._evaluateShareMode();
|
|
||||||
this._bindListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onShareModeChanged(active = false, descriptor = "") {
|
_onShareModeChanged(active = false, descriptor = "") {
|
||||||
// This is needed if the ShareMode is started AFTER the PeerUI is drawn.
|
this._shareMode.active = active;
|
||||||
PeerUI._shareMode.active = active;
|
this._shareMode.descriptor = descriptor;
|
||||||
PeerUI._shareMode.descriptor = descriptor;
|
|
||||||
|
|
||||||
this._evaluateShareMode();
|
this._evaluateShareMode();
|
||||||
this._bindListeners();
|
this._bindListeners();
|
||||||
|
@ -491,12 +509,12 @@ class PeerUI {
|
||||||
|
|
||||||
_evaluateShareMode() {
|
_evaluateShareMode() {
|
||||||
let title;
|
let title;
|
||||||
if (!PeerUI._shareMode.active) {
|
if (!this._shareMode.active) {
|
||||||
title = Localization.getTranslation("peer-ui.click-to-send");
|
title = Localization.getTranslation("peer-ui.click-to-send");
|
||||||
this.$input.removeAttribute('disabled');
|
this.$input.removeAttribute('disabled');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
title = Localization.getTranslation("peer-ui.click-to-send-share-mode", null, {descriptor: PeerUI._shareMode.descriptor});
|
title = Localization.getTranslation("peer-ui.click-to-send-share-mode", null, {descriptor: this._shareMode.descriptor});
|
||||||
this.$input.setAttribute('disabled', true);
|
this.$input.setAttribute('disabled', true);
|
||||||
}
|
}
|
||||||
this.$label.setAttribute('title', title);
|
this.$label.setAttribute('title', title);
|
||||||
|
@ -517,7 +535,7 @@ class PeerUI {
|
||||||
}
|
}
|
||||||
|
|
||||||
_bindListeners() {
|
_bindListeners() {
|
||||||
if(!PeerUI._shareMode.active) {
|
if(!this._shareMode.active) {
|
||||||
// Remove Events Share mode
|
// Remove Events Share mode
|
||||||
this.$el.removeEventListener('pointerdown', this._callbackPointerDown);
|
this.$el.removeEventListener('pointerdown', this._callbackPointerDown);
|
||||||
|
|
||||||
|
@ -559,6 +577,37 @@ class PeerUI {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_peerConnected(connected = true, connectionHash = "") {
|
||||||
|
if (connected) {
|
||||||
|
this._connected = true;
|
||||||
|
|
||||||
|
// on reconnect
|
||||||
|
this.setStatus(this.oldStatus);
|
||||||
|
this.oldStatus = null;
|
||||||
|
|
||||||
|
this._connectionHash = connectionHash;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._connected = false;
|
||||||
|
|
||||||
|
if (!this.oldStatus && this.currentStatus !== "connect") {
|
||||||
|
// save old status when reconnecting
|
||||||
|
this.oldStatus = this.currentStatus;
|
||||||
|
}
|
||||||
|
this.setStatus("connect");
|
||||||
|
|
||||||
|
this._connectionHash = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getConnectionHashWithSpaces() {
|
||||||
|
if (this._connectionHash.length !== 16) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${this._connectionHash.substring(0, 4)} ${this._connectionHash.substring(4, 8)} ${this._connectionHash.substring(8, 12)} ${this._connectionHash.substring(12, 16)}`;
|
||||||
|
}
|
||||||
|
|
||||||
_displayName() {
|
_displayName() {
|
||||||
return this._peer.name.displayName;
|
return this._peer.name.displayName;
|
||||||
}
|
}
|
||||||
|
@ -567,13 +616,26 @@ class PeerUI {
|
||||||
return this._peer.name.deviceName;
|
return this._peer.name.deviceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_setDisplayName(displayName) {
|
||||||
|
this._peer.name.displayName = displayName;
|
||||||
|
this.$displayName.textContent = displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
_roomTypes() {
|
||||||
|
return Object.keys(this._roomIds);
|
||||||
|
}
|
||||||
|
|
||||||
_badgeClassName() {
|
_badgeClassName() {
|
||||||
const roomTypes = Object.keys(this._peer._roomIds);
|
const roomTypes = this._roomTypes();
|
||||||
return roomTypes.includes('secret')
|
if (roomTypes.includes('secret')) {
|
||||||
? 'badge-room-secret'
|
return 'badge-room-secret';
|
||||||
: roomTypes.includes('ip')
|
}
|
||||||
? 'badge-room-ip'
|
else if (roomTypes.includes('ip')) {
|
||||||
: 'badge-room-public-id';
|
return 'badge-room-ip';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 'badge-room-public-id';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_icon() {
|
_icon() {
|
||||||
|
@ -590,6 +652,7 @@ class PeerUI {
|
||||||
_onFilesSelected(e) {
|
_onFilesSelected(e) {
|
||||||
const $input = e.target;
|
const $input = e.target;
|
||||||
const files = $input.files;
|
const files = $input.files;
|
||||||
|
|
||||||
Events.fire('files-selected', {
|
Events.fire('files-selected', {
|
||||||
files: files,
|
files: files,
|
||||||
to: this._peer.id
|
to: this._peer.id
|
||||||
|
@ -599,40 +662,54 @@ class PeerUI {
|
||||||
|
|
||||||
setProgress(progress, status) {
|
setProgress(progress, status) {
|
||||||
const $progress = this.$el.querySelector('.progress');
|
const $progress = this.$el.querySelector('.progress');
|
||||||
|
|
||||||
if (0.5 < progress && progress < 1) {
|
if (0.5 < progress && progress < 1) {
|
||||||
$progress.classList.add('over50');
|
$progress.classList.add('over50');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$progress.classList.remove('over50');
|
$progress.classList.remove('over50');
|
||||||
}
|
}
|
||||||
if (progress < 1) {
|
|
||||||
if (status !== this.currentStatus) {
|
|
||||||
let statusName = {
|
|
||||||
"prepare": Localization.getTranslation("peer-ui.preparing"),
|
|
||||||
"transfer": Localization.getTranslation("peer-ui.transferring"),
|
|
||||||
"process": Localization.getTranslation("peer-ui.processing"),
|
|
||||||
"wait": Localization.getTranslation("peer-ui.waiting")
|
|
||||||
}[status];
|
|
||||||
|
|
||||||
this.$el.setAttribute('status', status);
|
if (progress === 1) {
|
||||||
this.$el.querySelector('.status').innerText = statusName;
|
|
||||||
this.currentStatus = status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.$el.removeAttribute('status');
|
|
||||||
this.$el.querySelector('.status').innerHTML = '';
|
|
||||||
progress = 0;
|
progress = 0;
|
||||||
this.currentStatus = null;
|
status = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setStatus(status);
|
||||||
|
|
||||||
const degrees = `rotate(${360 * progress}deg)`;
|
const degrees = `rotate(${360 * progress}deg)`;
|
||||||
$progress.style.setProperty('--progress', degrees);
|
$progress.style.setProperty('--progress', degrees);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDrop(e) {
|
setStatus(status) {
|
||||||
e.preventDefault();
|
if (!status) {
|
||||||
|
this.$el.removeAttribute('status');
|
||||||
|
this.$el.querySelector('.status').innerHTML = '';
|
||||||
|
this.currentStatus = null;
|
||||||
|
NoSleepUI.disableIfPeersIdle();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (PeerUI._shareMode.active || Dialog.anyDialogShown()) return;
|
if (status === this.currentStatus) return;
|
||||||
|
|
||||||
|
let statusName = {
|
||||||
|
"connect": Localization.getTranslation("peer-ui.connecting"),
|
||||||
|
"prepare": Localization.getTranslation("peer-ui.preparing"),
|
||||||
|
"transfer": Localization.getTranslation("peer-ui.transferring"),
|
||||||
|
"receive": Localization.getTranslation("peer-ui.receiving"),
|
||||||
|
"process": Localization.getTranslation("peer-ui.processing"),
|
||||||
|
"wait": Localization.getTranslation("peer-ui.waiting")
|
||||||
|
}[status];
|
||||||
|
|
||||||
|
this.$el.setAttribute('status', status);
|
||||||
|
this.$el.querySelector('.status').innerText = statusName;
|
||||||
|
this.currentStatus = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDrop(e) {
|
||||||
|
if (this._shareMode.active || Dialog.anyDialogShown()) return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
if (e.dataTransfer.files.length > 0) {
|
if (e.dataTransfer.files.length > 0) {
|
||||||
Events.fire('files-selected', {
|
Events.fire('files-selected', {
|
||||||
|
@ -1315,8 +1392,8 @@ class PairDeviceDialog extends Dialog {
|
||||||
Events.on('ws-disconnected', _ => this.hide());
|
Events.on('ws-disconnected', _ => this.hide());
|
||||||
Events.on('pair-device-initiated', e => this._onPairDeviceInitiated(e.detail));
|
Events.on('pair-device-initiated', e => this._onPairDeviceInitiated(e.detail));
|
||||||
Events.on('pair-device-joined', e => this._onPairDeviceJoined(e.detail.peerId, e.detail.roomSecret));
|
Events.on('pair-device-joined', e => this._onPairDeviceJoined(e.detail.peerId, e.detail.roomSecret));
|
||||||
Events.on('peers', e => this._onPeers(e.detail));
|
Events.on('peers', e => this._onPeers(e.detail.peers, e.detail.roomType, e.detail.roomId));
|
||||||
Events.on('peer-joined', e => this._onPeerJoined(e.detail));
|
Events.on('peer-joined', e => this._onPeerJoined(e.detail.peer, e.detail.roomType, e.detail.roomId));
|
||||||
Events.on('pair-device-join-key-invalid', _ => this._onPublicRoomJoinKeyInvalid());
|
Events.on('pair-device-join-key-invalid', _ => this._onPublicRoomJoinKeyInvalid());
|
||||||
Events.on('pair-device-canceled', e => this._onPairDeviceCanceled(e.detail));
|
Events.on('pair-device-canceled', e => this._onPairDeviceCanceled(e.detail));
|
||||||
Events.on('evaluate-number-room-secrets', _ => this._evaluateNumberRoomSecrets())
|
Events.on('evaluate-number-room-secrets', _ => this._evaluateNumberRoomSecrets())
|
||||||
|
@ -1426,18 +1503,19 @@ class PairDeviceDialog extends Dialog {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPeers(message) {
|
_onPeers(peers, roomType, roomId) {
|
||||||
message.peers.forEach(messagePeer => {
|
peers.forEach(messagePeer => {
|
||||||
this._evaluateJoinedPeer(messagePeer.id, message.roomType, message.roomId);
|
this._evaluateJoinedPeer(messagePeer, roomType, roomId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPeerJoined(message) {
|
_onPeerJoined(peer, roomType, roomId) {
|
||||||
this._evaluateJoinedPeer(message.peer.id, message.roomType, message.roomId);
|
this._evaluateJoinedPeer(peer, roomType, roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
_evaluateJoinedPeer(peerId, roomType, roomId) {
|
_evaluateJoinedPeer(peer, roomType, roomId) {
|
||||||
const noPairPeerSaved = !Object.keys(this.pairPeer);
|
const noPairPeerSaved = !Object.keys(this.pairPeer);
|
||||||
|
const peerId = peer.id;
|
||||||
|
|
||||||
if (!peerId || !roomType || !roomId || noPairPeerSaved) return;
|
if (!peerId || !roomType || !roomId || noPairPeerSaved) return;
|
||||||
|
|
||||||
|
@ -1447,13 +1525,13 @@ class PairDeviceDialog extends Dialog {
|
||||||
|
|
||||||
if (!samePeerId || !sameRoomSecret || !typeIsSecret) return;
|
if (!samePeerId || !sameRoomSecret || !typeIsSecret) return;
|
||||||
|
|
||||||
this._onPairPeerJoined(peerId, roomId);
|
this._onPairPeerJoined(peer, roomId);
|
||||||
this.pairPeer = {};
|
this.pairPeer = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPairPeerJoined(peerId, roomSecret) {
|
_onPairPeerJoined(peer, roomSecret) {
|
||||||
// if devices are paired that are already connected we must save the names at this point
|
// if devices are paired that are already connected we must save the names at this point
|
||||||
const $peer = $(peerId);
|
const $peer = $(peer.id);
|
||||||
let displayName, deviceName;
|
let displayName, deviceName;
|
||||||
if ($peer) {
|
if ($peer) {
|
||||||
displayName = $peer.ui._peer.name.displayName;
|
displayName = $peer.ui._peer.name.displayName;
|
||||||
|
@ -1531,11 +1609,11 @@ class EditPairedDevicesDialog extends Dialog {
|
||||||
super('edit-paired-devices-dialog');
|
super('edit-paired-devices-dialog');
|
||||||
this.$pairedDevicesWrapper = this.$el.querySelector('.paired-devices-wrapper');
|
this.$pairedDevicesWrapper = this.$el.querySelector('.paired-devices-wrapper');
|
||||||
this.$footerBadgePairedDevices = $$('.discovery-wrapper .badge-room-secret');
|
this.$footerBadgePairedDevices = $$('.discovery-wrapper .badge-room-secret');
|
||||||
|
this.$editPairedDevices = $('edit-paired-devices');
|
||||||
|
|
||||||
$('edit-paired-devices').addEventListener('click', _ => this._onEditPairedDevices());
|
this.$editPairedDevices.addEventListener('click', _ => this._onEditPairedDevices());
|
||||||
this.$footerBadgePairedDevices.addEventListener('click', _ => this._onEditPairedDevices());
|
this.$footerBadgePairedDevices.addEventListener('click', _ => this._onEditPairedDevices());
|
||||||
|
|
||||||
Events.on('peer-display-name-changed', e => this._onPeerDisplayNameChanged(e));
|
|
||||||
Events.on('keydown', e => this._onKeyDown(e));
|
Events.on('keydown', e => this._onKeyDown(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1641,23 +1719,6 @@ class EditPairedDevicesDialog extends Dialog {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPeerDisplayNameChanged(e) {
|
|
||||||
const peerId = e.detail.peerId;
|
|
||||||
const peerNode = $(peerId);
|
|
||||||
|
|
||||||
if (!peerNode) return;
|
|
||||||
|
|
||||||
const peer = peerNode.ui._peer;
|
|
||||||
|
|
||||||
if (!peer || !peer._roomIds["secret"]) return;
|
|
||||||
|
|
||||||
PersistentStorage
|
|
||||||
.updateRoomSecretNames(peer._roomIds["secret"], peer.name.displayName, peer.name.deviceName)
|
|
||||||
.then(roomSecretEntry => {
|
|
||||||
console.log(`Successfully updated DisplayName and DeviceName for roomSecretEntry ${roomSecretEntry.key}`);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PublicRoomDialog extends Dialog {
|
class PublicRoomDialog extends Dialog {
|
||||||
|
@ -1692,7 +1753,7 @@ class PublicRoomDialog extends Dialog {
|
||||||
Events.on('keydown', e => this._onKeyDown(e));
|
Events.on('keydown', e => this._onKeyDown(e));
|
||||||
Events.on('public-room-created', e => this._onPublicRoomCreated(e.detail));
|
Events.on('public-room-created', e => this._onPublicRoomCreated(e.detail));
|
||||||
Events.on('peers', e => this._onPeers(e.detail));
|
Events.on('peers', e => this._onPeers(e.detail));
|
||||||
Events.on('peer-joined', e => this._onPeerJoined(e.detail));
|
Events.on('peer-joined', e => this._onPeerJoined(e.detail.peer, e.detail.roomId));
|
||||||
Events.on('public-room-id-invalid', e => this._onPublicRoomIdInvalid(e.detail));
|
Events.on('public-room-id-invalid', e => this._onPublicRoomIdInvalid(e.detail));
|
||||||
Events.on('public-room-left', _ => this._onPublicRoomLeft());
|
Events.on('public-room-left', _ => this._onPublicRoomLeft());
|
||||||
this.$el.addEventListener('paste', e => this._onPaste(e));
|
this.$el.addEventListener('paste', e => this._onPaste(e));
|
||||||
|
@ -1828,15 +1889,15 @@ class PublicRoomDialog extends Dialog {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPeerJoined(message) {
|
_onPeerJoined(peer, roomId) {
|
||||||
this._evaluateJoinedPeer(message.peer.id, message.roomId);
|
this._evaluateJoinedPeer(peer.id, roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
_evaluateJoinedPeer(peerId, roomId) {
|
_evaluateJoinedPeer(peerId, roomId) {
|
||||||
const isInitiatedRoomId = roomId === this.roomId;
|
const isInitiatedRoomId = roomId === this.roomId;
|
||||||
const isJoinedRoomId = roomId === this.roomIdJoin;
|
const isJoinedRoomId = roomId === this.roomIdJoin;
|
||||||
|
|
||||||
if (!peerId || !roomId || !(isInitiatedRoomId || isJoinedRoomId)) return;
|
if (!peerId || !roomId || (!isInitiatedRoomId && !isJoinedRoomId)) return;
|
||||||
|
|
||||||
this.hide();
|
this.hide();
|
||||||
|
|
||||||
|
@ -2624,11 +2685,12 @@ class NoSleepUI {
|
||||||
static enable() {
|
static enable() {
|
||||||
if (!this._interval) {
|
if (!this._interval) {
|
||||||
NoSleepUI._nosleep.enable();
|
NoSleepUI._nosleep.enable();
|
||||||
NoSleepUI._interval = setInterval(() => NoSleepUI.disable(), 10000);
|
// Disable after 10s if all peers are idle
|
||||||
|
NoSleepUI._interval = setInterval(() => NoSleepUI.disableIfPeersIdle(), 10000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static disable() {
|
static disableIfPeersIdle() {
|
||||||
if ($$('x-peer[status]') === null) {
|
if ($$('x-peer[status]') === null) {
|
||||||
clearInterval(NoSleepUI._interval);
|
clearInterval(NoSleepUI._interval);
|
||||||
NoSleepUI._nosleep.disable();
|
NoSleepUI._nosleep.disable();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue