diff --git a/docker/nginx/development.conf b/docker/nginx/development.conf
index c5aef38..aa7bd8b 100644
--- a/docker/nginx/development.conf
+++ b/docker/nginx/development.conf
@@ -18,6 +18,9 @@ server {
location /ca.crt {
alias /etc/ssl/certs/snapdropCA.crt;
}
+
+ # To allow POST on static pages
+ error_page 405 =200 $uri;
}
server {
@@ -42,5 +45,7 @@ server {
location /ca.crt {
alias /etc/ssl/certs/snapdropCA.crt;
}
+ # To allow POST on static pages
+ error_page 405 =200 $uri;
}
diff --git a/docker/nginx/production.conf b/docker/nginx/production.conf
index c0eedde..7a3d722 100644
--- a/docker/nginx/production.conf
+++ b/docker/nginx/production.conf
@@ -10,6 +10,8 @@ server {
location /ca.crt {
alias /etc/ssl/certs/snapdropCA.crt;
}
+ # To allow POST on static pages
+ error_page 405 =200 $uri;
}
server {
@@ -34,5 +36,7 @@ server {
location /ca.crt {
alias /etc/ssl/certs/snapdropCA.crt;
}
+ # To allow POST on static pages
+ error_page 405 =200 $uri;
}
diff --git a/public/index.html b/public/index.html
index 1e3d15c..0d1c106 100644
--- a/public/index.html
+++ b/public/index.html
@@ -88,7 +88,7 @@
You can be discovered by everyone on this network
-
+
+
+
+
+
+
+
+
+
+
+
File Transfer Completed
diff --git a/public/manifest.json b/public/manifest.json
index 71f8164..daf1f19 100644
--- a/public/manifest.json
+++ b/public/manifest.json
@@ -2,27 +2,27 @@
"name": "PairDrop",
"short_name": "PairDrop",
"icons": [{
- "src": "images/android-chrome-192x192.png",
- "sizes": "192x192",
- "type": "image/png"
- },{
- "src": "images/android-chrome-512x512.png",
- "sizes": "512x512",
- "type": "image/png"
- },{
- "src": "images/android-chrome-192x192-maskable.png",
- "sizes": "192x192",
- "type": "image/png",
- "purpose": "maskable"
- },{
- "src": "images/android-chrome-512x512-maskable.png",
- "sizes": "512x512",
- "type": "image/png",
- "purpose": "maskable"
- },{
- "src": "images/favicon-96x96.png",
- "sizes": "96x96",
- "type": "image/png"
+ "src": "images/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },{
+ "src": "images/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ },{
+ "src": "images/android-chrome-192x192-maskable.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "maskable"
+ },{
+ "src": "images/android-chrome-512x512-maskable.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "maskable"
+ },{
+ "src": "images/favicon-96x96.png",
+ "sizes": "96x96",
+ "type": "image/png"
}],
"background_color": "#efefef",
"start_url": "/",
@@ -30,12 +30,17 @@
"display": "minimal-ui",
"theme_color": "#3367d6",
"share_target": {
- "method":"GET",
- "action": "/?share_target",
+ "action": "/",
+ "method":"POST",
+ "enctype": "multipart/form-data",
"params": {
"title": "title",
"text": "text",
- "url": "url"
+ "url": "url",
+ "files": [{
+ "name": "allfiles",
+ "accept": ["*/*"]
+ }]
}
},
"file_handlers": [
diff --git a/public/scripts/ui.js b/public/scripts/ui.js
index eb037a4..83f5d39 100644
--- a/public/scripts/ui.js
+++ b/public/scripts/ui.js
@@ -931,7 +931,6 @@ class SendTextDialog extends Dialog {
_onRecipient(recipient) {
this._recipient = recipient;
- this._handleShareTargetText();
this.show();
const range = document.createRange();
@@ -943,12 +942,6 @@ class SendTextDialog extends Dialog {
sel.addRange(range);
}
- _handleShareTargetText() {
- if (!window.shareTargetText) return;
- this.$text.textContent = window.shareTargetText;
- window.shareTargetText = '';
- }
-
_send() {
Events.fire('send-text', {
to: this._recipient,
@@ -997,6 +990,45 @@ class ReceiveTextDialog extends Dialog {
}
}
+class Base64ZipDialog extends Dialog {
+
+ constructor() {
+ super('base64ZipDialog');
+ const urlParams = new URL(window.location).searchParams;
+ const base64zip = urlParams.get('base64zip');
+ this.$pasteBtn = this.$el.querySelector('#base64ZipPasteBtn')
+ this.$pasteBtn.addEventListener('click', _ => this.processClipboard())
+ if (base64zip) this.show();
+ }
+
+ async processClipboard() {
+ this.$pasteBtn.pointerEvents = "none";
+ this.$pasteBtn.innerText = "Processing...";
+ try {
+ const base64zip = await navigator.clipboard.readText();
+ let bstr = atob(base64zip), n = bstr.length, u8arr = new Uint8Array(n);
+ while (n--) {
+ u8arr[n] = bstr.charCodeAt(n);
+ }
+
+ const zipBlob = new File([u8arr], 'archive.zip');
+
+ let files = [];
+ const zipEntries = await zipper.getEntries(zipBlob);
+ for (let i = 0; i < zipEntries.length; i++) {
+ let fileBlob = await zipper.getData(zipEntries[i]);
+ files.push(new File([fileBlob], zipEntries[i].filename));
+ }
+ Events.fire('activate-paste-mode', {files: files, text: ""})
+ } catch (e) {
+ Events.fire('notify-user', 'Clipboard content is malformed.')
+ } finally {
+ window.history.replaceState({}, "Rewrite URL", '/');
+ this.hide();
+ }
+ }
+}
+
class Toast extends Dialog {
constructor() {
super('toast');
@@ -1135,20 +1167,35 @@ class NetworkStatusUI {
class WebShareTargetUI {
constructor() {
- const parsedUrl = new URL(window.location);
- const title = parsedUrl.searchParams.get('title');
- const text = parsedUrl.searchParams.get('text');
- const url = parsedUrl.searchParams.get('url');
+ const urlParams = new URL(window.location).searchParams;
+ const share_target_type = urlParams.get("share-target")
+ if (share_target_type) {
+ if (share_target_type === "text") {
+ const title = urlParams.get('title') || '';
+ const text = urlParams.get('text') || '';
+ const url = urlParams.get('url') || '';
+ let shareTargetText;
- let shareTargetText = title ? title : '';
- shareTargetText += text ? shareTargetText ? ' ' + text : text : '';
+ if (url) {
+ shareTargetText = url; // We share only the Link - no text. Because link-only text becomes clickable.
+ } else if (title && text) {
+ shareTargetText = title + '\r\n' + text;
+ } else {
+ shareTargetText = title + text;
+ }
- if(url) shareTargetText = url; // We share only the Link - no text. Because link-only text becomes clickable.
-
- if (!shareTargetText) return;
- window.shareTargetText = shareTargetText;
- history.pushState({}, 'URL Rewrite', '/');
- console.log('Shared Target Text:', '"' + shareTargetText + '"');
+ console.log('Shared Target Text:', '"' + shareTargetText + '"');
+ Events.fire('activate-paste-mode', {files: [], text: shareTargetText})
+ } else if (share_target_type === "files") {
+ caches.match("share_target_files")
+ .then(files => {
+ console.debug(files)
+ Events.fire('activate-paste-mode', {files: files, text: ""})
+ })
+ caches.delete("share_target_files").then( _ => console.log("shared files deleted from cache"));
+ }
+ window.history.replaceState({}, "Rewrite URL", '/');
+ }
}
}
@@ -1219,7 +1266,7 @@ class PersistentStorage {
}
logBrowserNotCapable() {
- console.log("This browser does not support IndexedDB. Paired devices will be gone after closing the browser.");
+ console.log("This browser does not support IndexedDB. Paired devices will be gone after the browser is closed.");
}
static set(key, value) {
@@ -1382,6 +1429,7 @@ class PairDrop {
const receiveTextDialog = new ReceiveTextDialog();
const pairDeviceDialog = new PairDeviceDialog();
const clearDevicesDialog = new ClearDevicesDialog();
+ const base64ZipDialog = new Base64ZipDialog();
const toast = new Toast();
const notifications = new Notifications();
const networkStatusUI = new NetworkStatusUI();
diff --git a/public/service-worker.js b/public/service-worker.js
index 17dc7ac..3a21c71 100644
--- a/public/service-worker.js
+++ b/public/service-worker.js
@@ -27,17 +27,48 @@ self.addEventListener('install', function(event) {
self.addEventListener('fetch', function(event) {
- event.respondWith(
- caches.match(event.request)
- .then(function(response) {
- // Cache hit - return response
- if (response) {
- return response;
- }
- return fetch(event.request);
- }
- )
- );
+ if (event.request.method === "POST") {
+ // Requests related to Web Share Target.
+ event.respondWith(
+ (async () => {
+ const formData = await event.request.formData();
+ const title = formData.get("title");
+ const text = formData.get("text");
+ const url = formData.get("url");
+ const files = formData.get("files");
+ console.debug(title)
+ console.debug(text)
+ console.debug(url)
+ console.debug(files)
+ let share_url = "/";
+ if (files.length > 0) {
+ // Save to Cache?
+ caches.open("share_target_files")
+ .then(cache => {
+ cache.addAll(files)
+ console.debug("files added to cache")
+ });
+ share_url = "/?share-target=files";
+ } else if (title.length > 0 || text.length > 0 || url.length) {
+ share_url = `/?share-target=text&title=${title}&text=${text}&url=${url}`;
+ }
+ return Response.redirect(encodeURI(share_url), 303);
+ })()
+ );
+ } else {
+ // Regular requests not related to Web Share Target.
+ event.respondWith(
+ caches.match(event.request)
+ .then(function (response) {
+ // Cache hit - return response
+ if (response) {
+ return response;
+ }
+ return fetch(event.request);
+ }
+ )
+ );
+ }
});
diff --git a/public/styles.css b/public/styles.css
index d06a626..2f219d0 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -532,10 +532,28 @@ x-dialog .row-reverse {
pointer-events: none;
}
+#base64ZipPasteBtn {
+ width: 100%;
+ height: 40vh;
+ border: solid 12px #438cff;
+}
+
+#base64ZipDialog button {
+ margin: auto;
+ border-radius: 8px;
+}
+
+#base64ZipDialog button[close] {
+ margin-top: 20px;
+}
+#base64ZipDialog button[close]:before {
+ border-radius: 8px;
+}
+
/* Button */
.button {
- padding: 0 16px;
+ padding: 2px 16px 0;
box-sizing: border-box;
min-height: 36px;
min-width: 100px;