Compare commits

...

19 Commits

Author SHA1 Message Date
schlagmichdoch
29b91cb17a increase version to v1.7.6 2023-06-01 01:51:51 +02:00
schlagmichdoch
26bf4d6dc3 ensure that otherPeers never receive peer-left after peer-joined on reconnect by leaving room before rejoining it 2023-06-01 01:49:07 +02:00
schlagmichdoch
f195c686e7 increase version to v1.7.5 2023-06-01 01:32:06 +02:00
schlagmichdoch
3505f161c6 strip 'NO-BREAK SPACE' (U+00A0) of received text as some browsers seem to add them when pasting text 2023-06-01 01:29:00 +02:00
schlagmichdoch
3e2368c0c9 stabilize connection on reconnect by terminating websocket only on timeout and not always when peer leaves its ip room 2023-06-01 01:26:53 +02:00
schlagmichdoch
d36cd3524c Fix clearBrowserHistory: url should not always be replaced by "/" as PairDrop might not always be hosted at domain root 2023-05-30 02:34:50 +02:00
schlagmichdoch
a3a8228327 increase version to v1.7.4 2023-05-26 20:37:38 +02:00
schlagmichdoch
520b772bc8 fix #112 and differentiate between textContent and innerText 2023-05-26 20:36:12 +02:00
schlagmichdoch
27bf0fa74f fix #113 2023-05-26 20:36:12 +02:00
schlagmichdoch
e9f3c39f38 Merge pull request #114 from fm-sys/patch-1
Add 'files-sent' event used by 'Snapdrop & PairDrop for Android' app to support vibrations
2023-05-26 20:35:54 +02:00
fm-sys
58b7f6bb7c Add 'files-sent' event 2023-05-26 09:52:17 +02:00
schlagmichdoch
b5987cf017 increase version to v1.7.3 2023-05-23 02:45:29 +02:00
schlagmichdoch
4433e1c58f add version number to about page 2023-05-23 02:44:25 +02:00
schlagmichdoch
b106d90b64 Fix ReferenceError: ipv6_lcl is not defined 2023-05-23 02:43:17 +02:00
schlagmichdoch
c9e1c2504a Merge pull request #110 from luckman212/luckman212-patch-1
Add new env var `IPV6_LOCALIZE` to enable auto discovery for IPv6 addresses
2023-05-23 01:53:23 +02:00
luckman212
32e909b8c2 fixes for https://github.com/schlagmichdoch/PairDrop/issues/69
(squashed, docs updated, IPV6_LOCALIZE input validation)
2023-05-19 12:18:20 -04:00
schlagmichdoch
a444be226f Fix canvas selector 2023-05-16 19:15:47 +02:00
schlagmichdoch
df778ba42c Speed up canvas by removing fade-in animation 2023-05-16 19:09:59 +02:00
schlagmichdoch
8a17b82fa4 Fix _textInputEmpty() for Chromium based browsers
Co-authored-by: luckman212 <1992842+luckman212@users.noreply.github.com>
2023-05-16 02:53:56 +02:00
15 changed files with 185 additions and 104 deletions

View File

@@ -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

View File

@@ -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) {
@@ -347,6 +363,7 @@ class PairDropServer {
const room = roomType === 'ip' ? peer.ip : roomSecret; const room = roomType === 'ip' ? peer.ip : roomSecret;
if (this._rooms[room] && this._rooms[room][peer.id]) { if (this._rooms[room] && this._rooms[room][peer.id]) {
// ensures that otherPeers never receive `peer-left` after `peer-joined` on reconnect.
this._leaveRoom(peer, roomType, roomSecret); this._leaveRoom(peer, roomType, roomSecret);
} }
@@ -374,10 +391,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 +470,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 +528,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");

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "pairdrop", "name": "pairdrop",
"version": "1.7.2", "version": "1.7.6",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "pairdrop", "name": "pairdrop",
"version": "1.7.2", "version": "1.7.6",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"express": "^4.18.2", "express": "^4.18.2",

View File

@@ -1,6 +1,6 @@
{ {
"name": "pairdrop", "name": "pairdrop",
"version": "1.7.2", "version": "1.7.6",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@@ -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.6</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">

View File

@@ -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();
} }

View File

@@ -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);
} }

View File

@@ -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}`;
}

View File

@@ -1,4 +1,4 @@
const cacheVersion = 'v1.7.2'; const cacheVersion = 'v1.7.6';
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);
} }
}); });
} }

View File

@@ -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,6 +1145,15 @@ 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;
--size: max(max(230vw, 230vh), calc(150vh + 150vw)); --size: max(max(230vw, 230vh), calc(150vh + 150vw));
@@ -1178,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 {

View File

@@ -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.6</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">

View File

@@ -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);
} }

View File

@@ -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);

View File

@@ -1,4 +1,4 @@
const cacheVersion = 'v1.7.2'; const cacheVersion = 'v1.7.6';
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);
} }
}); });
} }

View File

@@ -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,6 +1171,15 @@ 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;
--size: max(max(230vw, 230vh), calc(150vh + 150vw)); --size: max(max(230vw, 230vh), calc(150vh + 150vw));
@@ -1204,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 {