mirror of
https://github.com/schlagmichdoch/PairDrop.git
synced 2025-04-23 00:06:18 -04:00
Merge pull request #107 from schlagmichdoch/add_auto_accept
Add auto accept functionality via Edit Paired Devices Dialog + implement pair secret regeneration functionality
This commit is contained in:
commit
de0afce4ea
11 changed files with 1643 additions and 510 deletions
|
@ -78,9 +78,9 @@
|
|||
<use xlink:href="#pair-device-icon" />
|
||||
</svg>
|
||||
</div>
|
||||
<div id="clear-pair-devices" class="icon-button" title="Clear All Paired Devices" hidden>
|
||||
<div id="edit-paired-devices" class="icon-button" title="Edit Paired Devices" hidden>
|
||||
<svg class="icon">
|
||||
<use xlink:href="#clear-pair-devices-icon" />
|
||||
<use xlink:href="#edit-pair-devices-icon" />
|
||||
</svg>
|
||||
</div>
|
||||
<div id="cancel-paste-mode" class="button" hidden>Done</div>
|
||||
|
@ -142,16 +142,18 @@
|
|||
</x-background>
|
||||
</form>
|
||||
</x-dialog>
|
||||
<!-- Clear Devices Dialog -->
|
||||
<x-dialog id="clear-devices-dialog">
|
||||
<!-- Edit Paired Devices Dialog -->
|
||||
<x-dialog id="edit-paired-devices-dialog">
|
||||
<form action="#">
|
||||
<x-background class="full center text-center">
|
||||
<x-paper shadow="2">
|
||||
<h2 class="center">Unpair Devices</h2>
|
||||
<div class="font-subheading center text-center">Are you sure to unpair all devices?</div>
|
||||
<h2 class="center">Edit Paired Devices</h2>
|
||||
<div class="paired-devices-wrapper"></div>
|
||||
<div class="font-subheading center">
|
||||
<p>Activate <u>auto-accept</u> to automatically accept all files sent from that device.</p>
|
||||
</div>
|
||||
<div class="center row-reverse">
|
||||
<button class="button" type="submit">Unpair Devices</button>
|
||||
<button class="button" type="button" close>Cancel</button>
|
||||
<button class="button" type="button" close>Close</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
@ -355,9 +357,11 @@
|
|||
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/>
|
||||
</symbol>
|
||||
<symbol id="clear-pair-devices-icon" viewBox="0 0 640 512">
|
||||
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<path d="M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L489.3 358.2l90.5-90.5c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114l-96 96-31.9-25C430.9 239.6 420.1 175.1 377 132c-52.2-52.3-134.5-56.2-191.3-11.7L38.8 5.1zM239 162c30.1-14.9 67.7-9.9 92.8 15.3c20 20 27.5 48.3 21.7 74.5L239 162zM406.6 416.4L220.9 270c-2.1 39.8 12.2 80.1 42.2 110c38.9 38.9 94.4 51 143.6 36.3zm-290-228.5L60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5l61.8-61.8-50.6-39.9z"/>
|
||||
<symbol id="edit-pair-devices-icon" viewBox="-159 25 640 512">
|
||||
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<!--! edited by @schlagmichdoch -->
|
||||
<path d="M218,155.4c-56.5-56.5-148-56.5-204.5,0L-98.8,267.7c-56.5,56.5-56.5,148,0,204.5c50,50,128.8,56.5,186.3,15.4l1.6-1.1 c14.4-10.3,17.7-30.3,7.4-44.6s-30.3-17.7-44.6-7.4l-1.6,1.1c-32.1,22.9-76,19.3-103.8-8.6c-31.5-31.6-31.5-82.6,0-114.1L58.7,200.6 c31.5-31.5,82.5-31.5,114,0c15.8,15.8,23.8,36.7,23.6,57.6c7.9-8.3,18.9-13,30.6-13c4.5,0,8.9,0.7,13.2,2l17.4,5.5 c0.9-0.5,1.8-1,2.7-1.5C258.7,216.2,244.4,181.8,218,155.4z M420.8,86.6c-50-50-128.8-56.5-186.3-15.4l-1.6,1.1 c-14.4,10.3-17.7,30.3-7.4,44.6s30.3,17.7,44.6,7.4l1.6-1.1c32.1-22.9,76-19.3,103.8,8.6c25.8,25.8,30.5,64.7,14,95.2 c0.7,2,1.3,4,1.8,6.1l3.9,17.9c1.1,0.6,2.1,1.2,3.2,1.8l17.4-5.5c4.3-1.4,8.7-2,13.1-2c7.3,0,14.3,1.8,20.5,5.2 C474.7,196.8,465.1,130.9,420.8,86.6z M140.7,254.4l1.1-1.6c10.3-14.4,6.9-34.4-7.4-44.6s-34.4-6.9-44.6,7.4l-1.1,1.6 C47.5,274.6,54,353.4,104,403.4c18.7,18.7,41.2,31.2,65,37.5c-1.4-3.1-2.6-6.2-3.8-9.3c-6-16.4-1.5-34.6,11.6-46.4l7.2-6.6 c-12.7-3.6-24.7-10.5-34.8-20.5C121.4,330.3,117.8,286.4,140.7,254.4z"/>
|
||||
<path d="M458.9,407.4l-24.3-22.1c0.6-4.7,1-9.4,1-14.2s-0.3-9.6-1-14.2l24.3-22.1c3.9-3.5,5.4-8.9,3.6-13.8v-0.1 c-2.5-6.7-5.4-13.1-8.9-19.2l-2.6-4.5c-3.7-6.2-7.8-12-12.4-17.5c-3.3-4-8.8-5.4-13.7-3.8l-31.2,9.9c-7.5-5.8-15.8-10.6-24.7-14.2 l-7-32c-1.1-5.1-5-9.1-10.2-10c-7.7-1.3-15.7-2-23.8-2s-16.1,0.7-23.8,2c-5.2,0.9-9.1,4.9-10.2,10l-7,32 c-8.9,3.7-17.2,8.5-24.7,14.2l-31.2-9.9c-4.9-1.6-10.4-0.2-13.7,3.8c-4.5,5.5-8.7,11.3-12.4,17.5l-2.6,4.5 c-3.4,6.2-6.4,12.6-8.9,19.2c-1.8,4.9-0.3,10.3,3.6,13.8l24.3,22.1c-0.6,4.7-1,9.4-1,14.2s0.3,9.6,1,14.3L197,407.5 c-3.9,3.5-5.4,8.9-3.6,13.8c2.5,6.7,5.4,13.1,8.9,19.2l2.6,4.5c3.7,6.2,7.8,12,12.4,17.5c3.3,4,8.8,5.4,13.7,3.8l31.2-10 c7.5,5.8,15.8,10.6,24.7,14.2l7,32c1.1,5.1,5,9.1,10.2,10c7.7,1.3,15.7,2,23.8,2c8.1,0,16.1-0.7,23.8-2c5.2-0.8,9.1-4.9,10.2-10 l7-32c8.9-3.6,17.2-8.5,24.7-14.2l31.2,9.9c4.9,1.6,10.4,0.2,13.7-3.8c4.5-5.5,8.7-11.3,12.4-17.5l2.6-4.5 c3.4-6.2,6.4-12.6,8.9-19.2C464.2,416.3,462.7,410.9,458.9,407.4z M328,415.9c-24.8,0-44.9-20.1-44.9-44.8 c0-24.8,20.1-44.8,44.9-44.8s44.8,20.1,44.8,44.8C372.8,395.9,352.7,415.9,328,415.9z"/>
|
||||
</symbol>
|
||||
<symbol id="edit-pen-icon" viewBox="0 0 512 512">
|
||||
<!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
|
|
|
@ -3,16 +3,25 @@ window.isRtcSupported = !!(window.RTCPeerConnection || window.mozRTCPeerConnecti
|
|||
|
||||
if (!window.isRtcSupported) alert("WebRTC must be enabled for PairDrop to work");
|
||||
|
||||
window.hiddenProperty = 'hidden' in document ? 'hidden' :
|
||||
'webkitHidden' in document ? 'webkitHidden' :
|
||||
'mozHidden' in document ? 'mozHidden' :
|
||||
null;
|
||||
window.visibilityChangeEvent = 'visibilitychange' in document ? 'visibilitychange' :
|
||||
'webkitvisibilitychange' in document ? 'webkitvisibilitychange' :
|
||||
'mozvisibilitychange' in document ? 'mozvisibilitychange' :
|
||||
null;
|
||||
|
||||
class ServerConnection {
|
||||
|
||||
constructor() {
|
||||
this._connect();
|
||||
Events.on('pagehide', _ => this._disconnect());
|
||||
document.addEventListener('visibilitychange', _ => this._onVisibilityChange());
|
||||
document.addEventListener(window.visibilityChangeEvent, _ => this._onVisibilityChange());
|
||||
if (navigator.connection) navigator.connection.addEventListener('change', _ => this._reconnect());
|
||||
Events.on('room-secrets', e => this._sendRoomSecrets(e.detail));
|
||||
Events.on('room-secret-deleted', e => this.send({ type: 'room-secret-deleted', roomSecret: e.detail}));
|
||||
Events.on('room-secrets-cleared', e => this.send({ type: 'room-secrets-cleared', roomSecrets: e.detail}));
|
||||
Events.on('room-secrets-deleted', e => this.send({ type: 'room-secrets-deleted', roomSecrets: e.detail}));
|
||||
Events.on('regenerate-room-secret', e => this.send({ type: 'regenerate-room-secret', roomSecret: e.detail}));
|
||||
Events.on('resend-peers', _ => this.send({ type: 'resend-peers'}));
|
||||
Events.on('pair-device-initiate', _ => this._onPairDeviceInitiate());
|
||||
Events.on('pair-device-join', e => this._onPairDeviceJoin(e.detail));
|
||||
|
@ -65,13 +74,13 @@ class ServerConnection {
|
|||
|
||||
_onMessage(msg) {
|
||||
msg = JSON.parse(msg);
|
||||
if (msg.type !== 'ping') console.log('WS:', msg);
|
||||
if (msg.type !== 'ping') console.log('WS receive:', msg);
|
||||
switch (msg.type) {
|
||||
case 'rtc-config':
|
||||
this._setRtcConfig(msg.config);
|
||||
break;
|
||||
case 'peers':
|
||||
Events.fire('peers', msg);
|
||||
this._onPeers(msg);
|
||||
break;
|
||||
case 'peer-joined':
|
||||
Events.fire('peer-joined', msg);
|
||||
|
@ -106,19 +115,42 @@ class ServerConnection {
|
|||
case 'secret-room-deleted':
|
||||
Events.fire('secret-room-deleted', msg.roomSecret);
|
||||
break;
|
||||
case 'room-secret-regenerated':
|
||||
Events.fire('room-secret-regenerated', msg);
|
||||
break;
|
||||
default:
|
||||
console.error('WS: unknown message type', msg);
|
||||
console.error('WS receive: unknown message type', msg);
|
||||
}
|
||||
}
|
||||
|
||||
send(msg) {
|
||||
if (!this._isConnected()) return;
|
||||
if (msg.type !== 'pong') console.log("WS send:", msg)
|
||||
this._socket.send(JSON.stringify(msg));
|
||||
}
|
||||
|
||||
_onPeers(msg) {
|
||||
Events.fire('peers', msg);
|
||||
if (msg.roomType === "ip" && msg.peers.length === 0) {
|
||||
BrowserTabsConnector.removePeerIdsFromLocalStorage();
|
||||
BrowserTabsConnector.addPeerIdToLocalStorage().then(peerId => {
|
||||
if (peerId) return;
|
||||
console.log("successfully added peerId from localStorage");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_onDisplayName(msg) {
|
||||
// Add peerId and peerIdHash to sessionStorage to authenticate as the same device on page reload
|
||||
sessionStorage.setItem("peerId", msg.message.peerId);
|
||||
sessionStorage.setItem("peerIdHash", msg.message.peerIdHash);
|
||||
|
||||
// Add peerId to localStorage to mark it on other PairDrop tabs on the same browser
|
||||
BrowserTabsConnector.addPeerIdToLocalStorage().then(peerId => {
|
||||
if (peerId) return;
|
||||
console.log("successfully added peerId from localStorage");
|
||||
});
|
||||
|
||||
Events.fire('display-name', msg);
|
||||
}
|
||||
|
||||
|
@ -138,13 +170,19 @@ class ServerConnection {
|
|||
|
||||
_disconnect() {
|
||||
this.send({ type: 'disconnect' });
|
||||
if (this._socket) {
|
||||
this._socket.onclose = null;
|
||||
this._socket.close();
|
||||
this._socket = null;
|
||||
Events.fire('ws-disconnected');
|
||||
this._isReconnect = true;
|
||||
}
|
||||
|
||||
const peerId = sessionStorage.getItem("peerId");
|
||||
BrowserTabsConnector.removePeerIdFromLocalStorage(peerId).then(_ => {
|
||||
console.log("successfully removed peerId from localStorage");
|
||||
});
|
||||
|
||||
if (!this._socket) return;
|
||||
|
||||
this._socket.onclose = null;
|
||||
this._socket.close();
|
||||
this._socket = null;
|
||||
Events.fire('ws-disconnected');
|
||||
this._isReconnect = true;
|
||||
}
|
||||
|
||||
_onDisconnect() {
|
||||
|
@ -157,7 +195,7 @@ class ServerConnection {
|
|||
}
|
||||
|
||||
_onVisibilityChange() {
|
||||
if (document.hidden) return;
|
||||
if (window.hiddenProperty) return;
|
||||
this._connect();
|
||||
}
|
||||
|
||||
|
@ -183,11 +221,16 @@ class Peer {
|
|||
|
||||
constructor(serverConnection, peerId, roomType, roomSecret) {
|
||||
this._server = serverConnection;
|
||||
this._isCaller = !!peerId;
|
||||
this._peerId = peerId;
|
||||
this._roomType = roomType;
|
||||
this._roomSecret = roomSecret;
|
||||
this._updateRoomSecret(roomSecret);
|
||||
|
||||
this._filesQueue = [];
|
||||
this._busy = false;
|
||||
|
||||
// evaluate auto accept
|
||||
this._evaluateAutoAccept();
|
||||
}
|
||||
|
||||
sendJSON(message) {
|
||||
|
@ -198,12 +241,48 @@ class Peer {
|
|||
this.sendJSON({type: 'display-name-changed', displayName: displayName});
|
||||
}
|
||||
|
||||
async createHeader(file) {
|
||||
return {
|
||||
name: file.name,
|
||||
mime: file.type,
|
||||
size: file.size,
|
||||
};
|
||||
_updateRoomSecret(roomSecret) {
|
||||
// if peer is another browser tab, peer is not identifiable with roomSecret as browser tabs share all roomSecrets
|
||||
// -> abort
|
||||
if (BrowserTabsConnector.peerIsSameBrowser(this._peerId)) {
|
||||
this._roomSecret = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._roomSecret && this._roomSecret !== roomSecret) {
|
||||
// remove old roomSecrets to prevent multiple pairings with same peer
|
||||
PersistentStorage.deleteRoomSecret(this._roomSecret).then(deletedRoomSecret => {
|
||||
if (deletedRoomSecret) console.log("Successfully deleted duplicate room secret with same peer: ", deletedRoomSecret);
|
||||
})
|
||||
}
|
||||
|
||||
this._roomSecret = roomSecret;
|
||||
|
||||
if (this._roomSecret && this._roomSecret.length !== 256 && this._isCaller) {
|
||||
// increase security by increasing roomSecret length
|
||||
console.log('RoomSecret is regenerated to increase security')
|
||||
Events.fire('regenerate-room-secret', this._roomSecret);
|
||||
}
|
||||
}
|
||||
|
||||
_evaluateAutoAccept() {
|
||||
if (!this._roomSecret) {
|
||||
this._setAutoAccept(false);
|
||||
return;
|
||||
}
|
||||
|
||||
PersistentStorage.getRoomSecretEntry(this._roomSecret)
|
||||
.then(roomSecretEntry => {
|
||||
const autoAccept = roomSecretEntry ? roomSecretEntry.entry.auto_accept : false;
|
||||
this._setAutoAccept(autoAccept);
|
||||
})
|
||||
.catch(_ => {
|
||||
this._setAutoAccept(false);
|
||||
});
|
||||
}
|
||||
|
||||
_setAutoAccept(autoAccept) {
|
||||
this._autoAccept = autoAccept;
|
||||
}
|
||||
|
||||
getResizedImageDataUrl(file, width = undefined, height = undefined, quality = 0.7) {
|
||||
|
@ -248,7 +327,11 @@ class Peer {
|
|||
let imagesOnly = true
|
||||
for (let i=0; i<files.length; i++) {
|
||||
Events.fire('set-progress', {peerId: this._peerId, progress: 0.8*i/files.length, status: 'prepare'})
|
||||
header.push(await this.createHeader(files[i]));
|
||||
header.push({
|
||||
name: files[i].name,
|
||||
mime: files[i].type,
|
||||
size: files[i].size
|
||||
});
|
||||
totalSize += files[i].size;
|
||||
if (files[i].type.split('/')[0] !== 'image') imagesOnly = false;
|
||||
}
|
||||
|
@ -360,7 +443,7 @@ class Peer {
|
|||
|
||||
_onFilesTransferRequest(request) {
|
||||
if (this._requestPending) {
|
||||
// Only accept one request at a time
|
||||
// Only accept one request at a time per peer
|
||||
this.sendJSON({type: 'files-transfer-response', accepted: false});
|
||||
return;
|
||||
}
|
||||
|
@ -372,6 +455,14 @@ class Peer {
|
|||
}
|
||||
|
||||
this._requestPending = request;
|
||||
|
||||
if (this._autoAccept) {
|
||||
// auto accept if set via Edit Paired Devices Dialog
|
||||
this._respondToFileTransferRequest(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// default behavior: show user transfer request
|
||||
Events.fire('files-transfer-request', {
|
||||
request: request,
|
||||
peerId: this._peerId
|
||||
|
@ -497,9 +588,16 @@ class Peer {
|
|||
}
|
||||
|
||||
_onDisplayNameChanged(message) {
|
||||
if (!message.displayName || this._displayName === message.displayName) return;
|
||||
this._displayName = message.displayName;
|
||||
const displayNameHasChanged = this._displayName !== message.displayName
|
||||
|
||||
if (message.displayName && displayNameHasChanged) {
|
||||
this._displayName = message.displayName;
|
||||
}
|
||||
|
||||
Events.fire('peer-display-name-changed', {peerId: this._peerId, displayName: message.displayName});
|
||||
|
||||
if (!displayNameHasChanged) return;
|
||||
Events.fire('notify-peer-display-name-changed', this._peerId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -508,22 +606,21 @@ class RTCPeer extends Peer {
|
|||
constructor(serverConnection, peerId, roomType, roomSecret) {
|
||||
super(serverConnection, peerId, roomType, roomSecret);
|
||||
this.rtcSupported = true;
|
||||
if (!peerId) return; // we will listen for a caller
|
||||
if (!this._isCaller) return; // we will listen for a caller
|
||||
this._connect(peerId, true);
|
||||
}
|
||||
|
||||
_connect(peerId, isCaller) {
|
||||
if (!this._conn || this._conn.signalingState === "closed") this._openConnection(peerId, isCaller);
|
||||
_connect(peerId) {
|
||||
if (!this._conn || this._conn.signalingState === "closed") this._openConnection(peerId);
|
||||
|
||||
if (isCaller) {
|
||||
if (this._isCaller) {
|
||||
this._openChannel();
|
||||
} else {
|
||||
this._conn.ondatachannel = e => this._onChannelOpened(e);
|
||||
}
|
||||
}
|
||||
|
||||
_openConnection(peerId, isCaller) {
|
||||
this._isCaller = isCaller;
|
||||
_openConnection(peerId) {
|
||||
this._peerId = peerId;
|
||||
this._conn = new RTCPeerConnection(window.rtcConfig);
|
||||
this._conn.onicecandidate = e => this._onIceCandidate(e);
|
||||
|
@ -688,7 +785,11 @@ class RTCPeer extends Peer {
|
|||
refresh() {
|
||||
// check if channel is open. otherwise create one
|
||||
if (this._isConnected() || this._isConnecting()) return;
|
||||
this._connect(this._peerId, this._isCaller);
|
||||
|
||||
// only reconnect if peer is caller
|
||||
if (!this._isCaller) return;
|
||||
|
||||
this._connect(this._peerId);
|
||||
}
|
||||
|
||||
_isConnected() {
|
||||
|
@ -716,32 +817,60 @@ class PeersManager {
|
|||
Events.on('respond-to-files-transfer-request', e => this._onRespondToFileTransferRequest(e.detail))
|
||||
Events.on('send-text', e => this._onSendText(e.detail));
|
||||
Events.on('peer-left', e => this._onPeerLeft(e.detail));
|
||||
Events.on('peer-joined', e => this._onPeerJoined(e.detail));
|
||||
Events.on('peer-connected', e => this._onPeerConnected(e.detail.peerId));
|
||||
Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail));
|
||||
Events.on('secret-room-deleted', e => this._onSecretRoomDeleted(e.detail));
|
||||
Events.on('room-secret-regenerated', e => this._onRoomSecretRegenerated(e.detail));
|
||||
Events.on('display-name', e => this._onDisplayName(e.detail.message.displayName));
|
||||
Events.on('self-display-name-changed', e => this._notifyPeersDisplayNameChanged(e.detail));
|
||||
Events.on('peer-display-name-changed', e => this._notifyPeerDisplayNameChanged(e.detail.peerId));
|
||||
Events.on('notify-peer-display-name-changed', e => this._notifyPeerDisplayNameChanged(e.detail));
|
||||
Events.on('auto-accept-updated', e => this._onAutoAcceptUpdated(e.detail.roomSecret, e.detail.autoAccept));
|
||||
}
|
||||
|
||||
_onMessage(message) {
|
||||
// if different roomType -> abort
|
||||
if (this.peers[message.sender.id] && this.peers[message.sender.id]._roomType !== message.roomType) return;
|
||||
if (!this.peers[message.sender.id]) {
|
||||
this.peers[message.sender.id] = new RTCPeer(this._server, undefined, message.roomType, message.roomSecret);
|
||||
}
|
||||
this.peers[message.sender.id].onServerMessage(message);
|
||||
const peerId = message.sender.id;
|
||||
this.peers[peerId].onServerMessage(message);
|
||||
}
|
||||
|
||||
_onPeers(msg) {
|
||||
msg.peers.forEach(peer => {
|
||||
if (this.peers[peer.id]) {
|
||||
// if different roomType -> abort
|
||||
if (this.peers[peer.id].roomType !== msg.roomType || this.peers[peer.id].roomSecret !== msg.roomSecret) return;
|
||||
this.peers[peer.id].refresh();
|
||||
return;
|
||||
_refreshExistingPeer(peerId, roomType, roomSecret) {
|
||||
const peer = this.peers[peerId];
|
||||
if (peer) {
|
||||
const roomTypeIsSecret = roomType === "secret";
|
||||
const roomSecretsDiffer = peer._roomSecret !== roomSecret;
|
||||
|
||||
// if roomSecrets differs peer is already connected -> abort but update roomSecret and reevaluate auto accept
|
||||
if (roomTypeIsSecret && roomSecretsDiffer) {
|
||||
peer._updateRoomSecret(roomSecret);
|
||||
peer._evaluateAutoAccept();
|
||||
|
||||
return true;
|
||||
}
|
||||
this.peers[peer.id] = new RTCPeer(this._server, peer.id, msg.roomType, msg.roomSecret);
|
||||
|
||||
const roomTypesDiffer = peer._roomType !== roomType;
|
||||
|
||||
// if roomTypes differ peer is already connected -> abort
|
||||
if (roomTypesDiffer) return true;
|
||||
|
||||
peer.refresh();
|
||||
|
||||
return true;
|
||||
}
|
||||
// peer does not yet exist: return false
|
||||
return false;
|
||||
}
|
||||
|
||||
_onPeerJoined(message) {
|
||||
if (this._refreshExistingPeer(message.peer.id, message.roomType, message.roomSecret)) return;
|
||||
|
||||
this.peers[message.peer.id] = new RTCPeer(this._server, undefined, message.roomType, message.roomSecret);
|
||||
}
|
||||
|
||||
_onPeers(message) {
|
||||
message.peers.forEach(messagePeer => {
|
||||
if (this._refreshExistingPeer(messagePeer.id, message.roomType, message.roomSecret)) return;
|
||||
|
||||
this.peers[messagePeer.id] = new RTCPeer(this._server, messagePeer.id, message.roomType, message.roomSecret);
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -769,10 +898,10 @@ class PeersManager {
|
|||
this.peers[message.to].sendText(message.text);
|
||||
}
|
||||
|
||||
_onPeerLeft(msg) {
|
||||
if (msg.disconnect === true) {
|
||||
// if user actively disconnected from PairDrop disconnect all peer to peer connections immediately
|
||||
Events.fire('peer-disconnected', msg.peerId);
|
||||
_onPeerLeft(message) {
|
||||
if (message.disconnect === true) {
|
||||
// if user actively disconnected from PairDrop server, disconnect all peer to peer connections immediately
|
||||
Events.fire('peer-disconnected', message.peerId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -792,12 +921,19 @@ class PeersManager {
|
|||
_onSecretRoomDeleted(roomSecret) {
|
||||
for (const peerId in this.peers) {
|
||||
const peer = this.peers[peerId];
|
||||
if (peer._roomSecret === roomSecret) {
|
||||
if (peer._roomType === 'secret' && peer._roomSecret === roomSecret) {
|
||||
this._onPeerDisconnected(peerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onRoomSecretRegenerated(message) {
|
||||
PersistentStorage.updateRoomSecret(message.oldRoomSecret, message.newRoomSecret).then(_ => {
|
||||
console.log("successfully regenerated room secret");
|
||||
Events.fire("room-secrets", [message.newRoomSecret]);
|
||||
})
|
||||
}
|
||||
|
||||
_notifyPeersDisplayNameChanged(newDisplayName) {
|
||||
this._displayName = newDisplayName ? newDisplayName : this._originalDisplayName;
|
||||
for (const peerId in this.peers) {
|
||||
|
@ -813,6 +949,24 @@ class PeersManager {
|
|||
|
||||
_onDisplayName(displayName) {
|
||||
this._originalDisplayName = displayName;
|
||||
// if the displayName has not been changed (yet) set the displayName to the original displayName
|
||||
if (!this._displayName) this._displayName = displayName;
|
||||
}
|
||||
|
||||
_onAutoAcceptUpdated(roomSecret, autoAccept) {
|
||||
const peerId = this._getPeerIdFromRoomSecret(roomSecret);
|
||||
if (!peerId) return;
|
||||
this.peers[peerId]._setAutoAccept(autoAccept);
|
||||
}
|
||||
|
||||
_getPeerIdFromRoomSecret(roomSecret) {
|
||||
for (const peerId in this.peers) {
|
||||
const peer = this.peers[peerId];
|
||||
if (peer._roomSecret === roomSecret) {
|
||||
return peer._peerId;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ class PeersUI {
|
|||
this.$displayName.addEventListener('blur', e => this._saveDisplayName(e.target.innerText));
|
||||
|
||||
Events.on('self-display-name-changed', e => this._insertDisplayName(e.detail));
|
||||
Events.on('peer-display-name-changed', e => this._changePeerDisplayName(e.detail.peerId, e.detail.displayName));
|
||||
Events.on('peer-display-name-changed', e => this._onPeerDisplayNameChanged(e));
|
||||
|
||||
// Load saved display name on page load
|
||||
this._getSavedDisplayName().then(displayName => {
|
||||
|
@ -87,26 +87,31 @@ class PeersUI {
|
|||
if (newDisplayName === savedDisplayName) return;
|
||||
|
||||
if (newDisplayName) {
|
||||
PersistentStorage.set('editedDisplayName', newDisplayName).then(_ => {
|
||||
Events.fire('notify-user', 'Device name is changed permanently.');
|
||||
}).catch(_ => {
|
||||
console.log("This browser does not support IndexedDB. Use localStorage instead.");
|
||||
localStorage.setItem('editedDisplayName', newDisplayName);
|
||||
Events.fire('notify-user', 'Device name is changed only for this session.');
|
||||
}).finally(_ => {
|
||||
Events.fire('self-display-name-changed', newDisplayName);
|
||||
Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: newDisplayName});
|
||||
});
|
||||
PersistentStorage.set('editedDisplayName', newDisplayName)
|
||||
.then(_ => {
|
||||
Events.fire('notify-user', 'Device name is changed permanently.');
|
||||
})
|
||||
.catch(_ => {
|
||||
console.log("This browser does not support IndexedDB. Use localStorage instead.");
|
||||
localStorage.setItem('editedDisplayName', newDisplayName);
|
||||
Events.fire('notify-user', 'Device name is changed only for this session.');
|
||||
})
|
||||
.finally(_ => {
|
||||
Events.fire('self-display-name-changed', newDisplayName);
|
||||
Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: newDisplayName});
|
||||
});
|
||||
} else {
|
||||
PersistentStorage.delete('editedDisplayName').catch(_ => {
|
||||
console.log("This browser does not support IndexedDB. Use localStorage instead.")
|
||||
localStorage.removeItem('editedDisplayName');
|
||||
Events.fire('notify-user', 'Random Display name is used again.');
|
||||
}).finally(_ => {
|
||||
Events.fire('notify-user', 'Device name is randomly generated again.');
|
||||
Events.fire('self-display-name-changed', '');
|
||||
Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: ''});
|
||||
});
|
||||
PersistentStorage.delete('editedDisplayName')
|
||||
.catch(_ => {
|
||||
console.log("This browser does not support IndexedDB. Use localStorage instead.")
|
||||
localStorage.removeItem('editedDisplayName');
|
||||
Events.fire('notify-user', 'Random Display name is used again.');
|
||||
})
|
||||
.finally(_ => {
|
||||
Events.fire('notify-user', 'Device name is randomly generated again.');
|
||||
Events.fire('self-display-name-changed', '');
|
||||
Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: ''});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,6 +134,12 @@ class PeersUI {
|
|||
this.peers[peerId].name.displayName = displayName;
|
||||
const peerIdNode = $(peerId);
|
||||
if (peerIdNode && displayName) peerIdNode.querySelector('.name').textContent = displayName;
|
||||
this._redrawPeerRoomTypes(peerId);
|
||||
}
|
||||
|
||||
_onPeerDisplayNameChanged(e) {
|
||||
if (!e.detail.displayName) return;
|
||||
this._changePeerDisplayName(e.detail.peerId, e.detail.displayName);
|
||||
}
|
||||
|
||||
_onKeyDown(e) {
|
||||
|
@ -142,26 +153,43 @@ class PeersUI {
|
|||
}
|
||||
|
||||
_joinPeer(peer, roomType, roomSecret) {
|
||||
const existingPeer = this.peers[peer.id];
|
||||
if (existingPeer) {
|
||||
// peer already exists. Abort but add roomType to GUI and update roomSecret
|
||||
// skip if peer is a tab on the same browser
|
||||
if (!existingPeer.sameBrowser()) {
|
||||
// add roomType to PeerUI
|
||||
if (!existingPeer.roomTypes.includes(roomType)) {
|
||||
existingPeer.roomTypes.push(roomType);
|
||||
}
|
||||
this._redrawPeerRoomTypes(peer.id);
|
||||
|
||||
if (roomType === "secret") existingPeer.roomSecret = roomSecret;
|
||||
}
|
||||
return;
|
||||
}
|
||||
peer.sameBrowser = _ => BrowserTabsConnector.peerIsSameBrowser(peer.id);
|
||||
peer.roomTypes = [roomType];
|
||||
peer.roomSecret = roomSecret;
|
||||
if (this.peers[peer.id]) {
|
||||
if (!this.peers[peer.id].roomTypes.includes(roomType)) this.peers[peer.id].roomTypes.push(roomType);
|
||||
this._redrawPeer(this.peers[peer.id]);
|
||||
return; // peer already exists
|
||||
}
|
||||
this.peers[peer.id] = peer;
|
||||
}
|
||||
|
||||
_onPeerConnected(peerId, connectionHash) {
|
||||
if(this.peers[peerId] && !$(peerId))
|
||||
new PeerUI(this.peers[peerId], connectionHash);
|
||||
if (!this.peers[peerId] || $(peerId)) return;
|
||||
|
||||
const peer = this.peers[peerId];
|
||||
|
||||
new PeerUI(peer, connectionHash);
|
||||
}
|
||||
|
||||
_redrawPeer(peer) {
|
||||
const peerNode = $(peer.id);
|
||||
_redrawPeerRoomTypes(peerId) {
|
||||
const peer = this.peers[peerId]
|
||||
const peerNode = $(peerId);
|
||||
if (!peerNode) return;
|
||||
peerNode.classList.remove('type-ip', 'type-secret');
|
||||
peer.roomTypes.forEach(roomType => peerNode.classList.add(`type-${roomType}`));
|
||||
if (!peer.sameBrowser()) {
|
||||
peer.roomTypes.forEach(roomType => peerNode.classList.add(`type-${roomType}`));
|
||||
}
|
||||
}
|
||||
|
||||
evaluateOverflowing() {
|
||||
|
@ -187,7 +215,17 @@ class PeersUI {
|
|||
for (const peerId in this.peers) {
|
||||
const peer = this.peers[peerId];
|
||||
if (peer.roomSecret === roomSecret) {
|
||||
let index = peer.roomTypes.indexOf('secret');
|
||||
peer.roomTypes.splice(index, 1);
|
||||
peer.roomSecret = "";
|
||||
|
||||
if (peer.roomTypes.length) {
|
||||
this._redrawPeerRoomTypes(peerId)
|
||||
return;
|
||||
}
|
||||
|
||||
this._onPeerDisconnected(peerId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -364,8 +402,8 @@ class PeerUI {
|
|||
this.$el = document.createElement('x-peer');
|
||||
this.$el.id = this._peer.id;
|
||||
this.$el.ui = this;
|
||||
this._peer.roomTypes.forEach(roomType => this.$el.classList.add(`type-${roomType}`));
|
||||
this.$el.classList.add('center');
|
||||
if (!this._peer.sameBrowser()) this._peer.roomTypes.forEach(roomType => this.$el.classList.add(`type-${roomType}`));
|
||||
this.html();
|
||||
|
||||
this._callbackInput = e => this._onFilesSelected(e)
|
||||
|
@ -535,6 +573,10 @@ class Dialog {
|
|||
if (this.$autoFocus) this.$autoFocus.focus();
|
||||
}
|
||||
|
||||
isShown() {
|
||||
return !!this.$el.attributes["show"];
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.$el.removeAttribute('show');
|
||||
if (this.$autoFocus) {
|
||||
|
@ -543,10 +585,11 @@ class Dialog {
|
|||
}
|
||||
document.title = 'PairDrop';
|
||||
document.changeFavicon("images/favicon-96x96.png");
|
||||
this.correspondingPeerId = undefined;
|
||||
}
|
||||
|
||||
_onPeerDisconnected(peerId) {
|
||||
if (this.correspondingPeerId === peerId) {
|
||||
if (this.isShown() && this.correspondingPeerId === peerId) {
|
||||
this.hide();
|
||||
Events.fire('notify-user', 'Selected peer left.')
|
||||
}
|
||||
|
@ -812,14 +855,14 @@ class ReceiveRequestDialog extends ReceiveDialog {
|
|||
}
|
||||
|
||||
_onKeyDown(e) {
|
||||
if (this.$el.attributes["show"] && e.code === "Escape") {
|
||||
if (this.isShown() && e.code === "Escape") {
|
||||
this._respondToFileTransferRequest(false);
|
||||
}
|
||||
}
|
||||
|
||||
_onRequestFileTransfer(request, peerId) {
|
||||
this._filesTransferRequestQueue.push({request: request, peerId: peerId});
|
||||
if (this.$el.attributes["show"]) return;
|
||||
if (this.isShown()) return;
|
||||
this._dequeueRequests();
|
||||
}
|
||||
|
||||
|
@ -862,8 +905,12 @@ class ReceiveRequestDialog extends ReceiveDialog {
|
|||
}
|
||||
|
||||
hide() {
|
||||
this.$previewBox.innerHTML = '';
|
||||
// clear previewBox after dialog is closed
|
||||
setTimeout(_ => this.$previewBox.innerHTML = '', 300);
|
||||
|
||||
super.hide();
|
||||
|
||||
// show next request
|
||||
setTimeout(_ => this._dequeueRequests(), 500);
|
||||
}
|
||||
}
|
||||
|
@ -876,7 +923,7 @@ class PairDeviceDialog extends Dialog {
|
|||
this.$roomKey = this.$el.querySelector('#room-key');
|
||||
this.$qrCode = this.$el.querySelector('#room-key-qr-code');
|
||||
this.$pairDeviceBtn = $('pair-device');
|
||||
this.$clearSecretsBtn = $('clear-pair-devices');
|
||||
this.$editPairedDevicesBtn = $('edit-paired-devices');
|
||||
this.$footerInstructionsPairedDevices = $('and-by-paired-devices');
|
||||
this.$createJoinForm = this.$el.querySelector('form');
|
||||
|
||||
|
@ -894,14 +941,18 @@ class PairDeviceDialog extends Dialog {
|
|||
Events.on('ws-disconnected', _ => this.hide());
|
||||
Events.on('pair-device-initiated', e => this._pairDeviceInitiated(e.detail));
|
||||
Events.on('pair-device-joined', e => this._pairDeviceJoined(e.detail.peerId, e.detail.roomSecret));
|
||||
Events.on('peers', e => this._onPeers(e.detail));
|
||||
Events.on('peer-joined', e => this._onPeerJoined(e.detail));
|
||||
Events.on('pair-device-join-key-invalid', _ => this._pairDeviceJoinKeyInvalid());
|
||||
Events.on('pair-device-canceled', e => this._pairDeviceCanceled(e.detail));
|
||||
Events.on('clear-room-secrets', e => this._onClearRoomSecrets(e.detail))
|
||||
Events.on('evaluate-number-room-secrets', _ => this._evaluateNumberRoomSecrets())
|
||||
Events.on('secret-room-deleted', e => this._onSecretRoomDeleted(e.detail));
|
||||
this.$el.addEventListener('paste', e => this._onPaste(e));
|
||||
|
||||
this.evaluateRoomKeyChars();
|
||||
this.evaluateUrlAttributes();
|
||||
|
||||
this.pairPeer = {};
|
||||
}
|
||||
|
||||
_onCharsInput(e) {
|
||||
|
@ -917,7 +968,7 @@ class PairDeviceDialog extends Dialog {
|
|||
}
|
||||
|
||||
_onKeyDown(e) {
|
||||
if (this.$el.attributes["show"] && e.code === "Escape") {
|
||||
if (this.isShown() && e.code === "Escape") {
|
||||
// Timeout to prevent paste mode from getting cancelled simultaneously
|
||||
setTimeout(_ => this._pairDeviceCancel(), 50);
|
||||
}
|
||||
|
@ -978,7 +1029,7 @@ class PairDeviceDialog extends Dialog {
|
|||
PersistentStorage.getAllRoomSecrets().then(roomSecrets => {
|
||||
Events.fire('room-secrets', roomSecrets);
|
||||
this._evaluateNumberRoomSecrets();
|
||||
}).catch(_ => PersistentStorage.logBrowserNotCapable());
|
||||
});
|
||||
}
|
||||
|
||||
_pairDeviceInitiate() {
|
||||
|
@ -1026,22 +1077,69 @@ class PairDeviceDialog extends Dialog {
|
|||
}
|
||||
|
||||
_pairDeviceJoined(peerId, roomSecret) {
|
||||
this.hide();
|
||||
PersistentStorage.addRoomSecret(roomSecret).then(_ => {
|
||||
Events.fire('notify-user', 'Devices paired successfully.');
|
||||
const oldRoomSecret = $(peerId).ui.roomSecret;
|
||||
if (oldRoomSecret) PersistentStorage.deleteRoomSecret(oldRoomSecret);
|
||||
$(peerId).ui.roomSecret = roomSecret;
|
||||
this._evaluateNumberRoomSecrets();
|
||||
}).finally(_ => {
|
||||
// skip adding to IndexedDB if peer is another tab on the same browser
|
||||
if (BrowserTabsConnector.peerIsSameBrowser(peerId)) {
|
||||
this._cleanUp();
|
||||
})
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Paired devices are not persistent.');
|
||||
PersistentStorage.logBrowserNotCapable();
|
||||
this.hide();
|
||||
Events.fire('notify-user', 'Pairing of two browser tabs is not possible.');
|
||||
return;
|
||||
}
|
||||
|
||||
// save pairPeer and wait for it to connect to ensure both devices have gotten the roomSecret
|
||||
this.pairPeer = {
|
||||
"peerId": peerId,
|
||||
"roomSecret": roomSecret
|
||||
};
|
||||
}
|
||||
|
||||
_onPeers(message) {
|
||||
if (!Object.keys(this.pairPeer)) return;
|
||||
|
||||
message.peers.forEach(messagePeer => {
|
||||
this._evaluateJoinedPeer(messagePeer.id, message.roomSecret);
|
||||
});
|
||||
}
|
||||
|
||||
_onPeerJoined(message) {
|
||||
if (!Object.keys(this.pairPeer)) return;
|
||||
|
||||
this._evaluateJoinedPeer(message.peer.id, message.roomSecret);
|
||||
}
|
||||
|
||||
_evaluateJoinedPeer(peerId, roomSecret) {
|
||||
const samePeerId = peerId === this.pairPeer.peerId;
|
||||
const sameRoomSecret = roomSecret === this.pairPeer.roomSecret;
|
||||
|
||||
if (!peerId || !roomSecret || !samePeerId || !sameRoomSecret) return;
|
||||
|
||||
this._onPairPeerJoined(peerId, roomSecret);
|
||||
this.pairPeer = {};
|
||||
}
|
||||
|
||||
_onPairPeerJoined(peerId, roomSecret) {
|
||||
// if devices are paired that are already connected we must save the names at this point
|
||||
const $peer = $(peerId);
|
||||
let displayName, deviceName;
|
||||
if ($peer) {
|
||||
displayName = $peer.ui._peer.name.displayName;
|
||||
deviceName = $peer.ui._peer.name.deviceName;
|
||||
}
|
||||
|
||||
PersistentStorage.addRoomSecret(roomSecret, displayName, deviceName)
|
||||
.then(_ => {
|
||||
Events.fire('notify-user', 'Devices paired successfully.');
|
||||
this._evaluateNumberRoomSecrets();
|
||||
})
|
||||
.finally(_ => {
|
||||
this._cleanUp();
|
||||
this.hide();
|
||||
})
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Paired devices are not persistent.');
|
||||
PersistentStorage.logBrowserNotCapable();
|
||||
});
|
||||
}
|
||||
|
||||
_pairDeviceJoinKeyInvalid() {
|
||||
Events.fire('notify-user', 'Key not valid');
|
||||
}
|
||||
|
@ -1062,58 +1160,123 @@ class PairDeviceDialog extends Dialog {
|
|||
this.inputRoomKey = '';
|
||||
this.$inputRoomKeyChars.forEach(el => el.value = '');
|
||||
this.$inputRoomKeyChars.forEach(el => el.setAttribute("disabled", ""));
|
||||
}
|
||||
|
||||
_onClearRoomSecrets() {
|
||||
PersistentStorage.getAllRoomSecrets().then(roomSecrets => {
|
||||
Events.fire('room-secrets-cleared', roomSecrets);
|
||||
PersistentStorage.clearRoomSecrets().finally(_ => {
|
||||
Events.fire('notify-user', 'All Devices unpaired.')
|
||||
this._evaluateNumberRoomSecrets();
|
||||
})
|
||||
}).catch(_ => PersistentStorage.logBrowserNotCapable());
|
||||
this.pairPeer = {};
|
||||
}
|
||||
|
||||
_onSecretRoomDeleted(roomSecret) {
|
||||
PersistentStorage.deleteRoomSecret(roomSecret).then(_ => {
|
||||
this._evaluateNumberRoomSecrets();
|
||||
}).catch(e => console.error(e));
|
||||
});
|
||||
}
|
||||
|
||||
_evaluateNumberRoomSecrets() {
|
||||
PersistentStorage.getAllRoomSecrets().then(roomSecrets => {
|
||||
if (roomSecrets.length > 0) {
|
||||
this.$clearSecretsBtn.removeAttribute('hidden');
|
||||
this.$editPairedDevicesBtn.removeAttribute('hidden');
|
||||
this.$footerInstructionsPairedDevices.removeAttribute('hidden');
|
||||
} else {
|
||||
this.$clearSecretsBtn.setAttribute('hidden', '');
|
||||
this.$editPairedDevicesBtn.setAttribute('hidden', '');
|
||||
this.$footerInstructionsPairedDevices.setAttribute('hidden', '');
|
||||
}
|
||||
Events.fire('bg-resize');
|
||||
}).catch(_ => PersistentStorage.logBrowserNotCapable());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class ClearDevicesDialog extends Dialog {
|
||||
class EditPairedDevicesDialog extends Dialog {
|
||||
constructor() {
|
||||
super('clear-devices-dialog');
|
||||
$('clear-pair-devices').addEventListener('click', _ => this._onClearPairDevices());
|
||||
let clearDevicesForm = this.$el.querySelector('form');
|
||||
clearDevicesForm.addEventListener('submit', e => this._onSubmit(e));
|
||||
super('edit-paired-devices-dialog');
|
||||
this.$pairedDevicesWrapper = this.$el.querySelector('.paired-devices-wrapper');
|
||||
$('edit-paired-devices').addEventListener('click', _ => this._onEditPairedDevices());
|
||||
|
||||
Events.on('peer-display-name-changed', e => this._onPeerDisplayNameChanged(e));
|
||||
Events.on('keydown', e => this._onKeyDown(e));
|
||||
}
|
||||
|
||||
_onClearPairDevices() {
|
||||
this.show();
|
||||
_onKeyDown(e) {
|
||||
if (this.isShown() && e.code === "Escape") {
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
_onSubmit(e) {
|
||||
e.preventDefault();
|
||||
this._clearRoomSecrets();
|
||||
async _initDOM() {
|
||||
const roomSecretsEntries = await PersistentStorage.getAllRoomSecretEntries();
|
||||
roomSecretsEntries.forEach(roomSecretsEntry => {
|
||||
let $pairedDevice = document.createElement('div');
|
||||
$pairedDevice.classList = ["paired-device"];
|
||||
|
||||
$pairedDevice.innerHTML = `
|
||||
<div class="display-name">
|
||||
<span>${roomSecretsEntry.display_name}</span>
|
||||
</div>
|
||||
<div class="device-name">
|
||||
<span>${roomSecretsEntry.device_name}</span>
|
||||
</div>
|
||||
<div class="button-wrapper">
|
||||
<label class="auto-accept">auto-accept
|
||||
<input type="checkbox" ${roomSecretsEntry.auto_accept ? "checked" : ""}>
|
||||
</label>
|
||||
<button class="button" type="button">unpair</button>
|
||||
</div>`
|
||||
|
||||
$pairedDevice.querySelector('input[type="checkbox"]').addEventListener('click', e => {
|
||||
PersistentStorage.updateRoomSecretAutoAccept(roomSecretsEntry.secret, e.target.checked).then(roomSecretsEntry => {
|
||||
Events.fire('auto-accept-updated', {
|
||||
'roomSecret': roomSecretsEntry.entry.secret,
|
||||
'autoAccept': e.target.checked
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$pairedDevice.querySelector('button').addEventListener('click', e => {
|
||||
PersistentStorage.deleteRoomSecret(roomSecretsEntry.secret).then(roomSecret => {
|
||||
Events.fire('room-secrets-deleted', [roomSecret]);
|
||||
Events.fire('evaluate-number-room-secrets');
|
||||
e.target.parentNode.parentNode.remove();
|
||||
});
|
||||
})
|
||||
|
||||
this.$pairedDevicesWrapper.appendChild($pairedDevice)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
hide() {
|
||||
super.hide();
|
||||
setTimeout(_ => {
|
||||
this.$pairedDevicesWrapper.innerHTML = ""
|
||||
}, 300);
|
||||
}
|
||||
|
||||
_onEditPairedDevices() {
|
||||
this._initDOM().then(_ => this.show());
|
||||
}
|
||||
|
||||
_clearRoomSecrets() {
|
||||
Events.fire('clear-room-secrets');
|
||||
this.hide();
|
||||
PersistentStorage.getAllRoomSecrets()
|
||||
.then(roomSecrets => {
|
||||
PersistentStorage.clearRoomSecrets().finally(_ => {
|
||||
Events.fire('room-secrets-deleted', roomSecrets);
|
||||
Events.fire('evaluate-number-room-secrets');
|
||||
Events.fire('notify-user', 'All Devices unpaired.');
|
||||
this.hide();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
_onPeerDisplayNameChanged(e) {
|
||||
const peerId = e.detail.peerId;
|
||||
const peerNode = $(peerId);
|
||||
|
||||
if (!peerNode) return;
|
||||
|
||||
const peer = peerNode.ui._peer;
|
||||
|
||||
if (!peer.roomSecret) return;
|
||||
|
||||
PersistentStorage.updateRoomSecretNames(peer.roomSecret, peer.name.displayName, peer.name.deviceName).then(roomSecretEntry => {
|
||||
console.log(`Successfully updated DisplayName and DeviceName for roomSecretEntry ${roomSecretEntry.key}`);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1131,7 +1294,7 @@ class SendTextDialog extends Dialog {
|
|||
}
|
||||
|
||||
async _onKeyDown(e) {
|
||||
if (this.$el.attributes["show"]) {
|
||||
if (this.isShown()) {
|
||||
if (e.code === "Escape") {
|
||||
this.hide();
|
||||
} else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) {
|
||||
|
@ -1200,7 +1363,7 @@ class ReceiveTextDialog extends Dialog {
|
|||
}
|
||||
|
||||
async _onKeyDown(e) {
|
||||
if (this.$el.attributes["show"]) {
|
||||
if (this.isShown()) {
|
||||
if (e.code === "KeyC" && (e.ctrlKey || e.metaKey)) {
|
||||
await this._onCopy()
|
||||
this.hide();
|
||||
|
@ -1214,7 +1377,7 @@ class ReceiveTextDialog extends Dialog {
|
|||
window.blop.play();
|
||||
this._receiveTextQueue.push({text: text, peerId: peerId});
|
||||
this._setDocumentTitleMessages();
|
||||
if (this.$el.attributes["show"]) return;
|
||||
if (this.isShown()) return;
|
||||
this._dequeueRequests();
|
||||
}
|
||||
|
||||
|
@ -1682,7 +1845,7 @@ class PersistentStorage {
|
|||
PersistentStorage.logBrowserNotCapable();
|
||||
return;
|
||||
}
|
||||
const DBOpenRequest = window.indexedDB.open('pairdrop_store', 3);
|
||||
const DBOpenRequest = window.indexedDB.open('pairdrop_store', 4);
|
||||
DBOpenRequest.onerror = (e) => {
|
||||
PersistentStorage.logBrowserNotCapable();
|
||||
console.log('Error initializing database: ');
|
||||
|
@ -1693,27 +1856,32 @@ class PersistentStorage {
|
|||
};
|
||||
DBOpenRequest.onupgradeneeded = (e) => {
|
||||
const db = e.target.result;
|
||||
const txn = e.target.transaction;
|
||||
|
||||
db.onerror = e => console.log('Error loading database: ' + e);
|
||||
try {
|
||||
|
||||
console.log(`Upgrading IndexedDB database from version ${e.oldVersion} to version ${e.newVersion}`);
|
||||
|
||||
if (e.oldVersion === 0) {
|
||||
// initiate v1
|
||||
db.createObjectStore('keyval');
|
||||
} catch (error) {
|
||||
console.log("Object store named 'keyval' already exists")
|
||||
let roomSecretsObjectStore1 = db.createObjectStore('room_secrets', {autoIncrement: true});
|
||||
roomSecretsObjectStore1.createIndex('secret', 'secret', { unique: true });
|
||||
}
|
||||
|
||||
try {
|
||||
const roomSecretsObjectStore = db.createObjectStore('room_secrets', {autoIncrement: true});
|
||||
roomSecretsObjectStore.createIndex('secret', 'secret', { unique: true });
|
||||
} catch (error) {
|
||||
console.log("Object store named 'room_secrets' already exists")
|
||||
if (e.oldVersion <= 1) {
|
||||
// migrate to v2
|
||||
db.createObjectStore('share_target_files');
|
||||
}
|
||||
|
||||
try {
|
||||
if (db.objectStoreNames.contains('share_target_files')) {
|
||||
db.deleteObjectStore('share_target_files');
|
||||
}
|
||||
if (e.oldVersion <= 2) {
|
||||
// migrate to v3
|
||||
db.deleteObjectStore('share_target_files');
|
||||
db.createObjectStore('share_target_files', {autoIncrement: true});
|
||||
} catch (error) {
|
||||
console.log("Object store named 'share_target_files' already exists")
|
||||
}
|
||||
if (e.oldVersion <= 3) {
|
||||
// migrate to v4
|
||||
let roomSecretsObjectStore4 = txn.objectStore('room_secrets');
|
||||
roomSecretsObjectStore4.createIndex('display_name', 'display_name');
|
||||
roomSecretsObjectStore4.createIndex('auto_accept', 'auto_accept');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1746,7 +1914,7 @@ class PersistentStorage {
|
|||
const DBOpenRequest = window.indexedDB.open('pairdrop_store');
|
||||
DBOpenRequest.onsuccess = (e) => {
|
||||
const db = e.target.result;
|
||||
const transaction = db.transaction('keyval', 'readwrite');
|
||||
const transaction = db.transaction('keyval', 'readonly');
|
||||
const objectStore = transaction.objectStore('keyval');
|
||||
const objectStoreRequest = objectStore.get(key);
|
||||
objectStoreRequest.onsuccess = _ => {
|
||||
|
@ -1779,16 +1947,21 @@ class PersistentStorage {
|
|||
})
|
||||
}
|
||||
|
||||
static addRoomSecret(roomSecret) {
|
||||
static addRoomSecret(roomSecret, displayName, deviceName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const DBOpenRequest = window.indexedDB.open('pairdrop_store');
|
||||
DBOpenRequest.onsuccess = (e) => {
|
||||
const db = e.target.result;
|
||||
const transaction = db.transaction('room_secrets', 'readwrite');
|
||||
const objectStore = transaction.objectStore('room_secrets');
|
||||
const objectStoreRequest = objectStore.add({'secret': roomSecret});
|
||||
objectStoreRequest.onsuccess = _ => {
|
||||
console.log(`Request successful. RoomSecret added: ${roomSecret}`);
|
||||
const objectStoreRequest = objectStore.add({
|
||||
'secret': roomSecret,
|
||||
'display_name': displayName,
|
||||
'device_name': deviceName,
|
||||
'auto_accept': false
|
||||
});
|
||||
objectStoreRequest.onsuccess = e => {
|
||||
console.log(`Request successful. RoomSecret added: ${e.target.result}`);
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
|
@ -1798,21 +1971,26 @@ class PersistentStorage {
|
|||
})
|
||||
}
|
||||
|
||||
static getAllRoomSecrets() {
|
||||
static async getAllRoomSecrets() {
|
||||
const roomSecrets = await this.getAllRoomSecretEntries();
|
||||
let secrets = [];
|
||||
for (let i=0; i<roomSecrets.length; i++) {
|
||||
secrets.push(roomSecrets[i].secret);
|
||||
}
|
||||
console.log(`Request successful. Retrieved ${secrets.length} room_secrets`);
|
||||
return(secrets);
|
||||
}
|
||||
|
||||
static getAllRoomSecretEntries() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const DBOpenRequest = window.indexedDB.open('pairdrop_store');
|
||||
DBOpenRequest.onsuccess = (e) => {
|
||||
const db = e.target.result;
|
||||
const transaction = db.transaction('room_secrets', 'readwrite');
|
||||
const transaction = db.transaction('room_secrets', 'readonly');
|
||||
const objectStore = transaction.objectStore('room_secrets');
|
||||
const objectStoreRequest = objectStore.getAll();
|
||||
objectStoreRequest.onsuccess = e => {
|
||||
let secrets = [];
|
||||
for (let i=0; i<e.target.result.length; i++) {
|
||||
secrets.push(e.target.result[i].secret);
|
||||
}
|
||||
console.log(`Request successful. Retrieved ${secrets.length} room_secrets`);
|
||||
resolve(secrets);
|
||||
resolve(e.target.result);
|
||||
}
|
||||
}
|
||||
DBOpenRequest.onerror = (e) => {
|
||||
|
@ -1821,24 +1999,59 @@ class PersistentStorage {
|
|||
});
|
||||
}
|
||||
|
||||
static deleteRoomSecret(room_secret) {
|
||||
static getRoomSecretEntry(roomSecret) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const DBOpenRequest = window.indexedDB.open('pairdrop_store');
|
||||
DBOpenRequest.onsuccess = (e) => {
|
||||
const db = e.target.result;
|
||||
const transaction = db.transaction('room_secrets', 'readonly');
|
||||
const objectStore = transaction.objectStore('room_secrets');
|
||||
const objectStoreRequestKey = objectStore.index("secret").getKey(roomSecret);
|
||||
objectStoreRequestKey.onsuccess = e => {
|
||||
const key = e.target.result;
|
||||
if (!key) {
|
||||
console.log(`Nothing to retrieve. Entry for room_secret not existing: ${roomSecret}`);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const objectStoreRequestRetrieval = objectStore.get(key);
|
||||
objectStoreRequestRetrieval.onsuccess = e => {
|
||||
console.log(`Request successful. Retrieved entry for room_secret: ${key}`);
|
||||
resolve({
|
||||
"entry": e.target.result,
|
||||
"key": key
|
||||
});
|
||||
}
|
||||
objectStoreRequestRetrieval.onerror = (e) => {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
DBOpenRequest.onerror = (e) => {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static deleteRoomSecret(roomSecret) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const DBOpenRequest = window.indexedDB.open('pairdrop_store');
|
||||
DBOpenRequest.onsuccess = (e) => {
|
||||
const db = e.target.result;
|
||||
const transaction = db.transaction('room_secrets', 'readwrite');
|
||||
const objectStore = transaction.objectStore('room_secrets');
|
||||
const objectStoreRequestKey = objectStore.index("secret").getKey(room_secret);
|
||||
const objectStoreRequestKey = objectStore.index("secret").getKey(roomSecret);
|
||||
objectStoreRequestKey.onsuccess = e => {
|
||||
if (!e.target.result) {
|
||||
console.log(`Nothing to delete. room_secret not existing: ${room_secret}`);
|
||||
console.log(`Nothing to delete. room_secret not existing: ${roomSecret}`);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const objectStoreRequestDeletion = objectStore.delete(e.target.result);
|
||||
const key = e.target.result;
|
||||
const objectStoreRequestDeletion = objectStore.delete(key);
|
||||
objectStoreRequestDeletion.onsuccess = _ => {
|
||||
console.log(`Request successful. Deleted room_secret: ${room_secret}`);
|
||||
resolve();
|
||||
console.log(`Request successful. Deleted room_secret: ${key}`);
|
||||
resolve(roomSecret);
|
||||
}
|
||||
objectStoreRequestDeletion.onerror = (e) => {
|
||||
reject(e);
|
||||
|
@ -1869,22 +2082,108 @@ class PersistentStorage {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
static updateRoomSecretNames(roomSecret, displayName, deviceName) {
|
||||
return this.updateRoomSecret(roomSecret, undefined, displayName, deviceName);
|
||||
}
|
||||
|
||||
static updateRoomSecretAutoAccept(roomSecret, autoAccept) {
|
||||
return this.updateRoomSecret(roomSecret, undefined, undefined, undefined, autoAccept);
|
||||
}
|
||||
|
||||
static updateRoomSecret(roomSecret, updatedRoomSecret = undefined, updatedDisplayName = undefined, updatedDeviceName = undefined, updatedAutoAccept = undefined) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const DBOpenRequest = window.indexedDB.open('pairdrop_store');
|
||||
DBOpenRequest.onsuccess = (e) => {
|
||||
const db = e.target.result;
|
||||
this.getRoomSecretEntry(roomSecret)
|
||||
.then(roomSecretEntry => {
|
||||
if (!roomSecretEntry) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
const transaction = db.transaction('room_secrets', 'readwrite');
|
||||
const objectStore = transaction.objectStore('room_secrets');
|
||||
// Do not use `updatedRoomSecret ?? roomSecretEntry.entry.secret` to ensure compatibility with older browsers
|
||||
const updatedRoomSecretEntry = {
|
||||
'secret': updatedRoomSecret !== undefined ? updatedRoomSecret : roomSecretEntry.entry.secret,
|
||||
'display_name': updatedDisplayName !== undefined ? updatedDisplayName : roomSecretEntry.entry.display_name,
|
||||
'device_name': updatedDeviceName !== undefined ? updatedDeviceName : roomSecretEntry.entry.device_name,
|
||||
'auto_accept': updatedAutoAccept !== undefined ? updatedAutoAccept : roomSecretEntry.entry.auto_accept
|
||||
};
|
||||
|
||||
const objectStoreRequestUpdate = objectStore.put(updatedRoomSecretEntry, roomSecretEntry.key);
|
||||
|
||||
objectStoreRequestUpdate.onsuccess = e => {
|
||||
console.log(`Request successful. Updated room_secret: ${roomSecretEntry.key}`);
|
||||
resolve({
|
||||
"entry": updatedRoomSecretEntry,
|
||||
"key": roomSecretEntry.key
|
||||
});
|
||||
}
|
||||
|
||||
objectStoreRequestUpdate.onerror = (e) => {
|
||||
reject(e);
|
||||
}
|
||||
})
|
||||
.catch(e => reject(e));
|
||||
};
|
||||
|
||||
DBOpenRequest.onerror = e => reject(e);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class Broadcast {
|
||||
class BrowserTabsConnector {
|
||||
constructor() {
|
||||
this.bc = new BroadcastChannel('pairdrop');
|
||||
this.bc.addEventListener('message', e => this._onMessage(e));
|
||||
Events.on('broadcast-send', e => this._broadcastMessage(e.detail));
|
||||
Events.on('broadcast-send', e => this._broadcastSend(e.detail));
|
||||
}
|
||||
|
||||
_broadcastMessage(message) {
|
||||
_broadcastSend(message) {
|
||||
this.bc.postMessage(message);
|
||||
}
|
||||
|
||||
_onMessage(e) {
|
||||
console.log('Broadcast message received:', e.data)
|
||||
Events.fire(e.data.type, e.data.detail);
|
||||
console.log('Broadcast:', e.data)
|
||||
switch (e.data.type) {
|
||||
case 'self-display-name-changed':
|
||||
Events.fire('self-display-name-changed', e.data.detail);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static peerIsSameBrowser(peerId) {
|
||||
let peerIdsBrowser = JSON.parse(localStorage.getItem("peerIdsBrowser"));
|
||||
return peerIdsBrowser
|
||||
? peerIdsBrowser.indexOf(peerId) !== -1
|
||||
: false;
|
||||
}
|
||||
|
||||
static async addPeerIdToLocalStorage() {
|
||||
const peerId = sessionStorage.getItem("peerId");
|
||||
if (!peerId) return false;
|
||||
|
||||
let peerIdsBrowser = [];
|
||||
let peerIdsBrowserOld = JSON.parse(localStorage.getItem("peerIdsBrowser"));
|
||||
if (peerIdsBrowserOld) peerIdsBrowser.push(...peerIdsBrowserOld);
|
||||
peerIdsBrowser.push(peerId);
|
||||
peerIdsBrowser = peerIdsBrowser.filter(onlyUnique);
|
||||
localStorage.setItem("peerIdsBrowser", JSON.stringify(peerIdsBrowser));
|
||||
return peerId;
|
||||
}
|
||||
|
||||
static async removePeerIdFromLocalStorage(peerId) {
|
||||
let peerIdsBrowser = JSON.parse(localStorage.getItem("peerIdsBrowser"));
|
||||
const index = peerIdsBrowser.indexOf(peerId);
|
||||
peerIdsBrowser.splice(index, 1);
|
||||
localStorage.setItem("peerIdsBrowser", JSON.stringify(peerIdsBrowser));
|
||||
return peerId;
|
||||
}
|
||||
|
||||
static removePeerIdsFromLocalStorage() {
|
||||
localStorage.removeItem("peerIdsBrowser");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1899,7 +2198,7 @@ class PairDrop {
|
|||
const sendTextDialog = new SendTextDialog();
|
||||
const receiveTextDialog = new ReceiveTextDialog();
|
||||
const pairDeviceDialog = new PairDeviceDialog();
|
||||
const clearDevicesDialog = new ClearDevicesDialog();
|
||||
const clearDevicesDialog = new EditPairedDevicesDialog();
|
||||
const base64ZipDialog = new Base64ZipDialog();
|
||||
const toast = new Toast();
|
||||
const notifications = new Notifications();
|
||||
|
@ -1907,7 +2206,7 @@ class PairDrop {
|
|||
const webShareTargetUI = new WebShareTargetUI();
|
||||
const webFileHandlersUI = new WebFileHandlersUI();
|
||||
const noSleepUI = new NoSleepUI();
|
||||
const broadCast = new Broadcast();
|
||||
const broadCast = new BrowserTabsConnector();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -398,3 +398,7 @@ const cyrb53 = function(str, seed = 0) {
|
|||
h2 = Math.imul(h2 ^ (h2>>>16), 2246822507) ^ Math.imul(h1 ^ (h1>>>13), 3266489909);
|
||||
return 4294967296 * (2097151 & h2) + (h1>>>0);
|
||||
};
|
||||
|
||||
function onlyUnique (value, index, array) {
|
||||
return array.indexOf(value) === index;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,10 @@ body {
|
|||
overflow-x: hidden;
|
||||
overscroll-behavior: none;
|
||||
overflow-y: hidden;
|
||||
/* Only allow selection on message and pair key */
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
body {
|
||||
|
@ -215,6 +219,14 @@ hr {
|
|||
color: white;
|
||||
}
|
||||
|
||||
input {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
min-width: 13px;
|
||||
}
|
||||
|
||||
x-noscript {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
|
@ -273,7 +285,7 @@ x-noscript {
|
|||
}
|
||||
|
||||
@media screen and (max-width: 425px) {
|
||||
header:has(#clear-pair-devices:not([hidden]))~#center {
|
||||
header:has(#edit-pair-devices:not([hidden]))~#center {
|
||||
--footer-height: 150px;
|
||||
}
|
||||
}
|
||||
|
@ -442,8 +454,6 @@ x-no-peers[drop-bg] * {
|
|||
/* Peer */
|
||||
|
||||
x-peer {
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
padding: 8px;
|
||||
align-content: start;
|
||||
flex-wrap: wrap;
|
||||
|
@ -530,7 +540,6 @@ x-peer[status] x-icon {
|
|||
.status,
|
||||
.device-name,
|
||||
.connection-hash {
|
||||
height: 18px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
|
@ -691,6 +700,12 @@ x-dialog x-paper {
|
|||
height: 625px;
|
||||
}
|
||||
|
||||
#pair-device-dialog ::-moz-selection,
|
||||
#pair-device-dialog ::selection {
|
||||
color: black;
|
||||
background: var(--paired-device-color);
|
||||
}
|
||||
|
||||
x-dialog:not([show]) {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
@ -712,7 +727,7 @@ x-dialog .font-subheading {
|
|||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
/* PairDevicesDialog */
|
||||
/* Pair Devices Dialog */
|
||||
|
||||
#key-input-container {
|
||||
width: 100%;
|
||||
|
@ -720,7 +735,7 @@ x-dialog .font-subheading {
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
#key-input-container>input {
|
||||
#key-input-container > input {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
font-size: 30px;
|
||||
|
@ -736,15 +751,18 @@ x-dialog .font-subheading {
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
#key-input-container>input + * {
|
||||
#key-input-container > input + * {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
#key-input-container>input:nth-of-type(4) {
|
||||
#key-input-container > input:nth-of-type(4) {
|
||||
margin-left: 5%;
|
||||
}
|
||||
|
||||
#room-key {
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
user-select: text;
|
||||
font-size: 50px;
|
||||
letter-spacing: min(calc((100vw - 80px - 99px) / 100 * 7), 23px);
|
||||
display: inline-block;
|
||||
|
@ -756,14 +774,106 @@ x-dialog .font-subheading {
|
|||
margin: 16px;
|
||||
}
|
||||
|
||||
#pair-device-dialog hr {
|
||||
margin: 40px -24px;
|
||||
x-dialog hr {
|
||||
margin: 40px -24px 30px -24px;
|
||||
border: solid 1.25px var(--border-color);
|
||||
}
|
||||
|
||||
#pair-device-dialog x-background {
|
||||
padding: 16px!important;
|
||||
}
|
||||
|
||||
/* Edit Paired Devices Dialog */
|
||||
.paired-devices-wrapper:empty:before {
|
||||
content: "No paired devices.";
|
||||
}
|
||||
|
||||
.paired-devices-wrapper:empty {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.paired-devices-wrapper {
|
||||
border-top: solid 4px var(--paired-device-color);
|
||||
border-bottom: solid 4px var(--paired-device-color);
|
||||
max-height: 65vh;
|
||||
overflow: scroll;
|
||||
background: /* Shadow covers */ linear-gradient(rgb(var(--bg-color)) 30%, rgba(var(--bg-color), 0)),
|
||||
linear-gradient(rgba(var(--bg-color), 0), rgb(var(--bg-color)) 70%) 0 100%,
|
||||
/* Shadows */ radial-gradient(farthest-side at 50% 0, rgba(var(--text-color), .3), rgba(var(--text-color), 0)),
|
||||
radial-gradient(farthest-side at 50% 100%, rgba(var(--text-color), .3), rgba(var(--text-color), 0)) 0 100%;
|
||||
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 80px, 100% 80px, 100% 24px, 100% 24px;
|
||||
|
||||
/* Opera doesn't support this in the shorthand */
|
||||
background-attachment: local, local, scroll, scroll;
|
||||
}
|
||||
|
||||
.paired-device {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.paired-device:not(:last-child) {
|
||||
border-bottom: solid 4px var(--paired-device-color);
|
||||
}
|
||||
|
||||
.paired-device > .display-name,
|
||||
.paired-device > .device-name {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
align-self: center;
|
||||
border-bottom: solid 2px rgba(128, 128, 128, 0.5);
|
||||
opacity: 1;
|
||||
}
|
||||
.paired-device span {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.paired-device > .button-wrapper {
|
||||
display: flex;
|
||||
height: 36px;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.paired-device > .button-wrapper > label,
|
||||
.paired-device > .button-wrapper > button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
justify-content: center;
|
||||
width: 50%;
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.paired-device > .button-wrapper > :not(:last-child) {
|
||||
border-right: solid 1px rgba(128, 128, 128, 0.5);
|
||||
}
|
||||
|
||||
.paired-device > .button-wrapper > :not(:first-child) {
|
||||
border-left: solid 1px rgba(128, 128, 128, 0.5);
|
||||
}
|
||||
|
||||
.paired-device * {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.paired-device > .auto-accept {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Receive Dialog */
|
||||
|
||||
x-dialog .row {
|
||||
|
@ -812,7 +922,7 @@ x-paper > div:last-child > .button:not(:last-child) {
|
|||
}
|
||||
|
||||
/* Send Text Dialog */
|
||||
|
||||
/* Todo: add pair underline to send / receive dialogs displayName */
|
||||
x-dialog .dialog-subheader {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
@ -830,9 +940,9 @@ x-dialog .dialog-subheader {
|
|||
max-height: calc(100vh - 393px);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-user-select: all;
|
||||
-moz-user-select: all;
|
||||
user-select: all;
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
user-select: text;
|
||||
white-space: pre-wrap;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
@ -1034,8 +1144,6 @@ button::-moz-focus-inner {
|
|||
|
||||
#about x-background {
|
||||
position: absolute;
|
||||
top: calc(28px - 250px);
|
||||
right: calc(36px - 250px);
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
border-radius: 50%;
|
||||
|
@ -1178,23 +1286,6 @@ x-peers:empty~x-instructions {
|
|||
}
|
||||
}
|
||||
|
||||
#websocket-fallback {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
opacity: 0.5;
|
||||
transition: opacity 300ms;
|
||||
}
|
||||
|
||||
#websocket-fallback>span {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
#websocket-fallback > span > span {
|
||||
border-bottom: solid 4px var(--ws-peer-color);
|
||||
}
|
||||
|
||||
/* Responsive Styles */
|
||||
@media screen and (max-width: 360px) {
|
||||
x-dialog x-paper {
|
||||
|
@ -1337,3 +1428,9 @@ x-dialog x-paper {
|
|||
background: #bfbfbf;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-moz-selection,
|
||||
::selection {
|
||||
color: black;
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue