From 44c0f3dbaadb9f4925fa4d410057bd772e7e06cb Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 11 Nov 2024 15:37:04 +0100 Subject: [PATCH 01/45] Check for BroadcastChannel availability before instantiating it --- public/scripts/browser-tabs-connector.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/scripts/browser-tabs-connector.js b/public/scripts/browser-tabs-connector.js index da9c43e..2ce99af 100644 --- a/public/scripts/browser-tabs-connector.js +++ b/public/scripts/browser-tabs-connector.js @@ -1,5 +1,7 @@ class BrowserTabsConnector { constructor() { + if (!('BroadcastChannel' in window)) return; + this.bc = new BroadcastChannel('pairdrop'); this.bc.addEventListener('message', e => this._onMessage(e)); Events.on('broadcast-send', e => this._broadcastSend(e.detail)); From c52eeda3ffbfcfa5c0e1a0c6601f7522944cfefe Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 12 Feb 2025 00:35:24 +0100 Subject: [PATCH 02/45] Add BrowserStack to README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9fd70f1..3400704 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ Connect to others in complex network situations, or over the Internet. * [NoSleep](https://github.com/richtr/NoSleep.js) display sleep, add wake lock ([MIT](licenses/MIT-NoSleep)) * [heic2any](https://github.com/alexcorvi/heic2any) HEIC/HEIF to PNG/GIF/JPEG ([MIT](licenses/MIT-heic2any)) * [Weblate](https://weblate.org/) web-based localization tool +* [BrowserStack](https://www.browserstack.com/) This project is tested with BrowserStack [FAQ](docs/faq.md) From 940da7948cb9957f0614fe62e4ea39939ebf8314 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 13 Feb 2025 10:24:27 +0100 Subject: [PATCH 03/45] Stop usage of public class fields in order to support Safari 13.1 --- public/scripts/ui.js | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 873e612..93d4ae2 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -18,11 +18,12 @@ class PeersUI { this.peers = {}; - this.shareMode = {}; - this.shareMode.active = false; - this.shareMode.descriptor = ""; - this.shareMode.files = []; - this.shareMode.text = ""; + this.shareMode = { + active: false, + descriptor: "", + files: [], + text: "" + } Events.on('peer-joined', e => this._onPeerJoined(e.detail)); Events.on('peer-added', _ => this._evaluateOverflowingPeers()); @@ -394,12 +395,6 @@ class PeersUI { class PeerUI { - static _badgeClassNames = ["badge-room-ip", "badge-room-secret", "badge-room-public-id"]; - static _shareMode = { - active: false, - descriptor: "" - }; - constructor(peer, connectionHash, shareMode) { this.$xInstructions = $$('x-instructions'); this.$xPeers = $$('x-peers'); @@ -409,7 +404,7 @@ class PeerUI { `${connectionHash.substring(0, 4)} ${connectionHash.substring(4, 8)} ${connectionHash.substring(8, 12)} ${connectionHash.substring(12, 16)}`; // This is needed if the ShareMode is started BEFORE the PeerUI is drawn. - PeerUI._shareMode = shareMode; + this._shareMode = shareMode; this._initDom(); @@ -421,8 +416,8 @@ class PeerUI { } html() { - let title= PeerUI._shareMode.active - ? Localization.getTranslation("peer-ui.click-to-send-share-mode", null, {descriptor: PeerUI._shareMode.descriptor}) + let title= this._shareMode.active + ? Localization.getTranslation("peer-ui.click-to-send-share-mode", null, {descriptor: this._shareMode.descriptor}) : Localization.getTranslation("peer-ui.click-to-send"); this.$el.innerHTML = ` @@ -485,8 +480,8 @@ class PeerUI { _onShareModeChanged(active = false, descriptor = "") { // This is needed if the ShareMode is started AFTER the PeerUI is drawn. - PeerUI._shareMode.active = active; - PeerUI._shareMode.descriptor = descriptor; + this._shareMode.active = active; + this._shareMode.descriptor = descriptor; this._evaluateShareMode(); this._bindListeners(); @@ -494,12 +489,12 @@ class PeerUI { _evaluateShareMode() { let title; - if (!PeerUI._shareMode.active) { + if (!this._shareMode.active) { title = Localization.getTranslation("peer-ui.click-to-send"); this.$input.removeAttribute('disabled'); } else { - title = Localization.getTranslation("peer-ui.click-to-send-share-mode", null, {descriptor: PeerUI._shareMode.descriptor}); + title = Localization.getTranslation("peer-ui.click-to-send-share-mode", null, {descriptor: this._shareMode.descriptor}); this.$input.setAttribute('disabled', true); } this.$label.setAttribute('title', title); @@ -520,7 +515,7 @@ class PeerUI { } _bindListeners() { - if(!PeerUI._shareMode.active) { + if(!this._shareMode.active) { // Remove Events Share mode this.$el.removeEventListener('pointerdown', this._callbackPointerDown); @@ -636,7 +631,7 @@ class PeerUI { } _onDrop(e) { - if (PeerUI._shareMode.active || Dialog.anyDialogShown()) return; + if (this._shareMode.active || Dialog.anyDialogShown()) return; e.preventDefault(); @@ -1974,7 +1969,7 @@ class SendTextDialog extends Dialog { _onRecipient(peerId, deviceName) { this.correspondingPeerId = peerId; this.$peerDisplayName.innerText = deviceName; - this.$peerDisplayName.classList.remove(...PeerUI._badgeClassNames); + this.$peerDisplayName.classList.remove("badge-room-ip", "badge-room-secret", "badge-room-public-id"); this.$peerDisplayName.classList.add($(peerId).ui._badgeClassName()); this.show(); @@ -2056,7 +2051,7 @@ class ReceiveTextDialog extends Dialog { _showReceiveTextDialog(text, peerId) { this.$displayName.innerText = $(peerId).ui._displayName(); - this.$displayName.classList.remove(...PeerUI._badgeClassNames); + this.$displayName.classList.remove("badge-room-ip", "badge-room-secret", "badge-room-public-id"); this.$displayName.classList.add($(peerId).ui._badgeClassName()); this.$text.innerText = text; From f7fe303fa768f59d04906c17d76fa0b6ffb8cfe4 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 13 Feb 2025 11:39:10 +0100 Subject: [PATCH 04/45] Fix background css for clients iOS < 13.1 --- public/styles/styles-main.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/styles/styles-main.css b/public/styles/styles-main.css index 9401d45..cff0d1d 100644 --- a/public/styles/styles-main.css +++ b/public/styles/styles-main.css @@ -696,7 +696,6 @@ button::-moz-focus-inner { /* Info Animation */ - #about { color: white; z-index: 32; @@ -752,9 +751,11 @@ button::-moz-focus-inner { height: var(--size); z-index: -1; background: var(--primary-color); - background-image: radial-gradient(circle at calc(50% - 36px), var(--accent-color) 0%, color-mix(in srgb, var(--accent-color) 40%, black) 80%); + background-image: radial-gradient(circle at calc(50% - 36px), var(--primary-color) 0%, black 80%); --crop-size: 0px; clip-path: circle(var(--crop-size)); + /* For clients < iOS 13.1 */ + -webkit-clip-path: circle(var(--crop-size)); } html:not([dir="rtl"]) #about x-background { From ec520248b82a74a17f25a832b45f1e32fccb8ec3 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 13 Feb 2025 12:53:04 +0100 Subject: [PATCH 05/45] Remove duplicate instruction --- public/scripts/ui.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 93d4ae2..e1887cb 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -278,8 +278,6 @@ class PeersUI { descriptorInstructions = Localization.getTranslation("instructions.activate-share-mode-shared-file"); } - files = await mime.addMissingMimeTypesToFiles(files); - if (files[0].type.split('/')[0] === 'image') { try { let imageUrl = await getThumbnailAsDataUrl(files[0], 80, null, 0.9); From 351e7d42c9eece9ceaffcb64114546527725fa8a Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 13 Feb 2025 12:52:33 +0100 Subject: [PATCH 06/45] Fix share mode by converting FileList to Array --- public/scripts/network.js | 2 +- public/scripts/ui.js | 16 +++++++++++----- public/scripts/util.js | 10 +++++----- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/public/scripts/network.js b/public/scripts/network.js index 9128442..c92befc 100644 --- a/public/scripts/network.js +++ b/public/scripts/network.js @@ -1082,7 +1082,7 @@ class PeersManager { } async _onFilesSelected(message) { - let files = await mime.addMissingMimeTypesToFiles(message.files); + let files = mime.addMissingMimeTypesToFiles(message.files); await this.peers[message.to].requestFileTransfer(files); } diff --git a/public/scripts/ui.js b/public/scripts/ui.js index e1887cb..c988db7 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -179,10 +179,13 @@ class PeersUI { this._onDragEnd(); - if ($$('x-peer') || !$$('x-peer').contains(e.target)) return; // dropped on peer + if ($$('x-peer') && $$('x-peer').contains(e.target)) return; // dropped on peer - const files = e.dataTransfer.files; - const text = e.dataTransfer.getData("text"); + let files = e.dataTransfer.files; + let text = e.dataTransfer.getData("text"); + + // convert FileList to Array + files = [...files]; if (files.length > 0) { Events.fire('activate-share-mode', { @@ -215,8 +218,11 @@ class PeersUI { if (this.shareMode.active || Dialog.anyDialogShown()) return; e.preventDefault() - const files = e.clipboardData.files; - const text = e.clipboardData.getData("Text"); + let files = e.clipboardData.files; + let text = e.clipboardData.getData("Text"); + + // convert FileList to Array + files = [...files]; if (files.length > 0) { Events.fire('activate-share-mode', {files: files}); diff --git a/public/scripts/util.js b/public/scripts/util.js index 7d6c54a..08a790f 100644 --- a/public/scripts/util.js +++ b/public/scripts/util.js @@ -396,20 +396,20 @@ const mime = (() => { } return { - async guessMimeByFilename(filename) { + guessMimeByFilename(filename) { const split = filename.split('.'); if (split.length === 1) { // Filename does not include suffix - return ""; + return false; } const suffix = split[split.length - 1].toLowerCase(); - return suffixToMimeMap[suffix] || ""; + return suffixToMimeMap[suffix]; }, - async addMissingMimeTypesToFiles(files) { + addMissingMimeTypesToFiles(files) { // if filetype is empty guess via suffix otherwise leave unchanged for (let i = 0; i < files.length; i++) { if (!files[i].type) { - files[i] = new File([files[i]], files[i].name, {type: await mime.guessMimeByFilename(files[i].name) || ""}); + files[i] = new File([files[i]], files[i].name, {type: mime.guessMimeByFilename(files[i].name) || ""}); } } return files; From efc360e1063ae7dde1424b5934464f9a4f91dd34 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 13 Feb 2025 12:53:47 +0100 Subject: [PATCH 07/45] Remove text selection prevention to enable pasting for Firefox users --- public/styles/styles-main.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/public/styles/styles-main.css b/public/styles/styles-main.css index cff0d1d..5cff395 100644 --- a/public/styles/styles-main.css +++ b/public/styles/styles-main.css @@ -11,10 +11,6 @@ body { overflow-x: hidden; overscroll-behavior: none; overflow-y: hidden; - /* Only allow selection on message and pair key */ - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; transition: color 300ms; } From 629328c2f614b4077bdc58d5839ccdc6979618a9 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 13 Feb 2025 20:07:25 +0100 Subject: [PATCH 08/45] Use css instead of JS to detect offset on high viewports --- public/scripts/ui-main.js | 1 - public/styles/styles-main.css | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/public/scripts/ui-main.js b/public/scripts/ui-main.js index bbdece4..3289ee7 100644 --- a/public/scripts/ui-main.js +++ b/public/scripts/ui-main.js @@ -357,7 +357,6 @@ class BackgroundCanvas { this.w = document.documentElement.clientWidth; this.h = document.documentElement.clientHeight; this.offset = this.$footer.offsetHeight - 27; - if (this.h >= 800) this.offset += 10; if (oldW === this.w && oldH === this.h && oldOffset === this.offset) return; // nothing has changed diff --git a/public/styles/styles-main.css b/public/styles/styles-main.css index 5cff395..c36634a 100644 --- a/public/styles/styles-main.css +++ b/public/styles/styles-main.css @@ -899,7 +899,7 @@ x-peers:empty~x-instructions { @media screen and (min-height: 800px) { footer { - margin-bottom: 16px; + padding-bottom: 10px; } } From 8a833cd69dae86519d60b11ee5e094f232772c31 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 13 Feb 2025 20:07:38 +0100 Subject: [PATCH 09/45] Make PWA standalone --- public/manifest.json | 2 +- public/scripts/main.js | 2 +- public/styles/styles-main.css | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/public/manifest.json b/public/manifest.json index 5198d81..edcf69a 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -26,7 +26,7 @@ } ], "background_color": "#efefef", - "display": "minimal-ui", + "display": "standalone", "theme_color": "#3367d6", "screenshots" : [ { diff --git a/public/scripts/main.js b/public/scripts/main.js index 8960ed2..9929627 100644 --- a/public/scripts/main.js +++ b/public/scripts/main.js @@ -81,7 +81,7 @@ class PairDrop { } onPwaInstallable(e) { - if (!window.matchMedia('(display-mode: minimal-ui)').matches) { + if (!window.matchMedia('(display-mode: standalone)').matches) { // only display install btn when not installed this.$headerInstallBtn.removeAttribute('hidden'); this.$headerInstallBtn.addEventListener('click', () => { diff --git a/public/styles/styles-main.css b/public/styles/styles-main.css index c36634a..bbf3ddf 100644 --- a/public/styles/styles-main.css +++ b/public/styles/styles-main.css @@ -909,6 +909,13 @@ x-peers:empty~x-instructions { } } +/* PWA Standalone styles */ +@media all and (display-mode: standalone) { + footer { + padding-bottom: 34px; + } +} + /* Constants */ :root { From 82b329fea84416ab4c6d0208e6a5b758698d9828 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Fri, 14 Feb 2025 15:36:35 +0100 Subject: [PATCH 10/45] Fix padding of auto-accept instruction --- public/index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/index.html b/public/index.html index 5dec40a..bd391e7 100644 --- a/public/index.html +++ b/public/index.html @@ -416,12 +416,12 @@

-
-

+

+
-

+
From dabfe581248edbc49ff0abd0fdad9c28d8e658bd Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Fri, 14 Feb 2025 15:47:17 +0100 Subject: [PATCH 11/45] Fix word-break of base64zip button on mobile --- public/styles/styles-main.css | 1 - 1 file changed, 1 deletion(-) diff --git a/public/styles/styles-main.css b/public/styles/styles-main.css index 5cff395..8be401e 100644 --- a/public/styles/styles-main.css +++ b/public/styles/styles-main.css @@ -587,7 +587,6 @@ x-dialog:not([show]) x-background { font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; - white-space: nowrap; cursor: pointer; user-select: none; background: inherit; From 02c95dde9d411a585c5473991cbb1ce7fc1eeef6 Mon Sep 17 00:00:00 2001 From: Chris Lovett Date: Fri, 14 Feb 2025 10:17:10 -0500 Subject: [PATCH 12/45] Updated Twitter (X) icon and URL --- public/index.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/index.html b/public/index.html index 5dec40a..748e275 100644 --- a/public/index.html +++ b/public/index.html @@ -681,9 +681,9 @@ - + - + ${url}`; - return `${whitespaceOrSpecial}${linkNodePlaceholder}`; - } + if (!isUrlValid(link)) { // link is not valid -> do not replace return match; - }); + } + + // link is valid -> replace with link node placeholder + // find linkNodePlaceholder that is not yet present in text node + m++; + while (occP.includes(`${p}${m}`)) { + m++; + } + let linkNodePlaceholder = `${p}${m}`; + + // add linkNodePlaceholder to text node and save a reference to linkNodes object + linkNodes[linkNodePlaceholder] = `${url}`; + return `${whitespace}${linkNodePlaceholder}`; + } + + text = text.replace(rgxUrlAll, replaceMatchWithPlaceholder); + $textShadow.innerText = text.replace(rgxMailAll, replaceMatchWithPlaceholder); this.$text.innerHTML = $textShadow.innerHTML.replace(pRgx, diff --git a/public/scripts/util.js b/public/scripts/util.js index 08a790f..1dcc388 100644 --- a/public/scripts/util.js +++ b/public/scripts/util.js @@ -590,7 +590,7 @@ async function decodeBase64Text(base64) { function isUrlValid(url) { try { - let urlObj = new URL(url); + new URL(url); return true; } catch (e) { From 8a3c60d3a677d5dcd9b28d0b91f4de61be29db11 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Fri, 14 Feb 2025 20:54:15 +0100 Subject: [PATCH 15/45] Bring back optimized background animation --- public/scripts/ui-main.js | 183 +++++++++++++++++++++++++++----------- public/scripts/ui.js | 10 +++ 2 files changed, 140 insertions(+), 53 deletions(-) diff --git a/public/scripts/ui-main.js b/public/scripts/ui-main.js index bbdece4..682dbe3 100644 --- a/public/scripts/ui-main.js +++ b/public/scripts/ui-main.js @@ -333,65 +333,142 @@ class FooterUI { class BackgroundCanvas { constructor() { - this.c = $$('canvas'); - this.cCtx = this.c.getContext('2d'); - this.$footer = $$('footer'); + this.canvas = $$('canvas'); + this.initAnimation(); + } + + initAnimation() { + let c = this.canvas; + let cCtx = c.getContext('2d'); + let $footer = $$('footer'); + + let x0, y0, w, h, dw, offset, baseColor, baseOpacity; + + let offscreenCanvases; + let shareMode = false; + + let animate = true; + let currentFrame = 0; + + let fpsInterval, now, then, elapsed; + + let speed = 1.5; + + function init() { + let oldW = w; + let oldH = h; + let oldOffset = offset + w = document.documentElement.clientWidth; + h = document.documentElement.clientHeight; + offset = $footer.offsetHeight - 33; + if (h > 800) offset += 16; + + if (oldW === w && oldH === h && oldOffset === offset) return; // nothing has changed + + c.width = w; + c.height = h; + x0 = w / 2; + y0 = h - offset; + dw = Math.round(Math.max(w, h, 1000) / 12); + + drawCircles(cCtx, 0); + + // enforce redrawing of frames + offscreenCanvases = {true: [], false: []}; + } + + function drawCircle(ctx, radius) { + ctx.lineWidth = 2; + + baseColor = shareMode ? '168 168 255' : '168 168 168'; + baseOpacity = shareMode ? 0.8 : 0.4; + + let opacity = baseOpacity * radius / (dw * 8); + if (radius > dw * 5) { + opacity *= (6 * dw - radius) / dw + } + ctx.strokeStyle = `rgb(${baseColor} / ${opacity})`; + ctx.beginPath(); + ctx.arc(x0, y0, radius, 0, 2 * Math.PI); + ctx.stroke(); + } + + function drawCircles(ctx, frame) { + for (let i = 6; i >= 0; i--) { + drawCircle(ctx, dw * i + speed * frame + 33); + } + } + + function createOffscreenCanvas(frame) { + let canvas = document.createElement("canvas"); + canvas.width = c.width; + canvas.height = c.height; + offscreenCanvases[shareMode][frame] = canvas; + let ctx = canvas.getContext('2d'); + drawCircles(ctx, frame); + } + + function drawFrame(frame) { + cCtx.clearRect(0, 0, w, h); + + if (!offscreenCanvases[shareMode][frame]) { + createOffscreenCanvas(frame); + } + cCtx.drawImage(offscreenCanvases[shareMode][frame], 0, 0); + } + + function startAnimating(fps) { + fpsInterval = 1000 / fps; + then = Date.now(); + animateBg(); + } + + function animateBg() { + requestAnimationFrame(animateBg); + + now = Date.now(); + elapsed = now - then; + // if not enough time has elapsed, do not draw the next frame -> abort + if (elapsed < fpsInterval) { + return; + } + + then = now - (elapsed % fpsInterval); + + if (animate) { + currentFrame = (currentFrame + 1) % (dw/speed); + drawFrame(currentFrame); + } + } + + function switchAnimation(state) { + animate = state; + console.debug(state) + } + + function redrawOnShareModeChange(active) { + shareMode = active + } + + init(); + startAnimating(30) // redraw canvas - Events.on('resize', _ => this.init()); - Events.on('redraw-canvas', _ => this.init()); - Events.on('translation-loaded', _ => this.init()); + Events.on('resize', _ => init()); + Events.on('redraw-canvas', _ => init()); + Events.on('translation-loaded', _ => init()); // ShareMode - Events.on('share-mode-changed', e => this.onShareModeChanged(e.detail.active)); + Events.on('share-mode-changed', e => redrawOnShareModeChange(e.detail.active)); + + // Start and stop animation + Events.on('background-animation', e => switchAnimation(e.detail.animate)) + + Events.on('offline', _ => switchAnimation(false)); + Events.on('online', _ => switchAnimation(true)); } async fadeIn() { - this.c.classList.remove('opacity-0'); - } - - init() { - let oldW = this.w; - let oldH = this.h; - let oldOffset = this.offset - this.w = document.documentElement.clientWidth; - this.h = document.documentElement.clientHeight; - this.offset = this.$footer.offsetHeight - 27; - if (this.h >= 800) this.offset += 10; - - if (oldW === this.w && oldH === this.h && oldOffset === this.offset) return; // nothing has changed - - this.c.width = this.w; - this.c.height = this.h; - this.x0 = this.w / 2; - this.y0 = this.h - this.offset; - this.dw = Math.round(Math.max(this.w, this.h, 1000) / 13); - this.baseColor = '165, 165, 165'; - this.baseOpacity = 0.3; - - this.drawCircles(this.cCtx); - } - - onShareModeChanged(active) { - this.baseColor = active ? '165, 165, 255' : '165, 165, 165'; - this.baseOpacity = active ? 0.5 : 0.3; - this.drawCircles(this.cCtx); - } - - - drawCircle(ctx, radius) { - ctx.beginPath(); - ctx.lineWidth = 2; - let opacity = Math.max(0, this.baseOpacity * (1 - 1.2 * radius / Math.max(this.w, this.h))); - ctx.strokeStyle = `rgba(${this.baseColor}, ${opacity})`; - ctx.arc(this.x0, this.y0, radius, 0, 2 * Math.PI); - ctx.stroke(); - } - - drawCircles(ctx) { - ctx.clearRect(0, 0, this.w, this.h); - for (let i = 0; i < 13; i++) { - this.drawCircle(ctx, this.dw * i + 33 + 66); - } + this.canvas.classList.remove('opacity-0'); } } \ No newline at end of file diff --git a/public/scripts/ui.js b/public/scripts/ui.js index c988db7..835eaae 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -150,10 +150,17 @@ class PeersUI { } _onPeerDisconnected(peerId) { + // Remove peer from UI const $peer = $(peerId); if (!$peer) return; $peer.remove(); this._evaluateOverflowingPeers(); + + // If no peer is shown -> start background animation again + if ($$('x-peers:empty')) { + Events.fire('background-animation', {animate: true}); + } + } _onRoomTypeRemoved(peerId, roomType) { @@ -417,6 +424,9 @@ class PeerUI { // ShareMode Events.on('share-mode-changed', e => this._onShareModeChanged(e.detail.active, e.detail.descriptor)); + + // Stop background animation + Events.fire('background-animation', {animate: false}); } html() { From f0e72506175c43939bee38887a0acf794e2fffc3 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Sun, 16 Feb 2025 00:52:34 +0100 Subject: [PATCH 16/45] Use time based approach to smoothen reduced framerate --- public/scripts/ui-main.js | 91 ++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 34 deletions(-) diff --git a/public/scripts/ui-main.js b/public/scripts/ui-main.js index 682dbe3..a8c4a1f 100644 --- a/public/scripts/ui-main.js +++ b/public/scripts/ui-main.js @@ -344,15 +344,26 @@ class BackgroundCanvas { let x0, y0, w, h, dw, offset, baseColor, baseOpacity; - let offscreenCanvases; + let offscreenCanvases = {false: [], true: []}; let shareMode = false; + let startTime; let animate = true; - let currentFrame = 0; + let speed = 0.4; + let fps = 300; + let maxFrames = fps / speed; - let fpsInterval, now, then, elapsed; - - let speed = 1.5; + for (let frame = 0; frame < maxFrames; frame++) { + let canvas = document.createElement("canvas"); + offscreenCanvases[false][frame] = { + "redraw": true, + "canvas": canvas + }; + offscreenCanvases[true][frame] = { + "redraw": true, + "canvas": canvas + }; + } function init() { let oldW = w; @@ -360,8 +371,8 @@ class BackgroundCanvas { let oldOffset = offset w = document.documentElement.clientWidth; h = document.documentElement.clientHeight; - offset = $footer.offsetHeight - 33; - if (h > 800) offset += 16; + offset = $footer.offsetHeight - 28; + if (h > 800) offset += 11; if (oldW === w && oldH === h && oldOffset === offset) return; // nothing has changed @@ -369,12 +380,15 @@ class BackgroundCanvas { c.height = h; x0 = w / 2; y0 = h - offset; - dw = Math.round(Math.max(w, h, 1000) / 12); + dw = Math.round(Math.max(w, h, 1000) / 15); - drawCircles(cCtx, 0); + drawFrame(currentFrame); // enforce redrawing of frames - offscreenCanvases = {true: [], false: []}; + for (let frame = 0; frame < maxFrames; frame++) { + offscreenCanvases[true][frame]["redraw"] = true; + offscreenCanvases[false][frame]["redraw"] = true; + } } function drawCircle(ctx, radius) { @@ -383,8 +397,11 @@ class BackgroundCanvas { baseColor = shareMode ? '168 168 255' : '168 168 168'; baseOpacity = shareMode ? 0.8 : 0.4; - let opacity = baseOpacity * radius / (dw * 8); - if (radius > dw * 5) { + let opacity = Math.max(0, baseOpacity * (1 - 1.2 * radius / Math.max(w, h))); + if (radius < dw) { + opacity *= (radius - 33) / (dw - 33) + } + else if (radius > dw * 5) { opacity *= (6 * dw - radius) / dw } ctx.strokeStyle = `rgb(${baseColor} / ${opacity})`; @@ -394,16 +411,16 @@ class BackgroundCanvas { } function drawCircles(ctx, frame) { - for (let i = 6; i >= 0; i--) { - drawCircle(ctx, dw * i + speed * frame + 33); + ctx.clearRect(0, 0, w, h); + for (let i = 5; i >= 0; i--) { + drawCircle(ctx, dw * i + speed * dw * frame / fps + 33); } } - function createOffscreenCanvas(frame) { - let canvas = document.createElement("canvas"); + function drawOffscreenCanvas(frame) { + let canvas = offscreenCanvases[shareMode][frame]["canvas"]; canvas.width = c.width; canvas.height = c.height; - offscreenCanvases[shareMode][frame] = canvas; let ctx = canvas.getContext('2d'); drawCircles(ctx, frame); } @@ -411,39 +428,45 @@ class BackgroundCanvas { function drawFrame(frame) { cCtx.clearRect(0, 0, w, h); - if (!offscreenCanvases[shareMode][frame]) { - createOffscreenCanvas(frame); + if (offscreenCanvases[shareMode][frame]["redraw"]) { + drawOffscreenCanvas(frame); } - cCtx.drawImage(offscreenCanvases[shareMode][frame], 0, 0); + cCtx.drawImage(offscreenCanvases[shareMode][frame]["canvas"], 0, 0); } - function startAnimating(fps) { - fpsInterval = 1000 / fps; - then = Date.now(); + function startAnimating() { + startTime = Date.now(); animateBg(); } + let currentFrame = 0; function animateBg() { - requestAnimationFrame(animateBg); + let now = Date.now(); - now = Date.now(); - elapsed = now - then; - // if not enough time has elapsed, do not draw the next frame -> abort - if (elapsed < fpsInterval) { + if (!animate) { + // Animation stopped -> don't draw next frame return; } - then = now - (elapsed % fpsInterval); + let timeSinceLastFullCycle = (now - startTime) % (1000 / speed); + let nextFrame = Math.trunc(fps * timeSinceLastFullCycle / 1000); - if (animate) { - currentFrame = (currentFrame + 1) % (dw/speed); - drawFrame(currentFrame); + // Only draw frame if it differs from current frame + if (nextFrame !== currentFrame) { + drawFrame(nextFrame); + currentFrame = nextFrame; } + + requestAnimationFrame(animateBg); } function switchAnimation(state) { + if (!animate && state) { + // animation starts again. Set startTime to specific value to prevent frame jump + startTime = Date.now() - 1000 * currentFrame / fps; + } animate = state; - console.debug(state) + animateBg(); } function redrawOnShareModeChange(active) { @@ -451,7 +474,7 @@ class BackgroundCanvas { } init(); - startAnimating(30) + startAnimating(); // redraw canvas Events.on('resize', _ => init()); From 16523843bd7bd6241854666a51ae3bff9155809d Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Sun, 16 Feb 2025 19:23:04 +0100 Subject: [PATCH 17/45] Use OffscreenCanvas on supported browsers to offload canvas drawing to a service worker thread --- public/scripts/canvas-worker.js | 135 ++++++++++++++++++ public/scripts/ui-main.js | 241 ++++++++++++++++++++------------ public/service-worker.js | 1 + 3 files changed, 288 insertions(+), 89 deletions(-) create mode 100644 public/scripts/canvas-worker.js diff --git a/public/scripts/canvas-worker.js b/public/scripts/canvas-worker.js new file mode 100644 index 0000000..e7e6bc7 --- /dev/null +++ b/public/scripts/canvas-worker.js @@ -0,0 +1,135 @@ +self.onmessage = (e) => { + switch (e.data.type) { + case "createCanvas": createCanvas(e.data); + break; + case "initCanvas": initCanvas(e.data.footerOffsetHeight, e.data.clientWidth, e.data.clientHeight); + break; + case "startAnimation": startAnimation(); + break; + case "onShareModeChange": onShareModeChange(e.data.active); + break; + case "switchAnimation": switchAnimation(e.data.animate); + break; + } +}; + +let baseColorNormal; +let baseColorShareMode; +let baseOpacityNormal; +let baseOpacityShareMode; +let speed; +let fps; + +let c; +let cCtx; + +let x0, y0, w, h, dw, offset; + +let startTime; +let animate = true; +let currentFrame = 0; +let lastFrame; +let baseColor; +let baseOpacity; + +function createCanvas(data) { + baseColorNormal = data.baseColorNormal; + baseColorShareMode = data.baseColorShareMode; + baseOpacityNormal = data.baseOpacityNormal; + baseOpacityShareMode = data.baseOpacityShareMode; + speed = data.speed; + fps = data.fps; + + c = data.canvas; + cCtx = c.getContext("2d"); + + lastFrame = fps / speed - 1; + baseColor = baseColorNormal; + baseOpacity = baseOpacityNormal; +} + +function initCanvas(footerOffsetHeight, clientWidth, clientHeight) { + let oldW = w; + let oldH = h; + let oldOffset = offset; + w = clientWidth; + h = clientHeight; + offset = footerOffsetHeight - 28; + if (h > 800) offset += 11; + + if (oldW === w && oldH === h && oldOffset === offset) return; // nothing has changed + + c.width = w; + c.height = h; + x0 = w / 2; + y0 = h - offset; + dw = Math.round(Math.min(Math.max(w, h), 800) / 10); + + drawFrame(currentFrame); +} + +function startAnimation() { + startTime = Date.now(); + animateBg(); +} + +function switchAnimation(state) { + if (!animate && state) { + // animation starts again. Set startTime to specific value to prevent frame jump + startTime = Date.now() - 1000 * currentFrame / fps; + } + animate = state; + requestAnimationFrame(animateBg); +} + +function onShareModeChange(active) { + baseColor = active ? baseColorShareMode : baseColorNormal; + baseOpacity = active ? baseOpacityShareMode : baseOpacityNormal; + drawFrame(currentFrame); +} + +function drawCircle(ctx, radius) { + ctx.lineWidth = 2; + + let opacity = Math.max(0, baseOpacity * (1 - 1.2 * radius / Math.max(w, h))); + if (radius > dw * 7) { + opacity *= (8 * dw - radius) / dw + } + + ctx.strokeStyle = `rgb(${baseColor} / ${opacity})`; + ctx.beginPath(); + ctx.arc(x0, y0, radius, 0, 2 * Math.PI); + ctx.stroke(); +} + +function drawCircles(ctx, frame) { + ctx.clearRect(0, 0, w, h); + for (let i = 7; i >= 0; i--) { + drawCircle(ctx, dw * i + speed * dw * frame / fps + 33); + } +} + +function drawFrame(frame) { + cCtx.clearRect(0, 0, w, h); + drawCircles(cCtx, frame); +} + +function animateBg() { + let now = Date.now(); + + if (!animate && currentFrame === lastFrame) { + // Animation stopped and cycle finished -> stop drawing frames + return; + } + + let timeSinceLastFullCycle = (now - startTime) % (1000 / speed); + let nextFrame = Math.trunc(fps * timeSinceLastFullCycle / 1000); + + // Only draw frame if it differs from current frame + if (nextFrame !== currentFrame) { + drawFrame(nextFrame); + currentFrame = nextFrame; + } + + requestAnimationFrame(animateBg); +} \ No newline at end of file diff --git a/public/scripts/ui-main.js b/public/scripts/ui-main.js index a8c4a1f..3dbd612 100644 --- a/public/scripts/ui-main.js +++ b/public/scripts/ui-main.js @@ -333,45 +333,93 @@ class FooterUI { class BackgroundCanvas { constructor() { - this.canvas = $$('canvas'); + this.$canvas = $$('canvas'); + this.$footer = $$('footer'); + this.initAnimation(); } + async fadeIn() { + this.$canvas.classList.remove('opacity-0'); + } + initAnimation() { - let c = this.canvas; - let cCtx = c.getContext('2d'); - let $footer = $$('footer'); + this.baseColorNormal = '168 168 168'; + this.baseColorShareMode = '168 168 255'; + this.baseOpacityNormal = 0.4; + this.baseOpacityShareMode = 0.8; + this.speed = 0.4; + this.fps = 40; - let x0, y0, w, h, dw, offset, baseColor, baseOpacity; + // if browser supports OffscreenCanvas + // -> put canvas drawing into serviceworker to unblock main thread + // otherwise + // -> use main thread + let {init, startAnimation, switchAnimation, onShareModeChange} = + this.$canvas.transferControlToOffscreen + ? this.initAnimationOffscreen() + : this.initAnimationOnscreen(); - let offscreenCanvases = {false: [], true: []}; - let shareMode = false; + init(); + startAnimation(); + + // redraw canvas + Events.on('resize', _ => init()); + Events.on('redraw-canvas', _ => init()); + Events.on('translation-loaded', _ => init()); + + // ShareMode + Events.on('share-mode-changed', e => onShareModeChange(e.detail.active)); + + // Start and stop animation + Events.on('background-animation', e => switchAnimation(e.detail.animate)) + Events.on('offline', _ => switchAnimation(false)); + Events.on('online', _ => switchAnimation(true)); + } + + initAnimationOnscreen() { + let $canvas = this.$canvas; + let $footer = this.$footer; + + let baseColorNormal = this.baseColorNormal; + let baseColorShareMode = this.baseColorShareMode; + let baseOpacityNormal = this.baseOpacityNormal; + let baseOpacityShareMode = this.baseOpacityShareMode; + let speed = this.speed; + let fps = this.fps; + + let c; + let cCtx; + + let x0, y0, w, h, dw, offset; let startTime; let animate = true; - let speed = 0.4; - let fps = 300; - let maxFrames = fps / speed; + let currentFrame = 0; + let lastFrame; + let baseColor; + let baseOpacity; - for (let frame = 0; frame < maxFrames; frame++) { - let canvas = document.createElement("canvas"); - offscreenCanvases[false][frame] = { - "redraw": true, - "canvas": canvas - }; - offscreenCanvases[true][frame] = { - "redraw": true, - "canvas": canvas - }; + function createCanvas() { + c = $canvas; + cCtx = c.getContext('2d'); + + lastFrame = fps / speed - 1; + baseColor = baseColorNormal; + baseOpacity = baseOpacityNormal; } function init() { + initCanvas($footer.offsetHeight, document.documentElement.clientWidth, document.documentElement.clientHeight); + } + + function initCanvas(footerOffsetHeight, clientWidth, clientHeight) { let oldW = w; let oldH = h; - let oldOffset = offset - w = document.documentElement.clientWidth; - h = document.documentElement.clientHeight; - offset = $footer.offsetHeight - 28; + let oldOffset = offset; + w = clientWidth; + h = clientHeight; + offset = footerOffsetHeight - 28; if (h > 800) offset += 11; if (oldW === w && oldH === h && oldOffset === offset) return; // nothing has changed @@ -380,30 +428,39 @@ class BackgroundCanvas { c.height = h; x0 = w / 2; y0 = h - offset; - dw = Math.round(Math.max(w, h, 1000) / 15); + dw = Math.round(Math.min(Math.max(w, h), 800) / 10); drawFrame(currentFrame); + } - // enforce redrawing of frames - for (let frame = 0; frame < maxFrames; frame++) { - offscreenCanvases[true][frame]["redraw"] = true; - offscreenCanvases[false][frame]["redraw"] = true; + function startAnimation() { + startTime = Date.now(); + animateBg(); + } + + function switchAnimation(state) { + if (!animate && state) { + // animation starts again. Set startTime to specific value to prevent frame jump + startTime = Date.now() - 1000 * currentFrame / fps; } + animate = state; + requestAnimationFrame(animateBg); + } + + function onShareModeChange(active) { + baseColor = active ? baseColorShareMode : baseColorNormal; + baseOpacity = active ? baseOpacityShareMode : baseOpacityNormal; + drawFrame(currentFrame); } function drawCircle(ctx, radius) { ctx.lineWidth = 2; - baseColor = shareMode ? '168 168 255' : '168 168 168'; - baseOpacity = shareMode ? 0.8 : 0.4; - let opacity = Math.max(0, baseOpacity * (1 - 1.2 * radius / Math.max(w, h))); - if (radius < dw) { - opacity *= (radius - 33) / (dw - 33) - } - else if (radius > dw * 5) { - opacity *= (6 * dw - radius) / dw + if (radius > dw * 7) { + opacity *= (8 * dw - radius) / dw } + ctx.strokeStyle = `rgb(${baseColor} / ${opacity})`; ctx.beginPath(); ctx.arc(x0, y0, radius, 0, 2 * Math.PI); @@ -412,39 +469,21 @@ class BackgroundCanvas { function drawCircles(ctx, frame) { ctx.clearRect(0, 0, w, h); - for (let i = 5; i >= 0; i--) { + for (let i = 7; i >= 0; i--) { drawCircle(ctx, dw * i + speed * dw * frame / fps + 33); } } - function drawOffscreenCanvas(frame) { - let canvas = offscreenCanvases[shareMode][frame]["canvas"]; - canvas.width = c.width; - canvas.height = c.height; - let ctx = canvas.getContext('2d'); - drawCircles(ctx, frame); - } - function drawFrame(frame) { cCtx.clearRect(0, 0, w, h); - - if (offscreenCanvases[shareMode][frame]["redraw"]) { - drawOffscreenCanvas(frame); - } - cCtx.drawImage(offscreenCanvases[shareMode][frame]["canvas"], 0, 0); + drawCircles(cCtx, frame); } - function startAnimating() { - startTime = Date.now(); - animateBg(); - } - - let currentFrame = 0; function animateBg() { let now = Date.now(); - if (!animate) { - // Animation stopped -> don't draw next frame + if (!animate && currentFrame === lastFrame) { + // Animation stopped and cycle finished -> stop drawing frames return; } @@ -460,38 +499,62 @@ class BackgroundCanvas { requestAnimationFrame(animateBg); } - function switchAnimation(state) { - if (!animate && state) { - // animation starts again. Set startTime to specific value to prevent frame jump - startTime = Date.now() - 1000 * currentFrame / fps; - } - animate = state; - animateBg(); - } + createCanvas(); - function redrawOnShareModeChange(active) { - shareMode = active - } - - init(); - startAnimating(); - - // redraw canvas - Events.on('resize', _ => init()); - Events.on('redraw-canvas', _ => init()); - Events.on('translation-loaded', _ => init()); - - // ShareMode - Events.on('share-mode-changed', e => redrawOnShareModeChange(e.detail.active)); - - // Start and stop animation - Events.on('background-animation', e => switchAnimation(e.detail.animate)) - - Events.on('offline', _ => switchAnimation(false)); - Events.on('online', _ => switchAnimation(true)); + return {init, startAnimation, switchAnimation, onShareModeChange}; } - async fadeIn() { - this.canvas.classList.remove('opacity-0'); + initAnimationOffscreen() { + console.log("Use OffscreenCanvas to draw background animation.") + + let baseColorNormal = this.baseColorNormal; + let baseColorShareMode = this.baseColorShareMode; + let baseOpacityNormal = this.baseOpacityNormal; + let baseOpacityShareMode = this.baseOpacityShareMode; + let speed = this.speed; + let fps = this.fps; + let $canvas = this.$canvas; + let $footer = this.$footer; + + const offscreen = $canvas.transferControlToOffscreen(); + const worker = new Worker("scripts/canvas-worker.js"); + + function createCanvas() { + worker.postMessage({ + type: "createCanvas", + canvas: offscreen, + baseColorNormal: baseColorNormal, + baseColorShareMode: baseColorShareMode, + baseOpacityNormal: baseOpacityNormal, + baseOpacityShareMode: baseOpacityShareMode, + speed: speed, + fps: fps + }, [offscreen]); + } + + function init() { + worker.postMessage({ + type: "initCanvas", + footerOffsetHeight: $footer.offsetHeight, + clientWidth: document.documentElement.clientWidth, + clientHeight: document.documentElement.clientHeight + }); + } + + function startAnimation() { + worker.postMessage({ type: "startAnimation" }); + } + + function onShareModeChange(active) { + worker.postMessage({ type: "onShareModeChange", active: active }); + } + + function switchAnimation(animate) { + worker.postMessage({ type: "switchAnimation", animate: animate }); + } + + createCanvas(); + + return {init, startAnimation, switchAnimation, onShareModeChange}; } } \ No newline at end of file diff --git a/public/service-worker.js b/public/service-worker.js index c6420fe..f022b11 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -17,6 +17,7 @@ const relativePathsToCache = [ 'scripts/ui-main.js', 'scripts/util.js', 'scripts/zip.min.js', + 'scripts/canvas-worker.js', 'sounds/blop.mp3', 'sounds/blop.ogg', 'images/favicon-96x96.png', From ae68ede3f37445caa56b2b08ef307ff32490b38e Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Sun, 16 Feb 2025 19:29:14 +0100 Subject: [PATCH 18/45] Put worker and libraries in subfolders --- public/scripts/{ => libs}/heic2any.min.js | 0 public/scripts/{ => libs}/no-sleep.min.js | 0 public/scripts/{ => libs}/qr-code.min.js | 0 public/scripts/{ => libs}/zip.min.js | 0 public/scripts/main.js | 8 ++++---- public/scripts/ui-main.js | 2 +- public/scripts/{ => worker}/canvas-worker.js | 0 public/service-worker.js | 9 +++++---- 8 files changed, 10 insertions(+), 9 deletions(-) rename public/scripts/{ => libs}/heic2any.min.js (100%) rename public/scripts/{ => libs}/no-sleep.min.js (100%) rename public/scripts/{ => libs}/qr-code.min.js (100%) rename public/scripts/{ => libs}/zip.min.js (100%) rename public/scripts/{ => worker}/canvas-worker.js (100%) diff --git a/public/scripts/heic2any.min.js b/public/scripts/libs/heic2any.min.js similarity index 100% rename from public/scripts/heic2any.min.js rename to public/scripts/libs/heic2any.min.js diff --git a/public/scripts/no-sleep.min.js b/public/scripts/libs/no-sleep.min.js similarity index 100% rename from public/scripts/no-sleep.min.js rename to public/scripts/libs/no-sleep.min.js diff --git a/public/scripts/qr-code.min.js b/public/scripts/libs/qr-code.min.js similarity index 100% rename from public/scripts/qr-code.min.js rename to public/scripts/libs/qr-code.min.js diff --git a/public/scripts/zip.min.js b/public/scripts/libs/zip.min.js similarity index 100% rename from public/scripts/zip.min.js rename to public/scripts/libs/zip.min.js diff --git a/public/scripts/main.js b/public/scripts/main.js index 8960ed2..6ab999a 100644 --- a/public/scripts/main.js +++ b/public/scripts/main.js @@ -14,10 +14,10 @@ class PairDrop { "scripts/util.js", "scripts/network.js", "scripts/ui.js", - "scripts/qr-code.min.js", - "scripts/zip.min.js", - "scripts/no-sleep.min.js", - "scripts/heic2any.min.js" + "scripts/libs/heic2any.min.js", + "scripts/libs/no-sleep.min.js", + "scripts/libs/qr-code.min.js", + "scripts/libs/zip.min.js" ]; this.registerServiceWorker(); diff --git a/public/scripts/ui-main.js b/public/scripts/ui-main.js index 3dbd612..f9ecbad 100644 --- a/public/scripts/ui-main.js +++ b/public/scripts/ui-main.js @@ -517,7 +517,7 @@ class BackgroundCanvas { let $footer = this.$footer; const offscreen = $canvas.transferControlToOffscreen(); - const worker = new Worker("scripts/canvas-worker.js"); + const worker = new Worker("scripts/worker/canvas-worker.js"); function createCanvas() { worker.postMessage({ diff --git a/public/scripts/canvas-worker.js b/public/scripts/worker/canvas-worker.js similarity index 100% rename from public/scripts/canvas-worker.js rename to public/scripts/worker/canvas-worker.js diff --git a/public/service-worker.js b/public/service-worker.js index f022b11..07e2c50 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -10,14 +10,15 @@ const relativePathsToCache = [ 'scripts/localization.js', 'scripts/main.js', 'scripts/network.js', - 'scripts/no-sleep.min.js', 'scripts/persistent-storage.js', - 'scripts/qr-code.min.js', 'scripts/ui.js', 'scripts/ui-main.js', 'scripts/util.js', - 'scripts/zip.min.js', - 'scripts/canvas-worker.js', + 'scripts/worker/canvas-worker.js', + 'scripts/libs/heic2any.min.js', + 'scripts/libs/no-sleep.min.js', + 'scripts/libs/qr-code.min.js', + 'scripts/libs/zip.min.js', 'sounds/blop.mp3', 'sounds/blop.ogg', 'images/favicon-96x96.png', From 8eea54f8dd022fb67a4454ee0f93c7f8413cac86 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Sun, 16 Feb 2025 23:20:43 +0100 Subject: [PATCH 19/45] Make sure older webkit/blink based browsers are able to render opacity of circles --- public/scripts/ui-main.js | 9 ++++++++- public/scripts/worker/canvas-worker.js | 10 +++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/public/scripts/ui-main.js b/public/scripts/ui-main.js index f9ecbad..6a7dd44 100644 --- a/public/scripts/ui-main.js +++ b/public/scripts/ui-main.js @@ -461,7 +461,14 @@ class BackgroundCanvas { opacity *= (8 * dw - radius) / dw } - ctx.strokeStyle = `rgb(${baseColor} / ${opacity})`; + if (ctx.setStrokeColor) { + // older blink/webkit browsers do not understand opacity in strokeStyle. Use deprecated setStrokeColor + let baseColorRgb = baseColor.split(" "); + ctx.setStrokeColor(baseColorRgb[0], baseColorRgb[1], baseColorRgb[2], opacity); + } + else { + ctx.strokeStyle = `rgb(${baseColor} / ${opacity})`; + } ctx.beginPath(); ctx.arc(x0, y0, radius, 0, 2 * Math.PI); ctx.stroke(); diff --git a/public/scripts/worker/canvas-worker.js b/public/scripts/worker/canvas-worker.js index e7e6bc7..1428bc1 100644 --- a/public/scripts/worker/canvas-worker.js +++ b/public/scripts/worker/canvas-worker.js @@ -96,7 +96,15 @@ function drawCircle(ctx, radius) { opacity *= (8 * dw - radius) / dw } - ctx.strokeStyle = `rgb(${baseColor} / ${opacity})`; + if (ctx.setStrokeColor) { + // older blink/webkit based browsers do not understand opacity in strokeStyle. Use deprecated setStrokeColor instead + // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle#webkitblink-specific_note + let baseColorRgb = baseColor.split(" "); + ctx.setStrokeColor(baseColorRgb[0], baseColorRgb[1], baseColorRgb[2], opacity); + } + else { + ctx.strokeStyle = `rgb(${baseColor} / ${opacity})`; + } ctx.beginPath(); ctx.arc(x0, y0, radius, 0, 2 * Math.PI); ctx.stroke(); From ec0012ecd1d27133051452b822eb96843a9290f8 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Sun, 16 Feb 2025 23:23:27 +0100 Subject: [PATCH 20/45] Speed up animation slightly --- public/scripts/ui-main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/ui-main.js b/public/scripts/ui-main.js index 6a7dd44..812a4ae 100644 --- a/public/scripts/ui-main.js +++ b/public/scripts/ui-main.js @@ -348,7 +348,7 @@ class BackgroundCanvas { this.baseColorShareMode = '168 168 255'; this.baseOpacityNormal = 0.4; this.baseOpacityShareMode = 0.8; - this.speed = 0.4; + this.speed = 0.5; this.fps = 40; // if browser supports OffscreenCanvas From 09e4e5d289eb11793fea8b6fba6e54f9bfbe79e3 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Sun, 16 Feb 2025 23:47:35 +0100 Subject: [PATCH 21/45] Prevent background animation from being cut on devices with a notch --- public/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.html b/public/index.html index 5dec40a..d1873b2 100644 --- a/public/index.html +++ b/public/index.html @@ -6,7 +6,7 @@ PairDrop | Transfer Files Cross-Platform. No Setup, No Signup. - + From 61caa43ce9cef1b126c3d491a54a10f04127af18 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 17 Feb 2025 05:37:36 +0100 Subject: [PATCH 22/45] Translated using Weblate (Basque) Currently translated at 100.0% (166 of 166 strings) Co-authored-by: Hosted Weblate Co-authored-by: xabirequejo Translate-URL: https://hosted.weblate.org/projects/pairdrop/pairdrop-spa/eu/ Translation: PairDrop/pairdrop-spa --- public/lang/eu.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/lang/eu.json b/public/lang/eu.json index 2e528b3..bcae982 100644 --- a/public/lang/eu.json +++ b/public/lang/eu.json @@ -73,7 +73,7 @@ "copied-text": "Testua arbelera kopiatu da", "online": "Berriro zaude linean", "unfinished-transfers-warning": "Amaitu gabeko trukatzeak daude. Ziur PairDrop itxi nahi duzula?", - "selected-peer-left": "Falta diren hautatutako kideak", + "selected-peer-left": "Hautatutako kideak alde egin du", "pairing-key-invalidated": "{{key}} gakoa baliogabetu da" }, "dialogs": { From 43ce64d68abc078bfc42a1ed07a58ae27f941347 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 17 Feb 2025 05:37:37 +0100 Subject: [PATCH 23/45] Translated using Weblate (Bengali) Currently translated at 1.2% (2 of 166 strings) Added translation using Weblate (Bengali) Co-authored-by: Hosted Weblate Co-authored-by: Saif Mahmud Translate-URL: https://hosted.weblate.org/projects/pairdrop/pairdrop-spa/bn/ Translation: PairDrop/pairdrop-spa --- public/lang/bn.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 public/lang/bn.json diff --git a/public/lang/bn.json b/public/lang/bn.json new file mode 100644 index 0000000..4f3f52a --- /dev/null +++ b/public/lang/bn.json @@ -0,0 +1,6 @@ +{ + "header": { + "about_title": "পেয়ার ড্রপ সম্পর্কে", + "install_title": "পেয়ার ড্রপ ইন্সটল করুন" + } +} From 9f4309c866ce689c8de90545907e3835accd1f36 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 17 Feb 2025 05:37:37 +0100 Subject: [PATCH 24/45] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (166 of 166 strings) Co-authored-by: Hosted Weblate Co-authored-by: Tobbz Translate-URL: https://hosted.weblate.org/projects/pairdrop/pairdrop-spa/nb_NO/ Translation: PairDrop/pairdrop-spa --- public/lang/nb.json | 98 +++++++++++++++++++++++++++++++++------------ 1 file changed, 72 insertions(+), 26 deletions(-) diff --git a/public/lang/nb.json b/public/lang/nb.json index d9a4022..468ea19 100644 --- a/public/lang/nb.json +++ b/public/lang/nb.json @@ -3,50 +3,62 @@ "edit-paired-devices_title": "Rediger sammenkoblede enheter", "about_title": "Om PairDrop", "about_aria-label": "Åpne «Om PairDrop»", - "theme-auto_title": "Juster drakt til system", + "theme-auto_title": "Juster drakt til system automatisk", "theme-light_title": "Alltid bruk lys drakt", "theme-dark_title": "Alltid bruk mørk drakt", "notification_title": "Skru på varslinger", "cancel-share-mode": "Ferdig", "install_title": "Installer PairDrop", - "pair-device_title": "Sammenkoble enhet", - "language-selector_title": "Velg språk" + "pair-device_title": "Sammenkoble dine enheter permanent", + "language-selector_title": "Velg språk", + "edit-share-mode": "Rediger", + "expand_title": "Utvid overskriftknapprad", + "join-public-room_title": "Bli med i et offentlig rom midlertidig" }, "footer": { "webrtc": "hvis WebRTC ikke er tilgjengelig.", "display-name_data-placeholder": "Laster inn…", - "display-name_title": "Rediger det vedvarende enhetsnavnet ditt", + "display-name_title": "Rediger ditt enhetsnavn permanent", "traffic": "Trafikken", "on-this-network": "på dette nettverket", "known-as": "Du er kjent som:", - "paired-devices": "sammenkoblede enheter", - "routed": "Sendes gjennom tjeneren" + "paired-devices": "av sammenkoblede enheter", + "routed": "Sendes gjennom tjeneren", + "discovery": "Du kan bli oppdaget:", + "on-this-network_title": "Du kan bli oppdaget av alle på dette nettverket.", + "paired-devices_title": "Du kan alltid bli oppdaget av sammenkoblede enheter uavhengig av nettverk.", + "public-room-devices_title": "Du kan bli oppdaget av enheter i dette offentlige rommet uavhengig av nettverk.", + "public-room-devices": "i rom {{roomId}}" }, "instructions": { "x-instructions_desktop": "Klikk for å sende filer, eller høyreklikk for å sende en melding", "x-instructions_mobile": "Trykk for å sende filer, eller lang-trykk for å sende en melding", "x-instructions_data-drop-bg": "Slipp for å velge mottager", - "x-instructions-share-mode_desktop": "Klikk for å sende", + "x-instructions-share-mode_desktop": "Klikk for å sende {{descriptor}}", "no-peers_data-drop-bg": "Slipp for å velge mottager", "no-peers-title": "Åpne PairDrop på andre enheter for å sende filer", - "no-peers-subtitle": "Sammenkoble enheter for å kunne oppdages på andre nettverk", + "no-peers-subtitle": "Sammenkoble enheter eller bli med i et offentlig rom for å kunne oppdages på andre nettverk", "x-instructions_data-drop-peer": "Slipp for å sende til likemann", - "x-instructions-share-mode_mobile": "Trykk for å sende", + "x-instructions-share-mode_mobile": "Trykk for å sende {{descriptor}}", "activate-share-mode-base": "Åpne PairDrop på andre enheter for å sende", "activate-share-mode-and-other-files-plural": "og {{count}} andre filer", - "activate-share-mode-shared-text": "delt tekst" + "activate-share-mode-shared-text": "delt tekst", + "activate-share-mode-and-other-file": "og 1 annen fil", + "activate-share-mode-shared-file": "delt fil", + "activate-share-mode-shared-files-plural": "{{count}} delte filer", + "webrtc-requirement": "For å bruke denne PairDrop-økten, må WebRTC være aktivert!" }, "dialogs": { "input-key-on-this-device": "Skriv inn denne nøkkelen på en annen enhet", - "pair-devices-title": "Sammenkoble enheter", + "pair-devices-title": "Sammenkoble Enheter Permanent", "would-like-to-share": "ønsker å dele", "auto-accept-instructions-2": "for å godkjenne alle filer sendt fra den enheten automatisk.", "paired-devices-wrapper_data-empty": "Ingen sammenkoblede enheter.", - "enter-key-from-another-device": "Skriv inn nøkkel fra en annen enhet for å fortsette.", - "edit-paired-devices-title": "Rediger sammenkoblede enheter", + "enter-key-from-another-device": "Skriv inn nøkkel fra en annen enhet her.", + "edit-paired-devices-title": "Rediger Sammenkoblede Enheter", "accept": "Godta", "has-sent": "har sendt:", - "base64-paste-to-send": "Trykk her for å sende {{type}}", + "base64-paste-to-send": "Lim inn her for å dele {{type}}", "base64-text": "tekst", "base64-files": "filer", "file-other-description-image-plural": "og {{count}} andre bilder", @@ -64,9 +76,9 @@ "receive-text-title": "Melding mottatt", "auto-accept": "auto-godkjenn", "share": "Del", - "send-message-to": "Send en melding til", + "send-message-to": "Til:", "send": "Send", - "base64-tap-to-paste": "Trykk her for å lime inn {{type}}", + "base64-tap-to-paste": "Trykk her for å dele {{type}}", "file-other-description-image": "og ett annet bilde", "file-other-description-file-plural": "og {{count}} andre filer", "title-file-plural": "Filer", @@ -74,7 +86,28 @@ "file-other-description-file": "og én annen fil", "title-image": "Bilde", "title-file": "Fil", - "title-image-plural": "Bilder" + "title-image-plural": "Bilder", + "join": "Bli med", + "share-text-checkbox": "Alltid vis denne dialogen ved deling av tekst", + "language-selector-title": "Velg Språk", + "unpair": "Fjern sammenkobling", + "temporary-public-room-title": "Midlertidig Offentlig Rom", + "input-room-id-on-another-device": "Legg inn denne rom-IDen på en annen enhet", + "hr-or": "ELLER", + "leave": "Forlat", + "paired-device-removed": "Sammenkoblet enhet har blitt fjernet.", + "message_title": "Sett inn meldingen du vil sende", + "message_placeholder": "Tekst", + "base64-title-files": "Delte filer", + "system-language": "Systemspråk", + "public-room-qr-code_title": "Trykk for å kopiere lenke til offentlig rom", + "pair-devices-qr-code_title": "Trykk for å kopiere lenken til å sammenkoble denne enheten", + "approve": "godkjenn", + "share-text-title": "Del Tekstmelding", + "share-text-subtitle": "Rediger melding før sending:", + "close-toast_title": "Lukk varsel", + "enter-room-id-from-another-device": "Legg inn rom-ID fra en annen enhet for å bli med i rommet.", + "base64-title-text": "Delt Tekst" }, "about": { "close-about_aria-label": "Lukk «Om PairDrop»", @@ -82,7 +115,11 @@ "claim": "Den enkleste måten å overføre filer mellom enheter", "buy-me-a-coffee_title": "Spander drikke!", "tweet_title": "Tvitre om PairDrop", - "github_title": "PairDrop på GitHub" + "github_title": "PairDrop på GitHub", + "mastodon_title": "Skriv om PairDrop på Mastadon", + "bluesky_title": "Følg oss på BlueSky", + "custom_title": "Følg oss", + "privacypolicy_title": "Åpne vår personvernerklæring" }, "notifications": { "copied-to-clipboard": "Kopiert til utklippstavlen", @@ -95,7 +132,7 @@ "file-transfer-completed": "Filoverføring utført", "selected-peer-left": "Valgt likemann dro", "pairing-key-invalid": "Ugyldig nøkkel", - "connecting": "Kobler til …", + "connecting": "Kobler til…", "pairing-not-persistent": "Sammenkoblede enheter er ikke vedvarende", "offline": "Du er frakoblet", "online-requirement": "Du må være på nett for å koble sammen enheter.", @@ -110,28 +147,37 @@ "pairing-success": "Enheter sammenkoblet", "pairing-cleared": "Sammenkobling av alle enheter opphevet", "pairing-key-invalidated": "Nøkkel {{key}} ugyldiggjort", - "copied-text-error": "Kunne ikke legge innhold i utklkippstavlen. Kopier manuelt!", + "copied-text-error": "Kunne ikke legge innhold i utklippstavlen. Kopier manuelt!", "clipboard-content-incorrect": "Utklippstavleinnholdet er uriktig", "link-received": "Lenke mottatt av {{name}} - Klikk for å åpne", "request-title": "{{name}} ønsker å overføre {{count}} {{descriptor}}", "message-received": "Melding mottatt av {{name}} - Klikk for å åpne", "files-incorrect": "Filene er uriktige", "ios-memory-limit": "Forsendelse av filer til iOS er kun mulig opptil 200 MB av gangen", - "unfinished-transfers-warning": "Lukk med ufullførte overføringer?", - "rate-limit-join-key": "Forsøksgrense overskredet. Vent 10 sek. og prøv igjen." + "unfinished-transfers-warning": "Det er ufullførte overføringer. Er du sikker på at du vil lukke PairDrop?", + "rate-limit-join-key": "Grense nådd. Vent 10 sekunder og prøv igjen.", + "copied-to-clipboard-error": "Kopiering ikke mulig, Kopier manuelt.", + "public-room-id-invalid": "Ugyldig rom-ID", + "public-room-left": "Forlot offentlig rom {{publicRoomId}}", + "room-url-copied-to-clipboard": "Lenke for offentlig rom kopiert til utklippstavle", + "online-requirement-pairing": "Du må være på nett for å sammenkoble enheter", + "online-requirement-public-room": "Du må være på nett for å opprette et offentlig rom", + "pair-url-copied-to-clipboard": "Lenke for sammenkobling til denne enheten kopiert til utklipstavle", + "notifications-permissions-error": "Varlseltillatelse har blitt blokkert fordi brukeren har avvist forespørselen flere ganger. Dette kan tilbakestilles i Sideinnformasjon som kan bli funnet ved å trykke på låseikonet ved siden av URL-feltet." }, "document-titles": { "file-received": "Fil mottatt", "file-received-plural": "{{count}} filer mottatt", "message-received": "Melding mottatt", "file-transfer-requested": "Filoverføring forespurt", - "message-received-plural": "{{count}} meldinger mottatt" + "message-received-plural": "{{count}} meldinger mottatt", + "image-transfer-requested": "Blideoverføring forespurt" }, "peer-ui": { - "preparing": "Forbereder …", + "preparing": "Forbereder…", "waiting": "Venter…", - "processing": "Behandler …", - "transferring": "Overfører …", + "processing": "Behandler…", + "transferring": "Overfører…", "click-to-send": "Klikk for å sende filer, eller høyreklikk for å sende en melding", "click-to-send-share-mode": "Klikk for å sende {{descriptor}}", "connection-hash": "Sammenlign dette sikkerhetsnummeret på begge enhetene for å bekrefte ende-til-ende -krypteringen" From b8a973f037aa40de5300afd820368aa2ed347515 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 17 Feb 2025 11:52:07 +0100 Subject: [PATCH 25/45] Fix background animation size on 4k screens and decrease base opacity --- public/scripts/ui-main.js | 6 +++--- public/scripts/worker/canvas-worker.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/scripts/ui-main.js b/public/scripts/ui-main.js index 01ad2ab..95cb3ba 100644 --- a/public/scripts/ui-main.js +++ b/public/scripts/ui-main.js @@ -346,10 +346,10 @@ class BackgroundCanvas { initAnimation() { this.baseColorNormal = '168 168 168'; this.baseColorShareMode = '168 168 255'; - this.baseOpacityNormal = 0.4; + this.baseOpacityNormal = 0.3; this.baseOpacityShareMode = 0.8; this.speed = 0.5; - this.fps = 40; + this.fps = 60; // if browser supports OffscreenCanvas // -> put canvas drawing into serviceworker to unblock main thread @@ -427,7 +427,7 @@ class BackgroundCanvas { c.height = h; x0 = w / 2; y0 = h - offset; - dw = Math.round(Math.min(Math.max(w, h), 800) / 10); + dw = Math.round(Math.min(Math.max(0.6 * w, h)) / 10); drawFrame(currentFrame); } diff --git a/public/scripts/worker/canvas-worker.js b/public/scripts/worker/canvas-worker.js index 26ec1f0..055c328 100644 --- a/public/scripts/worker/canvas-worker.js +++ b/public/scripts/worker/canvas-worker.js @@ -62,7 +62,7 @@ function initCanvas(footerOffsetHeight, clientWidth, clientHeight) { c.height = h; x0 = w / 2; y0 = h - offset; - dw = Math.round(Math.min(Math.max(w, h), 800) / 10); + dw = Math.round(Math.min(Math.max(0.6 * w, h)) / 10); drawFrame(currentFrame); } From 800d492da55382944dcd0655131e1908d220f322 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 17 Feb 2025 12:36:49 +0100 Subject: [PATCH 26/45] Fix animation color on older webkit/blink based browsers --- public/scripts/ui-main.js | 4 ++-- public/scripts/worker/canvas-worker.js | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/public/scripts/ui-main.js b/public/scripts/ui-main.js index 95cb3ba..1af7c37 100644 --- a/public/scripts/ui-main.js +++ b/public/scripts/ui-main.js @@ -462,8 +462,8 @@ class BackgroundCanvas { if (ctx.setStrokeColor) { // older blink/webkit browsers do not understand opacity in strokeStyle. Use deprecated setStrokeColor - let baseColorRgb = baseColor.split(" "); - ctx.setStrokeColor(baseColorRgb[0], baseColorRgb[1], baseColorRgb[2], opacity); + // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle#webkitblink-specific_note + ctx.setStrokeColor("grey", opacity); } else { ctx.strokeStyle = `rgb(${baseColor} / ${opacity})`; diff --git a/public/scripts/worker/canvas-worker.js b/public/scripts/worker/canvas-worker.js index 055c328..ff2650f 100644 --- a/public/scripts/worker/canvas-worker.js +++ b/public/scripts/worker/canvas-worker.js @@ -98,8 +98,7 @@ function drawCircle(ctx, radius) { if (ctx.setStrokeColor) { // older blink/webkit based browsers do not understand opacity in strokeStyle. Use deprecated setStrokeColor instead // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle#webkitblink-specific_note - let baseColorRgb = baseColor.split(" "); - ctx.setStrokeColor(baseColorRgb[0], baseColorRgb[1], baseColorRgb[2], opacity); + ctx.setStrokeColor("grey", opacity); } else { ctx.strokeStyle = `rgb(${baseColor} / ${opacity})`; From fa992498fbbad363c878acd1c42ee41df6ba5db7 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 17 Feb 2025 13:15:51 +0100 Subject: [PATCH 27/45] Revert "Prevent background animation from being cut on devices with a notch" This reverts commit 09e4e5d289eb11793fea8b6fba6e54f9bfbe79e3. --- public/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.html b/public/index.html index 8e3a1ec..e47983f 100644 --- a/public/index.html +++ b/public/index.html @@ -6,7 +6,7 @@ PairDrop | Transfer Files Cross-Platform. No Setup, No Signup. - + From ef61cc4dfe8376eb138ff3b6919c62ccacf909d4 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 17 Feb 2025 13:33:07 +0100 Subject: [PATCH 28/45] Update node dependencies --- package-lock.json | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8812cb9..3fc7477 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "pairdrop", - "version": "1.10.11", + "version": "1.11.0", "license": "ISC", "dependencies": { "express": "^4.18.2", @@ -68,9 +68,9 @@ } }, "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -263,9 +263,9 @@ } }, "node_modules/express-rate-limit": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.3.1.tgz", - "integrity": "sha512-BbaryvkY4wEgDqLgD18/NSy2lDO2jTuT9Y8c1Mpx0X63Yz0sYd5zN6KPe7UvpuSVvV33T6RaE1o1IVZQjHMYgw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", "engines": { "node": ">= 16" }, @@ -273,7 +273,7 @@ "url": "https://github.com/sponsors/express-rate-limit" }, "peerDependencies": { - "express": "4 || 5 || ^5.0.0-beta.1" + "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, "node_modules/finalhandler": { @@ -758,9 +758,9 @@ } }, "node_modules/ua-parser-js": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", - "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", + "version": "1.0.40", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz", + "integrity": "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==", "funding": [ { "type": "opencollective", @@ -775,6 +775,9 @@ "url": "https://github.com/sponsors/faisalman" } ], + "bin": { + "ua-parser-js": "script/cli.js" + }, "engines": { "node": "*" } @@ -812,9 +815,9 @@ } }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "engines": { "node": ">=10.0.0" }, From b3c61f4bafb1884de789eda3cbe56c16b44d16ce Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 17 Feb 2025 13:50:15 +0100 Subject: [PATCH 29/45] Increase version to v1.11.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Enhancements - Make PWA standalone (#264) - Bring back background animation after performance optimization (#285) - Add support for Safari 11-15 (#358) - Update Twitter icon and URL to (X) (Thanks @realchrislovett) - Update node dependencies ## Fixes - Fix pasting of files on background to invoke share-mode and make it available on Firefox (#370) - Fix padding issue on EditPairedDevicesDialog and Base64Dialog - Fix hydration of URLs into links in received messages that have exclamation marks in path ## Languages - Translations updates from Hosted Weblate (Basque, Norwegian Bokmål) --- .github/ISSUE_TEMPLATE/bug-report.md | 4 ++-- docs/how-to.md | 4 ++-- package-lock.json | 2 +- package.json | 2 +- public/index.html | 2 +- public/service-worker.js | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 419d837..5263696 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -36,7 +36,7 @@ If applicable, add screenshots to help explain your problem. **Bug occurs on official PairDrop instance https://pairdrop.net/** No | Yes -Version: v1.10.11 +Version: v1.11.0 **Bug occurs on self-hosted PairDrop instance** No | Yes @@ -44,7 +44,7 @@ No | Yes **Self-Hosted Setup** Proxy: Nginx | Apache2 Deployment: docker run | docker compose | npm run start:prod -Version: v1.10.11 +Version: v1.11.0 **Additional context** Add any other context about the problem here. diff --git a/docs/how-to.md b/docs/how-to.md index 7d899db..fcad02f 100644 --- a/docs/how-to.md +++ b/docs/how-to.md @@ -45,11 +45,11 @@ This pairdrop-cli version was released alongside v1.10.4 #### Linux / Mac 1. Download the latest _pairdrop-cli.zip_ from the [releases page](https://github.com/schlagmichdoch/PairDrop/releases) ```shell - wget "https://github.com/schlagmichdoch/PairDrop/releases/download/v1.10.11/pairdrop-cli.zip" + wget "https://github.com/schlagmichdoch/PairDrop/releases/download/v1.11.0/pairdrop-cli.zip" ``` or ```shell - curl -LO "https://github.com/schlagmichdoch/PairDrop/releases/download/v1.10.11/pairdrop-cli.zip" + curl -LO "https://github.com/schlagmichdoch/PairDrop/releases/download/v1.11.0/pairdrop-cli.zip" ``` 2. Unzip the archive to a folder of your choice e.g. `/usr/share/pairdrop-cli/` ```shell diff --git a/package-lock.json b/package-lock.json index 3fc7477..bdc9acd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "pairdrop", - "version": "1.10.11", + "version": "1.11.0", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package.json b/package.json index f4fd629..b36e5d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pairdrop", - "version": "1.10.11-", + "version": "1.11.0", "type": "module", "description": "", "main": "server/index.js", diff --git a/public/index.html b/public/index.html index e47983f..45df0e4 100644 --- a/public/index.html +++ b/public/index.html @@ -668,7 +668,7 @@

PairDrop

-
v1.10.11
+
v1.11.0
diff --git a/public/service-worker.js b/public/service-worker.js index 07e2c50..551f25e 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.10.11'; +const cacheVersion = 'v1.11.0'; const cacheTitle = `pairdrop-cache-${cacheVersion}`; const forceFetch = false; // FOR DEVELOPMENT: Set to true to always update assets instead of using cached versions const relativePathsToCache = [ From a39d8cdc845e6356d9eae2a907aa4bf5576f818a Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 17 Feb 2025 19:20:17 +0100 Subject: [PATCH 30/45] Fix chromium filename by defaulting mime to "application/octet-stream" --- public/scripts/network.js | 5 ++--- public/scripts/util.js | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/public/scripts/network.js b/public/scripts/network.js index c92befc..4382597 100644 --- a/public/scripts/network.js +++ b/public/scripts/network.js @@ -1082,7 +1082,7 @@ class PeersManager { } async _onFilesSelected(message) { - let files = mime.addMissingMimeTypesToFiles(message.files); + let files = mime.addMissingMimeTypesToFiles([...message.files]); await this.peers[message.to].requestFileTransfer(files); } @@ -1306,9 +1306,8 @@ class FileDigester { const blob = new Blob(this._buffer) this._buffer = null; this._callback(new File([blob], this._name, { - type: this._mime, + type: this._mime || "application/octet-stream", lastModified: new Date().getTime() })); } - } diff --git a/public/scripts/util.js b/public/scripts/util.js index 1dcc388..24e2626 100644 --- a/public/scripts/util.js +++ b/public/scripts/util.js @@ -392,7 +392,8 @@ const mime = (() => { "vob": "video/x-ms-vob", "wmv": "video/x-ms-wmv", "avi": "video/x-msvideo", - "*": "video/x-sgi-movie" + "*": "video/x-sgi-movie", + "kdbx": "application/x-keepass2" } return { @@ -409,7 +410,7 @@ const mime = (() => { // if filetype is empty guess via suffix otherwise leave unchanged for (let i = 0; i < files.length; i++) { if (!files[i].type) { - files[i] = new File([files[i]], files[i].name, {type: mime.guessMimeByFilename(files[i].name) || ""}); + files[i] = new File([files[i]], files[i].name, {type: mime.guessMimeByFilename(files[i].name) || "application/octet-stream"}); } } return files; From 547038c9bca43a8281f422b902ed7885a439733d Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 17 Feb 2025 19:32:54 +0100 Subject: [PATCH 31/45] Fix file name display if no file extension is present --- public/scripts/ui.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/scripts/ui.js b/public/scripts/ui.js index cf4d014..25171ae 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -873,7 +873,9 @@ class ReceiveDialog extends Dialog { const fileName = files[0].name; const fileNameSplit = fileName.split('.'); - const fileExtension = '.' + fileNameSplit[fileNameSplit.length - 1]; + const fileExtension = fileNameSplit.length > 1 + ? '.' + fileNameSplit[fileNameSplit.length - 1] + : ''; this.$fileStem.innerText = fileName.substring(0, fileName.length - fileExtension.length); this.$fileExtension.innerText = fileExtension; this.$fileSize.innerText = this._formatFileSize(totalSize); From aa09da3076ac1ee610b5bd60c0b178a157f21d73 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 18 Feb 2025 13:34:01 +0100 Subject: [PATCH 32/45] Translated using Weblate (Japanese) Currently translated at 100.0% (166 of 166 strings) Co-authored-by: maboroshin Translate-URL: https://hosted.weblate.org/projects/pairdrop/pairdrop-spa/ja/ Translation: PairDrop/pairdrop-spa --- public/lang/ja.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/public/lang/ja.json b/public/lang/ja.json index 6184de8..d4e8fdb 100644 --- a/public/lang/ja.json +++ b/public/lang/ja.json @@ -12,7 +12,7 @@ "routed": "サーバーを経由します", "discovery": "このデバイスを検出可能なネットワーク:", "on-this-network_title": "このネットワーク上のすべてのデバイスからアクセスできます。", - "known-as": "他のデバイスに表示される名前:" + "known-as": "このデバイスの名前:" }, "notifications": { "request-title": "{{name}}は{{count}}個の{{descriptor}}を共有しようとしています", @@ -25,7 +25,7 @@ "connected": "接続しました", "pairing-not-persistent": "このデバイスとのペアリングは解除される可能性があります", "text-content-incorrect": "無効なテキスト内容です", - "message-transfer-completed": "メッセージの送信が完了しました", + "message-transfer-completed": "メッセージを送信しました", "file-transfer-completed": "ファイル転送が完了しました", "file-content-incorrect": "無効なファイル内容です", "files-incorrect": "ファイルが間違っています", @@ -59,7 +59,7 @@ }, "header": { "cancel-share-mode": "キャンセル", - "theme-auto_title": "システムテーマに合わせる", + "theme-auto_title": "システムのテーマに合わせる", "install_title": "PairDropをインストール", "theme-dark_title": "常にダークテーマを使用する", "pair-device_title": "他のデバイスとペアリングする", @@ -74,7 +74,7 @@ "expand_title": "ヘッダーボタン列を拡大する" }, "instructions": { - "x-instructions_mobile": "タップでファイルを送信、長押しでメッセージを送信します", + "x-instructions_mobile": "タップでファイル送信、長押しでメッセージ送信", "x-instructions-share-mode_desktop": "クリックして{{descriptor}}を送信", "activate-share-mode-and-other-files-plural": "とその他{{count}}個のファイル", "x-instructions-share-mode_mobile": "タップして{{descriptor}}を送信", @@ -82,7 +82,7 @@ "no-peers-subtitle": "ペアリングや公開ルームを使用すると、他のネットワーク上のデバイスと共有できます", "activate-share-mode-shared-text": "共有されたテキスト", "x-instructions_desktop": "左クリックでファイルを送信、右クリックでメッセージを送信します", - "no-peers-title": "ファイルを共有するには他のデバイスでPairDropを開いてください", + "no-peers-title": "ファイル共有するには他のデバイスでPairDropを開きます", "x-instructions_data-drop-peer": "ドロップするとこのデバイスに送信します", "x-instructions_data-drop-bg": "送信したいデバイスの上でドロップしてください", "no-peers_data-drop-bg": "送信したいデバイスの上でドロップしてください", @@ -103,7 +103,7 @@ "dialogs": { "base64-paste-to-send": "ここをタップして{{type}}を送信", "auto-accept-instructions-2": "」を有効にすると、そのデバイスから送信されたすべてのファイルを自動的に受け入れます。", - "receive-text-title": "メッセージを受信しました", + "receive-text-title": "メッセージを受信", "edit-paired-devices-title": "ペアリング設定", "cancel": "キャンセル", "auto-accept-instructions-1": "「", @@ -123,14 +123,14 @@ "file-other-description-image": "とその他1個の画像", "temporary-public-room-title": "公開ルーム", "base64-files": "ファイル", - "has-sent": "が送信しました:", + "has-sent": "が送信:", "file-other-description-file": "とその他1個のファイル", "close": "閉じる", "system-language": "システム言語", "unpair": "ペアリング解除", "title-image": "画像", "file-other-description-file-plural": "とその他{{count}}個のファイル", - "would-like-to-share": "が以下のファイルを共有しようとしています", + "would-like-to-share": "がこれを共有しています", "send-message-to": "このデバイスにメッセージを送信:", "language-selector-title": "言語設定", "pair": "ペアリング", @@ -174,8 +174,8 @@ "privacypolicy_title": "プライバシーポリシーを開く" }, "document-titles": { - "file-transfer-requested": "ファイルの転送がリクエストされました", - "image-transfer-requested": "画像の転送がリクエストされました", + "file-transfer-requested": "ファイル転送の要求があります", + "image-transfer-requested": "画像の転送の要求があります", "message-received-plural": "{{count}}個のメッセージを受信しました", "message-received": "メッセージを受信しました", "file-received": "ファイルを受信しました", From 341fa7fdf06b1caeeb538afe1f353c8f6ad30a2c Mon Sep 17 00:00:00 2001 From: Chris Lovett <57230137+realchrislovett@users.noreply.github.com> Date: Wed, 19 Feb 2025 05:46:00 -0500 Subject: [PATCH 33/45] Restore desktop Chrome PWA button (#383) * Restore desktop Chrome PWA button with start_url ./ --- public/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/public/manifest.json b/public/manifest.json index edcf69a..3cced84 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -26,6 +26,7 @@ } ], "background_color": "#efefef", + "start_url": "./", "display": "standalone", "theme_color": "#3367d6", "screenshots" : [ From c7b7badb3d65279a7902fc4f8c733d0c1516ca17 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 19 Feb 2025 12:24:41 +0100 Subject: [PATCH 34/45] Increase version to v1.11.1 ## Fixes - Fix PWA install button on chromium based browsers (#383) (Thanks @realchrislovett) - Fix wrong file extension on chromium based browsers if mime type is not set (#355) ## Languages - Translations updates from Hosted Weblate (Japanese) --- .github/ISSUE_TEMPLATE/bug-report.md | 4 ++-- docs/how-to.md | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- public/index.html | 2 +- public/service-worker.js | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 5263696..6883691 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -36,7 +36,7 @@ If applicable, add screenshots to help explain your problem. **Bug occurs on official PairDrop instance https://pairdrop.net/** No | Yes -Version: v1.11.0 +Version: v1.11.1 **Bug occurs on self-hosted PairDrop instance** No | Yes @@ -44,7 +44,7 @@ No | Yes **Self-Hosted Setup** Proxy: Nginx | Apache2 Deployment: docker run | docker compose | npm run start:prod -Version: v1.11.0 +Version: v1.11.1 **Additional context** Add any other context about the problem here. diff --git a/docs/how-to.md b/docs/how-to.md index fcad02f..e1cb4da 100644 --- a/docs/how-to.md +++ b/docs/how-to.md @@ -45,11 +45,11 @@ This pairdrop-cli version was released alongside v1.10.4 #### Linux / Mac 1. Download the latest _pairdrop-cli.zip_ from the [releases page](https://github.com/schlagmichdoch/PairDrop/releases) ```shell - wget "https://github.com/schlagmichdoch/PairDrop/releases/download/v1.11.0/pairdrop-cli.zip" + wget "https://github.com/schlagmichdoch/PairDrop/releases/download/v1.11.1/pairdrop-cli.zip" ``` or ```shell - curl -LO "https://github.com/schlagmichdoch/PairDrop/releases/download/v1.11.0/pairdrop-cli.zip" + curl -LO "https://github.com/schlagmichdoch/PairDrop/releases/download/v1.11.1/pairdrop-cli.zip" ``` 2. Unzip the archive to a folder of your choice e.g. `/usr/share/pairdrop-cli/` ```shell diff --git a/package-lock.json b/package-lock.json index bdc9acd..0a2d466 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pairdrop", - "version": "1.11.0", + "version": "1.11.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pairdrop", - "version": "1.11.0", + "version": "1.11.1", "license": "ISC", "dependencies": { "express": "^4.18.2", diff --git a/package.json b/package.json index b36e5d7..7542a9b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pairdrop", - "version": "1.11.0", + "version": "1.11.1", "type": "module", "description": "", "main": "server/index.js", diff --git a/public/index.html b/public/index.html index 45df0e4..9ede262 100644 --- a/public/index.html +++ b/public/index.html @@ -668,7 +668,7 @@

PairDrop

-
v1.11.0
+
v1.11.1
diff --git a/public/service-worker.js b/public/service-worker.js index 551f25e..eb3601f 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.11.0'; +const cacheVersion = 'v1.11.1'; const cacheTitle = `pairdrop-cache-${cacheVersion}`; const forceFetch = false; // FOR DEVELOPMENT: Set to true to always update assets instead of using cached versions const relativePathsToCache = [ From b0b091c4f761f9e952d8cc065eff9efbd9b40de7 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Sun, 23 Feb 2025 19:13:44 +0100 Subject: [PATCH 35/45] FIX: switch off twitter button via env var not possible (#388) --- public/scripts/ui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 25171ae..42da3bc 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -2404,7 +2404,7 @@ class Base64Dialog extends Dialog { class AboutUI { constructor() { this.$donationBtn = $('donation-btn'); - this.$twitterBtn = $('twitter-btn'); + this.$twitterBtn = $('x-twitter-btn'); this.$mastodonBtn = $('mastodon-btn'); this.$blueskyBtn = $('bluesky-btn'); this.$customBtn = $('custom-btn'); From 80615c533c7ed483c3913bdf19c0b10226e8fe78 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Sun, 23 Feb 2025 19:20:50 +0100 Subject: [PATCH 36/45] Update Bluesky icon from square to butterfly --- public/index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/index.html b/public/index.html index 9ede262..6bdc88e 100644 --- a/public/index.html +++ b/public/index.html @@ -811,9 +811,9 @@ - - - + + + From 148eb79ef0c53877857a863d7de7bdccf7adaef8 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 24 Feb 2025 20:09:00 +0100 Subject: [PATCH 37/45] Fix alphabetical sort in supportedLocales --- public/scripts/localization.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/localization.js b/public/scripts/localization.js index ec915a5..5121d0c 100644 --- a/public/scripts/localization.js +++ b/public/scripts/localization.js @@ -5,7 +5,7 @@ class Localization { Localization.defaultLocale = "en"; Localization.supportedLocales = [ "ar", "be", "bg", "ca", "cs", "da", "de", "en", "es", "et", "eu", "fa", "fr", "he", "hu", "id", "it", "ja", - "kn", "ko", "nb", "nn", "nl", "pl", "pt-BR", "ro", "ru", "sk", "ta", "tr", "uk", "zh-CN", "zh-HK", "zh-TW" + "kn", "ko", "nb", "nl", "nn", "pl", "pt-BR", "ro", "ru", "sk", "ta", "tr", "uk", "zh-CN", "zh-HK", "zh-TW" ]; Localization.supportedLocalesRtl = ["ar", "he"]; From d7b68e214ed60ac078544dad96cc32d07c0a4a21 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 24 Feb 2025 20:17:54 +0100 Subject: [PATCH 38/45] Prevent fetch response and cache update if response is redirected --- public/service-worker.js | 86 +++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/public/service-worker.js b/public/service-worker.js index eb3601f..30c2007 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,6 +1,5 @@ const cacheVersion = 'v1.11.1'; const cacheTitle = `pairdrop-cache-${cacheVersion}`; -const forceFetch = false; // FOR DEVELOPMENT: Set to true to always update assets instead of using cached versions const relativePathsToCache = [ './', 'index.html', @@ -76,20 +75,25 @@ self.addEventListener('install', function(event) { const fromNetwork = (request, timeout) => new Promise((resolve, reject) => { const timeoutId = setTimeout(reject, timeout); - fetch(request) + fetch(request, {cache: "no-store"}) .then(response => { + if (response.redirected) { + throw new Error("Fetch is redirect. Abort usage and cache!"); + } + clearTimeout(timeoutId); resolve(response); + // Prevent requests that are in relativePathsNotToCache from being cached if (doNotCacheRequest(request)) return; - update(request) + updateCache(request) .then(() => console.log("Cache successfully updated for", request.url)) - .catch(reason => console.log("Cache could not be updated for", request.url, "Reason:", reason)); + .catch(err => console.log("Cache could not be updated for", request.url, err)); }) .catch(error => { // Handle any errors that occurred during the fetch - console.error(`Could not fetch ${request.url}. Are you online?`); + console.error(`Could not fetch ${request.url}.`); reject(error); }); }); @@ -111,16 +115,16 @@ const doNotCacheRequest = request => { }; // cache the current page to make it available for offline -const update = request => new Promise((resolve, reject) => { - if (doNotCacheRequest(request)) { - reject("Url is specifically prevented from being cached in the serviceworker."); - return; - } +const updateCache = request => new Promise((resolve, reject) => { caches .open(cacheTitle) .then(cache => fetch(request, {cache: "no-store"}) .then(response => { + if (response.redirected) { + throw new Error("Fetch is redirect. Abort usage and cache!"); + } + cache .put(request, response) .then(() => resolve()); @@ -129,9 +133,10 @@ const update = request => new Promise((resolve, reject) => { ); }); -// general strategy when making a request (eg if online try to fetch it -// from cache, if something fails fetch from network. Update cache everytime files are fetched. -// This way files should only be fetched if cacheVersion is changed +// general strategy when making a request: +// 1. Try to retrieve file from cache +// 2. If cache is not available: Fetch from network and update cache. +// This way, cached files are only updated if the cacheVersion is changed self.addEventListener('fetch', function(event) { if (event.request.method === "POST") { // Requests related to Web Share Target. @@ -141,39 +146,46 @@ self.addEventListener('fetch', function(event) { })()); } else { - // Regular requests not related to Web Share Target. - if (forceFetch) { - event.respondWith(fromNetwork(event.request, 10000)); - } - else { - event.respondWith( - fromCache(event.request) + // Regular requests not related to Web Share Target: + // If request is excluded from cache -> respondWith fromNetwork + // else -> try fromCache first + event.respondWith( + doNotCacheRequest(event.request) + ? fromNetwork(event.request, 10000) + : fromCache(event.request) .then(rsp => { // if fromCache resolves to undefined fetch from network instead - return rsp || fromNetwork(event.request, 10000); + if (!rsp) { + throw new Error("No match found."); + } + return rsp; }) - ); - } + .catch(error => { + console.error("Could not retrieve request from cache:", event.request.url, error); + return fromNetwork(event.request, 10000); + }) + ); } }); // on activation, we clean up the previously registered service workers self.addEventListener('activate', evt => { - return evt.waitUntil( - caches.keys() - .then(cacheNames => { - return Promise.all( - cacheNames.map(cacheName => { - if (cacheName !== cacheTitle) { - return caches.delete(cacheName); - } - }) - ); - }) - ) - } -); + return evt.waitUntil( + caches + .keys() + .then(cacheNames => { + return Promise.all( + cacheNames.map(cacheName => { + if (cacheName !== cacheTitle) { + console.log("Delete cache:", cacheName); + return caches.delete(cacheName); + } + }) + ); + }) + ) +}); const evaluateRequestData = function (request) { return new Promise(async (resolve) => { From d18e290ad42f6a4d83d0c251a8fda07f4b901382 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 24 Feb 2025 20:18:53 +0100 Subject: [PATCH 39/45] Add missing files to paths that get cached upon sw installation --- public/service-worker.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/public/service-worker.js b/public/service-worker.js index 30c2007..12ca3c1 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -6,6 +6,7 @@ const relativePathsToCache = [ 'manifest.json', 'styles/styles-main.css', 'styles/styles-deferred.css', + 'scripts/browser-tabs-connector.js', 'scripts/localization.js', 'scripts/main.js', 'scripts/network.js', @@ -27,14 +28,19 @@ const relativePathsToCache = [ 'images/android-chrome-512x512.png', 'images/android-chrome-512x512-maskable.png', 'images/apple-touch-icon.png', + 'fonts/OpenSans/static/OpenSans-Medium.ttf', 'lang/ar.json', 'lang/be.json', + 'lang/bg.json', 'lang/ca.json', 'lang/cs.json', 'lang/da.json', 'lang/de.json', 'lang/en.json', 'lang/es.json', + 'lang/et.json', + 'lang/eu.json', + 'lang/fa.json', 'lang/fr.json', 'lang/he.json', 'lang/hu.json', @@ -42,15 +48,20 @@ const relativePathsToCache = [ 'lang/it.json', 'lang/ja.json', 'lang/kn.json', + 'lang/ko.json', 'lang/nb.json', 'lang/nl.json', + 'lang/nn.json', 'lang/pl.json', 'lang/pt-BR.json', 'lang/ro.json', 'lang/ru.json', + 'lang/sk.json', + 'lang/ta.json', 'lang/tr.json', 'lang/uk.json', 'lang/zh-CN.json', + 'lang/zh-HK.json', 'lang/zh-TW.json' ]; const relativePathsNotToCache = [ From dbd6321fecee8639992b2b6724176721ed436103 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 24 Feb 2025 20:21:16 +0100 Subject: [PATCH 40/45] Speed up update process by skipping waiting and claiming currently open pages -> no need to close all tabs in order to get an update anymore --- public/service-worker.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/public/service-worker.js b/public/service-worker.js index 12ca3c1..ad5e7ea 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -69,14 +69,16 @@ const relativePathsNotToCache = [ ] self.addEventListener('install', function(event) { - // Perform install steps + // Perform install steps + console.log("Cache files for sw:", cacheVersion); event.waitUntil( caches.open(cacheTitle) .then(function(cache) { return cache .addAll(relativePathsToCache) .then(_ => { - console.log('All files cached.'); + console.log('All files cached for sw:', cacheVersion); + self.skipWaiting(); }); }) ); @@ -182,6 +184,8 @@ self.addEventListener('fetch', function(event) { // on activation, we clean up the previously registered service workers self.addEventListener('activate', evt => { + console.log("Activate sw:", cacheVersion); + evt.waitUntil(clients.claim()); return evt.waitUntil( caches .keys() From abd3a0c47c5c4346e17d80267e205024cc5a01e9 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 24 Feb 2025 03:01:56 +0100 Subject: [PATCH 41/45] Translated using Weblate (Bengali) Currently translated at 17.4% (29 of 166 strings) Co-authored-by: Hosted Weblate Co-authored-by: Saif Mahmud Translate-URL: https://hosted.weblate.org/projects/pairdrop/pairdrop-spa/bn/ Translation: PairDrop/pairdrop-spa --- public/lang/bn.json | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/public/lang/bn.json b/public/lang/bn.json index 4f3f52a..1795d97 100644 --- a/public/lang/bn.json +++ b/public/lang/bn.json @@ -1,6 +1,35 @@ { "header": { "about_title": "পেয়ার ড্রপ সম্পর্কে", - "install_title": "পেয়ার ড্রপ ইন্সটল করুন" + "install_title": "পেয়ার ড্রপ ইন্সটল করুন", + "pair-device_title": "ডিভাইস স্থায়ী ভাবে যুক্ত করুন", + "cancel-share-mode": "বাতিল", + "theme-light_title": "সবসময় সাদা থিম ব্যাবহার", + "language-selector_title": "ভাষা সেট করুন", + "about_aria-label": "পেয়ারড্রপ সম্পর্কে", + "theme-auto_title": "থিমের ধরন ডিভাইস অনুযায়ী", + "theme-dark_title": "সবসময় কালো থিব ব্যাবহার", + "notification_title": "নোটিফিকেশন চালু করুন", + "edit-paired-devices_title": "যুক্ত ডিভাইস সম্পাদনা করুন", + "join-public-room_title": "সাময়িক ভাবে পাবলিক রুমে জয়েন করুন", + "edit-share-mode": "সম্পাদনা", + "expand_title": "হেডার বোতামের সারিটি বড় করুন" + }, + "instructions": { + "activate-share-mode-and-other-file": "আর একটি ফাইল যোগ করুন", + "activate-share-mode-shared-file": "পাঠানো ফাইল", + "no-peers-subtitle": "ডিভাইস প্রদর্শিত হতে নতুন ডিভাইস যুক্ত করুন অথবা পাবলিক রুমে জয়েন দিন", + "no-peers-title": "ফাইল পাঠানোর জন্য অন্যান্য ডিভাইসে পেয়ারড্রপ খুলুন", + "x-instructions_data-drop-bg": "প্রাপক নির্বাচন করতে ছেড়ে দিন", + "no-peers_data-drop-bg": "প্রাপক নির্বাচন ছেড়ে দিন", + "x-instructions_desktop": "ফাইল পাঠাতে ক্লিক করুন অথবা মেসেজ পাঠাতে ডানে চাপুন", + "x-instructions_mobile": "ফাইল পাঠাতে ক্লিক করুন অথবা বেশি চেপে মেসেজ পাঠান", + "x-instructions_data-drop-peer": "পিয়ারকে পাঠানোর জন্য রিলিজ করুন", + "x-instructions-share-mode_desktop": "পাঠাতে ক্লিক করুন", + "x-instructions-share-mode_mobile": "পাঠাতে ক্লিক করুন", + "activate-share-mode-base": "অন্য ডিভাইসে পাঠাতে পেয়ারড্রপ খুলুন", + "activate-share-mode-and-other-files-plural": "অন্য ফাইল যোগ করুন", + "activate-share-mode-shared-text": "পাঠানো টেক্সট", + "activate-share-mode-shared-files-plural": "পাঠানো ফাইল গুলো" } } From f6f3db2df96276a10528ffa141513b11ebba592e Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 24 Feb 2025 03:01:56 +0100 Subject: [PATCH 42/45] Translated using Weblate (Japanese) Currently translated at 100.0% (166 of 166 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (166 of 166 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (166 of 166 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (166 of 166 strings) Co-authored-by: Hosted Weblate Co-authored-by: maboroshin Co-authored-by: mottcha <89951503+mottcha@users.noreply.github.com> Translate-URL: https://hosted.weblate.org/projects/pairdrop/pairdrop-spa/ja/ Translation: PairDrop/pairdrop-spa --- public/lang/ja.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/public/lang/ja.json b/public/lang/ja.json index d4e8fdb..949621a 100644 --- a/public/lang/ja.json +++ b/public/lang/ja.json @@ -1,17 +1,17 @@ { "footer": { "webrtc": "(WebRTCが無効なため)", - "public-room-devices_title": "公開ルーム内のデバイスは、接続中のネットワークと関係なくアクセスできます。", + "public-room-devices_title": "公開ルーム内のデバイスは、別のネットワークからもアクセスできます。", "display-name_data-placeholder": "読み込み中…", "display-name_title": "デバイス名を変更する", "traffic": "この通信は", - "paired-devices_title": "ペアリング済みデバイスであれば、接続中のネットワークに関わらずアクセスできます。", + "paired-devices_title": "ペアリング済みデバイスは、別のネットワークからもアクセスできます。", "public-room-devices": "ルーム{{roomId}}", "paired-devices": "ペアリング済みデバイス", - "on-this-network": "このネットワーク上", + "on-this-network": "このネットワーク内", "routed": "サーバーを経由します", "discovery": "このデバイスを検出可能なネットワーク:", - "on-this-network_title": "このネットワーク上のすべてのデバイスからアクセスできます。", + "on-this-network_title": "このネットワーク内のすべてのデバイスからアクセスできます。", "known-as": "このデバイスの名前:" }, "notifications": { @@ -20,9 +20,9 @@ "message-received": "{{name}}から受信したメッセージ(クリックしてコピー)", "rate-limit-join-key": "レート制限に到達しました。10秒待ってから再度お試しください。", "connecting": "接続中…", - "pairing-key-invalidated": "コード{{key}}が失効しました", + "pairing-key-invalidated": "コード{{key}}は無効になりました", "pairing-key-invalid": "無効なコード", - "connected": "接続しました", + "connected": "接続済み", "pairing-not-persistent": "このデバイスとのペアリングは解除される可能性があります", "text-content-incorrect": "無効なテキスト内容です", "message-transfer-completed": "メッセージを送信しました", @@ -39,7 +39,7 @@ "copied-to-clipboard-error": "コピーできませんでした。手動でコピーしてください。", "pairing-success": "ペアリングしました", "clipboard-content-incorrect": "無効なクリップボード内容です", - "display-name-changed-temporarily": "この接続のみデバイス名が変更されました", + "display-name-changed-temporarily": "この接続でのみデバイス名が変更されました", "copied-to-clipboard": "クリップボードにコピーしました", "offline": "オフラインです", "pairing-tabs-error": "同じWebブラウザーで開いたタブ同士でペアリングすることはできません", @@ -79,9 +79,9 @@ "activate-share-mode-and-other-files-plural": "とその他{{count}}個のファイル", "x-instructions-share-mode_mobile": "タップして{{descriptor}}を送信", "activate-share-mode-base": "他のデバイスでPairDropを開いて送信します", - "no-peers-subtitle": "ペアリングや公開ルームを使用すると、他のネットワーク上のデバイスと共有できます", + "no-peers-subtitle": "ペアリングや公開ルームを使うと、別のネットワークにあるデバイスと共有できます", "activate-share-mode-shared-text": "共有されたテキスト", - "x-instructions_desktop": "左クリックでファイルを送信、右クリックでメッセージを送信します", + "x-instructions_desktop": "左クリックでファイル送信、右クリックでメッセージ送信", "no-peers-title": "ファイル共有するには他のデバイスでPairDropを開きます", "x-instructions_data-drop-peer": "ドロップするとこのデバイスに送信します", "x-instructions_data-drop-bg": "送信したいデバイスの上でドロップしてください", @@ -94,7 +94,7 @@ "peer-ui": { "processing": "処理中…", "click-to-send-share-mode": "クリックして{{descriptor}}を送信", - "click-to-send": "左クリックでファイルを送信、右クリックでメッセージを送信します", + "click-to-send": "左クリックでファイル送信、右クリックでメッセージ送信", "waiting": "待機中…", "connection-hash": "エンドツーエンド暗号化のセキュリティを確認するには、両方のデバイスのセキュリティナンバーを確認してください", "preparing": "準備中…", @@ -102,7 +102,7 @@ }, "dialogs": { "base64-paste-to-send": "ここをタップして{{type}}を送信", - "auto-accept-instructions-2": "」を有効にすると、そのデバイスから送信されたすべてのファイルを自動的に受け入れます。", + "auto-accept-instructions-2": "」が有効なら、そのデバイスが送信したすべてのファイルを自動で受け入れます。", "receive-text-title": "メッセージを受信", "edit-paired-devices-title": "ペアリング設定", "cancel": "キャンセル", @@ -126,7 +126,7 @@ "has-sent": "が送信:", "file-other-description-file": "とその他1個のファイル", "close": "閉じる", - "system-language": "システム言語", + "system-language": "システムの言語", "unpair": "ペアリング解除", "title-image": "画像", "file-other-description-file-plural": "とその他{{count}}個のファイル", @@ -135,7 +135,7 @@ "language-selector-title": "言語設定", "pair": "ペアリング", "hr-or": "または", - "scan-qr-code": "もしくはQRコードをスキャンしてください。", + "scan-qr-code": "QRコードをスキャンしてください。", "input-key-on-this-device": "このコードを他のデバイスに入力するか", "download-again": "もう一度ダウンロードする", "accept": "承諾", @@ -162,13 +162,13 @@ "share-text-title": "テキストメッセージを共有します" }, "about": { - "claim": "デバイス間のファイル共有を手軽に実現します", - "tweet_title": "PairDropのことをポストする", + "claim": "デバイス間でかんたんファイル共有", + "tweet_title": "PairDropについてポスト", "close-about_aria-label": "PairDropについてを閉じる", "buy-me-a-coffee_title": "コーヒーを一杯おごってください!", - "github_title": "PairDrop on GitHub", + "github_title": "GitHub上のPairDropプロジェクト", "faq_title": "FAQ", - "mastodon_title": "MastodonにPairDropのことをトゥートする", + "mastodon_title": "MastodonでPairDropについてトゥート", "bluesky_title": "BlueSkyでフォロー", "custom_title": "フォロー", "privacypolicy_title": "プライバシーポリシーを開く" From 7639aca84c0fb2400a970844d658571aedf4050f Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 24 Feb 2025 20:45:12 +0100 Subject: [PATCH 43/45] Update node dependencies --- package-lock.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0a2d466..7d76ba5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -318,16 +318,16 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", - "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", + "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "get-proto": "^1.0.0", + "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", @@ -815,9 +815,9 @@ } }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", "engines": { "node": ">=10.0.0" }, From 4862ba3067be1a0f2e0d1e94861dc9200b5bfeea Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 24 Feb 2025 20:46:32 +0100 Subject: [PATCH 44/45] Increase version to v1.11.2 ## Enhancements - Speed up update process by skipping waiting and claiming currently open pages -> no need to close all tabs in order to get an update anymore - Update Bluesky icon from square to butterfly - Update node dependencies ## Fixes - Prevent service-worker from responding with redirect (fixes #384) - Fix: switch off twitter button via env var not possible (#388) - Add missing files to paths that get cached upon sw installation ## Languages - Translations updates from Hosted Weblate (Japanese) --- .github/ISSUE_TEMPLATE/bug-report.md | 4 ++-- docs/how-to.md | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- public/index.html | 2 +- public/service-worker.js | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 6883691..5abfbb4 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -36,7 +36,7 @@ If applicable, add screenshots to help explain your problem. **Bug occurs on official PairDrop instance https://pairdrop.net/** No | Yes -Version: v1.11.1 +Version: v1.11.2 **Bug occurs on self-hosted PairDrop instance** No | Yes @@ -44,7 +44,7 @@ No | Yes **Self-Hosted Setup** Proxy: Nginx | Apache2 Deployment: docker run | docker compose | npm run start:prod -Version: v1.11.1 +Version: v1.11.2 **Additional context** Add any other context about the problem here. diff --git a/docs/how-to.md b/docs/how-to.md index e1cb4da..df6d3a1 100644 --- a/docs/how-to.md +++ b/docs/how-to.md @@ -45,11 +45,11 @@ This pairdrop-cli version was released alongside v1.10.4 #### Linux / Mac 1. Download the latest _pairdrop-cli.zip_ from the [releases page](https://github.com/schlagmichdoch/PairDrop/releases) ```shell - wget "https://github.com/schlagmichdoch/PairDrop/releases/download/v1.11.1/pairdrop-cli.zip" + wget "https://github.com/schlagmichdoch/PairDrop/releases/download/v1.11.2/pairdrop-cli.zip" ``` or ```shell - curl -LO "https://github.com/schlagmichdoch/PairDrop/releases/download/v1.11.1/pairdrop-cli.zip" + curl -LO "https://github.com/schlagmichdoch/PairDrop/releases/download/v1.11.2/pairdrop-cli.zip" ``` 2. Unzip the archive to a folder of your choice e.g. `/usr/share/pairdrop-cli/` ```shell diff --git a/package-lock.json b/package-lock.json index 7d76ba5..be60a08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pairdrop", - "version": "1.11.1", + "version": "1.11.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pairdrop", - "version": "1.11.1", + "version": "1.11.2", "license": "ISC", "dependencies": { "express": "^4.18.2", diff --git a/package.json b/package.json index 7542a9b..e4032f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pairdrop", - "version": "1.11.1", + "version": "1.11.2", "type": "module", "description": "", "main": "server/index.js", diff --git a/public/index.html b/public/index.html index 6bdc88e..70eda5b 100644 --- a/public/index.html +++ b/public/index.html @@ -668,7 +668,7 @@

PairDrop

-
v1.11.1
+
v1.11.2
diff --git a/public/service-worker.js b/public/service-worker.js index ad5e7ea..47822ae 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.11.1'; +const cacheVersion = 'v1.11.2'; const cacheTitle = `pairdrop-cache-${cacheVersion}`; const relativePathsToCache = [ './', From 31ec776fb304fd7ddf0eeffba41b1631f712887b Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Tue, 25 Feb 2025 18:19:38 +0100 Subject: [PATCH 45/45] Only handle requests with the same origin via the service worker --- public/service-worker.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/public/service-worker.js b/public/service-worker.js index 47822ae..e1f4f45 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -151,7 +151,14 @@ const updateCache = request => new Promise((resolve, reject) => { // 2. If cache is not available: Fetch from network and update cache. // This way, cached files are only updated if the cacheVersion is changed self.addEventListener('fetch', function(event) { - if (event.request.method === "POST") { + const swOrigin = new URL(self.location.href).origin; + const requestOrigin = new URL(event.request.url).origin; + + if (swOrigin !== requestOrigin) { + // Do not handle requests from other origin + event.respondWith(fetch(event.request)); + } + else if (event.request.method === "POST") { // Requests related to Web Share Target. event.respondWith((async () => { const share_url = await evaluateRequestData(event.request);