From be6813d71473f97200dd770a9b474da8fbded1c8 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 18 Jan 2023 21:01:29 +0100 Subject: [PATCH 1/4] implemented WebShareTarget in manifest and serviceworker for testing --- docker/nginx/development.conf | 5 ++++ docker/nginx/production.conf | 4 +++ public/manifest.json | 54 +++++++++++++++++++---------------- public/scripts/ui.js | 45 +++++++++++++++++------------ public/service-worker.js | 53 +++++++++++++++++++++++++++------- 5 files changed, 107 insertions(+), 54 deletions(-) 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/manifest.json b/public/manifest.json index 71f8164..768ac0c 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,18 @@ "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..8b565e8 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, @@ -1135,20 +1128,34 @@ 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: ""}) + }) + } + history.pushState({}, 'URL Rewrite', '/'); + } } } 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); + } + ) + ); + } }); From 7e7463de9052424501385f44c3d68f7243ee80f2 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 18 Jan 2023 22:42:47 +0100 Subject: [PATCH 2/4] delete cached files after query; consistency updates --- public/scripts/ui.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 8b565e8..112c79f 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -1153,9 +1153,11 @@ class WebShareTargetUI { console.debug(files) Events.fire('activate-paste-mode', {files: files, text: ""}) }) + caches.delete("share_target_files").then( _ => console.log("shared files deleted from cache")); } - history.pushState({}, 'URL Rewrite', '/'); + window.history.replaceState({}, "Rewrite URL", '/'); //remove room_key from url } + } } @@ -1226,7 +1228,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) { From a60d60009afc9e3dbdbe9ae0d89e942e84e42131 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 19 Jan 2023 01:27:28 +0100 Subject: [PATCH 3/4] fix manifest.json --- public/manifest.json | 1 - 1 file changed, 1 deletion(-) diff --git a/public/manifest.json b/public/manifest.json index 768ac0c..daf1f19 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -41,7 +41,6 @@ "name": "allfiles", "accept": ["*/*"] }] - } } }, "file_handlers": [ From c53221ba01701d3427739dce6c9ae536ef5bf764 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 19 Jan 2023 04:40:28 +0100 Subject: [PATCH 4/4] Add Base64ZipDialog to PairDrop via share-menu on iOS --- public/index.html | 12 +++++++++++- public/scripts/ui.js | 43 +++++++++++++++++++++++++++++++++++++++++-- public/styles.css | 20 +++++++++++++++++++- 3 files changed, 71 insertions(+), 4 deletions(-) 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
- +
@@ -116,6 +116,7 @@
+
@@ -192,6 +193,15 @@ + + + + + + + + +
File Transfer Completed diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 112c79f..83f5d39 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -990,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'); @@ -1155,9 +1194,8 @@ class WebShareTargetUI { }) caches.delete("share_target_files").then( _ => console.log("shared files deleted from cache")); } - window.history.replaceState({}, "Rewrite URL", '/'); //remove room_key from url + window.history.replaceState({}, "Rewrite URL", '/'); } - } } @@ -1391,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/styles.css b/public/styles.css index 6a7d28b..270563d 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;