mirror of
https://github.com/schlagmichdoch/PairDrop.git
synced 2026-04-22 07:04:53 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fea15d3ee1 | ||
|
|
028752a809 | ||
|
|
1093f4d246 | ||
|
|
7ddd600b0c | ||
|
|
715356aafb | ||
|
|
490e4db380 |
@@ -6,7 +6,7 @@
|
|||||||
<h1>PairDrop</h1>
|
<h1>PairDrop</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Local file sharing in your browser. Inspired by Apple's Airdrop.
|
Local file sharing in your browser. Inspired by Apple's AirDrop.
|
||||||
<br />
|
<br />
|
||||||
<a href="https://pairdrop.net"><strong>Explore »</strong></a>
|
<a href="https://pairdrop.net"><strong>Explore »</strong></a>
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@@ -58,8 +58,11 @@ If your devices are paired and behind a NAT, the public TURN Server from [Open R
|
|||||||
Yes. Your files are sent using WebRTC, which encrypts them on transit. To ensure the connection is secure and there is no MITM, compare the security number shown under the device name on both devices. The security number is different for every connection.
|
Yes. Your files are sent using WebRTC, which encrypts them on transit. To ensure the connection is secure and there is no MITM, compare the security number shown under the device name on both devices. The security number is different for every connection.
|
||||||
|
|
||||||
### Transferring many files with paired devices takes too long
|
### Transferring many files with paired devices takes too long
|
||||||
Naturally, if traffic needs to be routed through the turn server transfer speed decreases.
|
Naturally, if traffic needs to be routed through the turn server because your devices are behind different NATs, transfer speed decreases.
|
||||||
As a workaround you can open a hotspot on one of your devices to bridge the connection which makes transfers much faster.
|
|
||||||
|
As the public TURN server used is not super fast, you can easily [specify to use your own TURN server](https://github.com/schlagmichdoch/PairDrop/blob/master/docs/host-your-own.md#specify-stunturn-servers) if you host your own instance.
|
||||||
|
|
||||||
|
Alternatively, you can open a hotspot on one of your devices to bridge the connection which makes transfers much faster as no TURN server is needed.
|
||||||
|
|
||||||
- [How to open a hotspot on Windows](https://support.microsoft.com/en-us/windows/use-your-windows-pc-as-a-mobile-hotspot-c89b0fad-72d5-41e8-f7ea-406ad9036b85#WindowsVersion=Windows_11)
|
- [How to open a hotspot on Windows](https://support.microsoft.com/en-us/windows/use-your-windows-pc-as-a-mobile-hotspot-c89b0fad-72d5-41e8-f7ea-406ad9036b85#WindowsVersion=Windows_11)
|
||||||
- [How to open a hotspot on Mac](https://support.apple.com/guide/mac-help/share-internet-connection-mac-network-users-mchlp1540/mac)
|
- [How to open a hotspot on Mac](https://support.apple.com/guide/mac-help/share-internet-connection-mac-network-users-mchlp1540/mac)
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "pairdrop",
|
"name": "pairdrop",
|
||||||
"version": "1.4.4",
|
"version": "1.4.5",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pairdrop",
|
"name": "pairdrop",
|
||||||
"version": "1.4.4",
|
"version": "1.4.5",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pairdrop",
|
"name": "pairdrop",
|
||||||
"version": "1.4.4",
|
"version": "1.4.5",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -524,6 +524,7 @@ class RTCPeer extends Peer {
|
|||||||
this._peerId = peerId;
|
this._peerId = peerId;
|
||||||
this._conn = new RTCPeerConnection(window.rtcConfig);
|
this._conn = new RTCPeerConnection(window.rtcConfig);
|
||||||
this._conn.onicecandidate = e => this._onIceCandidate(e);
|
this._conn.onicecandidate = e => this._onIceCandidate(e);
|
||||||
|
this._conn.onicecandidateerror = e => this._onError(e);
|
||||||
this._conn.onconnectionstatechange = _ => this._onConnectionStateChange();
|
this._conn.onconnectionstatechange = _ => this._onConnectionStateChange();
|
||||||
this._conn.oniceconnectionstatechange = e => this._onIceConnectionStateChange(e);
|
this._conn.oniceconnectionstatechange = e => this._onIceConnectionStateChange(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -632,26 +632,34 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||||||
|
|
||||||
createPreviewElement(file) {
|
createPreviewElement(file) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let mime = file.type.split('/')[0]
|
try {
|
||||||
let previewElement = {
|
let mime = file.type.split('/')[0]
|
||||||
image: 'img',
|
let previewElement = {
|
||||||
audio: 'audio',
|
image: 'img',
|
||||||
video: 'video'
|
audio: 'audio',
|
||||||
}
|
video: 'video'
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(previewElement).indexOf(mime) === -1) {
|
if (Object.keys(previewElement).indexOf(mime) === -1) {
|
||||||
resolve(false);
|
resolve(false);
|
||||||
} else {
|
} else {
|
||||||
console.log('the file is able to preview');
|
let element = document.createElement(previewElement[mime]);
|
||||||
let element = document.createElement(previewElement[mime]);
|
element.controls = true;
|
||||||
element.src = URL.createObjectURL(file);
|
element.onload = _ => {
|
||||||
element.controls = true;
|
this.$previewBox.appendChild(element);
|
||||||
element.onload = _ => {
|
resolve(true);
|
||||||
this.$previewBox.appendChild(element);
|
};
|
||||||
resolve(true)
|
element.onloadeddata = _ => {
|
||||||
};
|
this.$previewBox.appendChild(element);
|
||||||
element.addEventListener('loadeddata', _ => resolve(true));
|
resolve(true);
|
||||||
element.onerror = _ => reject(`${mime} preview could not be loaded from type ${file.type}`);
|
};
|
||||||
|
element.onerror = _ => {
|
||||||
|
reject(`${mime} preview could not be loaded from type ${file.type}`);
|
||||||
|
};
|
||||||
|
element.src = URL.createObjectURL(file);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
reject(`preview could not be loaded from type ${file.type}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -734,20 +742,30 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||||||
setTimeout(_ => this.$downloadBtn.style.pointerEvents = "unset", 2000);
|
setTimeout(_ => this.$downloadBtn.style.pointerEvents = "unset", 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.createPreviewElement(files[0]).finally(_ => {
|
document.title = files.length === 1
|
||||||
document.title = files.length === 1
|
? 'File received - PairDrop'
|
||||||
? 'File received - PairDrop'
|
: `${files.length} Files received - PairDrop`;
|
||||||
: `${files.length} Files received - PairDrop`;
|
document.changeFavicon("images/favicon-96x96-notification.png");
|
||||||
document.changeFavicon("images/favicon-96x96-notification.png");
|
Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'})
|
||||||
Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'})
|
this.show();
|
||||||
this.show();
|
|
||||||
|
|
||||||
|
setTimeout(_ => {
|
||||||
if (canShare) {
|
if (canShare) {
|
||||||
this.$shareBtn.click();
|
this.$shareBtn.click();
|
||||||
} else {
|
} else {
|
||||||
this.$downloadBtn.click();
|
this.$downloadBtn.click();
|
||||||
}
|
}
|
||||||
}).catch(r => console.error(r));
|
}, 500);
|
||||||
|
|
||||||
|
this.createPreviewElement(files[0])
|
||||||
|
.then(canPreview => {
|
||||||
|
if (canPreview) {
|
||||||
|
console.log('the file is able to preview');
|
||||||
|
} else {
|
||||||
|
console.log('the file is not able to preview');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(r => console.error(r));
|
||||||
}
|
}
|
||||||
|
|
||||||
_downloadFilesIndividually(files) {
|
_downloadFilesIndividually(files) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const cacheVersion = 'v1.4.4';
|
const cacheVersion = 'v1.4.5';
|
||||||
const cacheTitle = `pairdrop-cache-${cacheVersion}`;
|
const cacheTitle = `pairdrop-cache-${cacheVersion}`;
|
||||||
const urlsToCache = [
|
const urlsToCache = [
|
||||||
'index.html',
|
'index.html',
|
||||||
|
|||||||
@@ -22,13 +22,18 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
min-height: 100vh;
|
height: 100%;
|
||||||
/* mobile viewport bug fix */
|
/* mobile viewport bug fix */
|
||||||
min-height: -webkit-fill-available;
|
min-height: -moz-available; /* WebKit-based browsers will ignore this. */
|
||||||
|
min-height: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
|
||||||
|
min-height: fill-available;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
height: -webkit-fill-available;
|
height: 100%;
|
||||||
|
min-height: -moz-available; /* WebKit-based browsers will ignore this. */
|
||||||
|
min-height: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
|
||||||
|
min-height: fill-available;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-reverse {
|
.row-reverse {
|
||||||
|
|||||||
@@ -535,6 +535,7 @@ class RTCPeer extends Peer {
|
|||||||
this._peerId = peerId;
|
this._peerId = peerId;
|
||||||
this._conn = new RTCPeerConnection(window.rtcConfig);
|
this._conn = new RTCPeerConnection(window.rtcConfig);
|
||||||
this._conn.onicecandidate = e => this._onIceCandidate(e);
|
this._conn.onicecandidate = e => this._onIceCandidate(e);
|
||||||
|
this._conn.onicecandidateerror = e => this._onError(e);
|
||||||
this._conn.onconnectionstatechange = _ => this._onConnectionStateChange();
|
this._conn.onconnectionstatechange = _ => this._onConnectionStateChange();
|
||||||
this._conn.oniceconnectionstatechange = e => this._onIceConnectionStateChange(e);
|
this._conn.oniceconnectionstatechange = e => this._onIceConnectionStateChange(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -633,26 +633,34 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||||||
|
|
||||||
createPreviewElement(file) {
|
createPreviewElement(file) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let mime = file.type.split('/')[0]
|
try {
|
||||||
let previewElement = {
|
let mime = file.type.split('/')[0]
|
||||||
image: 'img',
|
let previewElement = {
|
||||||
audio: 'audio',
|
image: 'img',
|
||||||
video: 'video'
|
audio: 'audio',
|
||||||
}
|
video: 'video'
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(previewElement).indexOf(mime) === -1) {
|
if (Object.keys(previewElement).indexOf(mime) === -1) {
|
||||||
resolve(false);
|
resolve(false);
|
||||||
} else {
|
} else {
|
||||||
console.log('the file is able to preview');
|
let element = document.createElement(previewElement[mime]);
|
||||||
let element = document.createElement(previewElement[mime]);
|
element.controls = true;
|
||||||
element.src = URL.createObjectURL(file);
|
element.onload = _ => {
|
||||||
element.controls = true;
|
this.$previewBox.appendChild(element);
|
||||||
element.onload = _ => {
|
resolve(true);
|
||||||
this.$previewBox.appendChild(element);
|
};
|
||||||
resolve(true)
|
element.onloadeddata = _ => {
|
||||||
};
|
this.$previewBox.appendChild(element);
|
||||||
element.addEventListener('loadeddata', _ => resolve(true));
|
resolve(true);
|
||||||
element.onerror = _ => reject(`${mime} preview could not be loaded from type ${file.type}`);
|
};
|
||||||
|
element.onerror = _ => {
|
||||||
|
reject(`${mime} preview could not be loaded from type ${file.type}`);
|
||||||
|
};
|
||||||
|
element.src = URL.createObjectURL(file);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
reject(`preview could not be loaded from type ${file.type}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -735,20 +743,30 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||||||
setTimeout(_ => this.$downloadBtn.style.pointerEvents = "unset", 2000);
|
setTimeout(_ => this.$downloadBtn.style.pointerEvents = "unset", 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.createPreviewElement(files[0]).finally(_ => {
|
document.title = files.length === 1
|
||||||
document.title = files.length === 1
|
? 'File received - PairDrop'
|
||||||
? 'File received - PairDrop'
|
: `${files.length} Files received - PairDrop`;
|
||||||
: `${files.length} Files received - PairDrop`;
|
document.changeFavicon("images/favicon-96x96-notification.png");
|
||||||
document.changeFavicon("images/favicon-96x96-notification.png");
|
Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'})
|
||||||
Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'})
|
this.show();
|
||||||
this.show();
|
|
||||||
|
|
||||||
|
setTimeout(_ => {
|
||||||
if (canShare) {
|
if (canShare) {
|
||||||
this.$shareBtn.click();
|
this.$shareBtn.click();
|
||||||
} else {
|
} else {
|
||||||
this.$downloadBtn.click();
|
this.$downloadBtn.click();
|
||||||
}
|
}
|
||||||
}).catch(r => console.error(r));
|
}, 500);
|
||||||
|
|
||||||
|
this.createPreviewElement(files[0])
|
||||||
|
.then(canPreview => {
|
||||||
|
if (canPreview) {
|
||||||
|
console.log('the file is able to preview');
|
||||||
|
} else {
|
||||||
|
console.log('the file is not able to preview');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(r => console.error(r));
|
||||||
}
|
}
|
||||||
|
|
||||||
_downloadFilesIndividually(files) {
|
_downloadFilesIndividually(files) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const cacheVersion = 'v1.4.4';
|
const cacheVersion = 'v1.4.5';
|
||||||
const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`;
|
const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`;
|
||||||
const urlsToCache = [
|
const urlsToCache = [
|
||||||
'index.html',
|
'index.html',
|
||||||
|
|||||||
@@ -23,13 +23,18 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
min-height: 100vh;
|
height: 100%;
|
||||||
/* mobile viewport bug fix */
|
/* mobile viewport bug fix */
|
||||||
min-height: -webkit-fill-available;
|
min-height: -moz-available; /* WebKit-based browsers will ignore this. */
|
||||||
|
min-height: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
|
||||||
|
min-height: fill-available;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
height: -webkit-fill-available;
|
height: 100%;
|
||||||
|
min-height: -moz-available; /* WebKit-based browsers will ignore this. */
|
||||||
|
min-height: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
|
||||||
|
min-height: fill-available;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-reverse {
|
.row-reverse {
|
||||||
|
|||||||
Reference in New Issue
Block a user