mirror of
https://github.com/schlagmichdoch/PairDrop.git
synced 2026-04-06 09:53:49 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fc8e85f75 | ||
|
|
5eeaae01fe | ||
|
|
660e523263 | ||
|
|
cdfbc7a2df | ||
|
|
c9dca7e083 | ||
|
|
79af04d95a | ||
|
|
954e9c7c3a | ||
|
|
c0d504f6a8 | ||
|
|
36e152dc7c | ||
|
|
fdf024f378 | ||
|
|
9f2e4c5f8f | ||
|
|
8e219914ec | ||
|
|
d1273ef9cc |
18
package-lock.json
generated
18
package-lock.json
generated
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "pairdrop",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pairdrop",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.3",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^6.7.0",
|
||||
"ua-parser-js": "^1.0.33",
|
||||
"ua-parser-js": "^1.0.34",
|
||||
"unique-names-generator": "^4.3.0",
|
||||
"ws": "^8.12.1"
|
||||
},
|
||||
@@ -583,9 +583,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ua-parser-js": {
|
||||
"version": "1.0.33",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz",
|
||||
"integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==",
|
||||
"version": "1.0.34",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.34.tgz",
|
||||
"integrity": "sha512-K9mwJm/DaB6mRLZfw6q8IMXipcrmuT6yfhYmwhAkuh+81sChuYstYA+znlgaflUPaYUa3odxKPKGw6Vw/lANew==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -1070,9 +1070,9 @@
|
||||
}
|
||||
},
|
||||
"ua-parser-js": {
|
||||
"version": "1.0.33",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz",
|
||||
"integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ=="
|
||||
"version": "1.0.34",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.34.tgz",
|
||||
"integrity": "sha512-K9mwJm/DaB6mRLZfw6q8IMXipcrmuT6yfhYmwhAkuh+81sChuYstYA+znlgaflUPaYUa3odxKPKGw6Vw/lANew=="
|
||||
},
|
||||
"unique-names-generator": {
|
||||
"version": "4.7.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pairdrop",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.3",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -12,7 +12,7 @@
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^6.7.0",
|
||||
"ua-parser-js": "^1.0.33",
|
||||
"ua-parser-js": "^1.0.34",
|
||||
"unique-names-generator": "^4.3.0",
|
||||
"ws": "^8.12.1"
|
||||
},
|
||||
|
||||
@@ -238,6 +238,7 @@
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<button class="button center" id="base64-paste-btn" title="Paste">Tap here to paste files</button>
|
||||
<div class="textarea" placeholder="Paste here to send files" title="CMD/⌘ + V" contenteditable hidden></div>
|
||||
<button class="button center" close>Close</button>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
||||
@@ -194,6 +194,10 @@ class Peer {
|
||||
this._send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
sendDisplayName(displayName) {
|
||||
this.sendJSON({type: 'display-name-changed', displayName: displayName});
|
||||
}
|
||||
|
||||
async createHeader(file) {
|
||||
return {
|
||||
name: file.name,
|
||||
@@ -490,7 +494,8 @@ class Peer {
|
||||
}
|
||||
|
||||
_onDisplayNameChanged(message) {
|
||||
if (!message.displayName) return;
|
||||
if (!message.displayName || this._displayName === message.displayName) return;
|
||||
this._displayName = message.displayName;
|
||||
Events.fire('peer-display-name-changed', {peerId: this._peerId, displayName: message.displayName});
|
||||
}
|
||||
}
|
||||
@@ -689,6 +694,11 @@ class RTCPeer extends Peer {
|
||||
_isConnecting() {
|
||||
return this._channel && this._channel.readyState === 'connecting';
|
||||
}
|
||||
|
||||
sendDisplayName(displayName) {
|
||||
if (!this._isConnected()) return;
|
||||
super.sendDisplayName(displayName);
|
||||
}
|
||||
}
|
||||
|
||||
class PeersManager {
|
||||
@@ -707,6 +717,7 @@ class PeersManager {
|
||||
Events.on('secret-room-deleted', e => this._onSecretRoomDeleted(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));
|
||||
}
|
||||
|
||||
_onMessage(message) {
|
||||
@@ -793,7 +804,7 @@ class PeersManager {
|
||||
_notifyPeerDisplayNameChanged(peerId) {
|
||||
const peer = this.peers[peerId];
|
||||
if (!peer) return;
|
||||
this.peers[peerId].sendJSON({type: 'display-name-changed', displayName: this._displayName});
|
||||
this.peers[peerId].sendDisplayName(this._displayName);
|
||||
}
|
||||
|
||||
_onDisplayName(displayName) {
|
||||
@@ -887,11 +898,11 @@ class Events {
|
||||
window.dispatchEvent(new CustomEvent(type, { detail: detail }));
|
||||
}
|
||||
|
||||
static on(type, callback) {
|
||||
return window.addEventListener(type, callback, false);
|
||||
static on(type, callback, options = false) {
|
||||
return window.addEventListener(type, callback, options);
|
||||
}
|
||||
|
||||
static off(type, callback) {
|
||||
return window.removeEventListener(type, callback, false);
|
||||
static off(type, callback, options = false) {
|
||||
return window.removeEventListener(type, callback, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ class PeersUI {
|
||||
|
||||
const _callback = (e) => this._sendClipboardData(e, files, text);
|
||||
Events.on('paste-pointerdown', _callback);
|
||||
Events.on('deactivate-paste-mode', _ => this._deactivatePasteMode(_callback));
|
||||
Events.on('deactivate-paste-mode', _ => this._deactivatePasteMode(_callback), { once: true });
|
||||
|
||||
this.$cancelPasteModeBtn.removeAttribute('hidden');
|
||||
|
||||
@@ -300,7 +300,8 @@ class PeerUI {
|
||||
|
||||
constructor(peer, connectionHash) {
|
||||
this._peer = peer;
|
||||
this._connectionHash = connectionHash;
|
||||
this._connectionHash =
|
||||
`${connectionHash.substring(0, 4)} ${connectionHash.substring(4, 8)} ${connectionHash.substring(8, 12)} ${connectionHash.substring(12, 16)}`;
|
||||
this._initDom();
|
||||
this._bindListeners();
|
||||
|
||||
@@ -345,8 +346,7 @@ class PeerUI {
|
||||
this.$el.querySelector('svg use').setAttribute('xlink:href', this._icon());
|
||||
this.$el.querySelector('.name').textContent = this._displayName();
|
||||
this.$el.querySelector('.device-name').textContent = this._deviceName();
|
||||
this.$el.querySelector('.connection-hash').textContent =
|
||||
this._connectionHash.substring(0, 4) + " " + this._connectionHash.substring(4, 8) + " " + this._connectionHash.substring(8, 12) + " " + this._connectionHash.substring(12, 16);
|
||||
this.$el.querySelector('.connection-hash').textContent = this._connectionHash;
|
||||
}
|
||||
|
||||
_initDom() {
|
||||
@@ -569,7 +569,7 @@ class ReceiveDialog extends Dialog {
|
||||
}
|
||||
}
|
||||
|
||||
_parseFileData(displayName, files, imagesOnly, totalSize) {
|
||||
_parseFileData(displayName, connectionHash, files, imagesOnly, totalSize) {
|
||||
if (files.length > 1) {
|
||||
let fileOtherText = ` and ${files.length - 1} other `;
|
||||
if (files.length === 2) {
|
||||
@@ -586,6 +586,7 @@ class ReceiveDialog extends Dialog {
|
||||
this.$fileStem.innerText = fileName.substring(0, fileName.length - fileExtension.length);
|
||||
this.$fileExtension.innerText = fileExtension;
|
||||
this.$displayName.innerText = displayName;
|
||||
this.$displayName.title = connectionHash;
|
||||
this.$fileSize.innerText = this._formatFileSize(totalSize);
|
||||
}
|
||||
}
|
||||
@@ -603,8 +604,9 @@ class ReceiveFileDialog extends ReceiveDialog {
|
||||
}
|
||||
|
||||
_onFilesReceived(sender, files, imagesOnly, totalSize) {
|
||||
const displayName = $(sender).ui._displayName()
|
||||
this._filesQueue.push({peer: sender, displayName: displayName, files: files, imagesOnly: imagesOnly, totalSize: totalSize});
|
||||
const displayName = $(sender).ui._displayName();
|
||||
const connectionHash = $(sender).ui._connectionHash;
|
||||
this._filesQueue.push({peer: sender, displayName: displayName, connectionHash: connectionHash, files: files, imagesOnly: imagesOnly, totalSize: totalSize});
|
||||
this._nextFiles();
|
||||
window.blop.play();
|
||||
}
|
||||
@@ -612,12 +614,11 @@ class ReceiveFileDialog extends ReceiveDialog {
|
||||
_nextFiles() {
|
||||
if (this._busy) return;
|
||||
this._busy = true;
|
||||
const {peer, displayName, files, imagesOnly, totalSize} = this._filesQueue.shift();
|
||||
this._displayFiles(peer, displayName, files, imagesOnly, totalSize);
|
||||
const {peer, displayName, connectionHash, files, imagesOnly, totalSize} = this._filesQueue.shift();
|
||||
this._displayFiles(peer, displayName, connectionHash, files, imagesOnly, totalSize);
|
||||
}
|
||||
|
||||
_dequeueFile() {
|
||||
// Todo: change count in document.title and move '- PairDrop' to back
|
||||
if (!this._filesQueue.length) { // nothing to do
|
||||
this._busy = false;
|
||||
return;
|
||||
@@ -655,8 +656,8 @@ class ReceiveFileDialog extends ReceiveDialog {
|
||||
});
|
||||
}
|
||||
|
||||
async _displayFiles(peerId, displayName, files, imagesOnly, totalSize) {
|
||||
this._parseFileData(displayName, files, imagesOnly, totalSize);
|
||||
async _displayFiles(peerId, displayName, connectionHash, files, imagesOnly, totalSize) {
|
||||
this._parseFileData(displayName, connectionHash, files, imagesOnly, totalSize);
|
||||
|
||||
let descriptor, url, filenameDownload;
|
||||
if (files.length === 1) {
|
||||
@@ -803,7 +804,8 @@ class ReceiveRequestDialog extends ReceiveDialog {
|
||||
this.correspondingPeerId = peerId;
|
||||
|
||||
const displayName = $(peerId).ui._displayName();
|
||||
this._parseFileData(displayName, request.header, request.imagesOnly, request.totalSize);
|
||||
const connectionHash = $(peerId).ui._connectionHash;
|
||||
this._parseFileData(displayName, connectionHash, request.header, request.imagesOnly, request.totalSize);
|
||||
|
||||
if (request.thumbnailDataUrl?.substring(0, 22) === "data:image/jpeg;base64") {
|
||||
let element = document.createElement('img');
|
||||
@@ -1245,21 +1247,21 @@ class Base64ZipDialog extends Dialog {
|
||||
const base64Hash = window.location.hash.substring(1);
|
||||
|
||||
this.$pasteBtn = this.$el.querySelector('#base64-paste-btn');
|
||||
this.$fallbackTextarea = this.$el.querySelector('.textarea');
|
||||
|
||||
if (base64Text) {
|
||||
this.show();
|
||||
if (base64Text === "paste") {
|
||||
// ?base64text=paste
|
||||
// base64 encoded string is ready to be pasted from clipboard
|
||||
this.$pasteBtn.innerText = 'Tap here to paste text';
|
||||
this.$pasteBtn.addEventListener('click', _ => this.processClipboard('text'));
|
||||
this.preparePasting("text");
|
||||
} else if (base64Text === "hash") {
|
||||
// ?base64text=hash#BASE64ENCODED
|
||||
// base64 encoded string is url hash which is never sent to server and faster (recommended)
|
||||
this.processBase64Text(base64Hash)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Text content is incorrect.');
|
||||
console.log("Text content incorrect.")
|
||||
console.log("Text content incorrect.");
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
@@ -1269,7 +1271,7 @@ class Base64ZipDialog extends Dialog {
|
||||
this.processBase64Text(base64Text)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Text content is incorrect.');
|
||||
console.log("Text content incorrect.")
|
||||
console.log("Text content incorrect.");
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
@@ -1282,14 +1284,13 @@ class Base64ZipDialog extends Dialog {
|
||||
this.processBase64Zip(base64Hash)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'File content is incorrect.');
|
||||
console.log("File content incorrect.")
|
||||
console.log("File content incorrect.");
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
} else {
|
||||
// ?base64zip=paste || ?base64zip=true
|
||||
this.$pasteBtn.innerText = 'Tap here to paste files';
|
||||
this.$pasteBtn.addEventListener('click', _ => this.processClipboard('file'));
|
||||
this.preparePasting('files');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1299,39 +1300,60 @@ class Base64ZipDialog extends Dialog {
|
||||
this.$pasteBtn.innerText = "Processing...";
|
||||
}
|
||||
|
||||
async processClipboard(type) {
|
||||
if (!navigator.clipboard.readText) {
|
||||
Events.fire('notify-user', 'This feature is not available on your browser.');
|
||||
console.log("navigator.clipboard.readText() is not available on your browser.")
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
this._setPasteBtnToProcessing();
|
||||
|
||||
const base64 = await navigator.clipboard.readText();
|
||||
|
||||
if (!base64) return;
|
||||
|
||||
if (type === "text") {
|
||||
this.processBase64Text(base64)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Clipboard content is incorrect.');
|
||||
console.log("Clipboard content is incorrect.")
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
preparePasting(type) {
|
||||
if (navigator.clipboard.readText) {
|
||||
this.$pasteBtn.innerText = `Tap here to paste ${type}`;
|
||||
this._clickCallback = _ => this.processClipboard(type);
|
||||
this.$pasteBtn.addEventListener('click', _ => this._clickCallback());
|
||||
} else {
|
||||
this.processBase64Zip(base64)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Clipboard content is incorrect.');
|
||||
console.log("Clipboard content is incorrect.")
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
console.log("`navigator.clipboard.readText()` is not available on your browser.\nOn Firefox you can set `dom.events.asyncClipboard.readText` to true under `about:config` for convenience.")
|
||||
this.$pasteBtn.setAttribute('hidden', '');
|
||||
this.$fallbackTextarea.setAttribute('placeholder', `Paste here to send ${type}`);
|
||||
this.$fallbackTextarea.removeAttribute('hidden');
|
||||
this._inputCallback = _ => this.processInput(type);
|
||||
this.$fallbackTextarea.addEventListener('input', _ => this._inputCallback());
|
||||
this.$fallbackTextarea.focus();
|
||||
}
|
||||
}
|
||||
|
||||
async processInput(type) {
|
||||
const base64 = this.$fallbackTextarea.textContent;
|
||||
this.$fallbackTextarea.textContent = '';
|
||||
await this.processBase64(type, base64);
|
||||
}
|
||||
|
||||
async processClipboard(type) {
|
||||
const base64 = await navigator.clipboard.readText();
|
||||
await this.processBase64(type, base64);
|
||||
}
|
||||
|
||||
isValidBase64(base64) {
|
||||
try {
|
||||
// check if input is base64 encoded
|
||||
window.atob(base64);
|
||||
return true;
|
||||
} catch (e) {
|
||||
// input is not base64 string.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async processBase64(type, base64) {
|
||||
if (!base64 || !this.isValidBase64(base64)) return;
|
||||
this._setPasteBtnToProcessing();
|
||||
try {
|
||||
if (type === "text") {
|
||||
await this.processBase64Text(base64);
|
||||
} else {
|
||||
await this.processBase64Zip(base64);
|
||||
}
|
||||
} catch(_) {
|
||||
Events.fire('notify-user', 'Clipboard content is incorrect.');
|
||||
console.log("Clipboard content is incorrect.")
|
||||
}
|
||||
this.hide();
|
||||
}
|
||||
|
||||
processBase64Text(base64Text){
|
||||
return new Promise((resolve) => {
|
||||
this._setPasteBtnToProcessing();
|
||||
@@ -1365,6 +1387,8 @@ class Base64ZipDialog extends Dialog {
|
||||
|
||||
hide() {
|
||||
this.clearBrowserHistory();
|
||||
this.$pasteBtn.removeEventListener('click', _ => this._clickCallback());
|
||||
this.$fallbackTextarea.removeEventListener('input', _ => this._inputCallback());
|
||||
super.hide();
|
||||
}
|
||||
}
|
||||
@@ -1395,9 +1419,9 @@ class Notifications {
|
||||
this.$button.removeAttribute('hidden');
|
||||
this.$button.addEventListener('click', _ => this._requestPermission());
|
||||
}
|
||||
// Todo: fix Notifications
|
||||
Events.on('text-received', e => this._messageNotification(e.detail.text, e.detail.peerId));
|
||||
Events.on('files-received', e => this._downloadNotification(e.detail.files));
|
||||
Events.on('files-transfer-request', e => this._requestNotification(e.detail.request, e.detail.peerId));
|
||||
}
|
||||
|
||||
_requestPermission() {
|
||||
@@ -1470,8 +1494,29 @@ class Notifications {
|
||||
}
|
||||
}
|
||||
|
||||
_requestNotification(request, peerId) {
|
||||
if (document.visibilityState !== 'visible') {
|
||||
let imagesOnly = true;
|
||||
for(let i=0; i<request.header.length; i++) {
|
||||
if (request.header[i].mime.split('/')[0] !== 'image') {
|
||||
imagesOnly = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let descriptor;
|
||||
if (request.header.length > 1) {
|
||||
descriptor = imagesOnly ? ' images' : ' files';
|
||||
} else {
|
||||
descriptor = imagesOnly ? ' image' : ' file';
|
||||
}
|
||||
let displayName = $(peerId).querySelector('.name').textContent
|
||||
let title = `${displayName} would like to transfer ${request.header.length} ${descriptor}`;
|
||||
const notification = this._notify(title, 'Click to show');
|
||||
}
|
||||
}
|
||||
|
||||
_download(notification) {
|
||||
$('share-or-download').click();
|
||||
$('download-btn').click();
|
||||
notification.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const cacheVersion = 'v1.4.1';
|
||||
const cacheVersion = 'v1.4.3';
|
||||
const cacheTitle = `pairdrop-cache-${cacheVersion}`;
|
||||
const urlsToCache = [
|
||||
'index.html',
|
||||
|
||||
@@ -791,10 +791,29 @@ x-dialog .dialog-subheader {
|
||||
margin: auto -24px;
|
||||
}
|
||||
|
||||
#base64-paste-btn {
|
||||
#base64-paste-btn,
|
||||
#base64-paste-dialog .textarea {
|
||||
width: 100%;
|
||||
height: 40vh;
|
||||
border: solid 12px #438cff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#base64-paste-dialog .textarea {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#base64-paste-dialog .textarea::before {
|
||||
font-size: 15px;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--primary-color);
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
content: attr(placeholder);
|
||||
}
|
||||
|
||||
#base64-paste-dialog button {
|
||||
|
||||
@@ -241,6 +241,7 @@
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<button class="button center" id="base64-paste-btn" title="Paste">Tap here to paste files</button>
|
||||
<div class="textarea" placeholder="Paste here to send files" title="CMD/⌘ + V" contenteditable hidden></div>
|
||||
<button class="button center" close>Close</button>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
||||
@@ -205,6 +205,10 @@ class Peer {
|
||||
this._send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
sendDisplayName(displayName) {
|
||||
this.sendJSON({type: 'display-name-changed', displayName: displayName});
|
||||
}
|
||||
|
||||
async createHeader(file) {
|
||||
return {
|
||||
name: file.name,
|
||||
@@ -501,7 +505,8 @@ class Peer {
|
||||
}
|
||||
|
||||
_onDisplayNameChanged(message) {
|
||||
if (!message.displayName) return;
|
||||
if (!message.displayName || this._displayName === message.displayName) return;
|
||||
this._displayName = message.displayName;
|
||||
Events.fire('peer-display-name-changed', {peerId: this._peerId, displayName: message.displayName});
|
||||
}
|
||||
}
|
||||
@@ -700,6 +705,11 @@ class RTCPeer extends Peer {
|
||||
_isConnecting() {
|
||||
return this._channel && this._channel.readyState === 'connecting';
|
||||
}
|
||||
|
||||
sendDisplayName(displayName) {
|
||||
if (!this._isConnected()) return;
|
||||
super.sendDisplayName(displayName);
|
||||
}
|
||||
}
|
||||
|
||||
class WSPeer extends Peer {
|
||||
@@ -760,6 +770,7 @@ class PeersManager {
|
||||
Events.on('secret-room-deleted', e => this._onSecretRoomDeleted(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('ws-disconnected', _ => this._onWsDisconnected());
|
||||
Events.on('ws-relay', e => this._onWsRelay(e.detail));
|
||||
}
|
||||
@@ -874,7 +885,7 @@ class PeersManager {
|
||||
_notifyPeerDisplayNameChanged(peerId) {
|
||||
const peer = this.peers[peerId];
|
||||
if (!peer) return;
|
||||
this.peers[peerId].sendJSON({type: 'display-name-changed', displayName: this._displayName});
|
||||
this.peers[peerId].sendDisplayName(this._displayName);
|
||||
}
|
||||
|
||||
_onDisplayName(displayName) {
|
||||
@@ -968,11 +979,11 @@ class Events {
|
||||
window.dispatchEvent(new CustomEvent(type, { detail: detail }));
|
||||
}
|
||||
|
||||
static on(type, callback) {
|
||||
return window.addEventListener(type, callback, false);
|
||||
static on(type, callback, options = false) {
|
||||
return window.addEventListener(type, callback, options);
|
||||
}
|
||||
|
||||
static off(type, callback) {
|
||||
return window.removeEventListener(type, callback, false);
|
||||
static off(type, callback, options = false) {
|
||||
return window.removeEventListener(type, callback, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ class PeersUI {
|
||||
|
||||
const _callback = (e) => this._sendClipboardData(e, files, text);
|
||||
Events.on('paste-pointerdown', _callback);
|
||||
Events.on('deactivate-paste-mode', _ => this._deactivatePasteMode(_callback));
|
||||
Events.on('deactivate-paste-mode', _ => this._deactivatePasteMode(_callback), { once: true });
|
||||
|
||||
this.$cancelPasteModeBtn.removeAttribute('hidden');
|
||||
|
||||
@@ -300,7 +300,8 @@ class PeerUI {
|
||||
|
||||
constructor(peer, connectionHash) {
|
||||
this._peer = peer;
|
||||
this._connectionHash = connectionHash;
|
||||
this._connectionHash =
|
||||
`${connectionHash.substring(0, 4)} ${connectionHash.substring(4, 8)} ${connectionHash.substring(8, 12)} ${connectionHash.substring(12, 16)}`;
|
||||
this._initDom();
|
||||
this._bindListeners();
|
||||
|
||||
@@ -345,8 +346,7 @@ class PeerUI {
|
||||
this.$el.querySelector('svg use').setAttribute('xlink:href', this._icon());
|
||||
this.$el.querySelector('.name').textContent = this._displayName();
|
||||
this.$el.querySelector('.device-name').textContent = this._deviceName();
|
||||
this.$el.querySelector('.connection-hash').textContent =
|
||||
this._connectionHash.substring(0, 4) + " " + this._connectionHash.substring(4, 8) + " " + this._connectionHash.substring(8, 12) + " " + this._connectionHash.substring(12, 16);
|
||||
this.$el.querySelector('.connection-hash').textContent = this._connectionHash;
|
||||
}
|
||||
|
||||
_initDom() {
|
||||
@@ -570,7 +570,7 @@ class ReceiveDialog extends Dialog {
|
||||
}
|
||||
}
|
||||
|
||||
_parseFileData(displayName, files, imagesOnly, totalSize) {
|
||||
_parseFileData(displayName, connectionHash, files, imagesOnly, totalSize) {
|
||||
if (files.length > 1) {
|
||||
let fileOtherText = ` and ${files.length - 1} other `;
|
||||
if (files.length === 2) {
|
||||
@@ -587,6 +587,7 @@ class ReceiveDialog extends Dialog {
|
||||
this.$fileStem.innerText = fileName.substring(0, fileName.length - fileExtension.length);
|
||||
this.$fileExtension.innerText = fileExtension;
|
||||
this.$displayName.innerText = displayName;
|
||||
this.$displayName.title = connectionHash;
|
||||
this.$fileSize.innerText = this._formatFileSize(totalSize);
|
||||
}
|
||||
}
|
||||
@@ -604,8 +605,9 @@ class ReceiveFileDialog extends ReceiveDialog {
|
||||
}
|
||||
|
||||
_onFilesReceived(sender, files, imagesOnly, totalSize) {
|
||||
const displayName = $(sender).ui._displayName()
|
||||
this._filesQueue.push({peer: sender, displayName: displayName, files: files, imagesOnly: imagesOnly, totalSize: totalSize});
|
||||
const displayName = $(sender).ui._displayName();
|
||||
const connectionHash = $(sender).ui._connectionHash;
|
||||
this._filesQueue.push({peer: sender, displayName: displayName, connectionHash: connectionHash, files: files, imagesOnly: imagesOnly, totalSize: totalSize});
|
||||
this._nextFiles();
|
||||
window.blop.play();
|
||||
}
|
||||
@@ -613,12 +615,11 @@ class ReceiveFileDialog extends ReceiveDialog {
|
||||
_nextFiles() {
|
||||
if (this._busy) return;
|
||||
this._busy = true;
|
||||
const {peer, displayName, files, imagesOnly, totalSize} = this._filesQueue.shift();
|
||||
this._displayFiles(peer, displayName, files, imagesOnly, totalSize);
|
||||
const {peer, displayName, connectionHash, files, imagesOnly, totalSize} = this._filesQueue.shift();
|
||||
this._displayFiles(peer, displayName, connectionHash, files, imagesOnly, totalSize);
|
||||
}
|
||||
|
||||
_dequeueFile() {
|
||||
// Todo: change count in document.title and move '- PairDrop' to back
|
||||
if (!this._filesQueue.length) { // nothing to do
|
||||
this._busy = false;
|
||||
return;
|
||||
@@ -656,8 +657,8 @@ class ReceiveFileDialog extends ReceiveDialog {
|
||||
});
|
||||
}
|
||||
|
||||
async _displayFiles(peerId, displayName, files, imagesOnly, totalSize) {
|
||||
this._parseFileData(displayName, files, imagesOnly, totalSize);
|
||||
async _displayFiles(peerId, displayName, connectionHash, files, imagesOnly, totalSize) {
|
||||
this._parseFileData(displayName, connectionHash, files, imagesOnly, totalSize);
|
||||
|
||||
let descriptor, url, filenameDownload;
|
||||
if (files.length === 1) {
|
||||
@@ -804,7 +805,8 @@ class ReceiveRequestDialog extends ReceiveDialog {
|
||||
this.correspondingPeerId = peerId;
|
||||
|
||||
const displayName = $(peerId).ui._displayName();
|
||||
this._parseFileData(displayName, request.header, request.imagesOnly, request.totalSize);
|
||||
const connectionHash = $(peerId).ui._connectionHash;
|
||||
this._parseFileData(displayName, connectionHash, request.header, request.imagesOnly, request.totalSize);
|
||||
|
||||
if (request.thumbnailDataUrl?.substring(0, 22) === "data:image/jpeg;base64") {
|
||||
let element = document.createElement('img');
|
||||
@@ -1246,21 +1248,21 @@ class Base64ZipDialog extends Dialog {
|
||||
const base64Hash = window.location.hash.substring(1);
|
||||
|
||||
this.$pasteBtn = this.$el.querySelector('#base64-paste-btn');
|
||||
this.$fallbackTextarea = this.$el.querySelector('.textarea');
|
||||
|
||||
if (base64Text) {
|
||||
this.show();
|
||||
if (base64Text === "paste") {
|
||||
// ?base64text=paste
|
||||
// base64 encoded string is ready to be pasted from clipboard
|
||||
this.$pasteBtn.innerText = 'Tap here to paste text';
|
||||
this.$pasteBtn.addEventListener('click', _ => this.processClipboard('text'));
|
||||
this.preparePasting("text");
|
||||
} else if (base64Text === "hash") {
|
||||
// ?base64text=hash#BASE64ENCODED
|
||||
// base64 encoded string is url hash which is never sent to server and faster (recommended)
|
||||
this.processBase64Text(base64Hash)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Text content is incorrect.');
|
||||
console.log("Text content incorrect.")
|
||||
console.log("Text content incorrect.");
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
@@ -1270,7 +1272,7 @@ class Base64ZipDialog extends Dialog {
|
||||
this.processBase64Text(base64Text)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Text content is incorrect.');
|
||||
console.log("Text content incorrect.")
|
||||
console.log("Text content incorrect.");
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
@@ -1283,14 +1285,13 @@ class Base64ZipDialog extends Dialog {
|
||||
this.processBase64Zip(base64Hash)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'File content is incorrect.');
|
||||
console.log("File content incorrect.")
|
||||
console.log("File content incorrect.");
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
} else {
|
||||
// ?base64zip=paste || ?base64zip=true
|
||||
this.$pasteBtn.innerText = 'Tap here to paste files';
|
||||
this.$pasteBtn.addEventListener('click', _ => this.processClipboard('file'));
|
||||
this.preparePasting('files');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1300,39 +1301,60 @@ class Base64ZipDialog extends Dialog {
|
||||
this.$pasteBtn.innerText = "Processing...";
|
||||
}
|
||||
|
||||
async processClipboard(type) {
|
||||
if (!navigator.clipboard.readText) {
|
||||
Events.fire('notify-user', 'This feature is not available on your browser.');
|
||||
console.log("navigator.clipboard.readText() is not available on your browser.")
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
this._setPasteBtnToProcessing();
|
||||
|
||||
const base64 = await navigator.clipboard.readText();
|
||||
|
||||
if (!base64) return;
|
||||
|
||||
if (type === "text") {
|
||||
this.processBase64Text(base64)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Clipboard content is incorrect.');
|
||||
console.log("Clipboard content is incorrect.")
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
preparePasting(type) {
|
||||
if (navigator.clipboard.readText) {
|
||||
this.$pasteBtn.innerText = `Tap here to paste ${type}`;
|
||||
this._clickCallback = _ => this.processClipboard(type);
|
||||
this.$pasteBtn.addEventListener('click', _ => this._clickCallback());
|
||||
} else {
|
||||
this.processBase64Zip(base64)
|
||||
.catch(_ => {
|
||||
Events.fire('notify-user', 'Clipboard content is incorrect.');
|
||||
console.log("Clipboard content is incorrect.")
|
||||
}).finally(_ => {
|
||||
this.hide();
|
||||
});
|
||||
console.log("`navigator.clipboard.readText()` is not available on your browser.\nOn Firefox you can set `dom.events.asyncClipboard.readText` to true under `about:config` for convenience.")
|
||||
this.$pasteBtn.setAttribute('hidden', '');
|
||||
this.$fallbackTextarea.setAttribute('placeholder', `Paste here to send ${type}`);
|
||||
this.$fallbackTextarea.removeAttribute('hidden');
|
||||
this._inputCallback = _ => this.processInput(type);
|
||||
this.$fallbackTextarea.addEventListener('input', _ => this._inputCallback());
|
||||
this.$fallbackTextarea.focus();
|
||||
}
|
||||
}
|
||||
|
||||
async processInput(type) {
|
||||
const base64 = this.$fallbackTextarea.textContent;
|
||||
this.$fallbackTextarea.textContent = '';
|
||||
await this.processBase64(type, base64);
|
||||
}
|
||||
|
||||
async processClipboard(type) {
|
||||
const base64 = await navigator.clipboard.readText();
|
||||
await this.processBase64(type, base64);
|
||||
}
|
||||
|
||||
isValidBase64(base64) {
|
||||
try {
|
||||
// check if input is base64 encoded
|
||||
window.atob(base64);
|
||||
return true;
|
||||
} catch (e) {
|
||||
// input is not base64 string.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async processBase64(type, base64) {
|
||||
if (!base64 || !this.isValidBase64(base64)) return;
|
||||
this._setPasteBtnToProcessing();
|
||||
try {
|
||||
if (type === "text") {
|
||||
await this.processBase64Text(base64);
|
||||
} else {
|
||||
await this.processBase64Zip(base64);
|
||||
}
|
||||
} catch(_) {
|
||||
Events.fire('notify-user', 'Clipboard content is incorrect.');
|
||||
console.log("Clipboard content is incorrect.")
|
||||
}
|
||||
this.hide();
|
||||
}
|
||||
|
||||
processBase64Text(base64Text){
|
||||
return new Promise((resolve) => {
|
||||
this._setPasteBtnToProcessing();
|
||||
@@ -1366,6 +1388,8 @@ class Base64ZipDialog extends Dialog {
|
||||
|
||||
hide() {
|
||||
this.clearBrowserHistory();
|
||||
this.$pasteBtn.removeEventListener('click', _ => this._clickCallback());
|
||||
this.$fallbackTextarea.removeEventListener('input', _ => this._inputCallback());
|
||||
super.hide();
|
||||
}
|
||||
}
|
||||
@@ -1396,9 +1420,9 @@ class Notifications {
|
||||
this.$button.removeAttribute('hidden');
|
||||
this.$button.addEventListener('click', _ => this._requestPermission());
|
||||
}
|
||||
// Todo: fix Notifications
|
||||
Events.on('text-received', e => this._messageNotification(e.detail.text, e.detail.peerId));
|
||||
Events.on('files-received', e => this._downloadNotification(e.detail.files));
|
||||
Events.on('files-transfer-request', e => this._requestNotification(e.detail.request, e.detail.peerId));
|
||||
}
|
||||
|
||||
_requestPermission() {
|
||||
@@ -1471,8 +1495,29 @@ class Notifications {
|
||||
}
|
||||
}
|
||||
|
||||
_requestNotification(request, peerId) {
|
||||
if (document.visibilityState !== 'visible') {
|
||||
let imagesOnly = true;
|
||||
for(let i=0; i<request.header.length; i++) {
|
||||
if (request.header[i].mime.split('/')[0] !== 'image') {
|
||||
imagesOnly = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let descriptor;
|
||||
if (request.header.length > 1) {
|
||||
descriptor = imagesOnly ? ' images' : ' files';
|
||||
} else {
|
||||
descriptor = imagesOnly ? ' image' : ' file';
|
||||
}
|
||||
let displayName = $(peerId).querySelector('.name').textContent
|
||||
let title = `${displayName} would like to transfer ${request.header.length} ${descriptor}`;
|
||||
const notification = this._notify(title, 'Click to show');
|
||||
}
|
||||
}
|
||||
|
||||
_download(notification) {
|
||||
$('share-or-download').click();
|
||||
$('download-btn').click();
|
||||
notification.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const cacheVersion = 'v1.4.1';
|
||||
const cacheVersion = 'v1.4.3';
|
||||
const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`;
|
||||
const urlsToCache = [
|
||||
'index.html',
|
||||
|
||||
@@ -817,10 +817,29 @@ x-dialog .dialog-subheader {
|
||||
margin: auto -24px;
|
||||
}
|
||||
|
||||
#base64-paste-btn {
|
||||
#base64-paste-btn,
|
||||
#base64-paste-dialog .textarea {
|
||||
width: 100%;
|
||||
height: 40vh;
|
||||
border: solid 12px #438cff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#base64-paste-dialog .textarea {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#base64-paste-dialog .textarea::before {
|
||||
font-size: 15px;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--primary-color);
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
content: attr(placeholder);
|
||||
}
|
||||
|
||||
#base64-paste-dialog button {
|
||||
|
||||
Reference in New Issue
Block a user