mirror of
https://github.com/schlagmichdoch/PairDrop.git
synced 2026-04-22 23:20:54 +08:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f195c686e7 | |||
| 3505f161c6 | |||
| 3e2368c0c9 | |||
| d36cd3524c | |||
| a3a8228327 | |||
| 520b772bc8 | |||
| 27bf0fa74f | |||
| e9f3c39f38 | |||
| 58b7f6bb7c | |||
| b5987cf017 | |||
| 4433e1c58f | |||
| b106d90b64 | |||
| c9e1c2504a | |||
| 32e909b8c2 | |||
| a444be226f | |||
| df778ba42c | |||
| 8a17b82fa4 | |||
| 56eb29c91b | |||
| 6e4bda0adf | |||
| 0baced640a | |||
| 3c2e73fc0c |
@@ -35,6 +35,14 @@ Set options by using the following flags in the `docker run` command:
|
|||||||
```
|
```
|
||||||
> Limits clients to 1000 requests per 5 min
|
> Limits clients to 1000 requests per 5 min
|
||||||
|
|
||||||
|
##### IPv6 Localization
|
||||||
|
```bash
|
||||||
|
-e IPV6_LOCALIZE=4
|
||||||
|
```
|
||||||
|
> To enable Peer Discovery among IPv6 peers, you can specify a reduced number of segments of the client IPv6 address to be evaluated as the peer's IP. This can be especially useful when using Cloudflare as a proxy.
|
||||||
|
>
|
||||||
|
> The flag must be set to an **integer** between `1` and `7`. The number represents the number of IPv6 [hextets](https://en.wikipedia.org/wiki/IPv6#Address_representation) to match the client IP against. The most common value would be `4`, which will group peers within the same `/64` subnet.
|
||||||
|
|
||||||
##### Websocket Fallback (for VPN)
|
##### Websocket Fallback (for VPN)
|
||||||
```bash
|
```bash
|
||||||
-e WS_FALLBACK=true
|
-e WS_FALLBACK=true
|
||||||
@@ -200,6 +208,12 @@ $env:PORT=3010; npm start
|
|||||||
```
|
```
|
||||||
> Specify the port PairDrop is running on. (Default: 3000)
|
> Specify the port PairDrop is running on. (Default: 3000)
|
||||||
|
|
||||||
|
#### IPv6 Localization
|
||||||
|
```bash
|
||||||
|
IPV6_LOCALIZE=4
|
||||||
|
```
|
||||||
|
> Truncate a portion of the client IPv6 address to make peers more discoverable. See [Options/Flags](#options--flags) above.
|
||||||
|
|
||||||
#### Specify STUN/TURN Server
|
#### Specify STUN/TURN Server
|
||||||
On Unix based systems
|
On Unix based systems
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -96,6 +96,17 @@ if (debugMode) {
|
|||||||
console.log("DEBUG_MODE is active. To protect privacy, do not use in production.")
|
console.log("DEBUG_MODE is active. To protect privacy, do not use in production.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ipv6_lcl;
|
||||||
|
if (process.env.IPV6_LOCALIZE) {
|
||||||
|
ipv6_lcl = parseInt(process.env.IPV6_LOCALIZE);
|
||||||
|
if (!ipv6_lcl || !(0 < ipv6_lcl && ipv6_lcl < 8)) {
|
||||||
|
console.error("IPV6_LOCALIZE must be an integer between 1 and 7");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("IPv6 client IPs will be localized to", ipv6_lcl, ipv6_lcl === 1 ? "segment" : "segments");
|
||||||
|
}
|
||||||
|
|
||||||
app.use(function(req, res) {
|
app.use(function(req, res) {
|
||||||
res.redirect('/');
|
res.redirect('/');
|
||||||
});
|
});
|
||||||
@@ -208,10 +219,15 @@ class PairDropServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onDisconnect(sender) {
|
_onDisconnect(sender) {
|
||||||
|
this._disconnect(sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
_disconnect(sender) {
|
||||||
this._leaveRoom(sender, 'ip', '', true);
|
this._leaveRoom(sender, 'ip', '', true);
|
||||||
this._leaveAllSecretRooms(sender, true);
|
this._leaveAllSecretRooms(sender, true);
|
||||||
this._removeRoomKey(sender.roomKey);
|
this._removeRoomKey(sender.roomKey);
|
||||||
sender.roomKey = null;
|
sender.roomKey = null;
|
||||||
|
sender.socket.terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onRoomSecrets(sender, message) {
|
_onRoomSecrets(sender, message) {
|
||||||
@@ -346,10 +362,6 @@ class PairDropServer {
|
|||||||
_joinRoom(peer, roomType = 'ip', roomSecret = '') {
|
_joinRoom(peer, roomType = 'ip', roomSecret = '') {
|
||||||
const room = roomType === 'ip' ? peer.ip : roomSecret;
|
const room = roomType === 'ip' ? peer.ip : roomSecret;
|
||||||
|
|
||||||
if (this._rooms[room] && this._rooms[room][peer.id]) {
|
|
||||||
this._leaveRoom(peer, roomType, roomSecret);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if room doesn't exist, create it
|
// if room doesn't exist, create it
|
||||||
if (!this._rooms[room]) {
|
if (!this._rooms[room]) {
|
||||||
this._rooms[room] = {};
|
this._rooms[room] = {};
|
||||||
@@ -374,10 +386,6 @@ class PairDropServer {
|
|||||||
// delete the peer
|
// delete the peer
|
||||||
delete this._rooms[room][peer.id];
|
delete this._rooms[room][peer.id];
|
||||||
|
|
||||||
if (roomType === 'ip') {
|
|
||||||
peer.socket.terminate();
|
|
||||||
}
|
|
||||||
|
|
||||||
//if room is empty, delete the room
|
//if room is empty, delete the room
|
||||||
if (!Object.keys(this._rooms[room]).length) {
|
if (!Object.keys(this._rooms[room]).length) {
|
||||||
delete this._rooms[room];
|
delete this._rooms[room];
|
||||||
@@ -457,8 +465,7 @@ class PairDropServer {
|
|||||||
peer.lastBeat = Date.now();
|
peer.lastBeat = Date.now();
|
||||||
}
|
}
|
||||||
if (Date.now() - peer.lastBeat > 2 * timeout) {
|
if (Date.now() - peer.lastBeat > 2 * timeout) {
|
||||||
this._leaveRoom(peer);
|
this._disconnect(peer);
|
||||||
this._leaveAllSecretRooms(peer);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,11 +523,19 @@ class Peer {
|
|||||||
if (this.ip.substring(0,7) === "::ffff:")
|
if (this.ip.substring(0,7) === "::ffff:")
|
||||||
this.ip = this.ip.substring(7);
|
this.ip = this.ip.substring(7);
|
||||||
|
|
||||||
|
let ipv6_was_localized = false;
|
||||||
|
if (ipv6_lcl && this.ip.includes(':')) {
|
||||||
|
this.ip = this.ip.split(':',ipv6_lcl).join(':');
|
||||||
|
ipv6_was_localized = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
console.debug("----DEBUGGING-PEER-IP-START----");
|
console.debug("----DEBUGGING-PEER-IP-START----");
|
||||||
console.debug("remoteAddress:", request.connection.remoteAddress);
|
console.debug("remoteAddress:", request.connection.remoteAddress);
|
||||||
console.debug("x-forwarded-for:", request.headers['x-forwarded-for']);
|
console.debug("x-forwarded-for:", request.headers['x-forwarded-for']);
|
||||||
console.debug("cf-connecting-ip:", request.headers['cf-connecting-ip']);
|
console.debug("cf-connecting-ip:", request.headers['cf-connecting-ip']);
|
||||||
|
if (ipv6_was_localized)
|
||||||
|
console.debug("IPv6 client IP was localized to", ipv6_lcl, ipv6_lcl > 1 ? "segments" : "segment");
|
||||||
console.debug("PairDrop uses:", this.ip);
|
console.debug("PairDrop uses:", this.ip);
|
||||||
console.debug("IP is private:", this.ipIsPrivate(this.ip));
|
console.debug("IP is private:", this.ipIsPrivate(this.ip));
|
||||||
console.debug("if IP is private, '127.0.0.1' is used instead");
|
console.debug("if IP is private, '127.0.0.1' is used instead");
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "pairdrop",
|
"name": "pairdrop",
|
||||||
"version": "1.7.1",
|
"version": "1.7.5",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pairdrop",
|
"name": "pairdrop",
|
||||||
"version": "1.7.1",
|
"version": "1.7.5",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pairdrop",
|
"name": "pairdrop",
|
||||||
"version": "1.7.1",
|
"version": "1.7.5",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+6
-2
@@ -224,7 +224,7 @@
|
|||||||
<div class="row-separator"></div>
|
<div class="row-separator"></div>
|
||||||
<div id="text-input" title="Message" class="textarea" role="textbox" autocapitalize="none" spellcheck="false" autofocus contenteditable></div>
|
<div id="text-input" title="Message" class="textarea" role="textbox" autocapitalize="none" spellcheck="false" autofocus contenteditable></div>
|
||||||
<div class="center row-reverse">
|
<div class="center row-reverse">
|
||||||
<button class="button" type="submit" title="STR + ENTER" disabled close>Send</button>
|
<button class="button" type="submit" title="CTRL/⌘ + ENTER" disabled>Send</button>
|
||||||
<button class="button" type="button" title="ESCAPE" close>Cancel</button>
|
<button class="button" type="button" title="ESCAPE" close>Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</x-paper>
|
</x-paper>
|
||||||
@@ -276,7 +276,10 @@
|
|||||||
<svg class="icon logo">
|
<svg class="icon logo">
|
||||||
<use xlink:href="#wifi-tethering" />
|
<use xlink:href="#wifi-tethering" />
|
||||||
</svg>
|
</svg>
|
||||||
<h1>PairDrop</h1>
|
<div class="title-wrapper">
|
||||||
|
<h1>PairDrop</h1>
|
||||||
|
<div class="font-subheading">v1.7.5</div>
|
||||||
|
</div>
|
||||||
<div class="font-subheading">The easiest way to transfer files across devices</div>
|
<div class="font-subheading">The easiest way to transfer files across devices</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<a class="icon-button" target="_blank" href="https://github.com/schlagmichdoch/pairdrop" title="PairDrop on Github" rel="noreferrer">
|
<a class="icon-button" target="_blank" href="https://github.com/schlagmichdoch/pairdrop" title="PairDrop on Github" rel="noreferrer">
|
||||||
@@ -303,6 +306,7 @@
|
|||||||
</section>
|
</section>
|
||||||
<x-background></x-background>
|
<x-background></x-background>
|
||||||
</x-about>
|
</x-about>
|
||||||
|
<canvas class="circles"></canvas>
|
||||||
<!-- SVG Icon Library -->
|
<!-- SVG Icon Library -->
|
||||||
<svg style="display: none;">
|
<svg style="display: none;">
|
||||||
<symbol id=wifi-tethering viewBox="0 0 24 24">
|
<symbol id=wifi-tethering viewBox="0 0 24 24">
|
||||||
|
|||||||
@@ -529,7 +529,7 @@ class Peer {
|
|||||||
this._abortTransfer();
|
this._abortTransfer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// include for compatibility with Snapdrop for Android app
|
// include for compatibility with 'Snapdrop & PairDrop for Android' app
|
||||||
Events.fire('file-received', fileBlob);
|
Events.fire('file-received', fileBlob);
|
||||||
|
|
||||||
this._filesReceived.push(fileBlob);
|
this._filesReceived.push(fileBlob);
|
||||||
@@ -547,6 +547,7 @@ class Peer {
|
|||||||
if (!this._filesQueue.length) {
|
if (!this._filesQueue.length) {
|
||||||
this._busy = false;
|
this._busy = false;
|
||||||
Events.fire('notify-user', 'File transfer completed.');
|
Events.fire('notify-user', 'File transfer completed.');
|
||||||
|
Events.fire('files-sent'); // used by 'Snapdrop & PairDrop for Android' app
|
||||||
} else {
|
} else {
|
||||||
this._dequeueFile();
|
this._dequeueFile();
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-26
@@ -1024,7 +1024,8 @@ class PairDeviceDialog extends Dialog {
|
|||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
if (urlParams.has('room_key')) {
|
if (urlParams.has('room_key')) {
|
||||||
this._pairDeviceJoin(urlParams.get('room_key'));
|
this._pairDeviceJoin(urlParams.get('room_key'));
|
||||||
window.history.replaceState({}, "title**", '/'); //remove room_key from url
|
const url = getUrlWithoutArguments();
|
||||||
|
window.history.replaceState({}, "Rewrite URL", url); //remove room_key from url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1295,18 +1296,18 @@ class SendTextDialog extends Dialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _onKeyDown(e) {
|
async _onKeyDown(e) {
|
||||||
if (this.isShown()) {
|
if (!this.isShown()) return;
|
||||||
if (e.code === "Escape") {
|
|
||||||
this.hide();
|
if (e.code === "Escape") {
|
||||||
} else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) {
|
this.hide();
|
||||||
if (this._textInputEmpty()) return;
|
} else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) {
|
||||||
this._send();
|
if (this._textInputEmpty()) return;
|
||||||
}
|
this._send();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_textInputEmpty() {
|
_textInputEmpty() {
|
||||||
return this.$text.innerText === "\n";
|
return !this.$text.innerText || this.$text.innerText === "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
_onChange(e) {
|
_onChange(e) {
|
||||||
@@ -1419,7 +1420,8 @@ class ReceiveTextDialog extends Dialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _onCopy() {
|
async _onCopy() {
|
||||||
await navigator.clipboard.writeText(this.$text.textContent);
|
const sanitizedText = this.$text.innerText.replace(/\u00A0/gm, ' ');
|
||||||
|
await navigator.clipboard.writeText(sanitizedText);
|
||||||
Events.fire('notify-user', 'Copied to clipboard');
|
Events.fire('notify-user', 'Copied to clipboard');
|
||||||
this.hide();
|
this.hide();
|
||||||
}
|
}
|
||||||
@@ -1575,7 +1577,8 @@ class Base64ZipDialog extends Dialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clearBrowserHistory() {
|
clearBrowserHistory() {
|
||||||
window.history.replaceState({}, "Rewrite URL", '/');
|
const url = getUrlWithoutArguments();
|
||||||
|
window.history.replaceState({}, "Rewrite URL", url);
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
@@ -1594,7 +1597,7 @@ class Toast extends Dialog {
|
|||||||
|
|
||||||
_onNotify(message) {
|
_onNotify(message) {
|
||||||
if (this.hideTimeout) clearTimeout(this.hideTimeout);
|
if (this.hideTimeout) clearTimeout(this.hideTimeout);
|
||||||
this.$el.textContent = message;
|
this.$el.innerText = message;
|
||||||
this.show();
|
this.show();
|
||||||
this.hideTimeout = setTimeout(_ => this.hide(), 5000);
|
this.hideTimeout = setTimeout(_ => this.hide(), 5000);
|
||||||
}
|
}
|
||||||
@@ -1791,7 +1794,8 @@ class WebShareTargetUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window.history.replaceState({}, "Rewrite URL", '/');
|
const url = getUrlWithoutArguments();
|
||||||
|
window.history.replaceState({}, "Rewrite URL", url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1815,7 +1819,8 @@ class WebFileHandlersUI {
|
|||||||
Events.fire('activate-paste-mode', {files: files, text: ""})
|
Events.fire('activate-paste-mode', {files: files, text: ""})
|
||||||
launchParams = null;
|
launchParams = null;
|
||||||
});
|
});
|
||||||
window.history.replaceState({}, "Rewrite URL", '/');
|
const url = getUrlWithoutArguments();
|
||||||
|
window.history.replaceState({}, "Rewrite URL", url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2249,14 +2254,7 @@ window.addEventListener('beforeinstallprompt', e => {
|
|||||||
|
|
||||||
// Background Circles
|
// Background Circles
|
||||||
Events.on('load', () => {
|
Events.on('load', () => {
|
||||||
let c = document.createElement('canvas');
|
let c = $$('canvas');
|
||||||
let style = c.style;
|
|
||||||
style.width = '100%';
|
|
||||||
style.position = 'absolute';
|
|
||||||
style.zIndex = -1;
|
|
||||||
style.top = 0;
|
|
||||||
style.left = 0;
|
|
||||||
style.animation = "fade-in 800ms";
|
|
||||||
let cCtx = c.getContext('2d');
|
let cCtx = c.getContext('2d');
|
||||||
let x0, y0, w, h, dw, offset;
|
let x0, y0, w, h, dw, offset;
|
||||||
|
|
||||||
@@ -2277,11 +2275,7 @@ Events.on('load', () => {
|
|||||||
y0 = h - offset;
|
y0 = h - offset;
|
||||||
dw = Math.round(Math.max(w, h, 1000) / 13);
|
dw = Math.round(Math.max(w, h, 1000) / 13);
|
||||||
|
|
||||||
if (document.body.contains(c)) {
|
|
||||||
document.body.removeChild(c);
|
|
||||||
}
|
|
||||||
drawCircles(cCtx, dw);
|
drawCircles(cCtx, dw);
|
||||||
document.body.appendChild(c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Events.on('bg-resize', _ => init());
|
Events.on('bg-resize', _ => init());
|
||||||
@@ -2297,6 +2291,7 @@ Events.on('load', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function drawCircles(ctx, frame) {
|
function drawCircles(ctx, frame) {
|
||||||
|
ctx.clearRect(0, 0, w, h);
|
||||||
for (let i = 0; i < 13; i++) {
|
for (let i = 0; i < 13; i++) {
|
||||||
drawCircle(ctx, dw * i + frame + 33);
|
drawCircle(ctx, dw * i + frame + 33);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ if (!navigator.clipboard) {
|
|||||||
|
|
||||||
// A <span> contains the text to copy
|
// A <span> contains the text to copy
|
||||||
const span = document.createElement('span');
|
const span = document.createElement('span');
|
||||||
span.textContent = text;
|
span.innerText = text;
|
||||||
span.style.whiteSpace = 'pre'; // Preserve consecutive spaces and newlines
|
span.style.whiteSpace = 'pre'; // Preserve consecutive spaces and newlines
|
||||||
|
|
||||||
// Paint the span outside the viewport
|
// Paint the span outside the viewport
|
||||||
@@ -402,3 +402,7 @@ const cyrb53 = function(str, seed = 0) {
|
|||||||
function onlyUnique (value, index, array) {
|
function onlyUnique (value, index, array) {
|
||||||
return array.indexOf(value) === index;
|
return array.indexOf(value) === index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getUrlWithoutArguments() {
|
||||||
|
return `${window.location.protocol}//${window.location.host}${window.location.pathname}`;
|
||||||
|
}
|
||||||
|
|||||||
+18
-18
@@ -1,4 +1,4 @@
|
|||||||
const cacheVersion = 'v1.7.1';
|
const cacheVersion = 'v1.7.5';
|
||||||
const cacheTitle = `pairdrop-cache-${cacheVersion}`;
|
const cacheTitle = `pairdrop-cache-${cacheVersion}`;
|
||||||
const urlsToCache = [
|
const urlsToCache = [
|
||||||
'index.html',
|
'index.html',
|
||||||
@@ -72,8 +72,7 @@ self.addEventListener('fetch', function(event) {
|
|||||||
if (event.request.method === "POST") {
|
if (event.request.method === "POST") {
|
||||||
// Requests related to Web Share Target.
|
// Requests related to Web Share Target.
|
||||||
event.respondWith((async () => {
|
event.respondWith((async () => {
|
||||||
let share_url = await evaluateRequestData(event.request);
|
const share_url = await evaluateRequestData(event.request);
|
||||||
share_url = event.request.url + share_url;
|
|
||||||
return Response.redirect(encodeURI(share_url), 302);
|
return Response.redirect(encodeURI(share_url), 302);
|
||||||
})());
|
})());
|
||||||
} else {
|
} else {
|
||||||
@@ -101,15 +100,16 @@ self.addEventListener('activate', evt =>
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const evaluateRequestData = async function (request) {
|
const evaluateRequestData = function (request) {
|
||||||
const formData = await request.formData();
|
|
||||||
const title = formData.get("title");
|
|
||||||
const text = formData.get("text");
|
|
||||||
const url = formData.get("url");
|
|
||||||
const files = formData.getAll("allfiles");
|
|
||||||
|
|
||||||
|
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const title = formData.get("title");
|
||||||
|
const text = formData.get("text");
|
||||||
|
const url = formData.get("url");
|
||||||
|
const files = formData.getAll("allfiles");
|
||||||
|
|
||||||
|
const pairDropUrl = request.url;
|
||||||
|
|
||||||
if (files && files.length > 0) {
|
if (files && files.length > 0) {
|
||||||
let fileObjects = [];
|
let fileObjects = [];
|
||||||
for (let i=0; i<files.length; i++) {
|
for (let i=0; i<files.length; i++) {
|
||||||
@@ -128,21 +128,21 @@ const evaluateRequestData = async function (request) {
|
|||||||
|
|
||||||
const objectStoreRequest = objectStore.add(fileObjects[i]);
|
const objectStoreRequest = objectStore.add(fileObjects[i]);
|
||||||
objectStoreRequest.onsuccess = _ => {
|
objectStoreRequest.onsuccess = _ => {
|
||||||
if (i === fileObjects.length - 1) resolve('?share-target=files');
|
if (i === fileObjects.length - 1) resolve(pairDropUrl + '?share-target=files');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DBOpenRequest.onerror = _ => {
|
DBOpenRequest.onerror = _ => {
|
||||||
resolve('');
|
resolve(pairDropUrl);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let share_url = '?share-target=text';
|
let urlArgument = '?share-target=text';
|
||||||
|
|
||||||
if (title) share_url += `&title=${title}`;
|
if (title) urlArgument += `&title=${title}`;
|
||||||
if (text) share_url += `&text=${text}`;
|
if (text) urlArgument += `&text=${text}`;
|
||||||
if (url) share_url += `&url=${url}`;
|
if (url) urlArgument += `&url=${url}`;
|
||||||
|
|
||||||
resolve(share_url);
|
resolve(pairDropUrl + urlArgument);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-5
@@ -1124,11 +1124,14 @@ button::-moz-focus-inner {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#about header {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
#about .fade-in {
|
#about .fade-in {
|
||||||
transition: opacity 300ms;
|
transition: opacity 300ms;
|
||||||
will-change: opacity;
|
will-change: opacity;
|
||||||
transition-delay: 300ms;
|
transition-delay: 300ms;
|
||||||
z-index: 11;
|
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1142,10 +1145,23 @@ button::-moz-focus-inner {
|
|||||||
--icon-size: 96px;
|
--icon-size: 96px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#about .title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
#about .title-wrapper > div {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
#about x-background {
|
#about x-background {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 500px;
|
--size: max(max(230vw, 230vh), calc(150vh + 150vw));
|
||||||
height: 500px;
|
--size-half: calc(var(--size)/2);
|
||||||
|
top: calc(28px - var(--size-half));
|
||||||
|
right: calc(36px - var(--size-half));
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--primary-color);
|
background: var(--primary-color);
|
||||||
transform: scale(0);
|
transform: scale(0);
|
||||||
@@ -1159,7 +1175,7 @@ button::-moz-focus-inner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#about:target x-background {
|
#about:target x-background {
|
||||||
transform: scale(10);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#about .row a {
|
#about .row a {
|
||||||
@@ -1174,6 +1190,14 @@ button::-moz-focus-inner {
|
|||||||
align-self: end;
|
align-self: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canvas.circles {
|
||||||
|
width: 100vw;
|
||||||
|
position: absolute;
|
||||||
|
z-index: -10;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Loading Indicator */
|
/* Loading Indicator */
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
@@ -1282,7 +1306,7 @@ x-peers:empty~x-instructions {
|
|||||||
@media (hover: none) and (pointer: coarse) {
|
@media (hover: none) and (pointer: coarse) {
|
||||||
x-peer {
|
x-peer {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
padding: 4px 0;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -227,7 +227,7 @@
|
|||||||
<div class="row-separator"></div>
|
<div class="row-separator"></div>
|
||||||
<div id="text-input" title="Message" class="textarea" role="textbox" autocapitalize="none" spellcheck="false" autofocus contenteditable></div>
|
<div id="text-input" title="Message" class="textarea" role="textbox" autocapitalize="none" spellcheck="false" autofocus contenteditable></div>
|
||||||
<div class="center row-reverse">
|
<div class="center row-reverse">
|
||||||
<button class="button" type="submit" title="STR + ENTER" disabled close>Send</button>
|
<button class="button" type="submit" title="CTRL/⌘ + ENTER" disabled>Send</button>
|
||||||
<button class="button" type="button" title="ESCAPE" close>Cancel</button>
|
<button class="button" type="button" title="ESCAPE" close>Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</x-paper>
|
</x-paper>
|
||||||
@@ -279,7 +279,10 @@
|
|||||||
<svg class="icon logo">
|
<svg class="icon logo">
|
||||||
<use xlink:href="#wifi-tethering" />
|
<use xlink:href="#wifi-tethering" />
|
||||||
</svg>
|
</svg>
|
||||||
<h1>PairDrop</h1>
|
<div class="title-wrapper">
|
||||||
|
<h1>PairDrop</h1>
|
||||||
|
<div class="font-subheading">v1.7.5</div>
|
||||||
|
</div>
|
||||||
<div class="font-subheading">The easiest way to transfer files across devices</div>
|
<div class="font-subheading">The easiest way to transfer files across devices</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<a class="icon-button" target="_blank" href="https://github.com/schlagmichdoch/pairdrop" title="PairDrop on Github" rel="noreferrer">
|
<a class="icon-button" target="_blank" href="https://github.com/schlagmichdoch/pairdrop" title="PairDrop on Github" rel="noreferrer">
|
||||||
@@ -306,6 +309,7 @@
|
|||||||
</section>
|
</section>
|
||||||
<x-background></x-background>
|
<x-background></x-background>
|
||||||
</x-about>
|
</x-about>
|
||||||
|
<canvas class="circles"></canvas>
|
||||||
<!-- SVG Icon Library -->
|
<!-- SVG Icon Library -->
|
||||||
<svg style="display: none;">
|
<svg style="display: none;">
|
||||||
<symbol id=wifi-tethering viewBox="0 0 24 24">
|
<symbol id=wifi-tethering viewBox="0 0 24 24">
|
||||||
|
|||||||
@@ -1025,7 +1025,8 @@ class PairDeviceDialog extends Dialog {
|
|||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
if (urlParams.has('room_key')) {
|
if (urlParams.has('room_key')) {
|
||||||
this._pairDeviceJoin(urlParams.get('room_key'));
|
this._pairDeviceJoin(urlParams.get('room_key'));
|
||||||
window.history.replaceState({}, "title**", '/'); //remove room_key from url
|
const url = getUrlWithoutArguments();
|
||||||
|
window.history.replaceState({}, "Rewrite URL", url); //remove room_key from url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1296,18 +1297,18 @@ class SendTextDialog extends Dialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _onKeyDown(e) {
|
async _onKeyDown(e) {
|
||||||
if (this.isShown()) {
|
if (!this.isShown()) return;
|
||||||
if (e.code === "Escape") {
|
|
||||||
this.hide();
|
if (e.code === "Escape") {
|
||||||
} else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) {
|
this.hide();
|
||||||
if (this._textInputEmpty()) return;
|
} else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) {
|
||||||
this._send();
|
if (this._textInputEmpty()) return;
|
||||||
}
|
this._send();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_textInputEmpty() {
|
_textInputEmpty() {
|
||||||
return this.$text.innerText === "\n";
|
return !this.$text.innerText || this.$text.innerText === "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
_onChange(e) {
|
_onChange(e) {
|
||||||
@@ -1420,7 +1421,8 @@ class ReceiveTextDialog extends Dialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _onCopy() {
|
async _onCopy() {
|
||||||
await navigator.clipboard.writeText(this.$text.textContent);
|
const sanitizedText = this.$text.innerText.replace(/\u00A0/gm, ' ');
|
||||||
|
await navigator.clipboard.writeText(sanitizedText);
|
||||||
Events.fire('notify-user', 'Copied to clipboard');
|
Events.fire('notify-user', 'Copied to clipboard');
|
||||||
this.hide();
|
this.hide();
|
||||||
}
|
}
|
||||||
@@ -1576,7 +1578,8 @@ class Base64ZipDialog extends Dialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clearBrowserHistory() {
|
clearBrowserHistory() {
|
||||||
window.history.replaceState({}, "Rewrite URL", '/');
|
const url = getUrlWithoutArguments();
|
||||||
|
window.history.replaceState({}, "Rewrite URL", url);
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
@@ -1595,7 +1598,7 @@ class Toast extends Dialog {
|
|||||||
|
|
||||||
_onNotify(message) {
|
_onNotify(message) {
|
||||||
if (this.hideTimeout) clearTimeout(this.hideTimeout);
|
if (this.hideTimeout) clearTimeout(this.hideTimeout);
|
||||||
this.$el.textContent = message;
|
this.$el.innerText = message;
|
||||||
this.show();
|
this.show();
|
||||||
this.hideTimeout = setTimeout(_ => this.hide(), 5000);
|
this.hideTimeout = setTimeout(_ => this.hide(), 5000);
|
||||||
}
|
}
|
||||||
@@ -1792,7 +1795,8 @@ class WebShareTargetUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window.history.replaceState({}, "Rewrite URL", '/');
|
const url = getUrlWithoutArguments();
|
||||||
|
window.history.replaceState({}, "Rewrite URL", url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1816,7 +1820,8 @@ class WebFileHandlersUI {
|
|||||||
Events.fire('activate-paste-mode', {files: files, text: ""})
|
Events.fire('activate-paste-mode', {files: files, text: ""})
|
||||||
launchParams = null;
|
launchParams = null;
|
||||||
});
|
});
|
||||||
window.history.replaceState({}, "Rewrite URL", '/');
|
const url = getUrlWithoutArguments();
|
||||||
|
window.history.replaceState({}, "Rewrite URL", url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2250,14 +2255,7 @@ window.addEventListener('beforeinstallprompt', e => {
|
|||||||
|
|
||||||
// Background Circles
|
// Background Circles
|
||||||
Events.on('load', () => {
|
Events.on('load', () => {
|
||||||
let c = document.createElement('canvas');
|
let c = $$('canvas');
|
||||||
let style = c.style;
|
|
||||||
style.width = '100%';
|
|
||||||
style.position = 'absolute';
|
|
||||||
style.zIndex = -1;
|
|
||||||
style.top = 0;
|
|
||||||
style.left = 0;
|
|
||||||
style.animation = "fade-in 800ms";
|
|
||||||
let cCtx = c.getContext('2d');
|
let cCtx = c.getContext('2d');
|
||||||
let x0, y0, w, h, dw, offset;
|
let x0, y0, w, h, dw, offset;
|
||||||
|
|
||||||
@@ -2277,11 +2275,7 @@ Events.on('load', () => {
|
|||||||
y0 = h - offset;
|
y0 = h - offset;
|
||||||
dw = Math.round(Math.max(w, h, 1000) / 13);
|
dw = Math.round(Math.max(w, h, 1000) / 13);
|
||||||
|
|
||||||
if (document.body.contains(c)) {
|
|
||||||
document.body.removeChild(c);
|
|
||||||
}
|
|
||||||
drawCircles(cCtx, dw);
|
drawCircles(cCtx, dw);
|
||||||
document.body.appendChild(c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Events.on('bg-resize', _ => init());
|
Events.on('bg-resize', _ => init());
|
||||||
@@ -2297,6 +2291,7 @@ Events.on('load', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function drawCircles(ctx, frame) {
|
function drawCircles(ctx, frame) {
|
||||||
|
ctx.clearRect(0, 0, w, h);
|
||||||
for (let i = 0; i < 13; i++) {
|
for (let i = 0; i < 13; i++) {
|
||||||
drawCircle(ctx, dw * i + frame + 33);
|
drawCircle(ctx, dw * i + frame + 33);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ if (!navigator.clipboard) {
|
|||||||
|
|
||||||
// A <span> contains the text to copy
|
// A <span> contains the text to copy
|
||||||
const span = document.createElement('span');
|
const span = document.createElement('span');
|
||||||
span.textContent = text;
|
span.innerText = text;
|
||||||
span.style.whiteSpace = 'pre'; // Preserve consecutive spaces and newlines
|
span.style.whiteSpace = 'pre'; // Preserve consecutive spaces and newlines
|
||||||
|
|
||||||
// Paint the span outside the viewport
|
// Paint the span outside the viewport
|
||||||
@@ -403,6 +403,10 @@ function onlyUnique (value, index, array) {
|
|||||||
return array.indexOf(value) === index;
|
return array.indexOf(value) === index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getUrlWithoutArguments() {
|
||||||
|
return `${window.location.protocol}//${window.location.host}${window.location.pathname}`;
|
||||||
|
}
|
||||||
|
|
||||||
function arrayBufferToBase64(buffer) {
|
function arrayBufferToBase64(buffer) {
|
||||||
var binary = '';
|
var binary = '';
|
||||||
var bytes = new Uint8Array(buffer);
|
var bytes = new Uint8Array(buffer);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const cacheVersion = 'v1.7.1';
|
const cacheVersion = 'v1.7.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',
|
||||||
@@ -72,8 +72,7 @@ self.addEventListener('fetch', function(event) {
|
|||||||
if (event.request.method === "POST") {
|
if (event.request.method === "POST") {
|
||||||
// Requests related to Web Share Target.
|
// Requests related to Web Share Target.
|
||||||
event.respondWith((async () => {
|
event.respondWith((async () => {
|
||||||
let share_url = await evaluateRequestData(event.request);
|
const share_url = await evaluateRequestData(event.request);
|
||||||
share_url = event.request.url + share_url;
|
|
||||||
return Response.redirect(encodeURI(share_url), 302);
|
return Response.redirect(encodeURI(share_url), 302);
|
||||||
})());
|
})());
|
||||||
} else {
|
} else {
|
||||||
@@ -101,15 +100,16 @@ self.addEventListener('activate', evt =>
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const evaluateRequestData = async function (request) {
|
const evaluateRequestData = function (request) {
|
||||||
const formData = await request.formData();
|
|
||||||
const title = formData.get("title");
|
|
||||||
const text = formData.get("text");
|
|
||||||
const url = formData.get("url");
|
|
||||||
const files = formData.getAll("allfiles");
|
|
||||||
|
|
||||||
|
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const title = formData.get("title");
|
||||||
|
const text = formData.get("text");
|
||||||
|
const url = formData.get("url");
|
||||||
|
const files = formData.getAll("allfiles");
|
||||||
|
|
||||||
|
const pairDropUrl = request.url;
|
||||||
|
|
||||||
if (files && files.length > 0) {
|
if (files && files.length > 0) {
|
||||||
let fileObjects = [];
|
let fileObjects = [];
|
||||||
for (let i=0; i<files.length; i++) {
|
for (let i=0; i<files.length; i++) {
|
||||||
@@ -128,21 +128,21 @@ const evaluateRequestData = async function (request) {
|
|||||||
|
|
||||||
const objectStoreRequest = objectStore.add(fileObjects[i]);
|
const objectStoreRequest = objectStore.add(fileObjects[i]);
|
||||||
objectStoreRequest.onsuccess = _ => {
|
objectStoreRequest.onsuccess = _ => {
|
||||||
if (i === fileObjects.length - 1) resolve('?share-target=files');
|
if (i === fileObjects.length - 1) resolve(pairDropUrl + '?share-target=files');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DBOpenRequest.onerror = _ => {
|
DBOpenRequest.onerror = _ => {
|
||||||
resolve('');
|
resolve(pairDropUrl);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let share_url = '?share-target=text';
|
let urlArgument = '?share-target=text';
|
||||||
|
|
||||||
if (title) share_url += `&title=${title}`;
|
if (title) urlArgument += `&title=${title}`;
|
||||||
if (text) share_url += `&text=${text}`;
|
if (text) urlArgument += `&text=${text}`;
|
||||||
if (url) share_url += `&url=${url}`;
|
if (url) urlArgument += `&url=${url}`;
|
||||||
|
|
||||||
resolve(share_url);
|
resolve(pairDropUrl + urlArgument);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1150,11 +1150,14 @@ button::-moz-focus-inner {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#about header {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
#about .fade-in {
|
#about .fade-in {
|
||||||
transition: opacity 300ms;
|
transition: opacity 300ms;
|
||||||
will-change: opacity;
|
will-change: opacity;
|
||||||
transition-delay: 300ms;
|
transition-delay: 300ms;
|
||||||
z-index: 11;
|
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1168,10 +1171,23 @@ button::-moz-focus-inner {
|
|||||||
--icon-size: 96px;
|
--icon-size: 96px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#about .title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
#about .title-wrapper > div {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
#about x-background {
|
#about x-background {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 500px;
|
--size: max(max(230vw, 230vh), calc(150vh + 150vw));
|
||||||
height: 500px;
|
--size-half: calc(var(--size)/2);
|
||||||
|
top: calc(28px - var(--size-half));
|
||||||
|
right: calc(36px - var(--size-half));
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--primary-color);
|
background: var(--primary-color);
|
||||||
transform: scale(0);
|
transform: scale(0);
|
||||||
@@ -1185,7 +1201,7 @@ button::-moz-focus-inner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#about:target x-background {
|
#about:target x-background {
|
||||||
transform: scale(10);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#about .row a {
|
#about .row a {
|
||||||
@@ -1200,6 +1216,14 @@ button::-moz-focus-inner {
|
|||||||
align-self: end;
|
align-self: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canvas.circles {
|
||||||
|
width: 100vw;
|
||||||
|
position: absolute;
|
||||||
|
z-index: -10;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Loading Indicator */
|
/* Loading Indicator */
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
@@ -1308,7 +1332,7 @@ x-peers:empty~x-instructions {
|
|||||||
@media (hover: none) and (pointer: coarse) {
|
@media (hover: none) and (pointer: coarse) {
|
||||||
x-peer {
|
x-peer {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
padding: 4px 0;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user