From fdf024f3787804a179af0fcf55dda3a62f939cfa Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 6 Mar 2023 02:18:07 +0100 Subject: [PATCH 1/3] pairdrop-cli: add fallback if navigator.clipboard.readText() is not available --- public/index.html | 1 + public/scripts/ui.js | 85 +++++++++++++---------- public/styles.css | 21 +++++- public_included_ws_fallback/index.html | 1 + public_included_ws_fallback/scripts/ui.js | 85 +++++++++++++---------- public_included_ws_fallback/styles.css | 21 +++++- 6 files changed, 140 insertions(+), 74 deletions(-) diff --git a/public/index.html b/public/index.html index cbb0337..ad8658b 100644 --- a/public/index.html +++ b/public/index.html @@ -238,6 +238,7 @@ + diff --git a/public/scripts/ui.js b/public/scripts/ui.js index b7e75f3..62c1f83 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -1245,21 +1245,21 @@ class Base64ZipDialog extends Dialog { const base64Hash = window.location.hash.substring(1); this.$pasteBtn = this.$el.querySelector('#base64-paste-btn'); + this.$fallbackTextarea = this.$el.querySelector('.textarea'); if (base64Text) { this.show(); if (base64Text === "paste") { // ?base64text=paste // base64 encoded string is ready to be pasted from clipboard - this.$pasteBtn.innerText = 'Tap here to paste text'; - this.$pasteBtn.addEventListener('click', _ => this.processClipboard('text')); + this.preparePasting("text"); } else if (base64Text === "hash") { // ?base64text=hash#BASE64ENCODED // base64 encoded string is url hash which is never sent to server and faster (recommended) this.processBase64Text(base64Hash) .catch(_ => { Events.fire('notify-user', 'Text content is incorrect.'); - console.log("Text content incorrect.") + console.log("Text content incorrect."); }).finally(_ => { this.hide(); }); @@ -1269,7 +1269,7 @@ class Base64ZipDialog extends Dialog { this.processBase64Text(base64Text) .catch(_ => { Events.fire('notify-user', 'Text content is incorrect.'); - console.log("Text content incorrect.") + console.log("Text content incorrect."); }).finally(_ => { this.hide(); }); @@ -1282,14 +1282,13 @@ class Base64ZipDialog extends Dialog { this.processBase64Zip(base64Hash) .catch(_ => { Events.fire('notify-user', 'File content is incorrect.'); - console.log("File content incorrect.") + console.log("File content incorrect."); }).finally(_ => { this.hide(); }); } else { // ?base64zip=paste || ?base64zip=true - this.$pasteBtn.innerText = 'Tap here to paste files'; - this.$pasteBtn.addEventListener('click', _ => this.processClipboard('file')); + this.preparePasting('files'); } } } @@ -1299,39 +1298,53 @@ class Base64ZipDialog extends Dialog { this.$pasteBtn.innerText = "Processing..."; } - async processClipboard(type) { - if (!navigator.clipboard.readText) { - Events.fire('notify-user', 'This feature is not available on your browser.'); - console.log("navigator.clipboard.readText() is not available on your browser.") - this.hide(); - return; - } - - this._setPasteBtnToProcessing(); - - const base64 = await navigator.clipboard.readText(); - - if (!base64) return; - - if (type === "text") { - this.processBase64Text(base64) - .catch(_ => { - Events.fire('notify-user', 'Clipboard content is incorrect.'); - console.log("Clipboard content is incorrect.") - }).finally(_ => { - this.hide(); - }); + preparePasting(type) { + if (navigator.clipboard.readText) { + this.$pasteBtn.innerText = `Tap here to paste ${type}`; + this.$pasteBtn.addEventListener('click', _ => this.processClipboard(type), { once: true }); } else { - this.processBase64Zip(base64) - .catch(_ => { - Events.fire('notify-user', 'Clipboard content is incorrect.'); - console.log("Clipboard content is incorrect.") - }).finally(_ => { - this.hide(); - }); + console.log("`navigator.clipboard.readText()` is not available on your browser.\nOn Firefox you can set `dom.events.asyncClipboard.readText` to true under `about:config` for convenience.") + this.$pasteBtn.setAttribute('hidden', ''); + this.$fallbackTextarea.setAttribute('placeholder', `Paste here to send ${type}`); + this.$fallbackTextarea.removeAttribute('hidden'); + this.$fallbackTextarea.addEventListener('input', _ => this.processInput(type), { once: true }); + this.$fallbackTextarea.focus(); } } + async processInput(type) { + const base64 = this.$fallbackTextarea.textContent; + this.$fallbackTextarea.textContent = ''; + try { + // check if input is base64 encoded + window.atob(base64); + await this.processBase64(type, base64); + } catch (e) { + // input is not base64 string. Do nothing. + } + } + + async processClipboard(type) { + this._setPasteBtnToProcessing(); + const base64 = await navigator.clipboard.readText(); + await this.processBase64(type, base64); + } + + async processBase64(type, base64) { + if (!base64) return; + try { + if (type === "text") { + await this.processBase64Text(base64); + } else { + await this.processBase64Zip(base64); + } + } catch(_) { + Events.fire('notify-user', 'Clipboard content is incorrect.'); + console.log("Clipboard content is incorrect.") + } + this.hide(); + } + processBase64Text(base64Text){ return new Promise((resolve) => { this._setPasteBtnToProcessing(); diff --git a/public/styles.css b/public/styles.css index 237764e..db87050 100644 --- a/public/styles.css +++ b/public/styles.css @@ -791,10 +791,29 @@ x-dialog .dialog-subheader { margin: auto -24px; } -#base64-paste-btn { +#base64-paste-btn, +#base64-paste-dialog .textarea { width: 100%; height: 40vh; border: solid 12px #438cff; + text-align: center; +} + +#base64-paste-dialog .textarea { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; +} + +#base64-paste-dialog .textarea::before { + font-size: 15px; + letter-spacing: 0.12em; + color: var(--primary-color); + font-weight: 700; + text-transform: uppercase; + content: attr(placeholder); } #base64-paste-dialog button { diff --git a/public_included_ws_fallback/index.html b/public_included_ws_fallback/index.html index a2a4b4d..549e16e 100644 --- a/public_included_ws_fallback/index.html +++ b/public_included_ws_fallback/index.html @@ -241,6 +241,7 @@ + diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index 896608e..b1f666b 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -1246,21 +1246,21 @@ class Base64ZipDialog extends Dialog { const base64Hash = window.location.hash.substring(1); this.$pasteBtn = this.$el.querySelector('#base64-paste-btn'); + this.$fallbackTextarea = this.$el.querySelector('.textarea'); if (base64Text) { this.show(); if (base64Text === "paste") { // ?base64text=paste // base64 encoded string is ready to be pasted from clipboard - this.$pasteBtn.innerText = 'Tap here to paste text'; - this.$pasteBtn.addEventListener('click', _ => this.processClipboard('text')); + this.preparePasting("text"); } else if (base64Text === "hash") { // ?base64text=hash#BASE64ENCODED // base64 encoded string is url hash which is never sent to server and faster (recommended) this.processBase64Text(base64Hash) .catch(_ => { Events.fire('notify-user', 'Text content is incorrect.'); - console.log("Text content incorrect.") + console.log("Text content incorrect."); }).finally(_ => { this.hide(); }); @@ -1270,7 +1270,7 @@ class Base64ZipDialog extends Dialog { this.processBase64Text(base64Text) .catch(_ => { Events.fire('notify-user', 'Text content is incorrect.'); - console.log("Text content incorrect.") + console.log("Text content incorrect."); }).finally(_ => { this.hide(); }); @@ -1283,14 +1283,13 @@ class Base64ZipDialog extends Dialog { this.processBase64Zip(base64Hash) .catch(_ => { Events.fire('notify-user', 'File content is incorrect.'); - console.log("File content incorrect.") + console.log("File content incorrect."); }).finally(_ => { this.hide(); }); } else { // ?base64zip=paste || ?base64zip=true - this.$pasteBtn.innerText = 'Tap here to paste files'; - this.$pasteBtn.addEventListener('click', _ => this.processClipboard('file')); + this.preparePasting('files'); } } } @@ -1300,39 +1299,53 @@ class Base64ZipDialog extends Dialog { this.$pasteBtn.innerText = "Processing..."; } - async processClipboard(type) { - if (!navigator.clipboard.readText) { - Events.fire('notify-user', 'This feature is not available on your browser.'); - console.log("navigator.clipboard.readText() is not available on your browser.") - this.hide(); - return; - } - - this._setPasteBtnToProcessing(); - - const base64 = await navigator.clipboard.readText(); - - if (!base64) return; - - if (type === "text") { - this.processBase64Text(base64) - .catch(_ => { - Events.fire('notify-user', 'Clipboard content is incorrect.'); - console.log("Clipboard content is incorrect.") - }).finally(_ => { - this.hide(); - }); + preparePasting(type) { + if (navigator.clipboard.readText) { + this.$pasteBtn.innerText = `Tap here to paste ${type}`; + this.$pasteBtn.addEventListener('click', _ => this.processClipboard(type), { once: true }); } else { - this.processBase64Zip(base64) - .catch(_ => { - Events.fire('notify-user', 'Clipboard content is incorrect.'); - console.log("Clipboard content is incorrect.") - }).finally(_ => { - this.hide(); - }); + console.log("`navigator.clipboard.readText()` is not available on your browser.\nOn Firefox you can set `dom.events.asyncClipboard.readText` to true under `about:config` for convenience.") + this.$pasteBtn.setAttribute('hidden', ''); + this.$fallbackTextarea.setAttribute('placeholder', `Paste here to send ${type}`); + this.$fallbackTextarea.removeAttribute('hidden'); + this.$fallbackTextarea.addEventListener('input', _ => this.processInput(type), { once: true }); + this.$fallbackTextarea.focus(); } } + async processInput(type) { + const base64 = this.$fallbackTextarea.textContent; + this.$fallbackTextarea.textContent = ''; + try { + // check if input is base64 encoded + window.atob(base64); + await this.processBase64(type, base64); + } catch (e) { + // input is not base64 string. Do nothing. + } + } + + async processClipboard(type) { + this._setPasteBtnToProcessing(); + const base64 = await navigator.clipboard.readText(); + await this.processBase64(type, base64); + } + + async processBase64(type, base64) { + if (!base64) return; + try { + if (type === "text") { + await this.processBase64Text(base64); + } else { + await this.processBase64Zip(base64); + } + } catch(_) { + Events.fire('notify-user', 'Clipboard content is incorrect.'); + console.log("Clipboard content is incorrect.") + } + this.hide(); + } + processBase64Text(base64Text){ return new Promise((resolve) => { this._setPasteBtnToProcessing(); diff --git a/public_included_ws_fallback/styles.css b/public_included_ws_fallback/styles.css index 0f8742d..2d29898 100644 --- a/public_included_ws_fallback/styles.css +++ b/public_included_ws_fallback/styles.css @@ -817,10 +817,29 @@ x-dialog .dialog-subheader { margin: auto -24px; } -#base64-paste-btn { +#base64-paste-btn, +#base64-paste-dialog .textarea { width: 100%; height: 40vh; border: solid 12px #438cff; + text-align: center; +} + +#base64-paste-dialog .textarea { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; +} + +#base64-paste-dialog .textarea::before { + font-size: 15px; + letter-spacing: 0.12em; + color: var(--primary-color); + font-weight: 700; + text-transform: uppercase; + content: attr(placeholder); } #base64-paste-dialog button { From 36e152dc7c1d4a112c0f11c89336291963055b2a Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 6 Mar 2023 11:59:56 +0100 Subject: [PATCH 2/3] add { once: true } to deactivate-paste-mode event listener --- public/scripts/network.js | 8 ++++---- public/scripts/ui.js | 2 +- public_included_ws_fallback/scripts/network.js | 8 ++++---- public_included_ws_fallback/scripts/ui.js | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/public/scripts/network.js b/public/scripts/network.js index 55fdc89..01eb27d 100644 --- a/public/scripts/network.js +++ b/public/scripts/network.js @@ -893,11 +893,11 @@ class Events { window.dispatchEvent(new CustomEvent(type, { detail: detail })); } - static on(type, callback) { - return window.addEventListener(type, callback, false); + static on(type, callback, options = false) { + return window.addEventListener(type, callback, options); } - static off(type, callback) { - return window.removeEventListener(type, callback, false); + static off(type, callback, options = false) { + return window.removeEventListener(type, callback, options); } } diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 62c1f83..340b22b 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -241,7 +241,7 @@ class PeersUI { const _callback = (e) => this._sendClipboardData(e, files, text); Events.on('paste-pointerdown', _callback); - Events.on('deactivate-paste-mode', _ => this._deactivatePasteMode(_callback)); + Events.on('deactivate-paste-mode', _ => this._deactivatePasteMode(_callback), { once: true }); this.$cancelPasteModeBtn.removeAttribute('hidden'); diff --git a/public_included_ws_fallback/scripts/network.js b/public_included_ws_fallback/scripts/network.js index 4c5b255..b805654 100644 --- a/public_included_ws_fallback/scripts/network.js +++ b/public_included_ws_fallback/scripts/network.js @@ -974,11 +974,11 @@ class Events { window.dispatchEvent(new CustomEvent(type, { detail: detail })); } - static on(type, callback) { - return window.addEventListener(type, callback, false); + static on(type, callback, options = false) { + return window.addEventListener(type, callback, options); } - static off(type, callback) { - return window.removeEventListener(type, callback, false); + static off(type, callback, options = false) { + return window.removeEventListener(type, callback, options); } } diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index b1f666b..0227167 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -241,7 +241,7 @@ class PeersUI { const _callback = (e) => this._sendClipboardData(e, files, text); Events.on('paste-pointerdown', _callback); - Events.on('deactivate-paste-mode', _ => this._deactivatePasteMode(_callback)); + Events.on('deactivate-paste-mode', _ => this._deactivatePasteMode(_callback), { once: true }); this.$cancelPasteModeBtn.removeAttribute('hidden'); From c0d504f6a800b6ce76c080f57682621f2f46eeba Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 6 Mar 2023 12:20:30 +0100 Subject: [PATCH 3/3] remove base64 event listeners manually on hide instead of once: true --- public/scripts/ui.js | 31 +++++++++++++++-------- public_included_ws_fallback/scripts/ui.js | 29 +++++++++++++-------- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 340b22b..580b73c 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -1301,13 +1301,15 @@ class Base64ZipDialog extends Dialog { preparePasting(type) { if (navigator.clipboard.readText) { this.$pasteBtn.innerText = `Tap here to paste ${type}`; - this.$pasteBtn.addEventListener('click', _ => this.processClipboard(type), { once: true }); + this._clickCallback = _ => this.processClipboard(type); + this.$pasteBtn.addEventListener('click', _ => this._clickCallback()); } else { console.log("`navigator.clipboard.readText()` is not available on your browser.\nOn Firefox you can set `dom.events.asyncClipboard.readText` to true under `about:config` for convenience.") this.$pasteBtn.setAttribute('hidden', ''); this.$fallbackTextarea.setAttribute('placeholder', `Paste here to send ${type}`); this.$fallbackTextarea.removeAttribute('hidden'); - this.$fallbackTextarea.addEventListener('input', _ => this.processInput(type), { once: true }); + this._inputCallback = _ => this.processInput(type); + this.$fallbackTextarea.addEventListener('input', _ => this._inputCallback()); this.$fallbackTextarea.focus(); } } @@ -1315,23 +1317,28 @@ class Base64ZipDialog extends Dialog { async processInput(type) { const base64 = this.$fallbackTextarea.textContent; this.$fallbackTextarea.textContent = ''; - try { - // check if input is base64 encoded - window.atob(base64); - await this.processBase64(type, base64); - } catch (e) { - // input is not base64 string. Do nothing. - } + await this.processBase64(type, base64); } async processClipboard(type) { - this._setPasteBtnToProcessing(); const base64 = await navigator.clipboard.readText(); await this.processBase64(type, base64); } + isValidBase64(base64) { + try { + // check if input is base64 encoded + window.atob(base64); + return true; + } catch (e) { + // input is not base64 string. + return false; + } + } + async processBase64(type, base64) { - if (!base64) return; + if (!base64 || !this.isValidBase64(base64)) return; + this._setPasteBtnToProcessing(); try { if (type === "text") { await this.processBase64Text(base64); @@ -1378,6 +1385,8 @@ class Base64ZipDialog extends Dialog { hide() { this.clearBrowserHistory(); + this.$pasteBtn.removeEventListener('click', _ => this._clickCallback()); + this.$fallbackTextarea.removeEventListener('input', _ => this._inputCallback()); super.hide(); } } diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index 0227167..e45143e 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -1302,13 +1302,15 @@ class Base64ZipDialog extends Dialog { preparePasting(type) { if (navigator.clipboard.readText) { this.$pasteBtn.innerText = `Tap here to paste ${type}`; - this.$pasteBtn.addEventListener('click', _ => this.processClipboard(type), { once: true }); + this._clickCallback = _ => this.processClipboard(type); + this.$pasteBtn.addEventListener('click', _ => this._clickCallback()); } else { console.log("`navigator.clipboard.readText()` is not available on your browser.\nOn Firefox you can set `dom.events.asyncClipboard.readText` to true under `about:config` for convenience.") this.$pasteBtn.setAttribute('hidden', ''); this.$fallbackTextarea.setAttribute('placeholder', `Paste here to send ${type}`); this.$fallbackTextarea.removeAttribute('hidden'); - this.$fallbackTextarea.addEventListener('input', _ => this.processInput(type), { once: true }); + this._inputCallback = _ => this.processInput(type); + this.$fallbackTextarea.addEventListener('input', _ => this._inputCallback()); this.$fallbackTextarea.focus(); } } @@ -1316,23 +1318,28 @@ class Base64ZipDialog extends Dialog { async processInput(type) { const base64 = this.$fallbackTextarea.textContent; this.$fallbackTextarea.textContent = ''; - try { - // check if input is base64 encoded - window.atob(base64); - await this.processBase64(type, base64); - } catch (e) { - // input is not base64 string. Do nothing. - } + await this.processBase64(type, base64); } async processClipboard(type) { - this._setPasteBtnToProcessing(); const base64 = await navigator.clipboard.readText(); await this.processBase64(type, base64); } + isValidBase64(base64) { + try { + // check if input is base64 encoded + window.atob(base64); + return true; + } catch (e) { + // input is not base64 string. + return false; + } + } + async processBase64(type, base64) { - if (!base64) return; + if (!base64 || !this.isValidBase64(base64)) return; + this._setPasteBtnToProcessing(); try { if (type === "text") { await this.processBase64Text(base64);