From 6c6f288c3dac8c6c24f8c82b2ad9701240e0cfd0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Mar 2023 05:17:56 +0000 Subject: [PATCH 001/568] Bump ws from 8.12.1 to 8.13.0 Bumps [ws](https://github.com/websockets/ws) from 8.12.1 to 8.13.0. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/8.12.1...8.13.0) --- updated-dependencies: - dependency-name: ws dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index deb6464..f77b5c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "express-rate-limit": "^6.7.0", "ua-parser-js": "^1.0.34", "unique-names-generator": "^4.3.0", - "ws": "^8.12.1" + "ws": "^8.13.0" }, "engines": { "node": ">=15" @@ -633,9 +633,9 @@ } }, "node_modules/ws": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz", - "integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "engines": { "node": ">=10.0.0" }, @@ -1095,9 +1095,9 @@ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, "ws": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz", - "integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "requires": {} } } diff --git a/package.json b/package.json index 787d815..8a0def4 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "express-rate-limit": "^6.7.0", "ua-parser-js": "^1.0.34", "unique-names-generator": "^4.3.0", - "ws": "^8.12.1" + "ws": "^8.13.0" }, "engines": { "node": ">=15" From 4e0fb89720a420fa59d08587486767d053c5a74d Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 13 Mar 2023 14:21:26 +0100 Subject: [PATCH 002/568] replace javascript operators `??` and `?.` to support older browsers (see #79) --- public/scripts/network.js | 2 +- public/scripts/ui.js | 13 ++++++++++--- public_included_ws_fallback/scripts/network.js | 2 +- public_included_ws_fallback/scripts/ui.js | 13 ++++++++++--- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/public/scripts/network.js b/public/scripts/network.js index 615dae2..e9692d5 100644 --- a/public/scripts/network.js +++ b/public/scripts/network.js @@ -390,7 +390,7 @@ class Peer { } _onFilesHeader(header) { - if (this._requestAccepted?.header.length) { + if (this._requestAccepted && this._requestAccepted.header.length) { this._lastProgress = 0; this._digester = new FileDigester({size: header.size, name: header.name, mime: header.mime}, this._requestAccepted.totalSize, diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 7610834..f471f4e 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -107,8 +107,15 @@ class PeersUI { _getSavedDisplayName() { return new Promise((resolve) => { PersistentStorage.get('editedDisplayName') - .then(displayName => resolve(displayName ?? "")) - .catch(_ => resolve(localStorage.getItem('editedDisplayName') ?? "")) + .then(displayName => { + if (!displayName) displayName = ""; + resolve(displayName); + }) + .catch(_ => { + let displayName = localStorage.getItem('editedDisplayName'); + if (!displayName) displayName = ""; + resolve(displayName); + }) }); } @@ -825,7 +832,7 @@ class ReceiveRequestDialog extends ReceiveDialog { const connectionHash = $(peerId).ui._connectionHash; this._parseFileData(displayName, connectionHash, request.header, request.imagesOnly, request.totalSize); - if (request.thumbnailDataUrl?.substring(0, 22) === "data:image/jpeg;base64") { + if (request.thumbnailDataUrl && request.thumbnailDataUrl.substring(0, 22) === "data:image/jpeg;base64") { let element = document.createElement('img'); element.src = request.thumbnailDataUrl; this.$previewBox.appendChild(element) diff --git a/public_included_ws_fallback/scripts/network.js b/public_included_ws_fallback/scripts/network.js index 0ddf0e7..f01b6bd 100644 --- a/public_included_ws_fallback/scripts/network.js +++ b/public_included_ws_fallback/scripts/network.js @@ -401,7 +401,7 @@ class Peer { } _onFilesHeader(header) { - if (this._requestAccepted?.header.length) { + if (this._requestAccepted && this._requestAccepted.header.length) { this._lastProgress = 0; this._digester = new FileDigester({size: header.size, name: header.name, mime: header.mime}, this._requestAccepted.totalSize, diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index 91d2f32..d705b9b 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -107,8 +107,15 @@ class PeersUI { _getSavedDisplayName() { return new Promise((resolve) => { PersistentStorage.get('editedDisplayName') - .then(displayName => resolve(displayName ?? "")) - .catch(_ => resolve(localStorage.getItem('editedDisplayName') ?? "")) + .then(displayName => { + if (!displayName) displayName = ""; + resolve(displayName); + }) + .catch(_ => { + let displayName = localStorage.getItem('editedDisplayName'); + if (!displayName) displayName = ""; + resolve(displayName); + }) }); } @@ -826,7 +833,7 @@ class ReceiveRequestDialog extends ReceiveDialog { const connectionHash = $(peerId).ui._connectionHash; this._parseFileData(displayName, connectionHash, request.header, request.imagesOnly, request.totalSize); - if (request.thumbnailDataUrl?.substring(0, 22) === "data:image/jpeg;base64") { + if (request.thumbnailDataUrl && request.thumbnailDataUrl.substring(0, 22) === "data:image/jpeg;base64") { let element = document.createElement('img'); element.src = request.thumbnailDataUrl; this.$previewBox.appendChild(element) From b7781e2bab9c171b7874b03044cc03fabaf9d12d Mon Sep 17 00:00:00 2001 From: Kaindl Network <82705244+kgncloud@users.noreply.github.com> Date: Mon, 13 Mar 2023 20:38:36 +0100 Subject: [PATCH 003/568] Add Healthcheck to Dockerfile --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 3057f35..a307a45 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,3 +9,6 @@ RUN npm ci COPY . . EXPOSE 3000 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD wget --quiet --tries=1 --spider http://localhost:3000 || exit 1 From 4a0cd1f49a7a51cb878d62b6148484abf0b858b9 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Tue, 14 Mar 2023 11:48:57 +0100 Subject: [PATCH 004/568] Update issue templates: Point out the FAQ --- .github/ISSUE_TEMPLATE/bug-report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index cbb6d42..39a6624 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -1,6 +1,6 @@ --- name: Bug Report -about: Create a report to help us improve +about: Create a report to help us improve. Please check the FAQ first. title: 'Bug:/Enhancement:/Feature Request: ' labels: '' assignees: '' From 17abc91c86a0f17a2174bd729313854843cd87c9 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 13 Mar 2023 12:15:55 +0100 Subject: [PATCH 005/568] rename function and add event to achieve compatibility with snapdrop-android app --- public/scripts/network.js | 7 +++++-- public_included_ws_fallback/scripts/network.js | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/public/scripts/network.js b/public/scripts/network.js index e9692d5..bf5ac2d 100644 --- a/public/scripts/network.js +++ b/public/scripts/network.js @@ -329,7 +329,7 @@ class Peer { this._onFilesTransferRequest(messageJSON); break; case 'header': - this._onFilesHeader(messageJSON); + this._onFileHeader(messageJSON); break; case 'partition': this._onReceivedPartitionEnd(messageJSON); @@ -389,7 +389,7 @@ class Peer { this._requestPending = null; } - _onFilesHeader(header) { + _onFileHeader(header) { if (this._requestAccepted && this._requestAccepted.header.length) { this._lastProgress = 0; this._digester = new FileDigester({size: header.size, name: header.name, mime: header.mime}, @@ -443,6 +443,9 @@ class Peer { this._abortTransfer(); } + // include for compatibility with Snapdrop for Android app + Events.fire('file-received', fileBlob); + this._filesReceived.push(fileBlob); if (!this._requestAccepted.header.length) { this._busy = false; diff --git a/public_included_ws_fallback/scripts/network.js b/public_included_ws_fallback/scripts/network.js index f01b6bd..78f6f5a 100644 --- a/public_included_ws_fallback/scripts/network.js +++ b/public_included_ws_fallback/scripts/network.js @@ -340,7 +340,7 @@ class Peer { this._onFilesTransferRequest(messageJSON); break; case 'header': - this._onFilesHeader(messageJSON); + this._onFileHeader(messageJSON); break; case 'partition': this._onReceivedPartitionEnd(messageJSON); @@ -400,7 +400,7 @@ class Peer { this._requestPending = null; } - _onFilesHeader(header) { + _onFileHeader(header) { if (this._requestAccepted && this._requestAccepted.header.length) { this._lastProgress = 0; this._digester = new FileDigester({size: header.size, name: header.name, mime: header.mime}, @@ -454,6 +454,9 @@ class Peer { this._abortTransfer(); } + // include for compatibility with Snapdrop for Android app + Events.fire('file-received', fileBlob); + this._filesReceived.push(fileBlob); if (!this._requestAccepted.header.length) { this._busy = false; From 1f97c12562ce71a5a632360eb5c0520095700913 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 13 Mar 2023 13:03:07 +0100 Subject: [PATCH 006/568] fix overflow of long device names for snapdrop-android --- public/styles.css | 2 +- public_included_ws_fallback/styles.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/styles.css b/public/styles.css index 73e4cb2..7b6229a 100644 --- a/public/styles.css +++ b/public/styles.css @@ -459,7 +459,7 @@ x-peer[status] x-icon { text-align: center; } -.name { +.device-descriptor > div { width: 100%; white-space: nowrap; overflow: hidden; diff --git a/public_included_ws_fallback/styles.css b/public_included_ws_fallback/styles.css index 0a68922..5ba37a1 100644 --- a/public_included_ws_fallback/styles.css +++ b/public_included_ws_fallback/styles.css @@ -486,7 +486,7 @@ x-peer.ws-peer .highlight-wrapper { text-align: center; } -.name { +.device-descriptor > div { width: 100%; white-space: nowrap; overflow: hidden; From 3f0909637bc19b08e9e2b3a9cc0f664a5f770cf2 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 13 Mar 2023 23:48:42 +0100 Subject: [PATCH 007/568] fix full button width if only one button is shown --- public/styles.css | 2 +- public_included_ws_fallback/styles.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/styles.css b/public/styles.css index 7b6229a..644bd04 100644 --- a/public/styles.css +++ b/public/styles.css @@ -720,7 +720,7 @@ x-paper > div:last-child { x-paper > div:last-child > .button { height: 100%; - width: 50%; + width: 100%; } x-paper > div:last-child > .button:not(:last-child) { diff --git a/public_included_ws_fallback/styles.css b/public_included_ws_fallback/styles.css index 5ba37a1..6193cf2 100644 --- a/public_included_ws_fallback/styles.css +++ b/public_included_ws_fallback/styles.css @@ -746,7 +746,7 @@ x-paper > div:last-child { x-paper > div:last-child > .button { height: 100%; - width: 50%; + width: 100%; } x-paper > div:last-child > .button:not(:last-child) { From 01cd670afc73365f41a815941bce5361a90fea7a Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Tue, 14 Mar 2023 15:43:40 +0100 Subject: [PATCH 008/568] Convert FAQ to collapsible sections and add back third-party apps --- docs/faq.md | 119 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 95 insertions(+), 24 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index ac493b7..6e594c1 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,31 +1,35 @@ # Frequently Asked Questions -### Instructions / Discussions -* [Video Instructions](https://www.youtube.com/watch?v=4XN02GkcHUM) (Big thanks to [TheiTeckHq](https://www.youtube.com/channel/UC_DUzWMb8gZZnAbISQjmAfQ)) -* [idownloadblog](http://www.idownloadblog.com/2015/12/29/snapdrop/) -* [thenextweb](http://thenextweb.com/insider/2015/12/27/snapdrop-is-a-handy-web-based-replacement-for-apples-fiddly-airdrop-file-transfer-tool/) -* [winboard](http://www.winboard.org/artikel-ratgeber/6253-dateien-vom-desktop-pc-mit-anderen-plattformen-teilen-mit-snapdrop.html) -* [免費資源網路社群](https://free.com.tw/snapdrop/) -* [Hackernews](https://news.ycombinator.com/front?day=2020-12-24) -* [Reddit](https://www.reddit.com/r/Android/comments/et4qny/snapdrop_is_a_free_open_source_cross_platform/) -* [Producthunt](https://www.producthunt.com/posts/snapdrop) +
+ + Help! I can't install the PWA! + -### Help! I can't install the PWA! if you are using a Chromium-based browser (Chrome, Edge, Brave, etc.), you can easily install PairDrop PWA on your desktop by clicking the install-button in the top-right corner while on [pairdrop.net](https://pairdrop.net). -Example on how to install a pwa with Edge +Example on how to install a pwa with Edge On Firefox, PWAs are installable via [this browser extensions](https://addons.mozilla.org/de/firefox/addon/pwas-for-firefox/) +
-### Are there any shortcuts? -Sure! +
+ + Shortcuts? + + +Shortcuts! - Send a message with `CTRL + ENTER` - Close all send and pair dialogs by pressing `Escape`. - Copy a received message to clipboard with `CTRL/⌘ + C`. - Accept file transfer request with `Enter` and decline with `Escape`. +
+ +
+ + How to save images directly to the gallery on iOS? + -### When I receive images on iOS I cannot add them directly to the gallery? Apparently, iOS does not allow images shared from a website to be saved to the gallery directly. It simply does not offer the option for images shared from a website. @@ -33,31 +37,80 @@ iOS Shortcuts to the win: I created a simple iOS shortcut that takes your photos and saves them to your gallery: https://routinehub.co/shortcut/13988/ -### Is it possible to send files or text directly from the context or share menu? +
+ +
+ + Is it possible to send files or text directly from the context or share menu? + + Yes, it finally is! * [Send files directly from context menu on Windows](/docs/how-to.md#send-files-directly-from-context-menu-on-windows) * [Send directly from share menu on iOS](/docs/how-to.md#send-directly-from-share-menu-on-ios) * [Send directly from share menu on Android](/docs/how-to.md#send-directly-from-share-menu-on-android) -### Is it possible to send files or text directly via CLI? +
+ +
+ + Is it possible to send files or text directly via CLI? + + Yes, it is! * [Send directly from command-line interface](/docs/how-to.md#send-directly-via-command-line-interface) -### What about the connection? Is it a P2P-connection directly from device to device or is there any third-party-server? + +
+ +
+ + Are there any Third-Party Apps? + + +Here's a list of some third-party apps compatible with PairDrop: + +1. [Snapdrop Android App](https://github.com/fm-sys/snapdrop-android) +2. [Snapdrop for Firefox (Addon)](https://github.com/ueen/SnapdropFirefoxAddon) +3. Feel free to make one :) +
+ +
+ + What about the connection? Is it a P2P-connection directly from device to device or is there any third-party-server? + + It uses a P2P connection if WebRTC is supported by the browser. WebRTC needs a Signaling Server, but it is only used to establish a connection and is not involved in the file transfer. If your devices are paired and behind a NAT, the public TURN Server from [Open Relay](https://www.metered.ca/tools/openrelay/) is used to route your files and messages. -### What about privacy? Will files be saved on third-party-servers? +
+ +
+ + What about privacy? Will files be saved on third-party-servers? + + None of your files are ever sent to any server. Files are sent only between peers. PairDrop doesn't even use a database. If you are curious have a look [at the Server](https://github.com/schlagmichdoch/pairdrop/blob/master/index.js). WebRTC encrypts the files on transit. If your devices are paired and behind a NAT, the public TURN Server from [Open Relay](https://www.metered.ca/tools/openrelay/) is used to route your files and messages. -### What about security? Are my files encrypted while being sent between the computers? +
+ +
+ + What about security? Are my files encrypted while being sent between the computers? + + Yes. Your files are sent using WebRTC, which encrypts them on transit. To ensure the connection is secure and there is no MITM, compare the security number shown under the device name on both devices. The security number is different for every connection. -### Transferring many files with paired devices takes too long +
+ +
+ + Transferring many files with paired devices takes too long + + Naturally, if traffic needs to be routed through the turn server because your devices are behind different NATs, transfer speed decreases. As the public TURN server used is not super fast, you can easily [specify to use your own TURN server](https://github.com/schlagmichdoch/PairDrop/blob/master/docs/host-your-own.md#specify-stunturn-servers) if you host your own instance. @@ -71,22 +124,40 @@ Alternatively, you can open a hotspot on one of your devices to bridge the conne You can also use mobile hotspots on phones to do that. Then, all data should be sent directly between devices and your data plan should not be charged. -### Why don't you implement feature xyz? +
+ +
+ + Why don't you implement feature xyz? + + Snapdrop and PairDrop are a study in radical simplicity. The user interface is insanely simple. Features are chosen very carefully because complexity grows quadratically since every feature potentially interferes with each other feature. We focus very narrowly on a single use case: instant file transfer. We are not trying to optimize for some edge-cases. We are optimizing the user flow of the average users. Don't be sad if we decline your feature request for the sake of simplicity. If you want to learn more about simplicity you can read [Insanely Simple: The Obsession that Drives Apple's Success](https://www.amazon.com/Insanely-Simple-Ken-Segall-audiobook/dp/B007Z9686O) or [Thinking, Fast and Slow](https://www.amazon.com/Thinking-Fast-Slow-Daniel-Kahneman/dp/0374533555). +
-### Snapdrop and PairDrop are awesome! How can I support them? -* [Buy me a cover to support open source software](https://www.buymeacoffee.com/pairdrop) +
+ + Snapdrop and PairDrop are awesome! How can I support them? + + +* [Buy me a coffee to support open source software](https://www.buymeacoffee.com/pairdrop) * [File bugs, give feedback, submit suggestions](https://github.com/schlagmichdoch/pairdrop/issues) * Share PairDrop on social media. * Fix bugs and make a pull request. * Do security analysis and suggestions +* To support the original Snapdrop and its creator go to [his GitHub page](https://github.com/RobinLinus/snapdrop) +
+ +
+ + How does it work? + -### How does it work? [See here for Information about the Technical Implementation](/docs/technical-documentation.md) +
[< Back](/README.md) From f12067739342ed10bf025aa89f64bba4afbddfea Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Tue, 14 Mar 2023 15:52:15 +0100 Subject: [PATCH 009/568] increase version to v1.5.0 --- package-lock.json | 4 ++-- package.json | 2 +- public/service-worker.js | 2 +- public_included_ws_fallback/service-worker.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index f77b5c1..06d0665 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pairdrop", - "version": "1.4.5", + "version": "1.5.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "pairdrop", - "version": "1.4.5", + "version": "1.5.0", "license": "ISC", "dependencies": { "express": "^4.18.2", diff --git a/package.json b/package.json index 8a0def4..536b9c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pairdrop", - "version": "1.4.5", + "version": "1.5.0", "description": "", "main": "index.js", "scripts": { diff --git a/public/service-worker.js b/public/service-worker.js index c5d4168..502b9aa 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.4.5'; +const cacheVersion = 'v1.5.0'; const cacheTitle = `pairdrop-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', diff --git a/public_included_ws_fallback/service-worker.js b/public_included_ws_fallback/service-worker.js index 7737ecd..3809ff5 100644 --- a/public_included_ws_fallback/service-worker.js +++ b/public_included_ws_fallback/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.4.5'; +const cacheVersion = 'v1.5.0'; const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', From 680ed81bd7eb5f0fe529dd1ffb52063703652ff5 Mon Sep 17 00:00:00 2001 From: Kaindl Network <82705244+kgncloud@users.noreply.github.com> Date: Sat, 18 Mar 2023 23:52:33 +0000 Subject: [PATCH 010/568] Create --- docs/docker-swarm-usage.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 docs/docker-swarm-usage.md diff --git a/docs/docker-swarm-usage.md b/docs/docker-swarm-usage.md new file mode 100644 index 0000000..7a28350 --- /dev/null +++ b/docs/docker-swarm-usage.md @@ -0,0 +1,18 @@ +# Docker Swarm Usage + +## Healthcheck + +The Docker Image includes a Healthcheck with the following options (Look at Dockerfile): + +--interval=30s: This option specifies the time interval at which the health check should be performed. In this case, the health check will be performed every 30 seconds. + +--timeout=10s: This option specifies the amount of time to wait for a response from the health check command. If the response does not arrive within 10 seconds, the health check will be considered a failure. + +--start-period=5s: This option specifies the amount of time to wait before starting the health check process. In this case, the health check process will begin 5 seconds after the container is started. + +--retries=3: This option specifies the number of times Docker should retry the health check before considering the container to be unhealthy. + +The CMD instruction is used to define the command that will be run as part of the health check. In this case, the command is "wget --quiet --tries=1 --spider http://localhost:3000/ || exit 1". This command will attempt to connect to http://localhost:3000/ and if it fails, it will exit with a status code of 1. If this command returns a status code other than 0, the health check will be considered a failure. + +Overall, this HEALTHCHECK instruction is defining a health check process that will run every 30 seconds, wait up to 10 seconds for a response, begin 5 seconds after the container is started, and retry up to 3 times. The health check will consist of attempting to connect to http://localhost:3000/ and will consider the container to be unhealthy if it is unable to connect. + From 195dfd0bb3ac10d4bea487bf2fc44f1fd6317c33 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Sat, 25 Mar 2023 03:37:15 +0100 Subject: [PATCH 011/568] Add https requirement for PWAs to faq.md --- docs/faq.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/docs/faq.md b/docs/faq.md index 6e594c1..2f5e260 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -11,6 +11,16 @@ by clicking the install-button in the top-right corner while on [pairdrop.net](h Example on how to install a pwa with Edge On Firefox, PWAs are installable via [this browser extensions](https://addons.mozilla.org/de/firefox/addon/pwas-for-firefox/) + +
+ +Self-Hosted Instance? + +To be able to install the PWA from a self-hosted instance, the connection needs to be [established through HTTPS](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Installable_PWAs). +See [this host your own section](https://github.com/schlagmichdoch/PairDrop/blob/master/docs/host-your-own.md#testing-pwa-related-features) for more information. + +
+
@@ -23,6 +33,9 @@ Shortcuts! - Close all send and pair dialogs by pressing `Escape`. - Copy a received message to clipboard with `CTRL/⌘ + C`. - Accept file transfer request with `Enter` and decline with `Escape`. + +
+
@@ -37,6 +50,9 @@ iOS Shortcuts to the win: I created a simple iOS shortcut that takes your photos and saves them to your gallery: https://routinehub.co/shortcut/13988/ + +
+
@@ -49,6 +65,9 @@ Yes, it finally is! * [Send directly from share menu on iOS](/docs/how-to.md#send-directly-from-share-menu-on-ios) * [Send directly from share menu on Android](/docs/how-to.md#send-directly-from-share-menu-on-android) + +
+
@@ -60,6 +79,9 @@ Yes, it is! * [Send directly from command-line interface](/docs/how-to.md#send-directly-via-command-line-interface) + +
+
@@ -72,6 +94,9 @@ Here's a list of some third-party apps compatible with PairDrop: 1. [Snapdrop Android App](https://github.com/fm-sys/snapdrop-android) 2. [Snapdrop for Firefox (Addon)](https://github.com/ueen/SnapdropFirefoxAddon) 3. Feel free to make one :) + +
+
@@ -83,6 +108,9 @@ It uses a P2P connection if WebRTC is supported by the browser. WebRTC needs a S If your devices are paired and behind a NAT, the public TURN Server from [Open Relay](https://www.metered.ca/tools/openrelay/) is used to route your files and messages. + +
+
@@ -95,6 +123,9 @@ WebRTC encrypts the files on transit. If your devices are paired and behind a NAT, the public TURN Server from [Open Relay](https://www.metered.ca/tools/openrelay/) is used to route your files and messages. + +
+
@@ -104,6 +135,9 @@ If your devices are paired and behind a NAT, the public TURN Server from [Open R Yes. Your files are sent using WebRTC, which encrypts them on transit. To ensure the connection is secure and there is no MITM, compare the security number shown under the device name on both devices. The security number is different for every connection. + +
+
@@ -124,6 +158,9 @@ Alternatively, you can open a hotspot on one of your devices to bridge the conne You can also use mobile hotspots on phones to do that. Then, all data should be sent directly between devices and your data plan should not be charged. + +
+
@@ -136,6 +173,9 @@ We are not trying to optimize for some edge-cases. We are optimizing the user fl If you want to learn more about simplicity you can read [Insanely Simple: The Obsession that Drives Apple's Success](https://www.amazon.com/Insanely-Simple-Ken-Segall-audiobook/dp/B007Z9686O) or [Thinking, Fast and Slow](https://www.amazon.com/Thinking-Fast-Slow-Daniel-Kahneman/dp/0374533555). + +
+
@@ -150,6 +190,9 @@ If you want to learn more about simplicity you can read [Insanely Simple: The Ob * Do security analysis and suggestions * To support the original Snapdrop and its creator go to [his GitHub page](https://github.com/RobinLinus/snapdrop) + +
+
@@ -158,6 +201,9 @@ If you want to learn more about simplicity you can read [Insanely Simple: The Ob [See here for Information about the Technical Implementation](/docs/technical-documentation.md) + +
+
[< Back](/README.md) From bdb39a1d2cca81078ecb109dd860149afec1543c Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Sat, 25 Mar 2023 04:08:11 +0100 Subject: [PATCH 012/568] add docker-swarm-usage.md reference to host-your-own.md and tidy up docker-swarm-usage.md --- docs/docker-swarm-usage.md | 39 +++++++++++++++++++++++++++++++------- docs/host-your-own.md | 4 ++++ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/docs/docker-swarm-usage.md b/docs/docker-swarm-usage.md index 7a28350..ae2c97e 100644 --- a/docs/docker-swarm-usage.md +++ b/docs/docker-swarm-usage.md @@ -2,17 +2,42 @@ ## Healthcheck -The Docker Image includes a Healthcheck with the following options (Look at Dockerfile): +The [Docker Image](../Dockerfile) includes a Healthcheck with the following options: ---interval=30s: This option specifies the time interval at which the health check should be performed. In this case, the health check will be performed every 30 seconds. +``` +--interval=30s +``` +> Specifies the time interval at which the health check should be performed. In this case, the health check will be performed every 30 seconds. ---timeout=10s: This option specifies the amount of time to wait for a response from the health check command. If the response does not arrive within 10 seconds, the health check will be considered a failure. +
---start-period=5s: This option specifies the amount of time to wait before starting the health check process. In this case, the health check process will begin 5 seconds after the container is started. +``` +--timeout=10s +``` +> Specifies the amount of time to wait for a response from the health check command. If the response does not arrive within 10 seconds, the health check will be considered a failure. ---retries=3: This option specifies the number of times Docker should retry the health check before considering the container to be unhealthy. +
-The CMD instruction is used to define the command that will be run as part of the health check. In this case, the command is "wget --quiet --tries=1 --spider http://localhost:3000/ || exit 1". This command will attempt to connect to http://localhost:3000/ and if it fails, it will exit with a status code of 1. If this command returns a status code other than 0, the health check will be considered a failure. +``` +--start-period=5s +``` +> Specifies the amount of time to wait before starting the health check process. In this case, the health check process will begin 5 seconds after the container is started. -Overall, this HEALTHCHECK instruction is defining a health check process that will run every 30 seconds, wait up to 10 seconds for a response, begin 5 seconds after the container is started, and retry up to 3 times. The health check will consist of attempting to connect to http://localhost:3000/ and will consider the container to be unhealthy if it is unable to connect. +
+ +``` +--retries=3 +``` +> Specifies the number of times Docker should retry the health check before considering the container to be unhealthy. + +
+ + +The CMD instruction is used to define the command that will be run as part of the health check. +In this case, the command is `wget --quiet --tries=1 --spider http://localhost:3000/ || exit 1`. This command will attempt to connect to `http://localhost:3000/` +and if it fails it will exit with a status code of `1`. If this command returns a status code other than `0`, the health check will be considered a failure. + +Overall, this HEALTHCHECK instruction is defining a health check process that will run every 30 seconds, wait up to 10 seconds for a response, +begin 5 seconds after the container is started, and retry up to 3 times. +The health check will consist of attempting to connect to http://localhost:3000/ and will consider the container to be unhealthy if it is unable to connect. diff --git a/docs/host-your-own.md b/docs/host-your-own.md index 6209895..f93f27a 100644 --- a/docs/host-your-own.md +++ b/docs/host-your-own.md @@ -82,6 +82,8 @@ docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 gh > > To specify options replace `npm run start:prod` according to [the documentation below.](#options--flags-1) +> The Docker Image includes a Healthcheck. To learn more see [Docker Swarm Usage](./docker-swarm-usage.md#docker-swarm-usage) + ### Docker Image self-built #### Build the image ```bash @@ -101,6 +103,8 @@ docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 -i > > To specify options replace `npm run start:prod` according to [the documentation below.](#options--flags-1) +> The Docker Image includes a Healthcheck. To learn more see [Docker Swarm Usage](./docker-swarm-usage.md#docker-swarm-usage) +
## Deployment with Docker Compose From dcc4e8b7479308db88ca7e6a536eb44e9298434f Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Sat, 25 Mar 2023 01:54:24 +0100 Subject: [PATCH 013/568] Optimize background animation drastically by using offscreen canvases to reuse frames. Rewrite animate function to prevent it from being called multiple times --- public/scripts/ui.js | 88 +++++++++++++---------- public_included_ws_fallback/scripts/ui.js | 87 +++++++++++++--------- 2 files changed, 104 insertions(+), 71 deletions(-) diff --git a/public/scripts/ui.js b/public/scripts/ui.js index f471f4e..fb44352 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -1943,68 +1943,84 @@ Events.on('load', () => { style.zIndex = -1; style.top = 0; style.left = 0; - let ctx = c.getContext('2d'); + let cCtx = c.getContext('2d'); let x0, y0, w, h, dw, offset; + let offscreenCanvases = []; + function init() { + let oldW = w; + let oldH = h; + let oldOffset = offset w = document.documentElement.clientWidth; h = document.documentElement.clientHeight; - c.width = w; - c.height = h; offset = $$('footer').offsetHeight - 32; 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.max(w, h, 1000) / 13; - drawCircles(); + dw = Math.round(Math.max(w, h, 1000) / 13); + drawCircles(cCtx, 0); + + // enforce redrawing of frames + offscreenCanvases = []; } Events.on('bg-resize', _ => init()); window.onresize = _ => Events.fire('bg-resize'); - function drawCircle(radius) { + function drawCircle(ctx, radius) { ctx.beginPath(); - let color = Math.round(255 * (1 - radius / Math.max(w, h))); - ctx.strokeStyle = 'rgba(' + color + ',' + color + ',' + color + ',0.1)'; + ctx.lineWidth = 2; + let opacity = 0.2 * (1 - 1.2 * radius / Math.max(w, h)); + ctx.strokeStyle = `rgb(128, 128, 128, ${opacity})`; ctx.arc(x0, y0, radius, 0, 2 * Math.PI); ctx.stroke(); - ctx.lineWidth = 2; } - let step = 0; - - function drawCircles() { - ctx.clearRect(0, 0, w, h); - for (let i = 0; i < 8; i++) { - drawCircle(dw * i + step % dw); - } - step += 1; - } - - let loading = true; - - function animate() { - if (loading || !finished()) { - requestAnimationFrame(function() { - drawCircles(); - animate(); - }); + function drawCircles(ctx, frame) { + for (let i = 0; i < 13; i++) { + drawCircle(ctx, dw * i + frame); } } - function finished() { - return step % dw >= dw - 5; + function createOffscreenCanvas(frame) { + let canvas = document.createElement("canvas"); + canvas.width = c.width; + canvas.height = c.height; + offscreenCanvases[frame] = canvas; + let ctx = canvas.getContext('2d'); + drawCircles(ctx, frame); + } + + function drawFrame(frame) { + cCtx.clearRect(0, 0, w, h); + if (!offscreenCanvases[frame]) { + createOffscreenCanvas(frame); + } + cCtx.drawImage(offscreenCanvases[frame], 0, 0); + } + + let animate = true; + let currentFrame = 0; + + function animateBg() { + if (currentFrame + 1 < dw || animate) { + currentFrame = (currentFrame + 1) % dw; + drawFrame(currentFrame); + } + setTimeout(_ => animateBg(), 3000 / dw); } window.animateBackground = function(l) { - if (!l) { - loading = false; - } else if (!loading) { - loading = true; - if (finished()) animate(); - } + animate = l; }; + init(); - animate(); + animateBg(); }); document.changeFavicon = function (src) { diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index d705b9b..8a28a19 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -1944,67 +1944,84 @@ Events.on('load', () => { style.zIndex = -1; style.top = 0; style.left = 0; - let ctx = c.getContext('2d'); + let cCtx = c.getContext('2d'); let x0, y0, w, h, dw, offset; + let offscreenCanvases = []; + function init() { + let oldW = w; + let oldH = h; + let oldOffset = offset w = document.documentElement.clientWidth; h = document.documentElement.clientHeight; + offset = $$('footer').offsetHeight - 32; + if (h > 800) offset += 16; + + if (oldW === w && oldH === h && oldOffset === offset) return; // nothing has changed + c.width = w; c.height = h; - offset = $$('footer').offsetHeight - 32; x0 = w / 2; y0 = h - offset; - dw = Math.max(w, h, 1000) / 13; - drawCircles(); + dw = Math.round(Math.max(w, h, 1000) / 13); + drawCircles(cCtx, 0); + + // enforce redrawing of frames + offscreenCanvases = []; } Events.on('bg-resize', _ => init()); window.onresize = _ => Events.fire('bg-resize'); - function drawCircle(radius) { + function drawCircle(ctx, radius) { ctx.beginPath(); - let color = Math.round(255 * (1 - radius / Math.max(w, h))); - ctx.strokeStyle = 'rgba(' + color + ',' + color + ',' + color + ',0.1)'; + ctx.lineWidth = 2; + let opacity = 0.2 * (1 - 1.2 * radius / Math.max(w, h)); + ctx.strokeStyle = `rgb(128, 128, 128, ${opacity})`; ctx.arc(x0, y0, radius, 0, 2 * Math.PI); ctx.stroke(); - ctx.lineWidth = 2; } - let step = 0; - - function drawCircles() { - ctx.clearRect(0, 0, w, h); - for (let i = 0; i < 8; i++) { - drawCircle(dw * i + step % dw); - } - step += 1; - } - - let loading = true; - - function animate() { - if (loading || !finished()) { - requestAnimationFrame(function() { - drawCircles(); - animate(); - }); + function drawCircles(ctx, frame) { + for (let i = 0; i < 13; i++) { + drawCircle(ctx, dw * i + frame); } } - function finished() { - return step % dw >= dw - 5; + function createOffscreenCanvas(frame) { + let canvas = document.createElement("canvas"); + canvas.width = c.width; + canvas.height = c.height; + offscreenCanvases[frame] = canvas; + let ctx = canvas.getContext('2d'); + drawCircles(ctx, frame); + } + + function drawFrame(frame) { + cCtx.clearRect(0, 0, w, h); + if (!offscreenCanvases[frame]) { + createOffscreenCanvas(frame); + } + cCtx.drawImage(offscreenCanvases[frame], 0, 0); + } + + let animate = true; + let currentFrame = 0; + + function animateBg() { + if (currentFrame + 1 < dw || animate) { + currentFrame = (currentFrame + 1) % dw; + drawFrame(currentFrame); + } + setTimeout(_ => animateBg(), 3000 / dw); } window.animateBackground = function(l) { - if (!l) { - loading = false; - } else if (!loading) { - loading = true; - if (finished()) animate(); - } + animate = l; }; + init(); - animate(); + animateBg(); }); document.changeFavicon = function (src) { From 1bb8a63eed1bb9e5aab344d7f3d7dcef01de0fd0 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 27 Mar 2023 02:31:56 +0200 Subject: [PATCH 014/568] increase version to v1.5.1 --- package-lock.json | 4 ++-- package.json | 2 +- public/service-worker.js | 2 +- public_included_ws_fallback/service-worker.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 06d0665..5856e1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pairdrop", - "version": "1.5.0", + "version": "1.5.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "pairdrop", - "version": "1.5.0", + "version": "1.5.1", "license": "ISC", "dependencies": { "express": "^4.18.2", diff --git a/package.json b/package.json index 536b9c2..b5b8d6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pairdrop", - "version": "1.5.0", + "version": "1.5.1", "description": "", "main": "index.js", "scripts": { diff --git a/public/service-worker.js b/public/service-worker.js index 502b9aa..d286eb9 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.5.0'; +const cacheVersion = 'v1.5.1'; const cacheTitle = `pairdrop-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', diff --git a/public_included_ws_fallback/service-worker.js b/public_included_ws_fallback/service-worker.js index 3809ff5..25bc379 100644 --- a/public_included_ws_fallback/service-worker.js +++ b/public_included_ws_fallback/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.5.0'; +const cacheVersion = 'v1.5.1'; const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', From 251df2fbff22b5acc767875ad960353c69c67dfd Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 9 Mar 2023 17:03:44 +0100 Subject: [PATCH 015/568] try to fix share target api --- public/scripts/ui.js | 22 ++++-- public/service-worker.js | 72 ++++++++++++------- public_included_ws_fallback/scripts/ui.js | 22 ++++-- public_included_ws_fallback/service-worker.js | 72 ++++++++++++------- 4 files changed, 128 insertions(+), 60 deletions(-) diff --git a/public/scripts/ui.js b/public/scripts/ui.js index fb44352..cc2a91b 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -1617,17 +1617,24 @@ class WebShareTargetUI { console.log('Shared Target Text:', '"' + shareTargetText + '"'); Events.fire('activate-paste-mode', {files: [], text: shareTargetText}) } else if (share_target_type === "files") { - const openRequest = window.indexedDB.open('pairdrop_store') - openRequest.onsuccess( db => { + let openRequest = window.indexedDB.open('pairdrop_store') + openRequest.onsuccess = e => { + const db = e.target.result; const tx = db.transaction('share_target_files', 'readwrite'); const store = tx.objectStore('share_target_files'); const request = store.getAll(); request.onsuccess = _ => { - Events.fire('activate-paste-mode', {files: request.result, text: ""}) + const fileObjects = request.result; + let filesReceived = []; + for (let i=0; i db.close(); } - }) + } } window.history.replaceState({}, "Rewrite URL", '/'); } @@ -1684,7 +1691,7 @@ class PersistentStorage { PersistentStorage.logBrowserNotCapable(); return; } - const DBOpenRequest = window.indexedDB.open('pairdrop_store', 2); + const DBOpenRequest = window.indexedDB.open('pairdrop_store', 3); DBOpenRequest.onerror = (e) => { PersistentStorage.logBrowserNotCapable(); console.log('Error initializing database: '); @@ -1710,7 +1717,10 @@ class PersistentStorage { } try { - db.createObjectStore('share_target_files'); + if (db.objectStoreNames.contains('share_target_files')) { + db.deleteObjectStore('share_target_files'); + } + db.createObjectStore('share_target_files', {autoIncrement: true}); } catch (error) { console.log("Object store named 'share_target_files' already exists") } diff --git a/public/service-worker.js b/public/service-worker.js index d286eb9..e6d1548 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -71,30 +71,12 @@ const update = request => self.addEventListener('fetch', function(event) { if (event.request.method === "POST") { // Requests related to Web Share Target. - event.respondWith( - (async () => { - const formData = await event.request.formData(); - const title = formData.get("title"); - const text = formData.get("text"); - const url = formData.get("url"); - const files = formData.get("files"); - let share_url = "/"; - if (files.length > 0) { - share_url = "/?share-target=files"; - const db = await window.indexedDB.open('pairdrop_store'); - const tx = db.transaction('share_target_files', 'readwrite'); - const store = tx.objectStore('share_target_files'); - for (let i=0; i 0 || text.length > 0 || url.length) { - share_url = `/?share-target=text&title=${title}&text=${text}&url=${url}`; - } - return Response.redirect(encodeURI(share_url), 303); - })() - ); + evaluateRequestData(event.request).then(share_url => { + console.debug(share_url); + event.respondWith( + Response.redirect(encodeURI(share_url), 302) + ); + }) } else { // Regular requests not related to Web Share Target. event.respondWith( @@ -119,3 +101,45 @@ self.addEventListener('activate', evt => }) ) ); + +const evaluateRequestData = async function (request) { + const formData = await request.formData(); + const title = formData.get("title"); + const text = formData.get("text"); + const url = formData.get("url"); + const files = formData.getAll("files"); + console.debug(files) + let fileObjects = []; + for (let i=0; i { + if (fileObjects?.length > 0) { + const DBOpenRequest = indexedDB.open('pairdrop_store'); + DBOpenRequest.onsuccess = (e) => { + const db = e.target.result; + for (let i = 0; i < fileObjects.length; i++) { + const transaction = db.transaction('share_target_files', 'readwrite'); + const objectStore = transaction.objectStore('share_target_files'); + + const objectStoreRequest = objectStore.add(fileObjects[i]); + objectStoreRequest.onsuccess = _ => { + if (i === fileObjects.length - 1) resolve('/?share-target=files'); + } + } + } + DBOpenRequest.onerror = _ => { + resolve('/'); + } + } else if (title?.length > 0 || text?.length > 0 || url?.length > 0) { + console.debug(title || text || url); + resolve(`/?share-target=text&title=${title}&text=${text}&url=${url}`); + } else { + resolve('/'); + } + }); +} diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index 8a28a19..53b418b 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -1618,17 +1618,24 @@ class WebShareTargetUI { console.log('Shared Target Text:', '"' + shareTargetText + '"'); Events.fire('activate-paste-mode', {files: [], text: shareTargetText}) } else if (share_target_type === "files") { - const openRequest = window.indexedDB.open('pairdrop_store') - openRequest.onsuccess( db => { + let openRequest = window.indexedDB.open('pairdrop_store') + openRequest.onsuccess = e => { + const db = e.target.result; const tx = db.transaction('share_target_files', 'readwrite'); const store = tx.objectStore('share_target_files'); const request = store.getAll(); request.onsuccess = _ => { - Events.fire('activate-paste-mode', {files: request.result, text: ""}) + const fileObjects = request.result; + let filesReceived = []; + for (let i=0; i db.close(); } - }) + } } window.history.replaceState({}, "Rewrite URL", '/'); } @@ -1685,7 +1692,7 @@ class PersistentStorage { PersistentStorage.logBrowserNotCapable(); return; } - const DBOpenRequest = window.indexedDB.open('pairdrop_store', 2); + const DBOpenRequest = window.indexedDB.open('pairdrop_store', 3); DBOpenRequest.onerror = (e) => { PersistentStorage.logBrowserNotCapable(); console.log('Error initializing database: '); @@ -1711,7 +1718,10 @@ class PersistentStorage { } try { - db.createObjectStore('share_target_files'); + if (db.objectStoreNames.contains('share_target_files')) { + db.deleteObjectStore('share_target_files'); + } + db.createObjectStore('share_target_files', {autoIncrement: true}); } catch (error) { console.log("Object store named 'share_target_files' already exists") } diff --git a/public_included_ws_fallback/service-worker.js b/public_included_ws_fallback/service-worker.js index 25bc379..d8f7ece 100644 --- a/public_included_ws_fallback/service-worker.js +++ b/public_included_ws_fallback/service-worker.js @@ -71,30 +71,12 @@ const update = request => self.addEventListener('fetch', function(event) { if (event.request.method === "POST") { // Requests related to Web Share Target. - event.respondWith( - (async () => { - const formData = await event.request.formData(); - const title = formData.get("title"); - const text = formData.get("text"); - const url = formData.get("url"); - const files = formData.get("files"); - let share_url = "/"; - if (files.length > 0) { - share_url = "/?share-target=files"; - const db = await window.indexedDB.open('pairdrop_store'); - const tx = db.transaction('share_target_files', 'readwrite'); - const store = tx.objectStore('share_target_files'); - for (let i=0; i 0 || text.length > 0 || url.length) { - share_url = `/?share-target=text&title=${title}&text=${text}&url=${url}`; - } - return Response.redirect(encodeURI(share_url), 303); - })() - ); + evaluateRequestData(event.request).then(share_url => { + console.debug(share_url); + event.respondWith( + Response.redirect(encodeURI(share_url), 302) + ); + }) } else { // Regular requests not related to Web Share Target. event.respondWith( @@ -119,3 +101,45 @@ self.addEventListener('activate', evt => }) ) ); + +const evaluateRequestData = async function (request) { + const formData = await request.formData(); + const title = formData.get("title"); + const text = formData.get("text"); + const url = formData.get("url"); + const files = formData.getAll("files"); + console.debug(files) + let fileObjects = []; + for (let i=0; i { + if (fileObjects?.length > 0) { + const DBOpenRequest = indexedDB.open('pairdrop_store'); + DBOpenRequest.onsuccess = (e) => { + const db = e.target.result; + for (let i = 0; i < fileObjects.length; i++) { + const transaction = db.transaction('share_target_files', 'readwrite'); + const objectStore = transaction.objectStore('share_target_files'); + + const objectStoreRequest = objectStore.add(fileObjects[i]); + objectStoreRequest.onsuccess = _ => { + if (i === fileObjects.length - 1) resolve('/?share-target=files'); + } + } + } + DBOpenRequest.onerror = _ => { + resolve('/'); + } + } else if (title?.length > 0 || text?.length > 0 || url?.length > 0) { + console.debug(title || text || url); + resolve(`/?share-target=text&title=${title}&text=${text}&url=${url}`); + } else { + resolve('/'); + } + }); +} From 34ebd603048602b3209ed085270ab66881d72801 Mon Sep 17 00:00:00 2001 From: Daniel Pham Date: Mon, 27 Mar 2023 21:49:33 +0200 Subject: [PATCH 016/568] Update service worker - files array now matches manifest files name - fixed handling fetch redirect --- public/service-worker.js | 12 ++++++------ public_included_ws_fallback/service-worker.js | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/public/service-worker.js b/public/service-worker.js index e6d1548..f49cc5f 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -71,12 +71,12 @@ const update = request => self.addEventListener('fetch', function(event) { if (event.request.method === "POST") { // Requests related to Web Share Target. - evaluateRequestData(event.request).then(share_url => { + event.respondWith((async () => { + let share_url = await evaluateRequestData(event.request); + share_url = event.request.url + share_url.substring(1); console.debug(share_url); - event.respondWith( - Response.redirect(encodeURI(share_url), 302) - ); - }) + return Response.redirect(encodeURI(share_url), 302); + })()); } else { // Regular requests not related to Web Share Target. event.respondWith( @@ -107,7 +107,7 @@ const evaluateRequestData = async function (request) { const title = formData.get("title"); const text = formData.get("text"); const url = formData.get("url"); - const files = formData.getAll("files"); + const files = formData.getAll("allfiles"); console.debug(files) let fileObjects = []; for (let i=0; i self.addEventListener('fetch', function(event) { if (event.request.method === "POST") { // Requests related to Web Share Target. - evaluateRequestData(event.request).then(share_url => { + event.respondWith((async () => { + let share_url = await evaluateRequestData(event.request); + share_url = event.request.url + share_url.substring(1); console.debug(share_url); - event.respondWith( - Response.redirect(encodeURI(share_url), 302) - ); - }) + return Response.redirect(encodeURI(share_url), 302); + })()); } else { // Regular requests not related to Web Share Target. event.respondWith( @@ -107,7 +107,7 @@ const evaluateRequestData = async function (request) { const title = formData.get("title"); const text = formData.get("text"); const url = formData.get("url"); - const files = formData.getAll("files"); + const files = formData.getAll("allfiles"); console.debug(files) let fileObjects = []; for (let i=0; i Date: Tue, 28 Mar 2023 00:42:30 +0200 Subject: [PATCH 017/568] Fix passed arguments for sharing text --- public/service-worker.js | 2 +- public_included_ws_fallback/service-worker.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/service-worker.js b/public/service-worker.js index f49cc5f..7e43687 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -137,7 +137,7 @@ const evaluateRequestData = async function (request) { } } else if (title?.length > 0 || text?.length > 0 || url?.length > 0) { console.debug(title || text || url); - resolve(`/?share-target=text&title=${title}&text=${text}&url=${url}`); + resolve(`/?share-target=text${title ? `&title=${title}` : ''}${text ? `&text=${text}` : ''}${url ? `&url=${url}` : ''}`); } else { resolve('/'); } diff --git a/public_included_ws_fallback/service-worker.js b/public_included_ws_fallback/service-worker.js index acb7225..6e815eb 100644 --- a/public_included_ws_fallback/service-worker.js +++ b/public_included_ws_fallback/service-worker.js @@ -137,7 +137,7 @@ const evaluateRequestData = async function (request) { } } else if (title?.length > 0 || text?.length > 0 || url?.length > 0) { console.debug(title || text || url); - resolve(`/?share-target=text&title=${title}&text=${text}&url=${url}`); + resolve(`/?share-target=text${title ? `&title=${title}` : ''}${text ? `&text=${text}` : ''}${url ? `&url=${url}` : ''}`); } else { resolve('/'); } From d0b2c8158286ad50015d59673ebd3ac9baec713d Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Tue, 28 Mar 2023 19:07:33 +0200 Subject: [PATCH 018/568] Tidy up code --- public/scripts/ui.js | 7 ++-- public/service-worker.js | 41 ++++++++++--------- public_included_ws_fallback/scripts/ui.js | 7 ++-- public_included_ws_fallback/service-worker.js | 41 ++++++++++--------- 4 files changed, 50 insertions(+), 46 deletions(-) diff --git a/public/scripts/ui.js b/public/scripts/ui.js index cc2a91b..94542bb 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -1607,14 +1607,13 @@ class WebShareTargetUI { let shareTargetText; if (url) { - shareTargetText = url; // We share only the Link - no text. Because link-only text becomes clickable. + shareTargetText = url; // we share only the link - no text. } else if (title && text) { shareTargetText = title + '\r\n' + text; } else { shareTargetText = title + text; } - console.log('Shared Target Text:', '"' + shareTargetText + '"'); Events.fire('activate-paste-mode', {files: [], text: shareTargetText}) } else if (share_target_type === "files") { let openRequest = window.indexedDB.open('pairdrop_store') @@ -1629,10 +1628,10 @@ class WebShareTargetUI { for (let i=0; i db.close(); + + Events.fire('activate-paste-mode', {files: filesReceived, text: ""}) } } } diff --git a/public/service-worker.js b/public/service-worker.js index 7e43687..24c8d08 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -73,8 +73,7 @@ self.addEventListener('fetch', function(event) { // Requests related to Web Share Target. event.respondWith((async () => { let share_url = await evaluateRequestData(event.request); - share_url = event.request.url + share_url.substring(1); - console.debug(share_url); + share_url = event.request.url + share_url; return Response.redirect(encodeURI(share_url), 302); })()); } else { @@ -108,19 +107,20 @@ const evaluateRequestData = async function (request) { const text = formData.get("text"); const url = formData.get("url"); const files = formData.getAll("allfiles"); - console.debug(files) - let fileObjects = []; - for (let i=0; i { - if (fileObjects?.length > 0) { + + return new Promise(async (resolve) => { + if (files && files.length > 0) { + let fileObjects = []; + for (let i=0; i { + DBOpenRequest.onsuccess = e => { const db = e.target.result; for (let i = 0; i < fileObjects.length; i++) { const transaction = db.transaction('share_target_files', 'readwrite'); @@ -128,18 +128,21 @@ const evaluateRequestData = async function (request) { const objectStoreRequest = objectStore.add(fileObjects[i]); objectStoreRequest.onsuccess = _ => { - if (i === fileObjects.length - 1) resolve('/?share-target=files'); + if (i === fileObjects.length - 1) resolve('?share-target=files'); } } } DBOpenRequest.onerror = _ => { - resolve('/'); + resolve(''); } - } else if (title?.length > 0 || text?.length > 0 || url?.length > 0) { - console.debug(title || text || url); - resolve(`/?share-target=text${title ? `&title=${title}` : ''}${text ? `&text=${text}` : ''}${url ? `&url=${url}` : ''}`); } else { - resolve('/'); + let share_url = '?share-target=text'; + + if (title) share_url += `&title=${title}`; + if (text) share_url += `&text=${text}`; + if (url) share_url += `&url=${url}`; + + resolve(share_url); } }); } diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index 53b418b..9c72d12 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -1608,14 +1608,13 @@ class WebShareTargetUI { let shareTargetText; if (url) { - shareTargetText = url; // We share only the Link - no text. Because link-only text becomes clickable. + shareTargetText = url; // we share only the link - no text. } else if (title && text) { shareTargetText = title + '\r\n' + text; } else { shareTargetText = title + text; } - console.log('Shared Target Text:', '"' + shareTargetText + '"'); Events.fire('activate-paste-mode', {files: [], text: shareTargetText}) } else if (share_target_type === "files") { let openRequest = window.indexedDB.open('pairdrop_store') @@ -1630,10 +1629,10 @@ class WebShareTargetUI { for (let i=0; i db.close(); + + Events.fire('activate-paste-mode', {files: filesReceived, text: ""}) } } } diff --git a/public_included_ws_fallback/service-worker.js b/public_included_ws_fallback/service-worker.js index 6e815eb..ea0886d 100644 --- a/public_included_ws_fallback/service-worker.js +++ b/public_included_ws_fallback/service-worker.js @@ -73,8 +73,7 @@ self.addEventListener('fetch', function(event) { // Requests related to Web Share Target. event.respondWith((async () => { let share_url = await evaluateRequestData(event.request); - share_url = event.request.url + share_url.substring(1); - console.debug(share_url); + share_url = event.request.url + share_url; return Response.redirect(encodeURI(share_url), 302); })()); } else { @@ -108,19 +107,20 @@ const evaluateRequestData = async function (request) { const text = formData.get("text"); const url = formData.get("url"); const files = formData.getAll("allfiles"); - console.debug(files) - let fileObjects = []; - for (let i=0; i { - if (fileObjects?.length > 0) { + + return new Promise(async (resolve) => { + if (files && files.length > 0) { + let fileObjects = []; + for (let i=0; i { + DBOpenRequest.onsuccess = e => { const db = e.target.result; for (let i = 0; i < fileObjects.length; i++) { const transaction = db.transaction('share_target_files', 'readwrite'); @@ -128,18 +128,21 @@ const evaluateRequestData = async function (request) { const objectStoreRequest = objectStore.add(fileObjects[i]); objectStoreRequest.onsuccess = _ => { - if (i === fileObjects.length - 1) resolve('/?share-target=files'); + if (i === fileObjects.length - 1) resolve('?share-target=files'); } } } DBOpenRequest.onerror = _ => { - resolve('/'); + resolve(''); } - } else if (title?.length > 0 || text?.length > 0 || url?.length > 0) { - console.debug(title || text || url); - resolve(`/?share-target=text${title ? `&title=${title}` : ''}${text ? `&text=${text}` : ''}${url ? `&url=${url}` : ''}`); } else { - resolve('/'); + let share_url = '?share-target=text'; + + if (title) share_url += `&title=${title}`; + if (text) share_url += `&text=${text}`; + if (url) share_url += `&url=${url}`; + + resolve(share_url); } }); } From ab08091f5dd7790c83c09ac222b7daf0196e2b57 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Tue, 28 Mar 2023 20:00:05 +0200 Subject: [PATCH 019/568] increase version to v1.5.2 --- package-lock.json | 4 ++-- package.json | 2 +- public/service-worker.js | 2 +- public_included_ws_fallback/service-worker.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5856e1f..67eab5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pairdrop", - "version": "1.5.1", + "version": "1.5.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "pairdrop", - "version": "1.5.1", + "version": "1.5.2", "license": "ISC", "dependencies": { "express": "^4.18.2", diff --git a/package.json b/package.json index b5b8d6f..fb1e7b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pairdrop", - "version": "1.5.1", + "version": "1.5.2", "description": "", "main": "index.js", "scripts": { diff --git a/public/service-worker.js b/public/service-worker.js index 24c8d08..881e496 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.5.1'; +const cacheVersion = 'v1.5.2'; const cacheTitle = `pairdrop-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', diff --git a/public_included_ws_fallback/service-worker.js b/public_included_ws_fallback/service-worker.js index ea0886d..255742e 100644 --- a/public_included_ws_fallback/service-worker.js +++ b/public_included_ws_fallback/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.5.1'; +const cacheVersion = 'v1.5.2'; const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', From 0fe36e132c0148105c510a09cd929962d2256897 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Tue, 28 Mar 2023 20:24:46 +0200 Subject: [PATCH 020/568] Remove the "under development" message from the share-menu section --- docs/how-to.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/how-to.md b/docs/how-to.md index 621ce5a..a764816 100644 --- a/docs/how-to.md +++ b/docs/how-to.md @@ -33,10 +33,10 @@ https://routinehub.co/shortcut/13990/ ## Send directly from share menu on Android -The [Web Share Target API](https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target) is implemented but not yet tested. -When the PWA is installed, it should register itself to the share-menu of the device automatically. +The [Web Share Target API](https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target) is implemented. + +When the PWA is installed, it will register itself to the share-menu of the device automatically. -This feature is still under development. Please test this feature and create an issue if it does not work. ## Send directly via command-line interface Send files or text with PairDrop via command-line interface. From ac1e88b6a01358c1ef61e0ef746b1f3307f6350d Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Tue, 28 Mar 2023 17:26:22 +0200 Subject: [PATCH 021/568] Add possibility to reset theme to auto --- public/index.html | 32 +++++-- public/scripts/theme.js | 97 ++++++++++++++------ public/styles.css | 82 ++++++++++++++--- public_included_ws_fallback/index.html | 32 +++++-- public_included_ws_fallback/scripts/theme.js | 97 ++++++++++++++------ public_included_ws_fallback/styles.css | 78 ++++++++++++++-- 6 files changed, 326 insertions(+), 92 deletions(-) diff --git a/public/index.html b/public/index.html index 5f2aa1e..759c9a4 100644 --- a/public/index.html +++ b/public/index.html @@ -44,11 +44,25 @@ - - - - - + - - - - - + - +
@@ -126,12 +126,12 @@
Input this key on another device
or scan the QR-Code.

- - - - - - + + + + + +
Enter key from another device to continue.
@@ -220,7 +220,7 @@
-
+
@@ -365,11 +365,11 @@ - + - - + + diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..eb05362 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/public/scripts/qrcode.js b/public/scripts/QRCode.min.js similarity index 100% rename from public/scripts/qrcode.js rename to public/scripts/QRCode.min.js diff --git a/public/styles.css b/public/styles.css index fd29f24..337a82c 100644 --- a/public/styles.css +++ b/public/styles.css @@ -96,7 +96,8 @@ header > div { align-self: flex-start; touch-action: manipulation; } -header > div a { + +header > div .icon-button { height: 40px; transition: all 300ms; } @@ -106,7 +107,7 @@ header > div > div { flex-direction: column; } -header > div:not(:hover) a:not(.selected) { +header > div:not(:hover) .icon-button:not(.selected) { height: 0; opacity: 0; } @@ -125,16 +126,16 @@ header > div:hover::before { margin-bottom: 8px; } -header > div:hover a.selected::before { +header > div:hover .icon-button.selected::before { opacity: 0.1; } @media (pointer: coarse) { - header > div:hover a.selected:hover::before { + header > div:hover .icon-button.selected:hover::before { opacity: 0.2; } - header > div a:not(.selected) { + header > div .icon-button:not(.selected) { height: 0; opacity: 0; pointer-events: none; diff --git a/public_included_ws_fallback/index.html b/public_included_ws_fallback/index.html index 6cc47b0..29e3ee7 100644 --- a/public_included_ws_fallback/index.html +++ b/public_included_ws_fallback/index.html @@ -45,45 +45,45 @@ - +
@@ -129,12 +129,12 @@
Input this key on another device
or scan the QR-Code.

- - - - - - + + + + + +
Enter key from another device to continue.
@@ -223,7 +223,7 @@
-
+
@@ -368,11 +368,11 @@ - + - - + + diff --git a/public_included_ws_fallback/scripts/qrcode.js b/public_included_ws_fallback/scripts/QRCode.min.js similarity index 100% rename from public_included_ws_fallback/scripts/qrcode.js rename to public_included_ws_fallback/scripts/QRCode.min.js diff --git a/public_included_ws_fallback/scripts/robots.txt b/public_included_ws_fallback/scripts/robots.txt new file mode 100644 index 0000000..eb05362 --- /dev/null +++ b/public_included_ws_fallback/scripts/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/public_included_ws_fallback/styles.css b/public_included_ws_fallback/styles.css index e68e226..0b7c5d9 100644 --- a/public_included_ws_fallback/styles.css +++ b/public_included_ws_fallback/styles.css @@ -97,7 +97,8 @@ header > div { align-self: flex-start; touch-action: manipulation; } -header > div a { + +header > div .icon-button { height: 40px; transition: all 300ms; } @@ -107,7 +108,7 @@ header > div > div { flex-direction: column; } -header > div:not(:hover) a:not(.selected) { +header > div:not(:hover) .icon-button:not(.selected) { height: 0; opacity: 0; } @@ -126,16 +127,16 @@ header > div:hover::before { margin-bottom: 8px; } -header > div:hover a.selected::before { +header > div:hover .icon-button.selected::before { opacity: 0.1; } @media (pointer: coarse) { - header > div:hover a.selected:hover::before { + header > div:hover .icon-button.selected:hover::before { opacity: 0.2; } - header > div a:not(.selected) { + header > div .icon-button:not(.selected) { height: 0; opacity: 0; pointer-events: none; From d0046e83cbf653da5189e94d710a60884a64f307 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 17 Apr 2023 15:24:31 +0200 Subject: [PATCH 027/568] remove openrelayproject from rtc_config --- index.js | 8 -------- rtc_config_example.json | 8 -------- 2 files changed, 16 deletions(-) diff --git a/index.js b/index.js index 0fa6dae..6792a36 100644 --- a/index.js +++ b/index.js @@ -63,14 +63,6 @@ const rtcConfig = process.env.RTC_CONFIG "iceServers": [ { "urls": "stun:stun.l.google.com:19302" - }, - { - "urls": "stun:openrelay.metered.ca:80" - }, - { - "urls": "turn:openrelay.metered.ca:443", - "username": "openrelayproject", - "credential": "openrelayproject" } ] }; diff --git a/rtc_config_example.json b/rtc_config_example.json index f78905d..bb327e3 100644 --- a/rtc_config_example.json +++ b/rtc_config_example.json @@ -3,14 +3,6 @@ "iceServers": [ { "urls": "stun:stun.l.google.com:19302" - }, - { - "urls": "stun:openrelay.metered.ca:80" - }, - { - "urls": "turn:openrelay.metered.ca:443", - "username": "openrelayproject", - "credential": "openrelayproject" } ] } From 59dca141b6386936d65f942b6da49e2a5cb69ba2 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 17 Apr 2023 15:25:52 +0200 Subject: [PATCH 028/568] increase version to v1.6.0 --- package-lock.json | 4 ++-- package.json | 2 +- public/service-worker.js | 2 +- public_included_ws_fallback/service-worker.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9d3f16d..d7ff48b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pairdrop", - "version": "1.5.3", + "version": "1.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "pairdrop", - "version": "1.5.3", + "version": "1.6.0", "license": "ISC", "dependencies": { "express": "^4.18.2", diff --git a/package.json b/package.json index 7deca5c..5a8da1e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pairdrop", - "version": "1.5.3", + "version": "1.6.0", "description": "", "main": "index.js", "scripts": { diff --git a/public/service-worker.js b/public/service-worker.js index 9f66380..04ab588 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.5.3'; +const cacheVersion = 'v1.6.0'; const cacheTitle = `pairdrop-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', diff --git a/public_included_ws_fallback/service-worker.js b/public_included_ws_fallback/service-worker.js index f1d1667..829b13f 100644 --- a/public_included_ws_fallback/service-worker.js +++ b/public_included_ws_fallback/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.5.3'; +const cacheVersion = 'v1.6.0'; const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', From cae3bb7c7bcbc43051a9453967c1f0230e2f4002 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Tue, 18 Apr 2023 13:06:21 +0200 Subject: [PATCH 029/568] Add server costs to README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 74b5e7d..fdc316d 100644 --- a/README.md +++ b/README.md @@ -89,9 +89,9 @@ You can [host your own instance with Docker](/docs/host-your-own.md). ## Support the Community -PairDrop is free and always will be. Still, we have to pay for the domain. +PairDrop is free and always will be. Still, we have to pay for the domain and the server. -To contribute and support me:
+To contribute and support:
Buy Me A Coffee From 2d8bbd5a79c001c769e5498588d9f668a4a87f3e Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 19 Apr 2023 16:50:22 +0200 Subject: [PATCH 030/568] Change docs to include the usage of our own TURN server instead of the TURN server of the Open Relay Project --- README.md | 4 ++-- docs/faq.md | 20 +++++++++++--------- docs/host-your-own.md | 26 ++++++++++---------------- rtc_config_example.json | 5 +++++ 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index fdc316d..0602ff0 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,13 @@ Developed based on [Snapdrop](https://github.com/RobinLinus/snapdrop) ## Differences to Snapdrop -### Device Pairing +### Device Pairing / Internet Transfer * Pair devices via 6-digit code or QR-Code * Pair devices outside your local network or in complex network environment (public Wi-Fi, company network, Apple Private Relay, VPN etc.). * Connect to devices on your mobile hotspot. * Paired devices will always find each other via shared secrets even after reopening the browser or the Progressive Web App * You will always discover devices on your local network. Paired devices are shown additionally. -* Paired devices outside your local network that are behind a NAT are connected automatically via [Open Relay: Free WebRTC TURN Server](https://www.metered.ca/tools/openrelay/) +* Paired devices outside your local network that are behind a NAT are connected automatically via the PairDrop TURN server. ### [Improved UI for sending/receiving files](https://github.com/RobinLinus/snapdrop/issues/560) * Files are transferred only after a request is accepted first. On transfer completion files are downloaded automatically if possible. diff --git a/docs/faq.md b/docs/faq.md index 2f5e260..cc95f17 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -104,10 +104,13 @@ Here's a list of some third-party apps compatible with PairDrop: What about the connection? Is it a P2P-connection directly from device to device or is there any third-party-server? -It uses a P2P connection if WebRTC is supported by the browser. WebRTC needs a Signaling Server, but it is only used to establish a connection and is not involved in the file transfer. +It uses a WebRTC peer to peer connection. WebRTC needs a Signaling Server that is only used to establish a connection. The server is not involved in the file transfer. -If your devices are paired and behind a NAT, the public TURN Server from [Open Relay](https://www.metered.ca/tools/openrelay/) is used to route your files and messages. +If devices are on the same network, none of your files are ever sent to any server. +If your devices are paired and behind a NAT, the PairDrop TURN Server is used to route your files and messages. See the [Technical Documentation](technical-documentation.md#encryption-webrtc-stun-and-turn) to learn more about STUN, TURN and WebRTC. + +If you host your own instance and want to support devices that do not support WebRTC, you can [start the PairDrop instance with an activated Websocket fallback](https://github.com/schlagmichdoch/PairDrop/blob/master/docs/host-your-own.md#websocket-fallback-for-vpn).
@@ -118,11 +121,12 @@ If your devices are paired and behind a NAT, the public TURN Server from [Open R What about privacy? Will files be saved on third-party-servers? -None of your files are ever sent to any server. Files are sent only between peers. PairDrop doesn't even use a database. If you are curious have a look [at the Server](https://github.com/schlagmichdoch/pairdrop/blob/master/index.js). +Files are sent directly between peers. PairDrop doesn't even use a database. If you are curious, have a look [at the Server](https://github.com/schlagmichdoch/pairdrop/blob/master/index.js). WebRTC encrypts the files on transit. -If your devices are paired and behind a NAT, the public TURN Server from [Open Relay](https://www.metered.ca/tools/openrelay/) is used to route your files and messages. +If devices are on the same network, none of your files are ever sent to any server. +If your devices are paired and behind a NAT, the PairDrop TURN Server is used to route your files and messages. See the [Technical Documentation](technical-documentation.md#encryption-webrtc-stun-and-turn) to learn more about STUN, TURN and WebRTC.
@@ -147,9 +151,7 @@ Yes. Your files are sent using WebRTC, which encrypts them on transit. To ensure Naturally, if traffic needs to be routed through the turn server because your devices are behind different NATs, transfer speed decreases. -As the public TURN server used is not super fast, you can easily [specify to use your own TURN server](https://github.com/schlagmichdoch/PairDrop/blob/master/docs/host-your-own.md#specify-stunturn-servers) if you host your own instance. - -Alternatively, you can open a hotspot on one of your devices to bridge the connection which makes transfers much faster as no TURN server is needed. +You can open a hotspot on one of your devices to bridge the connection which omits the need of the TURN server. - [How to open a hotspot on Windows](https://support.microsoft.com/en-us/windows/use-your-windows-pc-as-a-mobile-hotspot-c89b0fad-72d5-41e8-f7ea-406ad9036b85#WindowsVersion=Windows_11) - [How to open a hotspot on Mac](https://support.apple.com/guide/mac-help/share-internet-connection-mac-network-users-mchlp1540/mac) @@ -171,7 +173,7 @@ Then, all data should be sent directly between devices and your data plan should Snapdrop and PairDrop are a study in radical simplicity. The user interface is insanely simple. Features are chosen very carefully because complexity grows quadratically since every feature potentially interferes with each other feature. We focus very narrowly on a single use case: instant file transfer. We are not trying to optimize for some edge-cases. We are optimizing the user flow of the average users. Don't be sad if we decline your feature request for the sake of simplicity. -If you want to learn more about simplicity you can read [Insanely Simple: The Obsession that Drives Apple's Success](https://www.amazon.com/Insanely-Simple-Ken-Segall-audiobook/dp/B007Z9686O) or [Thinking, Fast and Slow](https://www.amazon.com/Thinking-Fast-Slow-Daniel-Kahneman/dp/0374533555). +If you want to learn more about simplicity you can read *Insanely Simple: The Obsession that Drives Apple's Success* or *Thinking, Fast and Slow*.
@@ -183,7 +185,7 @@ If you want to learn more about simplicity you can read [Insanely Simple: The Ob Snapdrop and PairDrop are awesome! How can I support them? -* [Buy me a coffee to support open source software](https://www.buymeacoffee.com/pairdrop) +* [Buy me a coffee](https://www.buymeacoffee.com/pairdrop) to pay for the domain and the server, and support open source software * [File bugs, give feedback, submit suggestions](https://github.com/schlagmichdoch/pairdrop/issues) * Share PairDrop on social media. * Fix bugs and make a pull request. diff --git a/docs/host-your-own.md b/docs/host-your-own.md index f93f27a..f9e5b4d 100644 --- a/docs/host-your-own.md +++ b/docs/host-your-own.md @@ -1,6 +1,12 @@ # Deployment Notes The easiest way to get PairDrop up and running is by using Docker. +> TURN server for Internet Transfer +> +> Beware that you have to host your own TURN server in order to enable transfers between different networks. +> +> You can follow [this guide](https://gabrieltanner.org/blog/turn-server/) to either install coturn directly on your system (Step 1) or deploy it via docker-compose (Step 5). + ## Deployment with Docker ### Docker Image from Docker Hub @@ -50,6 +56,8 @@ Set options by using the following flags in the `docker run` command: > Specify the STUN/TURN servers PairDrop clients use by setting `RTC_CONFIG` to a JSON file including the configuration. > You can use `pairdrop/rtc_config_example.json` as a starting point. > +> To host your own TURN server you can follow this guide: https://gabrieltanner.org/blog/turn-server/ +> > Default configuration: > ```json > { @@ -57,14 +65,6 @@ Set options by using the following flags in the `docker run` command: > "iceServers": [ > { > "urls": "stun:stun.l.google.com:19302" -> }, -> { -> "urls": "stun:openrelay.metered.ca:80" -> }, -> { -> "urls": "turn:openrelay.metered.ca:443", -> "username": "openrelayproject", -> "credential": "openrelayproject" > } > ] > } @@ -186,6 +186,8 @@ $env:RTC_CONFIG="rtc_config.json"; npm start ``` > Specify the STUN/TURN servers PairDrop clients use by setting `RTC_CONFIG` to a JSON file including the configuration. > You can use `pairdrop/rtc_config_example.json` as a starting point. +> +> To host your own TURN server you can follow this guide: https://gabrieltanner.org/blog/turn-server/ > > Default configuration: > ```json @@ -194,14 +196,6 @@ $env:RTC_CONFIG="rtc_config.json"; npm start > "iceServers": [ > { > "urls": "stun:stun.l.google.com:19302" -> }, -> { -> "urls": "stun:openrelay.metered.ca:80" -> }, -> { -> "urls": "turn:openrelay.metered.ca:443", -> "username": "openrelayproject", -> "credential": "openrelayproject" > } > ] > } diff --git a/rtc_config_example.json b/rtc_config_example.json index bb327e3..d7e48e8 100644 --- a/rtc_config_example.json +++ b/rtc_config_example.json @@ -3,6 +3,11 @@ "iceServers": [ { "urls": "stun:stun.l.google.com:19302" + }, + { + "urls": "turn:example.com:3478", + "username": "username", + "credential": "password" } ] } From b2fc6415daf32edaf03f684076b331e13913a4f9 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 19 Apr 2023 17:38:14 +0200 Subject: [PATCH 031/568] include example files to run an own TURN server via coturn or via docker-compose --- docker-compose-coturn.yml | 19 +++++++++++++++++++ turnserver_example.conf | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 docker-compose-coturn.yml create mode 100644 turnserver_example.conf diff --git a/docker-compose-coturn.yml b/docker-compose-coturn.yml new file mode 100644 index 0000000..e9a05b4 --- /dev/null +++ b/docker-compose-coturn.yml @@ -0,0 +1,19 @@ +version: "3" +services: + node: + image: "node:lts-alpine" + user: "node" + working_dir: /home/node/app + volumes: + - ./:/home/node/app + command: ash -c "npm i && npm run start:prod" + restart: unless-stopped + ports: + - "3000:3000" + coturn_server: + image: "coturn/coturn" + restart: always + network_mode: "host" + volumes: + - ./turnserver.conf:/etc/coturn/turnserver.conf + #you need to copy turnserver_example.conf to turnserver.conf and specify domain, IP address, user and password diff --git a/turnserver_example.conf b/turnserver_example.conf new file mode 100644 index 0000000..09e7986 --- /dev/null +++ b/turnserver_example.conf @@ -0,0 +1,38 @@ +# TURN server name and realm +realm= +server-name=pairdrop + +# IPs the TURN server listens to +listening-ip=0.0.0.0 + +# External IP-Address of the TURN server +external-ip= + +# Main listening port +listening-port=3478 + +# Further ports that are open for communication +min-port=10000 +max-port=20000 + +# Use fingerprint in TURN message +fingerprint + +# Log file path +log-file=/var/log/turnserver.log + +# Enable verbose logging +verbose + +# Specify the user for the TURN authentification +user=user:password + +# Enable long-term credential mechanism +lt-cred-mech + +# SSL certificates +cert=/etc/letsencrypt/live//cert.pem +pkey=/etc/letsencrypt/live//privkey.pem + +# 443 for TURN over TLS, which can bypass firewalls +tls-listening-port=443 From 87097e9cd420632f014964b1bc378f78e2680459 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 19 Apr 2023 21:15:03 +0200 Subject: [PATCH 032/568] fix header btn shadow styling --- public/styles.css | 5 +++-- public_included_ws_fallback/styles.css | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/public/styles.css b/public/styles.css index 337a82c..463e6bf 100644 --- a/public/styles.css +++ b/public/styles.css @@ -112,7 +112,7 @@ header > div:not(:hover) .icon-button:not(.selected) { opacity: 0; } -header > div:hover::before { +#theme-wrapper:hover::before { border-radius: 20px; background: currentColor; opacity: 0.1; @@ -204,7 +204,8 @@ body { line-height: 18px; } -a { +a, +.icon-button { text-decoration: none; color: currentColor; cursor: pointer; diff --git a/public_included_ws_fallback/styles.css b/public_included_ws_fallback/styles.css index 0b7c5d9..b5385c3 100644 --- a/public_included_ws_fallback/styles.css +++ b/public_included_ws_fallback/styles.css @@ -113,7 +113,7 @@ header > div:not(:hover) .icon-button:not(.selected) { opacity: 0; } -header > div:hover::before { +#theme-wrapper:hover::before { border-radius: 20px; background: currentColor; opacity: 0.1; @@ -205,7 +205,8 @@ body { line-height: 18px; } -a { +a, +.icon-button { text-decoration: none; color: currentColor; cursor: pointer; From 8de899f1241c1424d710ad48b5bbe1e383c864e2 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 19 Apr 2023 21:16:43 +0200 Subject: [PATCH 033/568] increase version to v1.6.1 --- package-lock.json | 4 ++-- package.json | 2 +- public/service-worker.js | 2 +- public_included_ws_fallback/service-worker.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d7ff48b..3041f6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pairdrop", - "version": "1.6.0", + "version": "1.6.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "pairdrop", - "version": "1.6.0", + "version": "1.6.1", "license": "ISC", "dependencies": { "express": "^4.18.2", diff --git a/package.json b/package.json index 5a8da1e..785fe78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pairdrop", - "version": "1.6.0", + "version": "1.6.1", "description": "", "main": "index.js", "scripts": { diff --git a/public/service-worker.js b/public/service-worker.js index 04ab588..387485a 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.6.0'; +const cacheVersion = 'v1.6.1'; const cacheTitle = `pairdrop-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', diff --git a/public_included_ws_fallback/service-worker.js b/public_included_ws_fallback/service-worker.js index 829b13f..154997b 100644 --- a/public_included_ws_fallback/service-worker.js +++ b/public_included_ws_fallback/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.6.0'; +const cacheVersion = 'v1.6.1'; const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', From 5c3f5ece7d640fcd7fb50954cf1c3a50ab50da82 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 20 Apr 2023 21:40:26 +0200 Subject: [PATCH 034/568] increase seo by adding an aria-label and removing 'user-scalable=no' --- public/index.html | 12 ++++++------ public_included_ws_fallback/index.html | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/public/index.html b/public/index.html index 2cad0c6..849f29b 100644 --- a/public/index.html +++ b/public/index.html @@ -6,7 +6,7 @@ PairDrop - + @@ -39,24 +39,24 @@
- +
-
+
-
+
-
+
@@ -264,7 +264,7 @@
- + diff --git a/public_included_ws_fallback/index.html b/public_included_ws_fallback/index.html index 29e3ee7..05c7fd5 100644 --- a/public_included_ws_fallback/index.html +++ b/public_included_ws_fallback/index.html @@ -6,7 +6,7 @@ PairDrop - + @@ -39,24 +39,24 @@
- +
-
+
-
+
-
+
@@ -267,7 +267,7 @@
- + From 3f72fa116055e9624eb78e7c5eac03827d7a8a31 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 20 Apr 2023 21:11:08 +0200 Subject: [PATCH 035/568] remove fade-in from description (LCP) on page load --- public/scripts/ui.js | 6 ++++++ public/styles.css | 4 ++-- public_included_ws_fallback/scripts/ui.js | 6 ++++++ public_included_ws_fallback/styles.css | 4 ++-- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 94542bb..eb944b5 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -57,6 +57,12 @@ class PeersUI { console.log("Retrieved edited display name:", displayName) if (displayName) Events.fire('self-display-name-changed', displayName); }); + + + /* prevent animation on load */ + setTimeout(_ => { + this.$xNoPeers.style.animationIterationCount = "1"; + }, 300); } _insertDisplayName(displayName) { diff --git a/public/styles.css b/public/styles.css index 463e6bf..860424d 100644 --- a/public/styles.css +++ b/public/styles.css @@ -405,10 +405,10 @@ x-no-peers { flex-direction: column; padding: 8px; text-align: center; - /* prevent flickering on load */ animation: fade-in 300ms; - animation-delay: 500ms; animation-fill-mode: backwards; + /* prevent flickering on load */ + animation-iteration-count: 0; } x-no-peers h2, diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index 9c72d12..71dc0a8 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -57,6 +57,12 @@ class PeersUI { console.log("Retrieved edited display name:", displayName) if (displayName) Events.fire('self-display-name-changed', displayName); }); + + + /* prevent animation on load */ + setTimeout(_ => { + this.$xNoPeers.style.animationIterationCount = "1"; + }, 300); } _insertDisplayName(displayName) { diff --git a/public_included_ws_fallback/styles.css b/public_included_ws_fallback/styles.css index b5385c3..33db610 100644 --- a/public_included_ws_fallback/styles.css +++ b/public_included_ws_fallback/styles.css @@ -415,10 +415,10 @@ x-no-peers { flex-direction: column; padding: 8px; text-align: center; - /* prevent flickering on load */ animation: fade-in 300ms; - animation-delay: 500ms; animation-fill-mode: backwards; + /* prevent flickering on load */ + animation-iteration-count: 0; } x-no-peers h2, From 4c7bdd3a0ff39a1d6961326a9514c5f2eba9c4f7 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 20 Apr 2023 21:44:14 +0200 Subject: [PATCH 036/568] move robots.txt into correct folder --- public_included_ws_fallback/{scripts => }/robots.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename public_included_ws_fallback/{scripts => }/robots.txt (100%) diff --git a/public_included_ws_fallback/scripts/robots.txt b/public_included_ws_fallback/robots.txt similarity index 100% rename from public_included_ws_fallback/scripts/robots.txt rename to public_included_ws_fallback/robots.txt From b42c8a0b1a1e0522af2b21a7d980183627735cd3 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 20 Apr 2023 21:12:06 +0200 Subject: [PATCH 037/568] remove background animation in favor of speed and efficiency --- public/scripts/ui.js | 70 ++++------------------ public_included_ws_fallback/scripts/ui.js | 71 ++++------------------- 2 files changed, 24 insertions(+), 117 deletions(-) diff --git a/public/scripts/ui.js b/public/scripts/ui.js index eb944b5..694a8af 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -181,7 +181,6 @@ class PeersUI { if (!$peer) return; $peer.remove(); this.evaluateOverflowing(); - if ($$('x-peers:empty')) setTimeout(_ => window.animateBackground(true), 1750); // Start animation again } _onSecretRoomDeleted(roomSecret) { @@ -321,7 +320,6 @@ class PeerUI { $$('x-peers').appendChild(this.$el) Events.fire('peer-added'); this.$xInstructions = $$('x-instructions'); - setTimeout(_ => window.animateBackground(false), 1750); // Stop animation } html() { @@ -1577,27 +1575,15 @@ class NetworkStatusUI { constructor() { Events.on('offline', _ => this._showOfflineMessage()); Events.on('online', _ => this._showOnlineMessage()); - Events.on('ws-connected', _ => this._onWsConnected()); - Events.on('ws-disconnected', _ => this._onWsDisconnected()); if (!navigator.onLine) this._showOfflineMessage(); } _showOfflineMessage() { Events.fire('notify-user', 'You are offline'); - window.animateBackground(false); } _showOnlineMessage() { Events.fire('notify-user', 'You are back online'); - window.animateBackground(true); - } - - _onWsConnected() { - window.animateBackground(true); - } - - _onWsDisconnected() { - window.animateBackground(false); } } @@ -1948,28 +1934,26 @@ window.addEventListener('beforeinstallprompt', e => { return e.preventDefault(); }); -// Background Animation +// Background Circles Events.on('load', () => { let c = document.createElement('canvas'); - document.body.appendChild(c); let style = c.style; style.width = '100%'; style.position = 'absolute'; style.zIndex = -1; style.top = 0; style.left = 0; + style.animation = "fade-in 800ms"; let cCtx = c.getContext('2d'); let x0, y0, w, h, dw, offset; - let offscreenCanvases = []; - function init() { let oldW = w; let oldH = h; let oldOffset = offset w = document.documentElement.clientWidth; h = document.documentElement.clientHeight; - offset = $$('footer').offsetHeight - 32; + offset = $$('footer').offsetHeight - 33; if (h > 800) offset += 16; if (oldW === w && oldH === h && oldOffset === offset) return; // nothing has changed @@ -1979,63 +1963,33 @@ Events.on('load', () => { x0 = w / 2; y0 = h - offset; dw = Math.round(Math.max(w, h, 1000) / 13); - drawCircles(cCtx, 0); - // enforce redrawing of frames - offscreenCanvases = []; + if (document.body.contains(c)) { + document.body.removeChild(c); + } + drawCircles(cCtx, dw); + document.body.appendChild(c); } + Events.on('bg-resize', _ => init()); window.onresize = _ => Events.fire('bg-resize'); function drawCircle(ctx, radius) { ctx.beginPath(); ctx.lineWidth = 2; - let opacity = 0.2 * (1 - 1.2 * radius / Math.max(w, h)); - ctx.strokeStyle = `rgb(128, 128, 128, ${opacity})`; + let opacity = 0.3 * (1 - 1.2 * radius / Math.max(w, h)); + ctx.strokeStyle = `rgba(128, 128, 128, ${opacity})`; ctx.arc(x0, y0, radius, 0, 2 * Math.PI); ctx.stroke(); } function drawCircles(ctx, frame) { for (let i = 0; i < 13; i++) { - drawCircle(ctx, dw * i + frame); + drawCircle(ctx, dw * i + frame + 33); } } - function createOffscreenCanvas(frame) { - let canvas = document.createElement("canvas"); - canvas.width = c.width; - canvas.height = c.height; - offscreenCanvases[frame] = canvas; - let ctx = canvas.getContext('2d'); - drawCircles(ctx, frame); - } - - function drawFrame(frame) { - cCtx.clearRect(0, 0, w, h); - if (!offscreenCanvases[frame]) { - createOffscreenCanvas(frame); - } - cCtx.drawImage(offscreenCanvases[frame], 0, 0); - } - - let animate = true; - let currentFrame = 0; - - function animateBg() { - if (currentFrame + 1 < dw || animate) { - currentFrame = (currentFrame + 1) % dw; - drawFrame(currentFrame); - } - setTimeout(_ => animateBg(), 3000 / dw); - } - - window.animateBackground = function(l) { - animate = l; - }; - init(); - animateBg(); }); document.changeFavicon = function (src) { diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index 71dc0a8..694a8af 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -181,7 +181,6 @@ class PeersUI { if (!$peer) return; $peer.remove(); this.evaluateOverflowing(); - if ($$('x-peers:empty')) setTimeout(_ => window.animateBackground(true), 1750); // Start animation again } _onSecretRoomDeleted(roomSecret) { @@ -321,7 +320,6 @@ class PeerUI { $$('x-peers').appendChild(this.$el) Events.fire('peer-added'); this.$xInstructions = $$('x-instructions'); - setTimeout(_ => window.animateBackground(false), 1750); // Stop animation } html() { @@ -368,7 +366,6 @@ class PeerUI { this.$el.ui = this; this._peer.roomTypes.forEach(roomType => this.$el.classList.add(`type-${roomType}`)); this.$el.classList.add('center'); - if (!this._peer.rtcSupported || !window.isRtcSupported) this.$el.classList.add('ws-peer') this.html(); this._callbackInput = e => this._onFilesSelected(e) @@ -1578,27 +1575,15 @@ class NetworkStatusUI { constructor() { Events.on('offline', _ => this._showOfflineMessage()); Events.on('online', _ => this._showOnlineMessage()); - Events.on('ws-connected', _ => this._onWsConnected()); - Events.on('ws-disconnected', _ => this._onWsDisconnected()); if (!navigator.onLine) this._showOfflineMessage(); } _showOfflineMessage() { Events.fire('notify-user', 'You are offline'); - window.animateBackground(false); } _showOnlineMessage() { Events.fire('notify-user', 'You are back online'); - window.animateBackground(true); - } - - _onWsConnected() { - window.animateBackground(true); - } - - _onWsDisconnected() { - window.animateBackground(false); } } @@ -1949,28 +1934,26 @@ window.addEventListener('beforeinstallprompt', e => { return e.preventDefault(); }); -// Background Animation +// Background Circles Events.on('load', () => { let c = document.createElement('canvas'); - document.body.appendChild(c); let style = c.style; style.width = '100%'; style.position = 'absolute'; style.zIndex = -1; style.top = 0; style.left = 0; + style.animation = "fade-in 800ms"; let cCtx = c.getContext('2d'); let x0, y0, w, h, dw, offset; - let offscreenCanvases = []; - function init() { let oldW = w; let oldH = h; let oldOffset = offset w = document.documentElement.clientWidth; h = document.documentElement.clientHeight; - offset = $$('footer').offsetHeight - 32; + offset = $$('footer').offsetHeight - 33; if (h > 800) offset += 16; if (oldW === w && oldH === h && oldOffset === offset) return; // nothing has changed @@ -1980,63 +1963,33 @@ Events.on('load', () => { x0 = w / 2; y0 = h - offset; dw = Math.round(Math.max(w, h, 1000) / 13); - drawCircles(cCtx, 0); - // enforce redrawing of frames - offscreenCanvases = []; + if (document.body.contains(c)) { + document.body.removeChild(c); + } + drawCircles(cCtx, dw); + document.body.appendChild(c); } + Events.on('bg-resize', _ => init()); window.onresize = _ => Events.fire('bg-resize'); function drawCircle(ctx, radius) { ctx.beginPath(); ctx.lineWidth = 2; - let opacity = 0.2 * (1 - 1.2 * radius / Math.max(w, h)); - ctx.strokeStyle = `rgb(128, 128, 128, ${opacity})`; + let opacity = 0.3 * (1 - 1.2 * radius / Math.max(w, h)); + ctx.strokeStyle = `rgba(128, 128, 128, ${opacity})`; ctx.arc(x0, y0, radius, 0, 2 * Math.PI); ctx.stroke(); } function drawCircles(ctx, frame) { for (let i = 0; i < 13; i++) { - drawCircle(ctx, dw * i + frame); + drawCircle(ctx, dw * i + frame + 33); } } - function createOffscreenCanvas(frame) { - let canvas = document.createElement("canvas"); - canvas.width = c.width; - canvas.height = c.height; - offscreenCanvases[frame] = canvas; - let ctx = canvas.getContext('2d'); - drawCircles(ctx, frame); - } - - function drawFrame(frame) { - cCtx.clearRect(0, 0, w, h); - if (!offscreenCanvases[frame]) { - createOffscreenCanvas(frame); - } - cCtx.drawImage(offscreenCanvases[frame], 0, 0); - } - - let animate = true; - let currentFrame = 0; - - function animateBg() { - if (currentFrame + 1 < dw || animate) { - currentFrame = (currentFrame + 1) % dw; - drawFrame(currentFrame); - } - setTimeout(_ => animateBg(), 3000 / dw); - } - - window.animateBackground = function(l) { - animate = l; - }; - init(); - animateBg(); }); document.changeFavicon = function (src) { From 8f4ce63a0cba1b51909ecd5bceb07941f9a1de77 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 20 Apr 2023 22:04:57 +0200 Subject: [PATCH 038/568] increase version to v1.6.2 --- package-lock.json | 4 ++-- package.json | 2 +- public/service-worker.js | 2 +- public_included_ws_fallback/service-worker.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3041f6e..2871a4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pairdrop", - "version": "1.6.1", + "version": "1.6.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "pairdrop", - "version": "1.6.1", + "version": "1.6.2", "license": "ISC", "dependencies": { "express": "^4.18.2", diff --git a/package.json b/package.json index 785fe78..2b9490f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pairdrop", - "version": "1.6.1", + "version": "1.6.2", "description": "", "main": "index.js", "scripts": { diff --git a/public/service-worker.js b/public/service-worker.js index 387485a..b17e463 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.6.1'; +const cacheVersion = 'v1.6.2'; const cacheTitle = `pairdrop-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', diff --git a/public_included_ws_fallback/service-worker.js b/public_included_ws_fallback/service-worker.js index 154997b..ab7f4d5 100644 --- a/public_included_ws_fallback/service-worker.js +++ b/public_included_ws_fallback/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.6.1'; +const cacheVersion = 'v1.6.2'; const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', From 5a363e90dd0b3bd6183378a3ce5d8edd962a1dde Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 9 Mar 2023 13:40:53 +0100 Subject: [PATCH 039/568] add debug mode to enable debugging auto discovery --- index.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/index.js b/index.js index 6792a36..5460308 100644 --- a/index.js +++ b/index.js @@ -90,6 +90,12 @@ if (process.argv.includes('--include-ws-fallback')) { app.use(express.static('public')); } +const debugMode = process.env.DEBUG_MODE === "true"; + +if (debugMode) { + console.log("DEBUG_MODE is active. To protect privacy, do not use in production.") +} + app.use(function(req, res) { res.redirect('/'); }); @@ -502,6 +508,17 @@ class Peer { if (this.ip.substring(0,7) === "::ffff:") this.ip = this.ip.substring(7); + if (debugMode) { + console.debug("----DEBUGGING-PEER-IP-START----"); + console.debug("remoteAddress:", request.connection.remoteAddress); + console.debug("x-forwarded-for:", request.headers['x-forwarded-for']); + console.debug("cf-connecting-ip:", request.headers['cf-connecting-ip']); + console.debug("PairDrop uses:", this.ip); + console.debug("IP is private:", this.ipIsPrivate(this.ip)); + console.debug("if IP is private, '127.0.0.1' is used instead"); + console.debug("----DEBUGGING-PEER-IP-END----"); + } + // IPv4 and IPv6 use different values to refer to localhost // put all peers on the same network as the server into the same room as well if (this.ip === '::1' || this.ipIsPrivate(this.ip)) { From fb08bdaf3642bbf2b1a8ea166fd8cc42bb2c2231 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 27 Apr 2023 18:13:44 +0200 Subject: [PATCH 040/568] add environment variable DEBUG_MODE to docs --- docs/host-your-own.md | 63 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/docs/host-your-own.md b/docs/host-your-own.md index f9e5b4d..4953b16 100644 --- a/docs/host-your-own.md +++ b/docs/host-your-own.md @@ -30,7 +30,7 @@ Set options by using the following flags in the `docker run` command: > - 3000 -> `-p 127.0.0.1:3000:3000` > - 8080 -> `-p 127.0.0.1:8080:3000` ##### Rate limiting requests -``` +```bash -e RATE_LIMIT=true ``` > Limits clients to 1000 requests per 5 min @@ -70,6 +70,31 @@ Set options by using the following flags in the `docker run` command: > } > ``` +##### Debug Mode +```bash +-e DEBUG_MODE="true" +``` + +> Use this flag to enable debugging information about the connecting peers IP addresses. This is quite useful to check whether the [#HTTP-Server](#http-server) +> is configured correctly, so the auto discovery feature works correctly. Otherwise, all clients discover each other mutually, independently of their network status. +> +> If this flag is set to `"true"` each peer that connects to the PairDrop server will produce a log to STDOUT like this: +> ``` +> ----DEBUGGING-PEER-IP-START---- +> remoteAddress: ::ffff:172.17.0.1 +> x-forwarded-for: 19.117.63.126 +> cf-connecting-ip: undefined +> PairDrop uses: 19.117.63.126 +> IP is private: false +> if IP is private, '127.0.0.1' is used instead +> ----DEBUGGING-PEER-IP-END---- +> ``` +> If the IP PairDrop uses is the public IP of your device everything is correctly setup. +>To find out your devices public IP visit https://www.whatismyip.com/. +> +> To preserve your clients' privacy, **never use this flag in production!** + +
### Docker Image from GHCR @@ -82,7 +107,7 @@ docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 gh > > To specify options replace `npm run start:prod` according to [the documentation below.](#options--flags-1) -> The Docker Image includes a Healthcheck. To learn more see [Docker Swarm Usage](./docker-swarm-usage.md#docker-swarm-usage) +> The Docker Image includes a Healthcheck. To learn more see [Docker Swarm Usage](docker-swarm-usage.md#docker-swarm-usage) ### Docker Image self-built #### Build the image @@ -103,7 +128,7 @@ docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 -i > > To specify options replace `npm run start:prod` according to [the documentation below.](#options--flags-1) -> The Docker Image includes a Healthcheck. To learn more see [Docker Swarm Usage](./docker-swarm-usage.md#docker-swarm-usage) +> The Docker Image includes a Healthcheck. To learn more see [Docker Swarm Usage](docker-swarm-usage.md#docker-swarm-usage)
@@ -201,6 +226,36 @@ $env:RTC_CONFIG="rtc_config.json"; npm start > } > ``` +#### Debug Mode +On Unix based systems +```bash +DEBUG_MODE="true" npm start +``` +On Windows +```bash +$env:DEBUG_MODE="true"; npm start +``` + +> Use this flag to enable debugging information about the connecting peers IP addresses. This is quite useful to check whether the [#HTTP-Server](#http-server) +> is configured correctly, so the auto discovery feature works correctly. Otherwise, all clients discover each other mutually, independently of their network status. +> +> If this flag is set to `"true"` each peer that connects to the PairDrop server will produce a log to STDOUT like this: +> ``` +> ----DEBUGGING-PEER-IP-START---- +> remoteAddress: ::ffff:172.17.0.1 +> x-forwarded-for: 19.117.63.126 +> cf-connecting-ip: undefined +> PairDrop uses: 19.117.63.126 +> IP is private: false +> if IP is private, '127.0.0.1' is used instead +> ----DEBUGGING-PEER-IP-END---- +> ``` +> If the IP PairDrop uses is the public IP of your device everything is correctly setup. +>To find out your devices public IP visit https://www.whatismyip.com/. +> +> To preserve your clients' privacy, **never use this flag in production!** + + ### Options / Flags #### Local Run ```bash @@ -257,6 +312,8 @@ npm run start:prod -- --localhost-only --include-ws-fallback ## HTTP-Server When running PairDrop, the `X-Forwarded-For` header has to be set by a proxy. Otherwise, all clients will be mutually visible. +To check if your setup is configured correctly [use the environment variable `DEBUG_MODE="true"`](#debug-mode). + ### Using nginx #### Allow http and https requests ``` From fafdbcc829ddd5f0b736da7e02fd86568f395c4c Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 27 Apr 2023 19:16:44 +0200 Subject: [PATCH 041/568] increase version to v1.6.3 --- package-lock.json | 4 ++-- package.json | 2 +- public/service-worker.js | 2 +- public_included_ws_fallback/service-worker.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2871a4b..8ed975b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pairdrop", - "version": "1.6.2", + "version": "1.6.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "pairdrop", - "version": "1.6.2", + "version": "1.6.3", "license": "ISC", "dependencies": { "express": "^4.18.2", diff --git a/package.json b/package.json index 2b9490f..d7ff1b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pairdrop", - "version": "1.6.2", + "version": "1.6.3", "description": "", "main": "index.js", "scripts": { diff --git a/public/service-worker.js b/public/service-worker.js index b17e463..d890675 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.6.2'; +const cacheVersion = 'v1.6.3'; const cacheTitle = `pairdrop-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', diff --git a/public_included_ws_fallback/service-worker.js b/public_included_ws_fallback/service-worker.js index ab7f4d5..af3bb0e 100644 --- a/public_included_ws_fallback/service-worker.js +++ b/public_included_ws_fallback/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.6.2'; +const cacheVersion = 'v1.6.3'; const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', From f39bfedf9841ce6e4e767e9b8587c6c733766088 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 4 May 2023 17:34:33 +0200 Subject: [PATCH 042/568] use sha3-512 hash instead of cyrb53 to authenticate peerIds on reconnect --- index.js | 84 +++++++++++++++++++++++--------------------------------- 1 file changed, 35 insertions(+), 49 deletions(-) diff --git a/index.js b/index.js index 6792a36..53f213a 100644 --- a/index.js +++ b/index.js @@ -136,7 +136,7 @@ class PairDropServer { displayName: peer.name.displayName, deviceName: peer.name.deviceName, peerId: peer.id, - peerIdHash: peer.id.hashCode128BitSalted() + peerIdHash: hasher.hashCodeSalted(peer.id) } }); } @@ -238,26 +238,8 @@ class PairDropServer { this._notifyPeers(sender); } - getRandomString(length) { - let string = ""; - while (string.length < length) { - let arr = new Uint16Array(length); - crypto.webcrypto.getRandomValues(arr); - arr = Array.apply([], arr); /* turn into non-typed array */ - arr = arr.map(function (r) { - return r % 128 - }) - arr = arr.filter(function (r) { - /* strip non-printables: if we transform into desirable range we have a propability bias, so I suppose we better skip this character */ - return r === 45 || r >= 47 && r <= 57 || r >= 64 && r <= 90 || r >= 97 && r <= 122; - }); - string += String.fromCharCode.apply(String, arr); - } - return string.substring(0, length) - } - _onPairDeviceInitiate(sender) { - let roomSecret = this.getRandomString(64); + let roomSecret = randomizer.getRandomString(64); let roomKey = this._createRoomKey(sender, roomSecret); if (sender.roomKey) this._removeRoomKey(sender.roomKey); sender.roomKey = roomKey; @@ -583,7 +565,7 @@ class Peer { separator: ' ', dictionaries: [colors, animals], style: 'capital', - seed: this.id.hashCode() + seed: cyrb53(this.id) }) this.name = { @@ -609,7 +591,7 @@ class Peer { } isPeerIdHashValid(peerId, peerIdHash) { - return peerIdHash === peerId.hashCode128BitSalted(); + return peerIdHash === hasher.hashCodeSalted(peerId); } addRoomSecret(roomSecret) { @@ -625,39 +607,43 @@ class Peer { } } -Object.defineProperty(String.prototype, 'hashCode', { - value: function() { - return cyrb53(this); - } -}); - -Object.defineProperty(String.prototype, 'hashCode128BitSalted', { - value: function() { - return hasher.hashCode128BitSalted(this); - } -}); - const hasher = (() => { - let seeds; + let password; return { - hashCode128BitSalted(str) { - if (!seeds) { - // seeds are created on first call to salt hash. - seeds = [4]; - for (let i=0; i<4; i++) { - const randomBuffer = new Uint32Array(1); - crypto.webcrypto.getRandomValues(randomBuffer); - seeds[i] = randomBuffer[0]; - } + hashCodeSalted(salt) { + if (!password) { + // password is created on first call. + password = randomizer.getRandomString(128); } - let hashCode = ""; - for (let i=0; i<4; i++) { - hashCode += cyrb53(str, seeds[i]); - } - return hashCode; + + return crypto.createHash("sha3-512") + .update(password) + .update(crypto.createHash("sha3-512").update(salt, "utf8").digest("hex")) + .digest("hex"); } } +})() +const randomizer = (() => { + return { + getRandomString(length) { + let string = ""; + while (string.length < length) { + let arr = new Uint16Array(length); + crypto.webcrypto.getRandomValues(arr); + arr = Array.apply([], arr); /* turn into non-typed array */ + arr = arr.map(function (r) { + return r % 128 + }) + arr = arr.filter(function (r) { + /* strip non-printables: if we transform into desirable range we have a probability bias, so I suppose we better skip this character */ + return r === 45 || r >= 47 && r <= 57 || r >= 64 && r <= 90 || r >= 97 && r <= 122; + }); + string += String.fromCharCode.apply(String, arr); + } + return string.substring(0, length) + } + } })() /* From 0ac3c5a11f920e9c8fff982321c8dc2dfb697f57 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 4 May 2023 17:39:12 +0200 Subject: [PATCH 043/568] remove debugging logs --- public_included_ws_fallback/scripts/network.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/public_included_ws_fallback/scripts/network.js b/public_included_ws_fallback/scripts/network.js index 78f6f5a..6575b18 100644 --- a/public_included_ws_fallback/scripts/network.js +++ b/public_included_ws_fallback/scripts/network.js @@ -734,7 +734,6 @@ class WSPeer extends Peer { } sendJSON(message) { - console.debug(message) message.to = this._peerId; message.roomType = this._roomType; message.roomSecret = this._roomSecret; @@ -854,7 +853,6 @@ class PeersManager { _onWsDisconnected() { for (const peerId in this.peers) { - console.debug(this.peers[peerId].rtcSupported); if (this.peers[peerId] && (!this.peers[peerId].rtcSupported || !window.isRtcSupported)) { Events.fire('peer-disconnected', peerId); } From 241ea4f98865c5a79c1a046dca3e98b0b774672b Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 4 May 2023 17:38:51 +0200 Subject: [PATCH 044/568] implement auto_accept (#91) and manual unpairing via new Edit Paired Devices Dialog and a BrowserTabsConnector --- index.js | 38 +- public/index.html | 26 +- public/scripts/network.js | 233 +++++++-- public/scripts/ui.js | 489 ++++++++++++----- public/scripts/util.js | 4 + public/styles.css | 163 ++++-- public_included_ws_fallback/index.html | 26 +- .../scripts/network.js | 255 ++++++--- public_included_ws_fallback/scripts/ui.js | 491 +++++++++++++----- public_included_ws_fallback/scripts/util.js | 8 +- public_included_ws_fallback/styles.css | 150 +++++- 11 files changed, 1442 insertions(+), 441 deletions(-) diff --git a/index.js b/index.js index 53f213a..bde7f98 100644 --- a/index.js +++ b/index.js @@ -159,11 +159,8 @@ class PairDropServer { case 'room-secrets': this._onRoomSecrets(sender, message); break; - case 'room-secret-deleted': - this._onRoomSecretDeleted(sender, message); - break; - case 'room-secrets-cleared': - this._onRoomSecretsCleared(sender, message); + case 'room-secrets-deleted': + this._onRoomSecretsDeleted(sender, message); break; case 'pair-device-initiate': this._onPairDeviceInitiate(sender); @@ -213,29 +210,26 @@ class PairDropServer { this._joinSecretRooms(sender, roomSecrets); } - _onRoomSecretDeleted(sender, message) { - this._deleteSecretRoom(sender, message.roomSecret) - } - - _onRoomSecretsCleared(sender, message) { + _onRoomSecretsDeleted(sender, message) { for (let i = 0; i
- -
@@ -161,7 +161,7 @@

Edit Paired Devices

-
+
The easiest way to transfer files across devices
- + - + - + - + diff --git a/public/lang/en.json b/public/lang/en.json index 7ae2e56..8ad7b7c 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -78,8 +78,12 @@ "download-again": "Download again" }, "about": { - "close-about-aria-label": "Close About PairDrop", - "claim": "The easiest way to transfer files across devices" + "close-about_aria-label": "Close About PairDrop", + "claim": "The easiest way to transfer files across devices", + "github_title": "PairDrop on Github", + "buy-me-a-coffee_title": "Buy me a coffee!", + "tweet_title": "Tweet about PairDrop", + "faq_title": "Frequently asked questions" }, "notifications": { "display-name-changed-permanently": "Display name is changed permanently.", diff --git a/public/scripts/localization.js b/public/scripts/localization.js index d09d5c0..c7d9716 100644 --- a/public/scripts/localization.js +++ b/public/scripts/localization.js @@ -2,8 +2,8 @@ class Localization { constructor() { Localization.defaultLocale = "en"; Localization.supportedLocales = ["en"]; - Localization.translations = {}; + Localization.defaultTranslations = {}; const initialLocale = Localization.supportedOrDefault(Localization.browserLocales()); @@ -29,13 +29,13 @@ class Localization { static async setLocale(newLocale) { if (newLocale === Localization.locale) return false; + const firstTranslation = !Localization.locale + Localization.defaultTranslations = await Localization.fetchTranslationsFor(Localization.defaultLocale); const newTranslations = await Localization.fetchTranslationsFor(newLocale); if(!newTranslations) return false; - const firstTranslation = !Localization.locale - Localization.locale = newLocale; Localization.translations = newTranslations; @@ -65,18 +65,20 @@ class Localization { for (let i in attrs) { let attr = attrs[i]; if (attr === "text") { - element.innerText = await Localization.getTranslation(key); + element.innerText = Localization.getTranslation(key); } else { - element.attr = await Localization.getTranslation(key, attr); + element.attr = Localization.getTranslation(key, attr); } } } - static getTranslation(key, attr, data) { + static getTranslation(key, attr, data, useDefault=false) { const keys = key.split("."); - let translationCandidates = Localization.translations; + let translationCandidates = useDefault + ? Localization.defaultTranslations + : Localization.translations; for (let i=0; i
- +
@@ -166,7 +166,7 @@

Edit Paired Devices

-
+
The easiest way to transfer files across devices
- + - + - + - + diff --git a/public_included_ws_fallback/lang/en.json b/public_included_ws_fallback/lang/en.json index 7ae2e56..8ad7b7c 100644 --- a/public_included_ws_fallback/lang/en.json +++ b/public_included_ws_fallback/lang/en.json @@ -78,8 +78,12 @@ "download-again": "Download again" }, "about": { - "close-about-aria-label": "Close About PairDrop", - "claim": "The easiest way to transfer files across devices" + "close-about_aria-label": "Close About PairDrop", + "claim": "The easiest way to transfer files across devices", + "github_title": "PairDrop on Github", + "buy-me-a-coffee_title": "Buy me a coffee!", + "tweet_title": "Tweet about PairDrop", + "faq_title": "Frequently asked questions" }, "notifications": { "display-name-changed-permanently": "Display name is changed permanently.", diff --git a/public_included_ws_fallback/scripts/localization.js b/public_included_ws_fallback/scripts/localization.js index d09d5c0..c7d9716 100644 --- a/public_included_ws_fallback/scripts/localization.js +++ b/public_included_ws_fallback/scripts/localization.js @@ -2,8 +2,8 @@ class Localization { constructor() { Localization.defaultLocale = "en"; Localization.supportedLocales = ["en"]; - Localization.translations = {}; + Localization.defaultTranslations = {}; const initialLocale = Localization.supportedOrDefault(Localization.browserLocales()); @@ -29,13 +29,13 @@ class Localization { static async setLocale(newLocale) { if (newLocale === Localization.locale) return false; + const firstTranslation = !Localization.locale + Localization.defaultTranslations = await Localization.fetchTranslationsFor(Localization.defaultLocale); const newTranslations = await Localization.fetchTranslationsFor(newLocale); if(!newTranslations) return false; - const firstTranslation = !Localization.locale - Localization.locale = newLocale; Localization.translations = newTranslations; @@ -65,18 +65,20 @@ class Localization { for (let i in attrs) { let attr = attrs[i]; if (attr === "text") { - element.innerText = await Localization.getTranslation(key); + element.innerText = Localization.getTranslation(key); } else { - element.attr = await Localization.getTranslation(key, attr); + element.attr = Localization.getTranslation(key, attr); } } } - static getTranslation(key, attr, data) { + static getTranslation(key, attr, data, useDefault=false) { const keys = key.split("."); - let translationCandidates = Localization.translations; + let translationCandidates = useDefault + ? Localization.defaultTranslations + : Localization.translations; for (let i=0; i Date: Fri, 7 Jul 2023 00:06:38 +0200 Subject: [PATCH 078/568] Added translation using Weblate (German) --- public/lang/de.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 public/lang/de.json diff --git a/public/lang/de.json b/public/lang/de.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/public/lang/de.json @@ -0,0 +1 @@ +{} From 1d333c850c37e6df5b97764b432d6078ce3380e4 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Fri, 7 Jul 2023 00:07:04 +0200 Subject: [PATCH 079/568] Added translation using Weblate (Russian) --- public/lang/ru.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 public/lang/ru.json diff --git a/public/lang/ru.json b/public/lang/ru.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/public/lang/ru.json @@ -0,0 +1 @@ +{} From 410936dcd888aaa4c140f71b9faf499e7720561c Mon Sep 17 00:00:00 2001 From: kek Date: Fri, 7 Jul 2023 00:04:00 +0000 Subject: [PATCH 080/568] Translated using Weblate (Russian) Currently translated at 100.0% (118 of 118 strings) Translation: PairDrop/pairdrop-spa Translate-URL: https://hosted.weblate.org/projects/pairdrop/pairdrop-spa/ru/ --- public/lang/ru.json | 137 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 1 deletion(-) diff --git a/public/lang/ru.json b/public/lang/ru.json index 0967ef4..bcd0103 100644 --- a/public/lang/ru.json +++ b/public/lang/ru.json @@ -1 +1,136 @@ -{} +{ + "header": { + "about_aria-label": "Открыть страницу \"О сервисе\"", + "pair-device_title": "Подключить устройство", + "install_title": "Установить PairDrop", + "cancel-paste-mode": "Выполнено", + "edit-paired-devices_title": "Редактировать сопряженные устройства", + "notification_title": "Включить уведомления", + "about_title": "О сервисе", + "theme-auto_title": "Адаптировать тему к системной", + "theme-dark_title": "Всегда использовать темную тему", + "theme-light_title": "Всегда использовать светлую тему" + }, + "instructions": { + "x-instructions_desktop": "Нажмите, чтобы отправить файлы, или щелкните правой кнопкой мыши, чтобы отправить сообщение", + "no-peers_data-drop-bg": "Отпустите, чтобы выбрать получателя", + "click-to-send": "Нажмите, чтобы отправить", + "x-instructions_data-drop-bg": "Отпустите, чтобы выбрать получателя", + "tap-to-send": "Нажмите, чтобы отправить", + "x-instructions_data-drop-peer": "Отпустите, чтобы послать узлу", + "x-instructions_mobile": "Нажмите, чтобы отправить файлы, или долго нажмите, чтобы отправить сообщение", + "no-peers-title": "Откройте PairDrop на других устройствах, чтобы отправить файлы", + "no-peers-subtitle": "Сопрягите устройства из разных сетей." + }, + "footer": { + "discovery-everyone": "О вас может узнать любой", + "display-name_placeholder": "Загрузка...", + "routed": "направляется через сервер", + "webrtc": "есть WebRTC недоступен.", + "traffic": "Трафик:", + "and-by": "и от", + "paired-devices": "сопряженные устройства", + "known-as": "Вы известны под именем:", + "on-this-network": "в этой сети", + "display-name_title": "Изменить имя вашего устройства навсегда" + }, + "dialogs": { + "activate-paste-mode-and-other-files": "и {{count}} других файлов", + "activate-paste-mode-base": "Откройте PairDrop на других устройствах, чтобы отправить", + "activate-paste-mode-activate-paste-mode-shared-text": "общий текст", + "edit-paired-devices-title": "Редактировать сопряженные устройства", + "auto-accept": "автоприем", + "close": "Закрыть", + "decline": "Отклонить", + "share": "Поделиться", + "would-like-to-share": "хотел бы поделиться", + "has-sent": "отправил:", + "paired-devices-wrapper_data-empty": "Нет сопряженных устройств.", + "download": "Скачать", + "receive-text-title": "Сообщение получено", + "send": "Отправить", + "send-message-to": "Отправить сообщение", + "send-message-title": "Отправить сообщение", + "copy": "Копировать", + "base64-files": "файлы", + "base64-paste-to-send": "Вставьте здесь, чтобы отправить {{type}}", + "base64-processing": "Обработка...", + "base64-tap-to-paste": "Нажмите здесь, чтобы вставить {{type}}", + "base64-text": "текст", + "title-file": "Файл", + "title-file-plural": "Файлы", + "title-image": "Изображение", + "title-image-plural": "Изображения", + "download-again": "Скачать снова", + "auto-accept-instructions-2": "чтобы автоматически принимать все файлы, отправленные с этого устройства.", + "enter-key-from-another-device": "Для продолжения введите ключ с другого устройства.", + "pair-devices-title": "Сопрягите устройства", + "input-key-on-this-device": "Введите этот ключ на другом устройстве", + "scan-qr-code": "или отсканируйте QR-код.", + "cancel": "Отменить", + "pair": "Подключить", + "accept": "Принять", + "auto-accept-instructions-1": "Активировать", + "file-other-description-file": "и 1 другой файл", + "file-other-description-image-plural": "и {{count}} других изображений", + "file-other-description-image": "и 1 другое изображение", + "file-other-description-file-plural": "и {{count}} других файлов", + "receive-title": "{{descriptor}} получен" + }, + "about": { + "close-about-aria-label": "Закрыть страницу \"О сервисе\"", + "claim": "Самый простой способ передачи файлов между устройствами" + }, + "notifications": { + "display-name-changed-permanently": "Отображаемое имя изменено навсегда.", + "display-name-random-again": "Отображаемое имя сгенерировалось случайным образом снова.", + "pairing-success": "Устройства сопряжены успешно.", + "pairing-tabs-error": "Сопряжение двух вкладок браузера невозможно.", + "copied-to-clipboard": "Скопировано в буфер обмена", + "pairing-not-persistent": "Сопряженные устройства непостоянны.", + "link-received": "Получена ссылка от {{name}} - нажмите, чтобы открыть", + "notifications-enabled": "Уведомления включены.", + "text-content-incorrect": "Содержание текста неверно.", + "message-received": "Получено сообщение от {{name}} - нажмите, чтобы скопировать", + "connected": "Подключено.", + "copied-text": "Текст скопирован в буфер обмена", + "online": "Вы снова в сети", + "offline": "Вы находитесь вне сети", + "online-requirement": "Для сопряжения устройств вам нужно быть в сети.", + "files-incorrect": "Файлы неверны.", + "message-transfer-completed": "Передача сообщения завершена.", + "ios-memory-limit": "Отправка файлов на iOS устройства возможна только до 200 МБ за один раз", + "selected-peer-left": "Выбранный узел вышел.", + "request-title": "{{name}} хотел бы передать {{count}} {{descriptor}}", + "rate-limit-join-key": "Достигнут предел скорости. Подождите 10 секунд и повторите попытку.", + "unfinished-transfers-warning": "Есть незавершенные передачи. Вы уверены, что хотите закрыть?", + "copied-text-error": "Запись в буфер обмена не удалась. Скопируйте вручную!", + "pairing-cleared": "Все устройства не сопряжены.", + "pairing-key-invalid": "Ключ недействителен", + "pairing-key-invalidated": "Ключ {{key}} признан недействительным.", + "click-to-download": "Нажмите, чтобы скачать", + "clipboard-content-incorrect": "Содержание буфера обмена неверно.", + "click-to-show": "Нажмите, чтобы показать", + "connecting": "Подключение...", + "download-successful": "{{descriptor}} успешно загружен", + "display-name-changed-temporarily": "Отображаемое имя было изменено только для этой сессии.", + "file-content-incorrect": "Содержимое файла неверно.", + "file-transfer-completed": "Передача файла завершена." + }, + "peer-ui": { + "click-to-send-paste-mode": "Нажмите, чтобы отправить {{descriptor}}", + "preparing": "Подготовка...", + "transferring": "Передача...", + "processing": "Обработка...", + "waiting": "Ожидание...", + "connection-hash": "Чтобы проверить безопасность сквозного шифрования, сравните этот номер безопасности на обоих устройствах", + "click-to-send": "Нажмите, чтобы отправить файлы, или щелкните правой кнопкой мыши, чтобы отправить сообщение" + }, + "document-titles": { + "file-received-plural": "{{count}} файлов получено", + "message-received-plural": "{{count}} сообщений получено", + "file-received": "Файл получен", + "file-transfer-requested": "Запрошена передача файлов", + "message-received": "Сообщение получено" + } +} From 525fd295b7e264ed332c12bb9a06ca39595fa1f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Fri, 7 Jul 2023 16:08:09 +0200 Subject: [PATCH 081/568] =?UTF-8?q?Added=20translation=20using=20Weblate?= =?UTF-8?q?=20(Norwegian=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/lang/nb-NO.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 public/lang/nb-NO.json diff --git a/public/lang/nb-NO.json b/public/lang/nb-NO.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/public/lang/nb-NO.json @@ -0,0 +1 @@ +{} From 99faa6bbfd9f7982a0abd9bac1440b5d7761fc36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Fri, 7 Jul 2023 14:25:46 +0000 Subject: [PATCH 082/568] Translated using Weblate (English) Currently translated at 100.0% (122 of 122 strings) Translation: PairDrop/pairdrop-spa Translate-URL: https://hosted.weblate.org/projects/pairdrop/pairdrop-spa/en/ --- public/lang/en.json | 276 ++++++++++++++++++++++---------------------- 1 file changed, 138 insertions(+), 138 deletions(-) diff --git a/public/lang/en.json b/public/lang/en.json index 8ad7b7c..ff8294d 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -1,140 +1,140 @@ { - "header": { - "about_title": "About PairDrop", - "about_aria-label": "Open About PairDrop", - "theme-auto_title": "Adapt Theme to System", - "theme-light_title": "Always Use Light-Theme", - "theme-dark_title": "Always Use Dark-Theme", - "notification_title": "Enable Notifications", - "install_title": "Install PairDrop", - "pair-device_title": "Pair Device", - "edit-paired-devices_title": "Edit Paired Devices", - "cancel-paste-mode": "Done" - }, - "instructions": { - "no-peers_data-drop-bg": "Release to select recipient", - "no-peers-title": "Open PairDrop on other devices to send files", - "no-peers-subtitle": "Pair devices to be discoverable on other networks", - "x-instructions_desktop": "Click to send files or right click to send a message", - "x-instructions_mobile": "Tap to send files or long tap to send a message", - "x-instructions_data-drop-peer": "Release to send to peer", - "x-instructions_data-drop-bg": "Release to select recipient", - "click-to-send": "Click to send", - "tap-to-send": "Tap to send" - }, - "footer": { - "known-as": "You are known as:", - "display-name_placeholder": "Loading...", - "display-name_title": "Edit your device name permanently", - "discovery-everyone": "You can be discovered by everyone", - "on-this-network": "on this network", - "and-by": "and by", - "paired-devices": "paired devices", - "traffic": "Traffic is", - "routed": "routed through the server", - "webrtc": "if WebRTC is not available." - }, - "dialogs": { - "activate-paste-mode-base": "Open PairDrop on other devices to send", - "activate-paste-mode-and-other-files": "and {{count}} other files", - "activate-paste-mode-activate-paste-mode-shared-text": "shared text", - "pair-devices-title": "Pair Devices", - "input-key-on-this-device": "Input this key on another device", - "scan-qr-code": "or scan the QR-Code.", - "enter-key-from-another-device": "Enter key from another device to continue.", - "pair": "Pair", - "cancel": "Cancel", - "edit-paired-devices-title": "Edit Paired Devices", - "paired-devices-wrapper_data-empty": "No paired devices.", - "auto-accept-instructions-1": "Activate", - "auto-accept": "auto-accept", - "auto-accept-instructions-2": "to automatically accept all files sent from that device.", - "close": "Close", - "would-like-to-share": "would like to share", - "accept": "Accept", - "decline": "Decline", - "has-sent": "has sent:", - "share": "Share", - "download": "Download", - "send-message-title": "Send Message", - "send-message-to": "Send a Message to", - "send": "Send", - "receive-text-title": "Message Received", - "copy": "Copy", - "base64-processing": "Processing...", - "base64-tap-to-paste": "Tap here to paste {{type}}", - "base64-paste-to-send": "Paste here to send {{type}}", - "base64-text": "text", - "base64-files": "files", - "file-other-description-image": "and 1 other image", - "file-other-description-file": "and 1 other file", - "file-other-description-image-plural": "and {{count}} other images", - "file-other-description-file-plural": "and {{count}} other files", - "title-image": "Image", - "title-file": "File", - "title-image-plural": "Images", - "title-file-plural": "Files", - "receive-title": "{{descriptor}} Received", - "download-again": "Download again" - }, - "about": { - "close-about_aria-label": "Close About PairDrop", - "claim": "The easiest way to transfer files across devices", - "github_title": "PairDrop on Github", - "buy-me-a-coffee_title": "Buy me a coffee!", - "tweet_title": "Tweet about PairDrop", - "faq_title": "Frequently asked questions" - }, - "notifications": { - "display-name-changed-permanently": "Display name is changed permanently.", - "display-name-changed-temporarily": "Display name is changed only for this session.", - "display-name-random-again": "Display name is randomly generated again.", - "download-successful": "{{descriptor}} downloaded successfully", - "pairing-tabs-error": "Pairing of two browser tabs is not possible.", - "pairing-success": "Devices paired successfully.", - "pairing-not-persistent": "Paired devices are not persistent.", - "pairing-key-invalid": "Key not valid", - "pairing-key-invalidated": "Key {{key}} invalidated.", - "pairing-cleared": "All Devices unpaired.", - "copied-to-clipboard": "Copied to clipboard", - "text-content-incorrect": "Text content is incorrect.", - "file-content-incorrect": "File content is incorrect.", - "clipboard-content-incorrect": "Clipboard content is incorrect.", - "notifications-enabled": "Notifications enabled.", - "link-received": "Link received by {{name}} - Click to open", - "message-received": "Message received by {{name}} - Click to copy", - "click-to-download": "Click to download", - "request-title": "{{name}} would like to transfer {{count}} {{descriptor}}", - "click-to-show": "Click to show", - "copied-text": "Copied text to clipboard", - "copied-text-error": "Writing to clipboard failed. Copy manually!", - "offline": "You are offline", - "online": "You are back online", - "connected": "Connected.", - "online-requirement": "You need to be online to pair devices.", - "connecting": "Connecting...", - "files-incorrect": "Files are incorrect.", - "file-transfer-completed": "File transfer completed.", - "ios-memory-limit": "Sending files to iOS is only possible up to 200MB at once", - "message-transfer-completed": "Message transfer completed.", - "unfinished-transfers-warning": "There are unfinished transfers. Are you sure you want to close?", - "rate-limit-join-key": "Rate limit reached. Wait 10 seconds and try again.", - "selected-peer-left": "Selected peer left." - }, - "document-titles": { - "file-received": "File Received", - "file-received-plural": "{{count}} Files Received", - "file-transfer-requested": "File Transfer Requested", - "message-received": "Message Received", - "message-received-plural": "{{count}} Messages Received" - }, - "peer-ui": { - "click-to-send-paste-mode": "Click to send {{descriptor}}", - "click-to-send": "Click to send files or right click to send a message", - "connection-hash": "To verify the security of the end-to-end encryption, compare this security number on both devices", - "preparing": "Preparing...", - "waiting": "Waiting...", - "processing": "Processing...", - "transferring": "Transferring..." - } + "header": { + "about_title": "About PairDrop", + "about_aria-label": "Open About PairDrop", + "theme-auto_title": "Adapt Theme to System", + "theme-light_title": "Always Use Light-Theme", + "theme-dark_title": "Always Use Dark-Theme", + "notification_title": "Enable Notifications", + "install_title": "Install PairDrop", + "pair-device_title": "Pair Device", + "edit-paired-devices_title": "Edit Paired Devices", + "cancel-paste-mode": "Done" + }, + "instructions": { + "no-peers_data-drop-bg": "Release to select recipient", + "no-peers-title": "Open PairDrop on other devices to send files", + "no-peers-subtitle": "Pair devices to be discoverable on other networks", + "x-instructions_desktop": "Click to send files or right click to send a message", + "x-instructions_mobile": "Tap to send files or long tap to send a message", + "x-instructions_data-drop-peer": "Release to send to peer", + "x-instructions_data-drop-bg": "Release to select recipient", + "click-to-send": "Click to send", + "tap-to-send": "Tap to send" + }, + "footer": { + "known-as": "You are known as:", + "display-name_placeholder": "Loading…", + "display-name_title": "Edit your device name permanently", + "discovery-everyone": "You can be discovered by everyone", + "on-this-network": "on this network", + "and-by": "and by", + "paired-devices": "paired devices", + "traffic": "Traffic is", + "routed": "routed through the server", + "webrtc": "if WebRTC is not available." + }, + "dialogs": { + "activate-paste-mode-base": "Open PairDrop on other devices to send", + "activate-paste-mode-and-other-files": "and {{count}} other files", + "activate-paste-mode-activate-paste-mode-shared-text": "shared text", + "pair-devices-title": "Pair Devices", + "input-key-on-this-device": "Input this key on another device", + "scan-qr-code": "or scan the QR-code.", + "enter-key-from-another-device": "Enter key from another device to continue.", + "pair": "Pair", + "cancel": "Cancel", + "edit-paired-devices-title": "Edit Paired Devices", + "paired-devices-wrapper_data-empty": "No paired devices.", + "auto-accept-instructions-1": "Activate", + "auto-accept": "auto-accept", + "auto-accept-instructions-2": "to automatically accept all files sent from that device.", + "close": "Close", + "would-like-to-share": "would like to share", + "accept": "Accept", + "decline": "Decline", + "has-sent": "has sent:", + "share": "Share", + "download": "Download", + "send-message-title": "Send Message", + "send-message-to": "Send a Message to", + "send": "Send", + "receive-text-title": "Message Received", + "copy": "Copy", + "base64-processing": "Processing...", + "base64-tap-to-paste": "Tap here to paste {{type}}", + "base64-paste-to-send": "Paste here to send {{type}}", + "base64-text": "text", + "base64-files": "files", + "file-other-description-image": "and 1 other image", + "file-other-description-file": "and 1 other file", + "file-other-description-image-plural": "and {{count}} other images", + "file-other-description-file-plural": "and {{count}} other files", + "title-image": "Image", + "title-file": "File", + "title-image-plural": "Images", + "title-file-plural": "Files", + "receive-title": "{{descriptor}} Received", + "download-again": "Download again" + }, + "about": { + "close-about_aria-label": "Close About PairDrop", + "claim": "The easiest way to transfer files across devices", + "github_title": "PairDrop on GitHub", + "buy-me-a-coffee_title": "Buy me a coffee!", + "tweet_title": "Tweet about PairDrop", + "faq_title": "Frequently asked questions" + }, + "notifications": { + "display-name-changed-permanently": "Display name is changed permanently.", + "display-name-changed-temporarily": "Display name is changed only for this session.", + "display-name-random-again": "Display name is randomly generated again.", + "download-successful": "{{descriptor}} downloaded", + "pairing-tabs-error": "Pairing two web browser tabs is impossible.", + "pairing-success": "Devices paired.", + "pairing-not-persistent": "Paired devices are not persistent.", + "pairing-key-invalid": "Invalid key", + "pairing-key-invalidated": "Key {{key}} invalidated.", + "pairing-cleared": "All Devices unpaired.", + "copied-to-clipboard": "Copied to clipboard", + "text-content-incorrect": "Text content is incorrect.", + "file-content-incorrect": "File content is incorrect.", + "clipboard-content-incorrect": "Clipboard content is incorrect.", + "notifications-enabled": "Notifications enabled.", + "link-received": "Link received by {{name}} - Click to open", + "message-received": "Message received by {{name}} - Click to copy", + "click-to-download": "Click to download", + "request-title": "{{name}} would like to transfer {{count}} {{descriptor}}", + "click-to-show": "Click to show", + "copied-text": "Copied text to clipboard", + "copied-text-error": "Writing to clipboard failed. Copy manually!", + "offline": "You are offline", + "online": "You are back online", + "connected": "Connected.", + "online-requirement": "You need to be online to pair devices.", + "connecting": "Connecting…", + "files-incorrect": "Files are incorrect.", + "file-transfer-completed": "File transfer completed.", + "ios-memory-limit": "Sending files to iOS is only possible up to 200 MB at once", + "message-transfer-completed": "Message transfer completed.", + "unfinished-transfers-warning": "There are unfinished transfers. Are you sure you want to close?", + "rate-limit-join-key": "Rate limit reached. Wait 10 seconds and try again.", + "selected-peer-left": "Selected peer left." + }, + "document-titles": { + "file-received": "File Received", + "file-received-plural": "{{count}} Files Received", + "file-transfer-requested": "File Transfer Requested", + "message-received": "Message Received", + "message-received-plural": "{{count}} Messages Received" + }, + "peer-ui": { + "click-to-send-paste-mode": "Click to send {{descriptor}}", + "click-to-send": "Click to send files or right click to send a message", + "connection-hash": "To verify the security of the end-to-end encryption, compare this security number on both devices", + "preparing": "Preparing…", + "waiting": "Waiting…", + "processing": "Processing…", + "transferring": "Transferring…" + } } From 65ec416646b4e58e1a27bfb775d4f11b5b65db3a Mon Sep 17 00:00:00 2001 From: kek Date: Fri, 7 Jul 2023 23:20:33 +0000 Subject: [PATCH 083/568] Translated using Weblate (Russian) Currently translated at 100.0% (122 of 122 strings) Translation: PairDrop/pairdrop-spa Translate-URL: https://hosted.weblate.org/projects/pairdrop/pairdrop-spa/ru/ --- public/lang/ru.json | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/public/lang/ru.json b/public/lang/ru.json index bcd0103..8617ec2 100644 --- a/public/lang/ru.json +++ b/public/lang/ru.json @@ -16,19 +16,19 @@ "no-peers_data-drop-bg": "Отпустите, чтобы выбрать получателя", "click-to-send": "Нажмите, чтобы отправить", "x-instructions_data-drop-bg": "Отпустите, чтобы выбрать получателя", - "tap-to-send": "Нажмите, чтобы отправить", + "tap-to-send": "Прикоснитесь, чтобы отправить", "x-instructions_data-drop-peer": "Отпустите, чтобы послать узлу", - "x-instructions_mobile": "Нажмите, чтобы отправить файлы, или долго нажмите, чтобы отправить сообщение", + "x-instructions_mobile": "Прикоснитесь коротко, чтобы отправить файлы, или долго, чтобы отправить сообщение", "no-peers-title": "Откройте PairDrop на других устройствах, чтобы отправить файлы", "no-peers-subtitle": "Сопрягите устройства из разных сетей." }, "footer": { - "discovery-everyone": "О вас может узнать любой", - "display-name_placeholder": "Загрузка...", + "discovery-everyone": "О вас может узнать каждый", + "display-name_placeholder": "Загрузка…", "routed": "направляется через сервер", - "webrtc": "есть WebRTC недоступен.", - "traffic": "Трафик:", - "and-by": "и от", + "webrtc": ", если WebRTC недоступен.", + "traffic": "Трафик", + "and-by": "и", "paired-devices": "сопряженные устройства", "known-as": "Вы известны под именем:", "on-this-network": "в этой сети", @@ -55,13 +55,13 @@ "base64-files": "файлы", "base64-paste-to-send": "Вставьте здесь, чтобы отправить {{type}}", "base64-processing": "Обработка...", - "base64-tap-to-paste": "Нажмите здесь, чтобы вставить {{type}}", + "base64-tap-to-paste": "Прикоснитесь здесь, чтобы вставить {{type}}", "base64-text": "текст", "title-file": "Файл", "title-file-plural": "Файлы", "title-image": "Изображение", "title-image-plural": "Изображения", - "download-again": "Скачать снова", + "download-again": "Скачать еще раз", "auto-accept-instructions-2": "чтобы автоматически принимать все файлы, отправленные с этого устройства.", "enter-key-from-another-device": "Для продолжения введите ключ с другого устройства.", "pair-devices-title": "Сопрягите устройства", @@ -79,12 +79,17 @@ }, "about": { "close-about-aria-label": "Закрыть страницу \"О сервисе\"", - "claim": "Самый простой способ передачи файлов между устройствами" + "claim": "Самый простой способ передачи файлов между устройствами", + "close-about_aria-label": "Закрыть страницу \"О сервисе\"", + "buy-me-a-coffee_title": "Купить мне кофе!", + "github_title": "PairDrop на GitHub", + "tweet_title": "Твит о PairDrop", + "faq_title": "Часто задаваемые вопросы" }, "notifications": { - "display-name-changed-permanently": "Отображаемое имя изменено навсегда.", + "display-name-changed-permanently": "Отображаемое имя было изменено навсегда.", "display-name-random-again": "Отображаемое имя сгенерировалось случайным образом снова.", - "pairing-success": "Устройства сопряжены успешно.", + "pairing-success": "Устройства сопряжены.", "pairing-tabs-error": "Сопряжение двух вкладок браузера невозможно.", "copied-to-clipboard": "Скопировано в буфер обмена", "pairing-not-persistent": "Сопряженные устройства непостоянны.", @@ -106,23 +111,23 @@ "unfinished-transfers-warning": "Есть незавершенные передачи. Вы уверены, что хотите закрыть?", "copied-text-error": "Запись в буфер обмена не удалась. Скопируйте вручную!", "pairing-cleared": "Все устройства не сопряжены.", - "pairing-key-invalid": "Ключ недействителен", + "pairing-key-invalid": "Неверный ключ", "pairing-key-invalidated": "Ключ {{key}} признан недействительным.", "click-to-download": "Нажмите, чтобы скачать", "clipboard-content-incorrect": "Содержание буфера обмена неверно.", "click-to-show": "Нажмите, чтобы показать", - "connecting": "Подключение...", - "download-successful": "{{descriptor}} успешно загружен", + "connecting": "Подключение…", + "download-successful": "{{descriptor}} загружен", "display-name-changed-temporarily": "Отображаемое имя было изменено только для этой сессии.", "file-content-incorrect": "Содержимое файла неверно.", "file-transfer-completed": "Передача файла завершена." }, "peer-ui": { "click-to-send-paste-mode": "Нажмите, чтобы отправить {{descriptor}}", - "preparing": "Подготовка...", - "transferring": "Передача...", - "processing": "Обработка...", - "waiting": "Ожидание...", + "preparing": "Подготовка…", + "transferring": "Передача…", + "processing": "Обработка…", + "waiting": "Ожидание…", "connection-hash": "Чтобы проверить безопасность сквозного шифрования, сравните этот номер безопасности на обоих устройствах", "click-to-send": "Нажмите, чтобы отправить файлы, или щелкните правой кнопкой мыши, чтобы отправить сообщение" }, From 9b71d93dd3dff789c77456a9ff3691dfbb3d8230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Fri, 7 Jul 2023 14:09:16 +0000 Subject: [PATCH 084/568] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 80.3% (98 of 122 strings) Translation: PairDrop/pairdrop-spa Translate-URL: https://hosted.weblate.org/projects/pairdrop/pairdrop-spa/nb_NO/ --- public/lang/nb-NO.json | 130 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/public/lang/nb-NO.json b/public/lang/nb-NO.json index 0967ef4..52f1d3d 100644 --- a/public/lang/nb-NO.json +++ b/public/lang/nb-NO.json @@ -1 +1,129 @@ -{} +{ + "header": { + "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-light_title": "Alltid bruk lys drakt", + "theme-dark_title": "Alltid bruk mørk drakt", + "notification_title": "Skru på merknader", + "cancel-paste-mode": "Ferdig", + "install_title": "Installer PairDrop", + "pair-device_title": "Sammenkoble enhet" + }, + "footer": { + "discovery-everyone": "Du kan oppdages av alle", + "and-by": "og av", + "webrtc": "hvis WebRTC ikke er tilgjengelig.", + "display-name_placeholder": "Laster inn …", + "display-name_title": "Rediger det vedvarende enhetsnavnet ditt", + "traffic": "Trafikken", + "on-this-network": "på dette nettverket", + "known-as": "Du er kjent som:", + "paired-devices": "sammenkoblede enheter", + "routed": "Sendes gjennom tjeneren" + }, + "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", + "click-to-send": "Klikk for å sende", + "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", + "x-instructions_data-drop-peer": "Slipp for å sende til likemann", + "tap-to-send": "Trykk for å sende" + }, + "dialogs": { + "input-key-on-this-device": "Skriv inn denne nøkkelen på en annen enhet", + "pair-devices-title": "Sammenkoble enheter", + "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", + "accept": "Godta", + "has-sent": "har sendt:", + "base64-paste-to-send": "Trykk her for å sende {{type}}", + "base64-text": "tekst", + "base64-files": "filer", + "file-other-description-image-plural": "og {{count}} andre bilder", + "receive-title": "{{descriptor}} mottatt", + "send-message-title": "Send melding", + "base64-processing": "Behandler …", + "close": "Lukk", + "decline": "Avslå", + "download": "Last ned", + "copy": "Kopier", + "pair": "Sammenkoble", + "cancel": "Avbryt", + "scan-qr-code": "eller skann QR-koden.", + "auto-accept-instructions-1": "Aktiver", + "receive-text-title": "Melding mottatt", + "auto-accept": "auto-godkjenn", + "share": "Del", + "send-message-to": "Send en melding til", + "send": "Send", + "base64-tap-to-paste": "Trykk her for å lime inn {{type]]", + "file-other-description-image": "og ett annet bilde", + "file-other-description-file-plural": "og {{count}} andre filer", + "title-file-plural": "Filer", + "download-again": "Last ned igjen", + "file-other-description-file": "og én annen fil", + "title-image": "Bilde", + "title-file": "Fil", + "title-image-plural": "Bilder", + "activate-paste-mode-base": "Åpne PairDrop på andre enheter for å sende", + "activate-paste-mode-and-other-files": "og {{count}} andre filer", + "activate-paste-mode-activate-paste-mode-shared-text": "delt tekst" + }, + "about": { + "close-about_aria-label": "Lukk «Om PairDrop»", + "faq_title": "Ofte stilte spørsmål", + "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" + }, + "notifications": { + "copied-to-clipboard": "Kopiert til utklippstavlen", + "pairing-tabs-error": "Sammenkobling av to nettleserfaner er ikke mulig.", + "notifications-enabled": "Merknader påskrudd.", + "click-to-show": "Klikk for å vise", + "copied-text": "Tekst kopiert til utklippstavlen", + "connected": "Tilkoblet.", + "online": "Du er tilbake på nett", + "file-transfer-completed": "Filoverføring utført.", + "selected-peer-left": "Valgt likemann dro.", + "pairing-key-invalid": "Ugyldig nøkkel", + "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.", + "display-name-random-again": "Visningsnavnet er tilfeldig generert igjen.", + "display-name-changed-permanently": "Visningsnavnet er endret for godt.", + "display-name-changed-temporarily": "Visningsnavnet er endret kun for denne økten.", + "text-content-incorrect": "Tekstinnholdet er uriktig.", + "file-content-incorrect": "Filinnholdet er uriktig.", + "click-to-download": "Klikk for å laste ned", + "message-transfer-completed": "Meldingsoverføring utført.", + "download-successful": "{{descriptor}} nedlastet", + "pairing-success": "Enheter sammenkoblet.", + "pairing-cleared": "Sammenkobling av alle enheter opphevet." + }, + "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" + }, + "peer-ui": { + "preparing": "Forbereder …", + "waiting": "Venter", + "processing": "Behandler …", + "transferring": "Overfører …", + "click-to-send": "Klikk for å sende filer, eller høyreklikk for å sende en melding", + "click-to-send-paste-mode": "Klikk for å sende {{descriptor}}" + } +} From 044d7aa20da61538448d8f2c586add8b38959062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Sat, 8 Jul 2023 12:59:58 +0000 Subject: [PATCH 085/568] docker-swarm-usage reworked --- docs/docker-swarm-usage.md | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/docs/docker-swarm-usage.md b/docs/docker-swarm-usage.md index ae2c97e..218ae82 100644 --- a/docs/docker-swarm-usage.md +++ b/docs/docker-swarm-usage.md @@ -2,42 +2,46 @@ ## Healthcheck -The [Docker Image](../Dockerfile) includes a Healthcheck with the following options: +The [Docker Image](../Dockerfile) includes a health check with the following options: ``` --interval=30s ``` -> Specifies the time interval at which the health check should be performed. In this case, the health check will be performed every 30 seconds. - +> Specifies the time interval to run the health check. \ +> In this case, the health check is performed every 30 seconds.
``` --timeout=10s ``` -> Specifies the amount of time to wait for a response from the health check command. If the response does not arrive within 10 seconds, the health check will be considered a failure. - +> Specifies the amount of time to wait for a response from the \"healthcheck\" command. \ +> If the response does not arrive within 10 seconds, the health check fails.
``` --start-period=5s ``` -> Specifies the amount of time to wait before starting the health check process. In this case, the health check process will begin 5 seconds after the container is started. - +> Specifies the amount of time to wait before starting the health check process. \ +> In this case, the health check process will begin 5 seconds after the container is started.
``` --retries=3 ``` -> Specifies the number of times Docker should retry the health check before considering the container to be unhealthy. - +> Specifies the number of times Docker should retry the health check \ +> before considering the container to be unhealthy.
-The CMD instruction is used to define the command that will be run as part of the health check. -In this case, the command is `wget --quiet --tries=1 --spider http://localhost:3000/ || exit 1`. This command will attempt to connect to `http://localhost:3000/` -and if it fails it will exit with a status code of `1`. If this command returns a status code other than `0`, the health check will be considered a failure. +The CMD instruction is used to define the command that will be run as part of the health check. \ +In this case, the command is `wget --quiet --tries=1 --spider http://localhost:3000/ || exit 1`. \ +This command will attempt to connect to `http://localhost:3000/` \ +and if it fails it will exit with a status code of `1`. \ +If this command returns a status code other than `0`, the health check fails. -Overall, this HEALTHCHECK instruction is defining a health check process that will run every 30 seconds, wait up to 10 seconds for a response, -begin 5 seconds after the container is started, and retry up to 3 times. -The health check will consist of attempting to connect to http://localhost:3000/ and will consider the container to be unhealthy if it is unable to connect. +Overall, this HEALTHCHECK instruction is defining a health check process \ +that runs every 30 seconds, and waits up to 10 seconds for a response, \ +begins 5 seconds after the container is started, and retries up to 3 times. \ +The health check attempts to connect to http://localhost:3000/ \ +and will considers the container unhealthy if unable to connect. From 9424f704bf6eae38fed9b13ec985703feb7c3f7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Sat, 8 Jul 2023 13:14:07 +0000 Subject: [PATCH 086/568] host-your-own reworked --- docs/host-your-own.md | 171 +++++++++++++++++++++++++++--------------- 1 file changed, 111 insertions(+), 60 deletions(-) diff --git a/docs/host-your-own.md b/docs/host-your-own.md index 41ebe9b..5816a95 100644 --- a/docs/host-your-own.md +++ b/docs/host-your-own.md @@ -3,9 +3,10 @@ The easiest way to get PairDrop up and running is by using Docker. > TURN server for Internet Transfer > -> Beware that you have to host your own TURN server in order to enable transfers between different networks. +> Beware that you have to host your own TURN server to enable transfers between different networks. > -> You can follow [this guide](https://gabrieltanner.org/blog/turn-server/) to either install coturn directly on your system (Step 1) or deploy it via docker-compose (Step 5). +> Follow [this guide](https://gabrieltanner.org/blog/turn-server/) to either install coturn directly on your system (Step 1) \ +> or deploy it via docker-compose (Step 5). ## Deployment with Docker @@ -15,9 +16,11 @@ The easiest way to get PairDrop up and running is by using Docker. docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 lscr.io/linuxserver/pairdrop ``` -> You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). +> You must use a server proxy to set the X-Forwarded-For \ +> to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). > -> To prevent bypassing the proxy by reaching the docker container directly, `127.0.0.1` is specified in the run command. +> To prevent bypassing the proxy by reaching the docker container directly, \ +> `127.0.0.1` is specified in the run command. #### Options / Flags Set options by using the following flags in the `docker run` command: @@ -39,21 +42,30 @@ Set options by using the following flags in the `docker run` command: ```bash -e IPV6_LOCALIZE=4 ``` -> To enable Peer Discovery among IPv6 peers, you can specify a reduced number of segments of the client IPv6 address to be evaluated as the peer's IP. This can be especially useful when using Cloudflare as a proxy. +> To enable Peer Discovery among IPv6 peers, you can specify a reduced number of segments \ +> of the client IPv6 address to be evaluated as the peer's IP. \ +> This can be especially useful when using Cloudflare as a proxy. > -> The flag must be set to an **integer** between `1` and `7`. The number represents the number of IPv6 [hextets](https://en.wikipedia.org/wiki/IPv6#Address_representation) to match the client IP against. The most common value would be `4`, which will group peers within the same `/64` subnet. +> The flag must be set to an **integer** between `1` and `7`. \ +> The number represents the number of IPv6 [hextets](https://en.wikipedia.org/wiki/IPv6#Address_representation) \ +> to match the client IP against. The most common value would be `4`, \ +> which will group peers within the same `/64` subnet. ##### Websocket Fallback (for VPN) ```bash -e WS_FALLBACK=true ``` -> Provides PairDrop to clients with an included websocket fallback if the peer to peer WebRTC connection is not available to the client. +> Provides PairDrop to clients with an included websocket fallback \ +> if the peer to peer WebRTC connection is not available to the client. > -> This is not used on the official https://pairdrop.net, but you can activate it on your self-hosted instance. -> This is especially useful if you connect to your instance via a VPN as most VPN services block WebRTC completely in order to hide your real IP address ([read more](https://privacysavvy.com/security/safe-browsing/disable-webrtc-chrome-firefox-safari-opera-edge/)). +> This is not used on the official https://pairdrop.net website, \ +> but you can activate it on your self-hosted instance. +> This is especially useful if you connect to your instance via a VPN (as most VPN services block WebRTC completely in order to hide your real IP address). ([Read more here](https://privacysavvy.com/security/safe-browsing/disable-webrtc-chrome-firefox-safari-opera-edge/)). > -> **Warning:** All traffic sent between devices using this fallback is routed through the server and therefor not peer to peer! -> Beware that the traffic routed via this fallback is readable by the server. Only ever use this on instances you can trust. +> **Warning:** All traffic sent between devices using this fallback \ +> is routed through the server and therefor not peer to peer! \ +> Beware that the traffic routed via this fallback is readable by the server. \ +> Only ever use this on instances you can trust. \ > Additionally, beware that all traffic using this fallback debits the servers data plan. ##### Specify STUN/TURN Servers @@ -61,7 +73,8 @@ Set options by using the following flags in the `docker run` command: -e RTC_CONFIG="rtc_config.json" ``` -> Specify the STUN/TURN servers PairDrop clients use by setting `RTC_CONFIG` to a JSON file including the configuration. +> Specify the STUN/TURN servers PairDrop clients use by setting \ +> `RTC_CONFIG` to a JSON file including the configuration. \ > You can use `pairdrop/rtc_config_example.json` as a starting point. > > To host your own TURN server you can follow this guide: https://gabrieltanner.org/blog/turn-server/ @@ -83,8 +96,10 @@ Set options by using the following flags in the `docker run` command: -e DEBUG_MODE="true" ``` -> Use this flag to enable debugging information about the connecting peers IP addresses. This is quite useful to check whether the [#HTTP-Server](#http-server) -> is configured correctly, so the auto discovery feature works correctly. Otherwise, all clients discover each other mutually, independently of their network status. +> Use this flag to enable debugging information about the connecting peers IP addresses. \ +> This is quite useful to check whether the [#HTTP-Server](#http-server) \ +> is configured correctly, so the auto-discovery feature works correctly. \ +> Otherwise, all clients discover each other mutually, independently of their network status. > > If this flag is set to `"true"` each peer that connects to the PairDrop server will produce a log to STDOUT like this: > ``` @@ -97,7 +112,7 @@ Set options by using the following flags in the `docker run` command: > if IP is private, '127.0.0.1' is used instead > ----DEBUGGING-PEER-IP-END---- > ``` -> If the IP PairDrop uses is the public IP of your device everything is correctly setup. +> If the IP PairDrop uses is the public IP of your device, everything is set up correctly. \ >To find out your devices public IP visit https://www.whatismyip.com/. > > To preserve your clients' privacy, **never use this flag in production!** @@ -109,13 +124,17 @@ Set options by using the following flags in the `docker run` command: ```bash docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 ghcr.io/schlagmichdoch/pairdrop npm run start:prod ``` -> You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). +> You must use a server proxy to set the X-Forwarded-For to prevent \ +> all clients from discovering each other (See [#HTTP-Server](#http-server)). > -> To prevent bypassing the proxy by reaching the docker container directly, `127.0.0.1` is specified in the run command. +> To prevent bypassing the proxy by reaching the Docker container directly, \ +> `127.0.0.1` is specified in the run command. > -> To specify options replace `npm run start:prod` according to [the documentation below.](#options--flags-1) +> To specify options replace `npm run start:prod` \ +> according to [the documentation below.](#options--flags-1) -> The Docker Image includes a Healthcheck. To learn more see [Docker Swarm Usage](docker-swarm-usage.md#docker-swarm-usage) +> The Docker Image includes a healthcheck. \ +> Read more about [Docker Swarm Usage](docker-swarm-usage.md#docker-swarm-usage). ### Docker Image self-built #### Build the image @@ -130,13 +149,17 @@ docker build --pull . -f Dockerfile -t pairdrop ```bash docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 -it pairdrop npm run start:prod ``` -> You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). +> You must use a server proxy to set the X-Forwarded-For \ +> to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). > -> To prevent bypassing the proxy by reaching the docker container directly, `127.0.0.1` is specified in the run command. +> To prevent bypassing the proxy by reaching the Docker container \ +> directly, `127.0.0.1` is specified in the run command. > -> To specify options replace `npm run start:prod` according to [the documentation below.](#options--flags-1) +> To specify options replace `npm run start:prod` \ +> according to [the documentation below.](#options--flags-1) -> The Docker Image includes a Healthcheck. To learn more see [Docker Swarm Usage](docker-swarm-usage.md#docker-swarm-usage) +> The Docker Image includes a Healthcheck. \ +Read more about [Docker Swarm Usage](docker-swarm-usage.md#docker-swarm-usage).
@@ -162,9 +185,11 @@ services: Run the compose file with `docker compose up -d`. -> You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). +> You must use a server proxy to set the X-Forwarded-For \ +> to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). > -> To prevent bypassing the proxy by reaching the docker container directly, `127.0.0.1` is specified in the run command. +> To prevent bypassing the proxy by reaching the Docker container \ +> directly, `127.0.0.1` is specified in the run command.
@@ -190,7 +215,7 @@ or npm start ``` -> Remember to check your IP Address using your OS command to see where you can access the server. +> Remember to check your IP address using your OS command to see where you can access the server. > By default, the node server listens on port 3000. @@ -212,7 +237,8 @@ $env:PORT=3010; npm start ```bash IPV6_LOCALIZE=4 ``` -> Truncate a portion of the client IPv6 address to make peers more discoverable. See [Options/Flags](#options--flags) above. +> Truncate a portion of the client IPv6 address to make peers more discoverable. \ +> See [Options/Flags](#options--flags) above. #### Specify STUN/TURN Server On Unix based systems @@ -223,10 +249,12 @@ On Windows ```bash $env:RTC_CONFIG="rtc_config.json"; npm start ``` -> Specify the STUN/TURN servers PairDrop clients use by setting `RTC_CONFIG` to a JSON file including the configuration. +> Specify the STUN/TURN servers PairDrop clients use by setting `RTC_CONFIG` \ +> to a JSON file including the configuration. \ > You can use `pairdrop/rtc_config_example.json` as a starting point. > -> To host your own TURN server you can follow this guide: https://gabrieltanner.org/blog/turn-server/ +> To host your own TURN server you can follow this guide: \ +> https://gabrieltanner.org/blog/turn-server/ > > Default configuration: > ```json @@ -250,10 +278,13 @@ On Windows $env:DEBUG_MODE="true"; npm start ``` -> Use this flag to enable debugging information about the connecting peers IP addresses. This is quite useful to check whether the [#HTTP-Server](#http-server) -> is configured correctly, so the auto discovery feature works correctly. Otherwise, all clients discover each other mutually, independently of their network status. +> Use this flag to enable debugging info about the connecting peers IP addresses. \ +> This is quite useful to check whether the [#HTTP-Server](#http-server) \ +> is configured correctly, so the auto discovery feature works correctly. \ +> Otherwise, all clients discover each other mutually, independently of their network status. > -> If this flag is set to `"true"` each peer that connects to the PairDrop server will produce a log to STDOUT like this: +> If this flag is set to `"true"` each peer that connects to the \ +> PairDrop server will produce a log to STDOUT like this: > ``` > ----DEBUGGING-PEER-IP-START---- > remoteAddress: ::ffff:172.17.0.1 @@ -264,10 +295,10 @@ $env:DEBUG_MODE="true"; npm start > if IP is private, '127.0.0.1' is used instead > ----DEBUGGING-PEER-IP-END---- > ``` -> If the IP PairDrop uses is the public IP of your device everything is correctly setup. ->To find out your devices public IP visit https://www.whatismyip.com/. +> If the IP PairDrop uses is the public IP of your device everything is set up correctly. \ +>Find your devices public IP by visiting https://www.whatismyip.com/. > -> To preserve your clients' privacy, **never use this flag in production!** +> Preserve your clients' privacy. **Never use this flag in production!** ### Options / Flags @@ -277,9 +308,11 @@ npm start -- --localhost-only ``` > Only allow connections from localhost. > -> You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). +> You must use a server proxy to set the X-Forwarded-For \ +> to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). > -> Use this when deploying PairDrop with node to prevent bypassing the proxy by reaching the docker container directly. +> Use this when deploying PairDrop with node to prevent \ +> bypassing the proxy by reaching the Docker container directly. #### Automatic restart on error ```bash @@ -301,13 +334,19 @@ npm start -- --rate-limit ```bash npm start -- --include-ws-fallback ``` -> Provides PairDrop to clients with an included websocket fallback if the peer to peer WebRTC connection is not available to the client. +> Provides PairDrop to clients with an included websocket fallback \ +> if the peer to peer WebRTC connection is not available to the client. > -> This is not used on the official https://pairdrop.net, but you can activate it on your self-hosted instance. -> This is especially useful if you connect to your instance via a VPN as most VPN services block WebRTC completely in order to hide your real IP address ([read more](https://privacysavvy.com/security/safe-browsing/disable-webrtc-chrome-firefox-safari-opera-edge/)). +> This is not used on the official https://pairdrop.net, \ +but you can activate it on your self-hosted instance. \ +> This is especially useful if you connect to your instance \ +> via a VPN as most VPN services block WebRTC completely in order to hide your real IP address. +> ([Read more](https://privacysavvy.com/security/safe-browsing/disable-webrtc-chrome-firefox-safari-opera-edge/)). > -> **Warning:** All traffic sent between devices using this fallback is routed through the server and therefor not peer to peer! -> Beware that the traffic routed via this fallback is readable by the server. Only ever use this on instances you can trust. +> **Warning:** All traffic sent between devices using this fallback \ +> is routed through the server and therefor not peer to peer! \ +> Beware that the traffic routed via this fallback is readable by the server. \ +> Only ever use this on instances you can trust. \ > Additionally, beware that all traffic using this fallback debits the servers data plan.
@@ -321,10 +360,12 @@ npm run start:prod ```bash npm run start:prod -- --localhost-only --include-ws-fallback ``` -> To prevent connections to the node server from bypassing the proxy server you should always use "--localhost-only" on production. +> To prevent connections to the node server from bypassing \ +> the proxy server you should always use "--localhost-only" on production. ## HTTP-Server -When running PairDrop, the `X-Forwarded-For` header has to be set by a proxy. Otherwise, all clients will be mutually visible. +When running PairDrop, the `X-Forwarded-For` header has to be set by a proxy. \ +Otherwise, all clients will be mutually visible. To check if your setup is configured correctly [use the environment variable `DEBUG_MODE="true"`](#debug-mode). @@ -405,10 +446,10 @@ a2enmod proxy_wstunnel
-Create a new configuration file under `/etc/apache2/sites-available` (on debian) +Create a new configuration file under `/etc/apache2/sites-available` (on Debian) **pairdrop.conf** -#### Allow http and https requests +#### Allow HTTP and HTTPS requests ```apacheconf ProxyPass / http://127.0.0.1:3000/ @@ -425,7 +466,7 @@ Create a new configuration file under `/etc/apache2/sites-available` (on debian) RewriteRule ^/?(.*) "wws://127.0.0.1:3000/$1" [P,L] ``` -#### Automatic http to https redirect: +#### Automatic HTTP to HTTPS redirect: ```apacheconf Redirect permanent / https://127.0.0.1:3000/ @@ -438,7 +479,7 @@ Create a new configuration file under `/etc/apache2/sites-available` (on debian) RewriteRule ^/?(.*) "wws://127.0.0.1:3000/$1" [P,L] ``` -Activate the new virtual host and reload apache: +Activate the new virtual host and reload Apache: ```bash a2ensite pairdrop ``` @@ -462,28 +503,38 @@ Then, clone the repository and run docker-compose: docker-compose up -d ``` -Now point your browser to `http://localhost:8080`. +Now point your web browser to `http://localhost:8080`. -- To restart the containers run `docker-compose restart`. -- To stop the containers run `docker-compose stop`. -- To debug the NodeJS server run `docker logs pairdrop_node_1`. +- To restart the containers, run `docker-compose restart`. +- To stop the containers, run `docker-compose stop`. +- To debug the NodeJS server, run `docker logs pairdrop_node_1`.
## Testing PWA related features -PWAs require that the app is served under a correctly set up and trusted TLS endpoint. +PWAs requires the app to be served under a correctly set up and trusted TLS endpoint. -The nginx container creates a CA certificate and a website certificate for you. To correctly set the common name of the certificate, you need to change the FQDN environment variable in `docker/fqdn.env` to the fully qualified domain name of your workstation. +The NGINX container creates a CA certificate and a website certificate for you. \ +To correctly set the common name of the certificate, \ +you need to change the FQDN environment variable in `docker/fqdn.env` \ +to the fully qualified domain name of your workstation. -If you want to test PWA features, you need to trust the CA of the certificate for your local deployment. For your convenience, you can download the crt file from `http://:8080/ca.crt`. Install that certificate to the trust store of your operating system. -- On Windows, make sure to install it to the `Trusted Root Certification Authorities` store. -- On macOS, double-click the installed CA certificate in `Keychain Access`, expand `Trust`, and select `Always Trust` for SSL. -- Firefox uses its own trust store. To install the CA, point Firefox at `http://:8080/ca.crt`. When prompted, select `Trust this CA to identify websites` and click OK. -- When using Chrome, you need to restart Chrome so it reloads the trust store (`chrome://restart`). Additionally, after installing a new cert, you need to clear the Storage (DevTools -> Application -> Clear storage -> Clear site data). +If you want to test PWA features, you need to trust the CA of the certificate for your local deployment. \ +For your convenience, you can download the crt file from `http://:8080/ca.crt`. \ +Install that certificate to the trust store of your operating system. \ +- On Windows, make sure to install it to the `Trusted Root Certification Authorities` store. \ +- On macOS, double-click the installed CA certificate in `Keychain Access`, \ +- expand `Trust`, and select `Always Trust` for SSL. \ +- Firefox uses its own trust store. To install the CA, \ +- point Firefox at `http://:8080/ca.crt`. \ +- When prompted, select `Trust this CA to identify websites` and click \"OK\". \ +- When using Chrome, you need to restart Chrome so it reloads the trust store (`chrome://restart`). \ +- Additionally, after installing a new cert, \ +- you need to clear the Storage (DevTools → Application → Clear storage → Clear site data). Please note that the certificates (CA and webserver cert) expire after a day. -Also, whenever you restart the nginx docker, container new certificates are created. +Also, whenever you restart the NGINX Docker, container new certificates are created. The site is served on `https://:8443`. From 913b60b71270919f66923a74d855bc11938e8c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Sat, 8 Jul 2023 13:17:35 +0000 Subject: [PATCH 087/568] how-to reworked --- docs/how-to.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/how-to.md b/docs/how-to.md index a764816..f38d5f2 100644 --- a/docs/how-to.md +++ b/docs/how-to.md @@ -5,7 +5,7 @@ The [File Handling API](https://learn.microsoft.com/en-us/microsoft-edge/progres This is still experimental and must be enabled via a flag **before** the PWA is installed to Windows. 1. [Enabled feature in Edge](https://learn.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/handle-files#enable-the-file-handling-api) -2. Install PairDrop by visiting https://pairdrop.net/ with the Edge browser and install it as described [here](faq.md#help--i-cant-install-the-pwa-). +2. Install PairDrop by visiting https://pairdrop.net/ with the Edge web browser and install it as described [here](faq.md#help--i-cant-install-the-pwa-). 3. You are done! You can now send most files one at a time via PairDrop: _context menu > Open with > PairDrop_ @@ -13,7 +13,8 @@ This is still experimental and must be enabled via a flag **before** the PWA is [//]: # (Todo: add screenshots) ### Sending multiple files to PairDrop -Outstandingly, it is also possible to send multiple files to PairDrop via the context menu by adding PairDrop to the `Send to` menu: +Outstandingly, it is also possible to send multiple files to PairDrop \ +via the context menu by adding PairDrop to the `Send to` menu: 1. [Register PairDrop as file handler](#registering-to-open-files-with-pairdrop) 2. Hit Windows Key+R, type: `shell:programs` and hit Enter. 3. Copy the PairDrop shortcut from the directory @@ -26,7 +27,8 @@ Outstandingly, it is also possible to send multiple files to PairDrop via the co [//]: # (Todo: add screenshots) ## Send directly from share menu on iOS -I created an iOS shortcut to send images, files, folder, URLs or text directly from the share-menu +I created an iOS shortcut to send images, files, folder, URLs \ +or text directly from the share-menu https://routinehub.co/shortcut/13990/ [//]: # (Todo: add doku with screenshots) @@ -63,7 +65,7 @@ On Windows Command Prompt you need to use bash: `bash pairdrop -h` Download the bash file: [pairdrop-cli/pairdrop](/pairdrop-cli/pairdrop). #### Linux -1. Put file in a preferred folder e.g. `/usr/local/bin` +1. Put the file in a preferred folder e.g. `/usr/local/bin` 2. Make sure the bash file is executable. Otherwise, use `chmod +x pairdrop` 3. Add absolute path of the folder to PATH variable to make `pairdrop` available globally by executing `export PATH=$PATH:/opt/pairdrop-cli` @@ -74,7 +76,7 @@ Download the bash file: [pairdrop-cli/pairdrop](/pairdrop-cli/pairdrop). #### Windows 1. Put file in a preferred folder e.g. `C:\Users\Public\pairdrop-cli` 2. Search for and open `Edit environment variables for your account` -3. Click `Environment Variables...` +3. Click `Environment Variables…` 4. Under *System Variables* select `Path` and click *Edit...* 5. Click *New*, insert the preferred folder (`C:\Users\Public\pairdrop-cli`), click *OK* until all windows are closed 6. Reopen Command prompt window From 17a12baa2a48643f7c015c4b2dcbb67e31bbdfa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Sat, 8 Jul 2023 13:24:42 +0000 Subject: [PATCH 088/568] technical-documentation reworked --- docs/technical-documentation.md | 80 +++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 24 deletions(-) diff --git a/docs/technical-documentation.md b/docs/technical-documentation.md index bf050ae..b8783c0 100644 --- a/docs/technical-documentation.md +++ b/docs/technical-documentation.md @@ -3,48 +3,80 @@ Encryption is mandatory for WebRTC connections and completely done by the browser itself. -When the peers are first connecting, a channel is created by exchanging their signaling information. -This signaling information includes some sort of public key and is specific to the clients ip address. -That is what the STUN Server is used for: it simply returns your public IP address as you only know your local ip address +When the peers are first connecting, \ +a channel is created by exchanging their signaling info. \ +This signaling information includes some sort of public key \ +and is specific to the clients IP address. \ +That is what the STUN Server is used for: \ +it simply returns your public IP address \ +as you only know your local ip address \ if behind a NAT (router). -The transfer of the signaling information is done by the PairDrop / Snapdrop server using secure websockets. -After that the channel itself is completely peer-2-peer and all information can only be decrypted by the receiver. -When the two peers are on the same network or when they are not behind any NAT system (which they are always for classic -Snapdrop and for not paired users on PairDrop) the files are send directly peer to peer. +The transfer of the signaling info is done by the \ +PairDrop / Snapdrop server using secure websockets. \ +After that the channel itself is completely peer-to-peer \ +and all info can only be decrypted by the receiver. \ +When the two peers are on the same network \ +or when they are not behind any NAT system \ +(which they are always for classic \ +Snapdrop and for not paired users on PairDrop) \ +the files are send directly peer-to-peer. -When a user is behind a NAT (behind a router) the contents are channeled through a TURN server. -But again, the contents send via the channel can only be decrypted by the receiver. So a rogue TURN server could only -see that there is a connection, but not what is sent. Obviously, connections which are channeled through a TURN server -are not as fast as peer to peer. +When a user is behind a NAT (behind a router) \ +the contents are channeled through a TURN server. \ +But again, the contents send via the channel \ +can only be decrypted by the receiver. \ +So a rogue TURN server could only \ +see that there is a connection, but not what is sent. \ +Obviously, connections which are channeled through a TURN server \ +are not as fast as peer-to-peer. -The selection whether a TURN server is needed or not is also done automatically by the browser. -It simply iterated through the configured RTC iceServers and checks what works. Only if the STUN server is not sufficient, +The selection whether a TURN server is needed \ +or not is also done automatically by the web browser. \ +It simply iterated through the configured \ +RTC iceServers and checks what works. \ +Only if the STUN server is not sufficient, \ the TURN server is used. ![img](https://www.wowza.com/wp-content/uploads/WeRTC-Encryption-Diagrams-01.jpg) _Diagram created by wowza.com_ -Good thing: if your device has an IPv6 address it is uniquely reachable by that address. As I understand it, when both devices are using IPv6 addresses there is no need for a TURN server in any scenario. +Good thing: if your device has an IPv6 address \ +it is uniquely reachable by that address. \ +As I understand it, when both devices are using \ +IPv6 addresses there is no need for a TURN server in any scenario. -To learn more take a look at https://www.wowza.com/blog/webrtc-encryption-and-security which gives a good insight into stun, turn and webrtc +Learn more by reading https://www.wowza.com/blog/webrtc-encryption-and-security \ +which gives a good insight into STUN, TURN and WebRTC. ## Device Pairing The pairing functionality uses the [IndexedDB API](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API). -It works by creating long secrets that are served by the server to the initiating and requesting pair peer, -when the inserted key is correct. These long secrets are then saved to an indexedDB database in the browser. -IndexedDB is somewhat the successor of localStorage as saved data is shared between all tabs. -It goes one step further by making the data persistent and available offline if implemented to a PWA. +It works by creating long secrets that are served \ +by the server to the initiating and requesting pair peer, \ +when the inserted key is correct. \ +These long secrets are then saved to an \ +indexedDB database in the web browser. \ +IndexedDB is somewhat the successor of localStorage \ +as saved data is shared between all tabs. \ +It goes one step further by making the data persistent \ +and available offline if implemented to a PWA. -All secrets a client has saved to its database are send to the PairDrop server. Peers with a common secret are discoverable -to each other analog to peers with the same ip-address are discoverable to each other. +All secrets a client has saved to its database \ +are sent to the PairDrop server. \ +Peers with a common secret are discoverable \ +to each other analog to peers with the same \ +IP address are discoverable by each other. -What I really like about this approach, and the reason why I implemented it, is that devices on the same network are always -visible regardless whether any devices are paired or not. The main user flow is never obstructed. Paired devices are simply -shown additionally. This makes it in my idea better than the idea of using a room system as [discussed here](https://github.com/RobinLinus/snapdrop/pull/214). +What I really like about this approach (and the reason I implemented it) \ +is that devices on the same network are always \ +visible regardless whether any devices are paired or not. \ +The main user flow is never obstructed. \ +Paired devices are simply shown additionally. \ +This makes it in my idea better than the idea of \ +using a room system as [discussed here](https://github.com/RobinLinus/snapdrop/pull/214). [< Back](/README.md) From 6563ec98b39fdf4aa3abcdd34c3fad60577d74ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Sat, 8 Jul 2023 13:26:48 +0000 Subject: [PATCH 089/568] Bold button scheme --- docs/host-your-own.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/host-your-own.md b/docs/host-your-own.md index 5816a95..2a63446 100644 --- a/docs/host-your-own.md +++ b/docs/host-your-own.md @@ -528,7 +528,7 @@ Install that certificate to the trust store of your operating system. \ - expand `Trust`, and select `Always Trust` for SSL. \ - Firefox uses its own trust store. To install the CA, \ - point Firefox at `http://:8080/ca.crt`. \ -- When prompted, select `Trust this CA to identify websites` and click \"OK\". \ +- When prompted, select `Trust this CA to identify websites` and click *OK*. \ - When using Chrome, you need to restart Chrome so it reloads the trust store (`chrome://restart`). \ - Additionally, after installing a new cert, \ - you need to clear the Storage (DevTools → Application → Clear storage → Clear site data). From dccc17400cb493581bef27ffd51e916e3bf020da Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 19 Jul 2023 10:50:01 +0200 Subject: [PATCH 090/568] Added translation using Weblate (Chinese (Simplified)) --- public/lang/zh-Hans.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 public/lang/zh-Hans.json diff --git a/public/lang/zh-Hans.json b/public/lang/zh-Hans.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/public/lang/zh-Hans.json @@ -0,0 +1 @@ +{} From 6d7c13775f2a2ccbc0a16fc5499983ac94ddd811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8D=E5=BB=BA=E5=85=B4?= Date: Wed, 19 Jul 2023 09:18:40 +0000 Subject: [PATCH 091/568] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (122 of 122 strings) Translation: PairDrop/pairdrop-spa Translate-URL: https://hosted.weblate.org/projects/pairdrop/pairdrop-spa/zh_Hans/ --- public/lang/zh-Hans.json | 141 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 1 deletion(-) diff --git a/public/lang/zh-Hans.json b/public/lang/zh-Hans.json index 0967ef4..4191c7d 100644 --- a/public/lang/zh-Hans.json +++ b/public/lang/zh-Hans.json @@ -1 +1,140 @@ -{} +{ + "header": { + "about_title": "关于 PairDrop", + "about_aria-label": "打开 关于 PairDrop", + "theme-light_title": "总是使用明亮主题", + "install_title": "安装 PairDrop", + "pair-device_title": "配对新设备", + "theme-auto_title": "主题适应系统", + "theme-dark_title": "总是使用暗黑主题", + "notification_title": "开启通知", + "edit-paired-devices_title": "管理已配对设备", + "cancel-paste-mode": "完成" + }, + "instructions": { + "x-instructions_data-drop-peer": "释放以发送到此设备", + "no-peers_data-drop-bg": "释放来选择接收者", + "no-peers-subtitle": "配对新设备使在其他网络上可见", + "no-peers-title": "在其他设备上打开 PairDrop 来发送文件", + "x-instructions_desktop": "点击以发送文件 或 右键来发送信息", + "x-instructions_mobile": "轻触以发送文件 或 长按来发送信息", + "x-instructions_data-drop-bg": "释放来选择接收者", + "click-to-send": "点击发送", + "tap-to-send": "轻触发送" + }, + "footer": { + "routed": "途径服务器", + "webrtc": "如果 WebRTC 不可用。", + "known-as": "你的名字是:", + "display-name_placeholder": "加载中…", + "and-by": "和", + "display-name_title": "长久修改你的设备名", + "discovery-everyone": "你对所有人可见", + "on-this-network": "在此网络上", + "paired-devices": "已配对的设备", + "traffic": "流量将" + }, + "dialogs": { + "activate-paste-mode-base": "在其他设备上打开 PairDrop 来发送", + "activate-paste-mode-and-other-files": "和 {{count}} 个其他的文件", + "activate-paste-mode-activate-paste-mode-shared-text": "分享文本", + "pair-devices-title": "配对新设备", + "input-key-on-this-device": "在另一个设备上输入这串数字", + "base64-text": "信息", + "enter-key-from-another-device": "输入从另一个设备上获得的数字以继续。", + "edit-paired-devices-title": "管理已配对的设备", + "pair": "配对", + "cancel": "取消", + "scan-qr-code": "或者 扫描二维码。", + "paired-devices-wrapper_data-empty": "无已配对设备。", + "auto-accept-instructions-1": "启用", + "auto-accept": "自动接收", + "decline": "拒绝", + "base64-processing": "处理中...", + "base64-tap-to-paste": "轻触此处粘贴{{type}}", + "base64-paste-to-send": "粘贴到此处以发送 {{type}}", + "auto-accept-instructions-2": "以无需同意而自动接收从那个设备上发送的所有文件。", + "would-like-to-share": "想要分享", + "accept": "接收", + "close": "关闭", + "share": "分享", + "download": "保存", + "send": "发送", + "receive-text-title": "收到信息", + "copy": "复制", + "send-message-title": "发送信息", + "send-message-to": "发了一条信息给", + "has-sent": "发送了:", + "base64-files": "文件", + "file-other-description-file": "和 1 个其他的文件", + "file-other-description-image": "和 1 个其他的图片", + "file-other-description-image-plural": "和 {{count}} 个其他的图片", + "file-other-description-file-plural": "和 {{count}} 个其他的文件", + "title-image-plural": "图片", + "receive-title": "收到 {{descriptor}}", + "title-image": "图片", + "title-file": "文件", + "title-file-plural": "文件", + "download-again": "再次保存" + }, + "about": { + "faq_title": "常见问题", + "close-about_aria-label": "关闭 关于 PairDrop", + "github_title": "PairDrop 在 GitHub 上开源", + "claim": "最简单的跨设备传输方案", + "buy-me-a-coffee_title": "帮我买杯咖啡!", + "tweet_title": "关于 PairDrop 的推特" + }, + "notifications": { + "display-name-changed-permanently": "展示的名字已经长久变更。", + "display-name-changed-temporarily": "展示的名字已经变更 仅在此会话中。", + "display-name-random-again": "展示的名字再次随机生成。", + "download-successful": "{{descriptor}} 已下载", + "pairing-tabs-error": "无法配对两个浏览器标签页。", + "pairing-success": "新设备已配对。", + "pairing-not-persistent": "配对的设备不是持久的。", + "pairing-key-invalid": "无效配对码", + "pairing-key-invalidated": "配对码 {{key}} 已失效。", + "text-content-incorrect": "文本内容不合法。", + "file-content-incorrect": "文件内容不合法。", + "clipboard-content-incorrect": "剪贴板内容不合法。", + "link-received": "收到来自 {{name}} 的链接 - 点击打开", + "message-received": "收到来自 {{name}} 的信息 - 点击复制", + "request-title": "{{name}} 想要发送 {{count}} 个 {{descriptor}}", + "click-to-show": "点击展示", + "copied-text": "复制到剪贴板", + "selected-peer-left": "选择的设备已离开。", + "pairing-cleared": "所有设备已解除配对。", + "copied-to-clipboard": "已复制到剪贴板", + "notifications-enabled": "通知已启用。", + "copied-text-error": "写入剪贴板失败。请手动复制!", + "click-to-download": "点击以保存", + "unfinished-transfers-warning": "还有未完成的传输任务。你确定要关闭吗?", + "message-transfer-completed": "信息传输已完成。", + "offline": "你未连接到网络", + "online": "你已重新连接到网络", + "connected": "已连接。", + "online-requirement": "你需要连接网络来配对新设备。", + "files-incorrect": "文件不合法。", + "file-transfer-completed": "文件传输已完成。", + "connecting": "连接中…", + "ios-memory-limit": "向 iOS 发送文件 一次最多只能发送 200 MB", + "rate-limit-join-key": "已达连接限制。请等待 10秒 后再试。" + }, + "document-titles": { + "message-received": "收到信息", + "message-received-plural": "收到 {{count}} 条信息", + "file-transfer-requested": "文件传输请求", + "file-received-plural": "收到 {{count}} 个文件", + "file-received": "收到文件" + }, + "peer-ui": { + "click-to-send-paste-mode": "点击发送 {{descriptor}}", + "click-to-send": "点击以发送文件 或 右键来发送信息", + "connection-hash": "若要验证端到端加密的安全性,请在两个设备上比较此安全编号", + "preparing": "准备中…", + "waiting": "请等待…", + "transferring": "传输中…", + "processing": "处理中…" + } +} From 445a29540498f7bc8fa764e099dde8af5528cccc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 04:58:27 +0000 Subject: [PATCH 092/568] Bump express-rate-limit from 6.7.0 to 6.8.0 Bumps [express-rate-limit](https://github.com/express-rate-limit/express-rate-limit) from 6.7.0 to 6.8.0. - [Release notes](https://github.com/express-rate-limit/express-rate-limit/releases) - [Changelog](https://github.com/express-rate-limit/express-rate-limit/blob/main/changelog.md) - [Commits](https://github.com/express-rate-limit/express-rate-limit/compare/v6.7.0...v6.8.0) --- updated-dependencies: - dependency-name: express-rate-limit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index c49aca5..b96bb32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "express": "^4.18.2", - "express-rate-limit": "^6.7.0", + "express-rate-limit": "^6.8.0", "ua-parser-js": "^1.0.35", "unique-names-generator": "^4.3.0", "ws": "^8.13.0" @@ -204,11 +204,11 @@ } }, "node_modules/express-rate-limit": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz", - "integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.8.0.tgz", + "integrity": "sha512-yVeDWczkh8qgo9INJB1tT4j7LFu+n6ei/oqSMsqpsUIGYjTM+gk+Q3wv19TMUdo8chvus8XohAuOhG7RYRM9ZQ==", "engines": { - "node": ">= 12.9.0" + "node": ">= 14.0.0" }, "peerDependencies": { "express": "^4 || ^5" @@ -801,9 +801,9 @@ } }, "express-rate-limit": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz", - "integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.8.0.tgz", + "integrity": "sha512-yVeDWczkh8qgo9INJB1tT4j7LFu+n6ei/oqSMsqpsUIGYjTM+gk+Q3wv19TMUdo8chvus8XohAuOhG7RYRM9ZQ==", "requires": {} }, "finalhandler": { diff --git a/package.json b/package.json index aae3823..0b8860a 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "license": "ISC", "dependencies": { "express": "^4.18.2", - "express-rate-limit": "^6.7.0", + "express-rate-limit": "^6.8.0", "ua-parser-js": "^1.0.35", "unique-names-generator": "^4.3.0", "ws": "^8.13.0" From 471278f7b0727b3ec9476e88be7422db301101ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Mon, 24 Jul 2023 16:02:13 +0000 Subject: [PATCH 093/568] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 81.9% (100 of 122 strings) Translation: PairDrop/pairdrop-spa Translate-URL: https://hosted.weblate.org/projects/pairdrop/pairdrop-spa/nb_NO/ --- public/lang/nb-NO.json | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/public/lang/nb-NO.json b/public/lang/nb-NO.json index 52f1d3d..b11b664 100644 --- a/public/lang/nb-NO.json +++ b/public/lang/nb-NO.json @@ -109,7 +109,17 @@ "message-transfer-completed": "Meldingsoverføring utført.", "download-successful": "{{descriptor}} nedlastet", "pairing-success": "Enheter sammenkoblet.", - "pairing-cleared": "Sammenkobling av alle enheter opphevet." + "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.", + "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." }, "document-titles": { "file-received": "Fil mottatt", @@ -124,6 +134,7 @@ "processing": "Behandler …", "transferring": "Overfører …", "click-to-send": "Klikk for å sende filer, eller høyreklikk for å sende en melding", - "click-to-send-paste-mode": "Klikk for å sende {{descriptor}}" + "click-to-send-paste-mode": "Klikk for å sende {{descriptor}}", + "connection-hash": "Sammenlign dette sikkerhetsnummeret på begge enhetene for å bekrefte ende-til-ende -krypteringen." } } From 714608ce978abf568d3334320e6143c89b8b6fe6 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Tue, 25 Jul 2023 18:56:56 +0200 Subject: [PATCH 094/568] Added translation using Weblate (Turkish) --- public/lang/tr.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 public/lang/tr.json diff --git a/public/lang/tr.json b/public/lang/tr.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/public/lang/tr.json @@ -0,0 +1 @@ +{} From b319fbe1560bbd147a615261275d3977a43f1de6 Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 25 Jul 2023 22:58:58 +0000 Subject: [PATCH 095/568] Translated using Weblate (Turkish) Currently translated at 12.2% (15 of 122 strings) Translation: PairDrop/pairdrop-spa Translate-URL: https://hosted.weblate.org/projects/pairdrop/pairdrop-spa/tr/ --- public/lang/tr.json | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/public/lang/tr.json b/public/lang/tr.json index 0967ef4..783e25b 100644 --- a/public/lang/tr.json +++ b/public/lang/tr.json @@ -1 +1,25 @@ -{} +{ + "header": { + "about_title": "PairDrop Hakkında", + "about_aria-label": "PairDrop Hakkında Aç", + "theme-auto_title": "Temayı Sisteme Uyarla", + "theme-light_title": "Daima Açık Tema Kullan", + "theme-dark_title": "Daima Koyu Tema Kullan", + "notification_title": "Bildirimleri Etkinleştir", + "install_title": "PairDrop'u Yükle", + "pair-device_title": "Cihaz Eşleştir", + "edit-paired-devices_title": "Eşleştirilmiş Cihazları Düzenle", + "cancel-paste-mode": "Bitti" + }, + "instructions": { + "no-peers_data-drop-bg": "Alıcıyı seçmek için bırakın" + }, + "footer": { + "display-name_placeholder": "Yükleniyor…", + "display-name_title": "Cihazının adını kalıcı olarak düzenle" + }, + "dialogs": { + "cancel": "İptal", + "edit-paired-devices-title": "Eşleştirilmiş Cihazları Düzenle" + } +} From da5038a51a015057c925ee7762e192f3dfd91fd5 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Fri, 7 Jul 2023 14:58:15 +0200 Subject: [PATCH 096/568] include translations for about buttons and implement translation fallback if used translation is not complete --- public/index.html | 14 +++--- public/lang/en.json | 8 +++- public/scripts/localization.js | 43 +++++++++++-------- public_included_ws_fallback/index.html | 14 +++--- public_included_ws_fallback/lang/en.json | 8 +++- .../scripts/localization.js | 43 +++++++++++-------- 6 files changed, 78 insertions(+), 52 deletions(-) diff --git a/public/index.html b/public/index.html index 15b82da..21f6005 100644 --- a/public/index.html +++ b/public/index.html @@ -83,7 +83,7 @@
- +
@@ -161,7 +161,7 @@

Edit Paired Devices

-
+
The easiest way to transfer files across devices
- + - + - + - + diff --git a/public/lang/en.json b/public/lang/en.json index 7ae2e56..8ad7b7c 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -78,8 +78,12 @@ "download-again": "Download again" }, "about": { - "close-about-aria-label": "Close About PairDrop", - "claim": "The easiest way to transfer files across devices" + "close-about_aria-label": "Close About PairDrop", + "claim": "The easiest way to transfer files across devices", + "github_title": "PairDrop on Github", + "buy-me-a-coffee_title": "Buy me a coffee!", + "tweet_title": "Tweet about PairDrop", + "faq_title": "Frequently asked questions" }, "notifications": { "display-name-changed-permanently": "Display name is changed permanently.", diff --git a/public/scripts/localization.js b/public/scripts/localization.js index d09d5c0..5f2730b 100644 --- a/public/scripts/localization.js +++ b/public/scripts/localization.js @@ -2,10 +2,10 @@ class Localization { constructor() { Localization.defaultLocale = "en"; Localization.supportedLocales = ["en"]; - Localization.translations = {}; + Localization.defaultTranslations = {}; - const initialLocale = Localization.supportedOrDefault(Localization.browserLocales()); + const initialLocale = Localization.supportedOrDefault(navigator.languages); Localization.setLocale(initialLocale) .then(_ => { @@ -21,25 +21,21 @@ class Localization { return locales.find(Localization.isSupported) || Localization.defaultLocale; } - static browserLocales() { - return navigator.languages.map(locale => - locale.split("-")[0] - ); - } - static async setLocale(newLocale) { if (newLocale === Localization.locale) return false; + const isFirstTranslation = !Localization.locale + + Localization.defaultTranslations = await Localization.fetchTranslationsFor(Localization.defaultLocale); + const newTranslations = await Localization.fetchTranslationsFor(newLocale); if(!newTranslations) return false; - const firstTranslation = !Localization.locale - Localization.locale = newLocale; Localization.translations = newTranslations; - if (firstTranslation) { + if (isFirstTranslation) { Events.fire("translation-loaded"); } } @@ -65,30 +61,43 @@ class Localization { for (let i in attrs) { let attr = attrs[i]; if (attr === "text") { - element.innerText = await Localization.getTranslation(key); + element.innerText = Localization.getTranslation(key); } else { - element.attr = await Localization.getTranslation(key, attr); + element.attr = Localization.getTranslation(key, attr); } } } - static getTranslation(key, attr, data) { + static getTranslation(key, attr, data, useDefault=false) { const keys = key.split("."); - let translationCandidates = Localization.translations; + let translationCandidates = useDefault + ? Localization.defaultTranslations + : Localization.translations; for (let i=0; i
- +
@@ -166,7 +166,7 @@

Edit Paired Devices

-
+
The easiest way to transfer files across devices
- + - + - + - + diff --git a/public_included_ws_fallback/lang/en.json b/public_included_ws_fallback/lang/en.json index 7ae2e56..8ad7b7c 100644 --- a/public_included_ws_fallback/lang/en.json +++ b/public_included_ws_fallback/lang/en.json @@ -78,8 +78,12 @@ "download-again": "Download again" }, "about": { - "close-about-aria-label": "Close About PairDrop", - "claim": "The easiest way to transfer files across devices" + "close-about_aria-label": "Close About PairDrop", + "claim": "The easiest way to transfer files across devices", + "github_title": "PairDrop on Github", + "buy-me-a-coffee_title": "Buy me a coffee!", + "tweet_title": "Tweet about PairDrop", + "faq_title": "Frequently asked questions" }, "notifications": { "display-name-changed-permanently": "Display name is changed permanently.", diff --git a/public_included_ws_fallback/scripts/localization.js b/public_included_ws_fallback/scripts/localization.js index d09d5c0..5f2730b 100644 --- a/public_included_ws_fallback/scripts/localization.js +++ b/public_included_ws_fallback/scripts/localization.js @@ -2,10 +2,10 @@ class Localization { constructor() { Localization.defaultLocale = "en"; Localization.supportedLocales = ["en"]; - Localization.translations = {}; + Localization.defaultTranslations = {}; - const initialLocale = Localization.supportedOrDefault(Localization.browserLocales()); + const initialLocale = Localization.supportedOrDefault(navigator.languages); Localization.setLocale(initialLocale) .then(_ => { @@ -21,25 +21,21 @@ class Localization { return locales.find(Localization.isSupported) || Localization.defaultLocale; } - static browserLocales() { - return navigator.languages.map(locale => - locale.split("-")[0] - ); - } - static async setLocale(newLocale) { if (newLocale === Localization.locale) return false; + const isFirstTranslation = !Localization.locale + + Localization.defaultTranslations = await Localization.fetchTranslationsFor(Localization.defaultLocale); + const newTranslations = await Localization.fetchTranslationsFor(newLocale); if(!newTranslations) return false; - const firstTranslation = !Localization.locale - Localization.locale = newLocale; Localization.translations = newTranslations; - if (firstTranslation) { + if (isFirstTranslation) { Events.fire("translation-loaded"); } } @@ -65,30 +61,43 @@ class Localization { for (let i in attrs) { let attr = attrs[i]; if (attr === "text") { - element.innerText = await Localization.getTranslation(key); + element.innerText = Localization.getTranslation(key); } else { - element.attr = await Localization.getTranslation(key, attr); + element.attr = Localization.getTranslation(key, attr); } } } - static getTranslation(key, attr, data) { + static getTranslation(key, attr, data, useDefault=false) { const keys = key.split("."); - let translationCandidates = Localization.translations; + let translationCandidates = useDefault + ? Localization.defaultTranslations + : Localization.translations; for (let i=0; i Date: Sun, 30 Jul 2023 17:55:35 +0200 Subject: [PATCH 097/568] Add Weblate and a mention of translations to the README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0602ff0..f2a093b 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ Developed based on [Snapdrop](https://github.com/RobinLinus/snapdrop) * Lots of stability fixes (Thanks [@MWY001](https://github.com/MWY001) [@skiby7](https://github.com/skiby7) and [@willstott101](https://github.com/willstott101)) * To host PairDrop on your local network (e.g. on Raspberry Pi): [All peers connected with private IPs are discoverable by each other](https://github.com/RobinLinus/snapdrop/pull/558) * When hosting PairDrop yourself you can [set your own STUN/TURN servers](/docs/host-your-own.md#specify-stunturn-servers) +* Built-in translations ## Screenshots
@@ -82,6 +83,7 @@ Developed based on [Snapdrop](https://github.com/RobinLinus/snapdrop) * [IndexedDB API](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) * [zip.js](https://gildas-lormeau.github.io/zip.js/) * [cyrb53](https://github.com/bryc) super fast hash function +* [Weblate](https://weblate.org/) Web based localization tool Have any questions? Read our [FAQ](/docs/faq.md). From 8869c3c27e68fb3e0ea6cac28f85888923775df7 Mon Sep 17 00:00:00 2001 From: zhongbing Date: Sun, 6 Aug 2023 00:47:01 +0800 Subject: [PATCH 098/568] revise the command line tool --- pairdrop-cli/pairdrop | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/pairdrop-cli/pairdrop b/pairdrop-cli/pairdrop index 9172412..b5e80bd 100644 --- a/pairdrop-cli/pairdrop +++ b/pairdrop-cli/pairdrop @@ -38,7 +38,10 @@ openPairDrop() else xdg-open "$url" fi + + exit + } setOs() @@ -98,13 +101,19 @@ sendFiles() [[ -a "$zipPath" ]] && echo "Cannot overwrite $zipPath. Please remove first." && exit if [[ -d $path ]]; then - zipPathTemp="temp_${zipPath}" + zipPathTemp="${path}_pairdrop_temp.zip" [[ -a "$zipPathTemp" ]] && echo "Cannot overwrite $zipPathTemp. Please remove first." && exit echo "Processing directory..." # Create zip files temporarily to send directory - zip -q -b /tmp/ -r "$zipPath" "$path" - zip -q -b /tmp/ "$zipPathTemp" "$zipPath" + if [[ $OS == "Windows" ]];then + powershell.exe -Command "Compress-Archive -Path ${path} -DestinationPath ${zipPath}" + echo "Compress-Archive -Path ${zipPath} -DestinationPath ${zipPathTemp}" + powershell.exe -Command "Compress-Archive -Path ${zipPath} -DestinationPath ${zipPathTemp}" + else + zip -q -b /tmp/ -r "$zipPath" "$path" + zip -q -b /tmp/ "$zipPathTemp" "$zipPath" + fi if [[ $OS == "Mac" ]];then hash=$(base64 -i "$zipPathTemp") @@ -118,8 +127,12 @@ sendFiles() echo "Processing file..." # Create zip file temporarily to send file - zip -q -b /tmp/ "$zipPath" "$path" + if [[ $OS == "Windows" ]];then + powershell.exe -Command "Compress-Archive -Path ${path} -DestinationPath ${zipPath} -CompressionLevel Optimal" + else + zip -q -b /tmp/ "$zipPath" "$path" + fi if [[ $OS == "Mac" ]];then hash=$(base64 -i "$zipPath") else @@ -142,6 +155,7 @@ sendFiles() hash= fi + openPairDrop exit } From 395c3e00a4c23320ced4849ceabc12b220cef80e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 04:24:11 +0000 Subject: [PATCH 099/568] Bump express-rate-limit from 6.8.0 to 6.9.0 Bumps [express-rate-limit](https://github.com/express-rate-limit/express-rate-limit) from 6.8.0 to 6.9.0. - [Release notes](https://github.com/express-rate-limit/express-rate-limit/releases) - [Changelog](https://github.com/express-rate-limit/express-rate-limit/blob/main/changelog.md) - [Commits](https://github.com/express-rate-limit/express-rate-limit/compare/v6.8.0...v6.9.0) --- updated-dependencies: - dependency-name: express-rate-limit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index b96bb32..d5b6737 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "express": "^4.18.2", - "express-rate-limit": "^6.8.0", + "express-rate-limit": "^6.9.0", "ua-parser-js": "^1.0.35", "unique-names-generator": "^4.3.0", "ws": "^8.13.0" @@ -204,9 +204,9 @@ } }, "node_modules/express-rate-limit": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.8.0.tgz", - "integrity": "sha512-yVeDWczkh8qgo9INJB1tT4j7LFu+n6ei/oqSMsqpsUIGYjTM+gk+Q3wv19TMUdo8chvus8XohAuOhG7RYRM9ZQ==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.9.0.tgz", + "integrity": "sha512-AnISR3V8qy4gpKM62/TzYdoFO9NV84fBx0POXzTryHU/qGUJBWuVGd+JhbvtVmKBv37t8/afmqdnv16xWoQxag==", "engines": { "node": ">= 14.0.0" }, @@ -801,9 +801,9 @@ } }, "express-rate-limit": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.8.0.tgz", - "integrity": "sha512-yVeDWczkh8qgo9INJB1tT4j7LFu+n6ei/oqSMsqpsUIGYjTM+gk+Q3wv19TMUdo8chvus8XohAuOhG7RYRM9ZQ==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.9.0.tgz", + "integrity": "sha512-AnISR3V8qy4gpKM62/TzYdoFO9NV84fBx0POXzTryHU/qGUJBWuVGd+JhbvtVmKBv37t8/afmqdnv16xWoQxag==", "requires": {} }, "finalhandler": { diff --git a/package.json b/package.json index 0b8860a..015e006 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "license": "ISC", "dependencies": { "express": "^4.18.2", - "express-rate-limit": "^6.8.0", + "express-rate-limit": "^6.9.0", "ua-parser-js": "^1.0.35", "unique-names-generator": "^4.3.0", "ws": "^8.13.0" From 43824d0de20d1fecf6525ff5e79b99a429b8936a Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 10 Aug 2023 17:09:51 +0200 Subject: [PATCH 100/568] increase version to v1.7.7 --- package-lock.json | 4 ++-- package.json | 2 +- public/index.html | 2 +- public/service-worker.js | 2 +- public_included_ws_fallback/index.html | 2 +- public_included_ws_fallback/service-worker.js | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index d5b6737..6ea5f15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pairdrop", - "version": "1.7.6", + "version": "1.7.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "pairdrop", - "version": "1.7.6", + "version": "1.7.7", "license": "ISC", "dependencies": { "express": "^4.18.2", diff --git a/package.json b/package.json index 015e006..baca71c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pairdrop", - "version": "1.7.6", + "version": "1.7.7", "description": "", "main": "index.js", "scripts": { diff --git a/public/index.html b/public/index.html index 755673a..ab0c9a2 100644 --- a/public/index.html +++ b/public/index.html @@ -278,7 +278,7 @@

PairDrop

-
v1.7.6
+
v1.7.7
The easiest way to transfer files across devices
diff --git a/public/service-worker.js b/public/service-worker.js index b409118..88f2a13 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.7.6'; +const cacheVersion = 'v1.7.7'; const cacheTitle = `pairdrop-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', diff --git a/public_included_ws_fallback/index.html b/public_included_ws_fallback/index.html index 6beae65..838d8d2 100644 --- a/public_included_ws_fallback/index.html +++ b/public_included_ws_fallback/index.html @@ -281,7 +281,7 @@

PairDrop

-
v1.7.6
+
v1.7.7
The easiest way to transfer files across devices
diff --git a/public_included_ws_fallback/service-worker.js b/public_included_ws_fallback/service-worker.js index 78249b2..51c320d 100644 --- a/public_included_ws_fallback/service-worker.js +++ b/public_included_ws_fallback/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.7.6'; +const cacheVersion = 'v1.7.7'; const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', From 69f1688dfe7ae6c703b71e91e22fc2f112f241f4 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Fri, 11 Aug 2023 13:11:51 +0200 Subject: [PATCH 101/568] Update bug-report.md --- .github/ISSUE_TEMPLATE/bug-report.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 39a6624..f5aa0fb 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -1,8 +1,8 @@ --- name: Bug Report about: Create a report to help us improve. Please check the FAQ first. -title: 'Bug:/Enhancement:/Feature Request: ' -labels: '' +title: '[Bug] ' +labels: 'bug' assignees: '' --- @@ -34,12 +34,17 @@ If applicable, add screenshots to help explain your problem. - Browser [e.g. stock browser, safari] - Version [e.g. 22] -**Self-Hosted** +**Bug occurs on official PairDrop instance https://pairdrop.net/** +No | Yes +Version: v1.7.7 + +**Bug occurs on self-hosted PairDrop instance** No | Yes **Self-Hosted Setup** Proxy: Nginx | Apache2 Deployment: docker run | docker-compose | npm run start:prod +Version: v1.7.7 **Additional context** Add any other context about the problem here. From f95181c057e75722ef3de61ffe083edb52d663a0 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Fri, 11 Aug 2023 13:20:38 +0200 Subject: [PATCH 102/568] Create enhancement issue template --- .github/ISSUE_TEMPLATE/enhancement | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/enhancement diff --git a/.github/ISSUE_TEMPLATE/enhancement b/.github/ISSUE_TEMPLATE/enhancement new file mode 100644 index 0000000..ba85fef --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement @@ -0,0 +1,20 @@ +--- +name: Enhancement +about: Enhancements and feature requests are always welcome. See discussions regarding central topics. +title: '[Enhancement] ' +labels: 'enhancement' +assignees: '' + +--- + +**What problem is solved by the new feature** +What's the motivation for this topic + +**Describe the feature** +A clear and concise description of what the new feature/enhancement is. + +**Drafts** +Screenshots of Draw.io graph or drawn sketch. + +**Additional context** +Add any other context here. From e0210b030793e91e3c75763d249c74856c93b07a Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Fri, 11 Aug 2023 13:21:10 +0200 Subject: [PATCH 103/568] Rename enhancement to enhancement.md --- .github/ISSUE_TEMPLATE/{enhancement => enhancement.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/ISSUE_TEMPLATE/{enhancement => enhancement.md} (100%) diff --git a/.github/ISSUE_TEMPLATE/enhancement b/.github/ISSUE_TEMPLATE/enhancement.md similarity index 100% rename from .github/ISSUE_TEMPLATE/enhancement rename to .github/ISSUE_TEMPLATE/enhancement.md From efeff843202cad67cd79d3e960bd90736cdd8c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Fri, 11 Aug 2023 18:59:26 +0000 Subject: [PATCH 104/568] "HEALTHCHECK" --- docs/docker-swarm-usage.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docker-swarm-usage.md b/docs/docker-swarm-usage.md index 218ae82..4d1abf2 100644 --- a/docs/docker-swarm-usage.md +++ b/docs/docker-swarm-usage.md @@ -14,7 +14,7 @@ The [Docker Image](../Dockerfile) includes a health check with the following opt ``` --timeout=10s ``` -> Specifies the amount of time to wait for a response from the \"healthcheck\" command. \ +> Specifies the amount of time to wait for a response from the \"HEALTHCHECK\" command. \ > If the response does not arrive within 10 seconds, the health check fails.
@@ -39,7 +39,7 @@ This command will attempt to connect to `http://localhost:3000/` \ and if it fails it will exit with a status code of `1`. \ If this command returns a status code other than `0`, the health check fails. -Overall, this HEALTHCHECK instruction is defining a health check process \ +Overall, this \"HEALTHCHECK\" instruction is defining a health check process \ that runs every 30 seconds, and waits up to 10 seconds for a response, \ begins 5 seconds after the container is started, and retries up to 3 times. \ The health check attempts to connect to http://localhost:3000/ \ From 22bf1be2b711c3be31146fd4d716177addd68dfb Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Mon, 28 Aug 2023 13:41:12 +0200 Subject: [PATCH 105/568] Add TLS requirement to host-your-own.md --- docs/host-your-own.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/host-your-own.md b/docs/host-your-own.md index 2a63446..e0e33f7 100644 --- a/docs/host-your-own.md +++ b/docs/host-your-own.md @@ -8,6 +8,10 @@ The easiest way to get PairDrop up and running is by using Docker. > Follow [this guide](https://gabrieltanner.org/blog/turn-server/) to either install coturn directly on your system (Step 1) \ > or deploy it via docker-compose (Step 5). +> PairDrop via HTTPS +> +> On some browsers PairDrop must be served over TLS in order for some feautures to work properly. These may include copying an incoming message via the 'copy' button, installing PairDrop as PWA, persistent pairing of devices and changing of the display name, and notifications. Naturally, this is also recommended to increase security. + ## Deployment with Docker ### Docker Image from Docker Hub From 161bd2be84f1f03b713a1c6221aa30021cb9fe52 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Tue, 29 Aug 2023 01:49:09 +0200 Subject: [PATCH 106/568] rename language files to map language codes properly --- public/lang/{nb-NO.json => nb.json} | 0 public/lang/{zh-Hans.json => zh-CN.json} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename public/lang/{nb-NO.json => nb.json} (100%) rename public/lang/{zh-Hans.json => zh-CN.json} (100%) diff --git a/public/lang/nb-NO.json b/public/lang/nb.json similarity index 100% rename from public/lang/nb-NO.json rename to public/lang/nb.json diff --git a/public/lang/zh-Hans.json b/public/lang/zh-CN.json similarity index 100% rename from public/lang/zh-Hans.json rename to public/lang/zh-CN.json From 72f0aff60e541c273d3d71c905c597bb89b5a82e Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Tue, 29 Aug 2023 01:49:32 +0200 Subject: [PATCH 107/568] copy lang files to ws_fallback version --- public_included_ws_fallback/lang/de.json | 1 + public_included_ws_fallback/lang/en.json | 276 ++++++++++---------- public_included_ws_fallback/lang/nb.json | 140 ++++++++++ public_included_ws_fallback/lang/ru.json | 141 ++++++++++ public_included_ws_fallback/lang/tr.json | 25 ++ public_included_ws_fallback/lang/zh-CN.json | 140 ++++++++++ 6 files changed, 585 insertions(+), 138 deletions(-) create mode 100644 public_included_ws_fallback/lang/de.json create mode 100644 public_included_ws_fallback/lang/nb.json create mode 100644 public_included_ws_fallback/lang/ru.json create mode 100644 public_included_ws_fallback/lang/tr.json create mode 100644 public_included_ws_fallback/lang/zh-CN.json diff --git a/public_included_ws_fallback/lang/de.json b/public_included_ws_fallback/lang/de.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/public_included_ws_fallback/lang/de.json @@ -0,0 +1 @@ +{} diff --git a/public_included_ws_fallback/lang/en.json b/public_included_ws_fallback/lang/en.json index 8ad7b7c..ff8294d 100644 --- a/public_included_ws_fallback/lang/en.json +++ b/public_included_ws_fallback/lang/en.json @@ -1,140 +1,140 @@ { - "header": { - "about_title": "About PairDrop", - "about_aria-label": "Open About PairDrop", - "theme-auto_title": "Adapt Theme to System", - "theme-light_title": "Always Use Light-Theme", - "theme-dark_title": "Always Use Dark-Theme", - "notification_title": "Enable Notifications", - "install_title": "Install PairDrop", - "pair-device_title": "Pair Device", - "edit-paired-devices_title": "Edit Paired Devices", - "cancel-paste-mode": "Done" - }, - "instructions": { - "no-peers_data-drop-bg": "Release to select recipient", - "no-peers-title": "Open PairDrop on other devices to send files", - "no-peers-subtitle": "Pair devices to be discoverable on other networks", - "x-instructions_desktop": "Click to send files or right click to send a message", - "x-instructions_mobile": "Tap to send files or long tap to send a message", - "x-instructions_data-drop-peer": "Release to send to peer", - "x-instructions_data-drop-bg": "Release to select recipient", - "click-to-send": "Click to send", - "tap-to-send": "Tap to send" - }, - "footer": { - "known-as": "You are known as:", - "display-name_placeholder": "Loading...", - "display-name_title": "Edit your device name permanently", - "discovery-everyone": "You can be discovered by everyone", - "on-this-network": "on this network", - "and-by": "and by", - "paired-devices": "paired devices", - "traffic": "Traffic is", - "routed": "routed through the server", - "webrtc": "if WebRTC is not available." - }, - "dialogs": { - "activate-paste-mode-base": "Open PairDrop on other devices to send", - "activate-paste-mode-and-other-files": "and {{count}} other files", - "activate-paste-mode-activate-paste-mode-shared-text": "shared text", - "pair-devices-title": "Pair Devices", - "input-key-on-this-device": "Input this key on another device", - "scan-qr-code": "or scan the QR-Code.", - "enter-key-from-another-device": "Enter key from another device to continue.", - "pair": "Pair", - "cancel": "Cancel", - "edit-paired-devices-title": "Edit Paired Devices", - "paired-devices-wrapper_data-empty": "No paired devices.", - "auto-accept-instructions-1": "Activate", - "auto-accept": "auto-accept", - "auto-accept-instructions-2": "to automatically accept all files sent from that device.", - "close": "Close", - "would-like-to-share": "would like to share", - "accept": "Accept", - "decline": "Decline", - "has-sent": "has sent:", - "share": "Share", - "download": "Download", - "send-message-title": "Send Message", - "send-message-to": "Send a Message to", - "send": "Send", - "receive-text-title": "Message Received", - "copy": "Copy", - "base64-processing": "Processing...", - "base64-tap-to-paste": "Tap here to paste {{type}}", - "base64-paste-to-send": "Paste here to send {{type}}", - "base64-text": "text", - "base64-files": "files", - "file-other-description-image": "and 1 other image", - "file-other-description-file": "and 1 other file", - "file-other-description-image-plural": "and {{count}} other images", - "file-other-description-file-plural": "and {{count}} other files", - "title-image": "Image", - "title-file": "File", - "title-image-plural": "Images", - "title-file-plural": "Files", - "receive-title": "{{descriptor}} Received", - "download-again": "Download again" - }, - "about": { - "close-about_aria-label": "Close About PairDrop", - "claim": "The easiest way to transfer files across devices", - "github_title": "PairDrop on Github", - "buy-me-a-coffee_title": "Buy me a coffee!", - "tweet_title": "Tweet about PairDrop", - "faq_title": "Frequently asked questions" - }, - "notifications": { - "display-name-changed-permanently": "Display name is changed permanently.", - "display-name-changed-temporarily": "Display name is changed only for this session.", - "display-name-random-again": "Display name is randomly generated again.", - "download-successful": "{{descriptor}} downloaded successfully", - "pairing-tabs-error": "Pairing of two browser tabs is not possible.", - "pairing-success": "Devices paired successfully.", - "pairing-not-persistent": "Paired devices are not persistent.", - "pairing-key-invalid": "Key not valid", - "pairing-key-invalidated": "Key {{key}} invalidated.", - "pairing-cleared": "All Devices unpaired.", - "copied-to-clipboard": "Copied to clipboard", - "text-content-incorrect": "Text content is incorrect.", - "file-content-incorrect": "File content is incorrect.", - "clipboard-content-incorrect": "Clipboard content is incorrect.", - "notifications-enabled": "Notifications enabled.", - "link-received": "Link received by {{name}} - Click to open", - "message-received": "Message received by {{name}} - Click to copy", - "click-to-download": "Click to download", - "request-title": "{{name}} would like to transfer {{count}} {{descriptor}}", - "click-to-show": "Click to show", - "copied-text": "Copied text to clipboard", - "copied-text-error": "Writing to clipboard failed. Copy manually!", - "offline": "You are offline", - "online": "You are back online", - "connected": "Connected.", - "online-requirement": "You need to be online to pair devices.", - "connecting": "Connecting...", - "files-incorrect": "Files are incorrect.", - "file-transfer-completed": "File transfer completed.", - "ios-memory-limit": "Sending files to iOS is only possible up to 200MB at once", - "message-transfer-completed": "Message transfer completed.", - "unfinished-transfers-warning": "There are unfinished transfers. Are you sure you want to close?", - "rate-limit-join-key": "Rate limit reached. Wait 10 seconds and try again.", - "selected-peer-left": "Selected peer left." - }, - "document-titles": { - "file-received": "File Received", - "file-received-plural": "{{count}} Files Received", - "file-transfer-requested": "File Transfer Requested", - "message-received": "Message Received", - "message-received-plural": "{{count}} Messages Received" - }, - "peer-ui": { - "click-to-send-paste-mode": "Click to send {{descriptor}}", - "click-to-send": "Click to send files or right click to send a message", - "connection-hash": "To verify the security of the end-to-end encryption, compare this security number on both devices", - "preparing": "Preparing...", - "waiting": "Waiting...", - "processing": "Processing...", - "transferring": "Transferring..." - } + "header": { + "about_title": "About PairDrop", + "about_aria-label": "Open About PairDrop", + "theme-auto_title": "Adapt Theme to System", + "theme-light_title": "Always Use Light-Theme", + "theme-dark_title": "Always Use Dark-Theme", + "notification_title": "Enable Notifications", + "install_title": "Install PairDrop", + "pair-device_title": "Pair Device", + "edit-paired-devices_title": "Edit Paired Devices", + "cancel-paste-mode": "Done" + }, + "instructions": { + "no-peers_data-drop-bg": "Release to select recipient", + "no-peers-title": "Open PairDrop on other devices to send files", + "no-peers-subtitle": "Pair devices to be discoverable on other networks", + "x-instructions_desktop": "Click to send files or right click to send a message", + "x-instructions_mobile": "Tap to send files or long tap to send a message", + "x-instructions_data-drop-peer": "Release to send to peer", + "x-instructions_data-drop-bg": "Release to select recipient", + "click-to-send": "Click to send", + "tap-to-send": "Tap to send" + }, + "footer": { + "known-as": "You are known as:", + "display-name_placeholder": "Loading…", + "display-name_title": "Edit your device name permanently", + "discovery-everyone": "You can be discovered by everyone", + "on-this-network": "on this network", + "and-by": "and by", + "paired-devices": "paired devices", + "traffic": "Traffic is", + "routed": "routed through the server", + "webrtc": "if WebRTC is not available." + }, + "dialogs": { + "activate-paste-mode-base": "Open PairDrop on other devices to send", + "activate-paste-mode-and-other-files": "and {{count}} other files", + "activate-paste-mode-activate-paste-mode-shared-text": "shared text", + "pair-devices-title": "Pair Devices", + "input-key-on-this-device": "Input this key on another device", + "scan-qr-code": "or scan the QR-code.", + "enter-key-from-another-device": "Enter key from another device to continue.", + "pair": "Pair", + "cancel": "Cancel", + "edit-paired-devices-title": "Edit Paired Devices", + "paired-devices-wrapper_data-empty": "No paired devices.", + "auto-accept-instructions-1": "Activate", + "auto-accept": "auto-accept", + "auto-accept-instructions-2": "to automatically accept all files sent from that device.", + "close": "Close", + "would-like-to-share": "would like to share", + "accept": "Accept", + "decline": "Decline", + "has-sent": "has sent:", + "share": "Share", + "download": "Download", + "send-message-title": "Send Message", + "send-message-to": "Send a Message to", + "send": "Send", + "receive-text-title": "Message Received", + "copy": "Copy", + "base64-processing": "Processing...", + "base64-tap-to-paste": "Tap here to paste {{type}}", + "base64-paste-to-send": "Paste here to send {{type}}", + "base64-text": "text", + "base64-files": "files", + "file-other-description-image": "and 1 other image", + "file-other-description-file": "and 1 other file", + "file-other-description-image-plural": "and {{count}} other images", + "file-other-description-file-plural": "and {{count}} other files", + "title-image": "Image", + "title-file": "File", + "title-image-plural": "Images", + "title-file-plural": "Files", + "receive-title": "{{descriptor}} Received", + "download-again": "Download again" + }, + "about": { + "close-about_aria-label": "Close About PairDrop", + "claim": "The easiest way to transfer files across devices", + "github_title": "PairDrop on GitHub", + "buy-me-a-coffee_title": "Buy me a coffee!", + "tweet_title": "Tweet about PairDrop", + "faq_title": "Frequently asked questions" + }, + "notifications": { + "display-name-changed-permanently": "Display name is changed permanently.", + "display-name-changed-temporarily": "Display name is changed only for this session.", + "display-name-random-again": "Display name is randomly generated again.", + "download-successful": "{{descriptor}} downloaded", + "pairing-tabs-error": "Pairing two web browser tabs is impossible.", + "pairing-success": "Devices paired.", + "pairing-not-persistent": "Paired devices are not persistent.", + "pairing-key-invalid": "Invalid key", + "pairing-key-invalidated": "Key {{key}} invalidated.", + "pairing-cleared": "All Devices unpaired.", + "copied-to-clipboard": "Copied to clipboard", + "text-content-incorrect": "Text content is incorrect.", + "file-content-incorrect": "File content is incorrect.", + "clipboard-content-incorrect": "Clipboard content is incorrect.", + "notifications-enabled": "Notifications enabled.", + "link-received": "Link received by {{name}} - Click to open", + "message-received": "Message received by {{name}} - Click to copy", + "click-to-download": "Click to download", + "request-title": "{{name}} would like to transfer {{count}} {{descriptor}}", + "click-to-show": "Click to show", + "copied-text": "Copied text to clipboard", + "copied-text-error": "Writing to clipboard failed. Copy manually!", + "offline": "You are offline", + "online": "You are back online", + "connected": "Connected.", + "online-requirement": "You need to be online to pair devices.", + "connecting": "Connecting…", + "files-incorrect": "Files are incorrect.", + "file-transfer-completed": "File transfer completed.", + "ios-memory-limit": "Sending files to iOS is only possible up to 200 MB at once", + "message-transfer-completed": "Message transfer completed.", + "unfinished-transfers-warning": "There are unfinished transfers. Are you sure you want to close?", + "rate-limit-join-key": "Rate limit reached. Wait 10 seconds and try again.", + "selected-peer-left": "Selected peer left." + }, + "document-titles": { + "file-received": "File Received", + "file-received-plural": "{{count}} Files Received", + "file-transfer-requested": "File Transfer Requested", + "message-received": "Message Received", + "message-received-plural": "{{count}} Messages Received" + }, + "peer-ui": { + "click-to-send-paste-mode": "Click to send {{descriptor}}", + "click-to-send": "Click to send files or right click to send a message", + "connection-hash": "To verify the security of the end-to-end encryption, compare this security number on both devices", + "preparing": "Preparing…", + "waiting": "Waiting…", + "processing": "Processing…", + "transferring": "Transferring…" + } } diff --git a/public_included_ws_fallback/lang/nb.json b/public_included_ws_fallback/lang/nb.json new file mode 100644 index 0000000..b11b664 --- /dev/null +++ b/public_included_ws_fallback/lang/nb.json @@ -0,0 +1,140 @@ +{ + "header": { + "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-light_title": "Alltid bruk lys drakt", + "theme-dark_title": "Alltid bruk mørk drakt", + "notification_title": "Skru på merknader", + "cancel-paste-mode": "Ferdig", + "install_title": "Installer PairDrop", + "pair-device_title": "Sammenkoble enhet" + }, + "footer": { + "discovery-everyone": "Du kan oppdages av alle", + "and-by": "og av", + "webrtc": "hvis WebRTC ikke er tilgjengelig.", + "display-name_placeholder": "Laster inn …", + "display-name_title": "Rediger det vedvarende enhetsnavnet ditt", + "traffic": "Trafikken", + "on-this-network": "på dette nettverket", + "known-as": "Du er kjent som:", + "paired-devices": "sammenkoblede enheter", + "routed": "Sendes gjennom tjeneren" + }, + "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", + "click-to-send": "Klikk for å sende", + "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", + "x-instructions_data-drop-peer": "Slipp for å sende til likemann", + "tap-to-send": "Trykk for å sende" + }, + "dialogs": { + "input-key-on-this-device": "Skriv inn denne nøkkelen på en annen enhet", + "pair-devices-title": "Sammenkoble enheter", + "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", + "accept": "Godta", + "has-sent": "har sendt:", + "base64-paste-to-send": "Trykk her for å sende {{type}}", + "base64-text": "tekst", + "base64-files": "filer", + "file-other-description-image-plural": "og {{count}} andre bilder", + "receive-title": "{{descriptor}} mottatt", + "send-message-title": "Send melding", + "base64-processing": "Behandler …", + "close": "Lukk", + "decline": "Avslå", + "download": "Last ned", + "copy": "Kopier", + "pair": "Sammenkoble", + "cancel": "Avbryt", + "scan-qr-code": "eller skann QR-koden.", + "auto-accept-instructions-1": "Aktiver", + "receive-text-title": "Melding mottatt", + "auto-accept": "auto-godkjenn", + "share": "Del", + "send-message-to": "Send en melding til", + "send": "Send", + "base64-tap-to-paste": "Trykk her for å lime inn {{type]]", + "file-other-description-image": "og ett annet bilde", + "file-other-description-file-plural": "og {{count}} andre filer", + "title-file-plural": "Filer", + "download-again": "Last ned igjen", + "file-other-description-file": "og én annen fil", + "title-image": "Bilde", + "title-file": "Fil", + "title-image-plural": "Bilder", + "activate-paste-mode-base": "Åpne PairDrop på andre enheter for å sende", + "activate-paste-mode-and-other-files": "og {{count}} andre filer", + "activate-paste-mode-activate-paste-mode-shared-text": "delt tekst" + }, + "about": { + "close-about_aria-label": "Lukk «Om PairDrop»", + "faq_title": "Ofte stilte spørsmål", + "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" + }, + "notifications": { + "copied-to-clipboard": "Kopiert til utklippstavlen", + "pairing-tabs-error": "Sammenkobling av to nettleserfaner er ikke mulig.", + "notifications-enabled": "Merknader påskrudd.", + "click-to-show": "Klikk for å vise", + "copied-text": "Tekst kopiert til utklippstavlen", + "connected": "Tilkoblet.", + "online": "Du er tilbake på nett", + "file-transfer-completed": "Filoverføring utført.", + "selected-peer-left": "Valgt likemann dro.", + "pairing-key-invalid": "Ugyldig nøkkel", + "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.", + "display-name-random-again": "Visningsnavnet er tilfeldig generert igjen.", + "display-name-changed-permanently": "Visningsnavnet er endret for godt.", + "display-name-changed-temporarily": "Visningsnavnet er endret kun for denne økten.", + "text-content-incorrect": "Tekstinnholdet er uriktig.", + "file-content-incorrect": "Filinnholdet er uriktig.", + "click-to-download": "Klikk for å laste ned", + "message-transfer-completed": "Meldingsoverføring utført.", + "download-successful": "{{descriptor}} nedlastet", + "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.", + "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." + }, + "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" + }, + "peer-ui": { + "preparing": "Forbereder …", + "waiting": "Venter", + "processing": "Behandler …", + "transferring": "Overfører …", + "click-to-send": "Klikk for å sende filer, eller høyreklikk for å sende en melding", + "click-to-send-paste-mode": "Klikk for å sende {{descriptor}}", + "connection-hash": "Sammenlign dette sikkerhetsnummeret på begge enhetene for å bekrefte ende-til-ende -krypteringen." + } +} diff --git a/public_included_ws_fallback/lang/ru.json b/public_included_ws_fallback/lang/ru.json new file mode 100644 index 0000000..8617ec2 --- /dev/null +++ b/public_included_ws_fallback/lang/ru.json @@ -0,0 +1,141 @@ +{ + "header": { + "about_aria-label": "Открыть страницу \"О сервисе\"", + "pair-device_title": "Подключить устройство", + "install_title": "Установить PairDrop", + "cancel-paste-mode": "Выполнено", + "edit-paired-devices_title": "Редактировать сопряженные устройства", + "notification_title": "Включить уведомления", + "about_title": "О сервисе", + "theme-auto_title": "Адаптировать тему к системной", + "theme-dark_title": "Всегда использовать темную тему", + "theme-light_title": "Всегда использовать светлую тему" + }, + "instructions": { + "x-instructions_desktop": "Нажмите, чтобы отправить файлы, или щелкните правой кнопкой мыши, чтобы отправить сообщение", + "no-peers_data-drop-bg": "Отпустите, чтобы выбрать получателя", + "click-to-send": "Нажмите, чтобы отправить", + "x-instructions_data-drop-bg": "Отпустите, чтобы выбрать получателя", + "tap-to-send": "Прикоснитесь, чтобы отправить", + "x-instructions_data-drop-peer": "Отпустите, чтобы послать узлу", + "x-instructions_mobile": "Прикоснитесь коротко, чтобы отправить файлы, или долго, чтобы отправить сообщение", + "no-peers-title": "Откройте PairDrop на других устройствах, чтобы отправить файлы", + "no-peers-subtitle": "Сопрягите устройства из разных сетей." + }, + "footer": { + "discovery-everyone": "О вас может узнать каждый", + "display-name_placeholder": "Загрузка…", + "routed": "направляется через сервер", + "webrtc": ", если WebRTC недоступен.", + "traffic": "Трафик", + "and-by": "и", + "paired-devices": "сопряженные устройства", + "known-as": "Вы известны под именем:", + "on-this-network": "в этой сети", + "display-name_title": "Изменить имя вашего устройства навсегда" + }, + "dialogs": { + "activate-paste-mode-and-other-files": "и {{count}} других файлов", + "activate-paste-mode-base": "Откройте PairDrop на других устройствах, чтобы отправить", + "activate-paste-mode-activate-paste-mode-shared-text": "общий текст", + "edit-paired-devices-title": "Редактировать сопряженные устройства", + "auto-accept": "автоприем", + "close": "Закрыть", + "decline": "Отклонить", + "share": "Поделиться", + "would-like-to-share": "хотел бы поделиться", + "has-sent": "отправил:", + "paired-devices-wrapper_data-empty": "Нет сопряженных устройств.", + "download": "Скачать", + "receive-text-title": "Сообщение получено", + "send": "Отправить", + "send-message-to": "Отправить сообщение", + "send-message-title": "Отправить сообщение", + "copy": "Копировать", + "base64-files": "файлы", + "base64-paste-to-send": "Вставьте здесь, чтобы отправить {{type}}", + "base64-processing": "Обработка...", + "base64-tap-to-paste": "Прикоснитесь здесь, чтобы вставить {{type}}", + "base64-text": "текст", + "title-file": "Файл", + "title-file-plural": "Файлы", + "title-image": "Изображение", + "title-image-plural": "Изображения", + "download-again": "Скачать еще раз", + "auto-accept-instructions-2": "чтобы автоматически принимать все файлы, отправленные с этого устройства.", + "enter-key-from-another-device": "Для продолжения введите ключ с другого устройства.", + "pair-devices-title": "Сопрягите устройства", + "input-key-on-this-device": "Введите этот ключ на другом устройстве", + "scan-qr-code": "или отсканируйте QR-код.", + "cancel": "Отменить", + "pair": "Подключить", + "accept": "Принять", + "auto-accept-instructions-1": "Активировать", + "file-other-description-file": "и 1 другой файл", + "file-other-description-image-plural": "и {{count}} других изображений", + "file-other-description-image": "и 1 другое изображение", + "file-other-description-file-plural": "и {{count}} других файлов", + "receive-title": "{{descriptor}} получен" + }, + "about": { + "close-about-aria-label": "Закрыть страницу \"О сервисе\"", + "claim": "Самый простой способ передачи файлов между устройствами", + "close-about_aria-label": "Закрыть страницу \"О сервисе\"", + "buy-me-a-coffee_title": "Купить мне кофе!", + "github_title": "PairDrop на GitHub", + "tweet_title": "Твит о PairDrop", + "faq_title": "Часто задаваемые вопросы" + }, + "notifications": { + "display-name-changed-permanently": "Отображаемое имя было изменено навсегда.", + "display-name-random-again": "Отображаемое имя сгенерировалось случайным образом снова.", + "pairing-success": "Устройства сопряжены.", + "pairing-tabs-error": "Сопряжение двух вкладок браузера невозможно.", + "copied-to-clipboard": "Скопировано в буфер обмена", + "pairing-not-persistent": "Сопряженные устройства непостоянны.", + "link-received": "Получена ссылка от {{name}} - нажмите, чтобы открыть", + "notifications-enabled": "Уведомления включены.", + "text-content-incorrect": "Содержание текста неверно.", + "message-received": "Получено сообщение от {{name}} - нажмите, чтобы скопировать", + "connected": "Подключено.", + "copied-text": "Текст скопирован в буфер обмена", + "online": "Вы снова в сети", + "offline": "Вы находитесь вне сети", + "online-requirement": "Для сопряжения устройств вам нужно быть в сети.", + "files-incorrect": "Файлы неверны.", + "message-transfer-completed": "Передача сообщения завершена.", + "ios-memory-limit": "Отправка файлов на iOS устройства возможна только до 200 МБ за один раз", + "selected-peer-left": "Выбранный узел вышел.", + "request-title": "{{name}} хотел бы передать {{count}} {{descriptor}}", + "rate-limit-join-key": "Достигнут предел скорости. Подождите 10 секунд и повторите попытку.", + "unfinished-transfers-warning": "Есть незавершенные передачи. Вы уверены, что хотите закрыть?", + "copied-text-error": "Запись в буфер обмена не удалась. Скопируйте вручную!", + "pairing-cleared": "Все устройства не сопряжены.", + "pairing-key-invalid": "Неверный ключ", + "pairing-key-invalidated": "Ключ {{key}} признан недействительным.", + "click-to-download": "Нажмите, чтобы скачать", + "clipboard-content-incorrect": "Содержание буфера обмена неверно.", + "click-to-show": "Нажмите, чтобы показать", + "connecting": "Подключение…", + "download-successful": "{{descriptor}} загружен", + "display-name-changed-temporarily": "Отображаемое имя было изменено только для этой сессии.", + "file-content-incorrect": "Содержимое файла неверно.", + "file-transfer-completed": "Передача файла завершена." + }, + "peer-ui": { + "click-to-send-paste-mode": "Нажмите, чтобы отправить {{descriptor}}", + "preparing": "Подготовка…", + "transferring": "Передача…", + "processing": "Обработка…", + "waiting": "Ожидание…", + "connection-hash": "Чтобы проверить безопасность сквозного шифрования, сравните этот номер безопасности на обоих устройствах", + "click-to-send": "Нажмите, чтобы отправить файлы, или щелкните правой кнопкой мыши, чтобы отправить сообщение" + }, + "document-titles": { + "file-received-plural": "{{count}} файлов получено", + "message-received-plural": "{{count}} сообщений получено", + "file-received": "Файл получен", + "file-transfer-requested": "Запрошена передача файлов", + "message-received": "Сообщение получено" + } +} diff --git a/public_included_ws_fallback/lang/tr.json b/public_included_ws_fallback/lang/tr.json new file mode 100644 index 0000000..783e25b --- /dev/null +++ b/public_included_ws_fallback/lang/tr.json @@ -0,0 +1,25 @@ +{ + "header": { + "about_title": "PairDrop Hakkında", + "about_aria-label": "PairDrop Hakkında Aç", + "theme-auto_title": "Temayı Sisteme Uyarla", + "theme-light_title": "Daima Açık Tema Kullan", + "theme-dark_title": "Daima Koyu Tema Kullan", + "notification_title": "Bildirimleri Etkinleştir", + "install_title": "PairDrop'u Yükle", + "pair-device_title": "Cihaz Eşleştir", + "edit-paired-devices_title": "Eşleştirilmiş Cihazları Düzenle", + "cancel-paste-mode": "Bitti" + }, + "instructions": { + "no-peers_data-drop-bg": "Alıcıyı seçmek için bırakın" + }, + "footer": { + "display-name_placeholder": "Yükleniyor…", + "display-name_title": "Cihazının adını kalıcı olarak düzenle" + }, + "dialogs": { + "cancel": "İptal", + "edit-paired-devices-title": "Eşleştirilmiş Cihazları Düzenle" + } +} diff --git a/public_included_ws_fallback/lang/zh-CN.json b/public_included_ws_fallback/lang/zh-CN.json new file mode 100644 index 0000000..4191c7d --- /dev/null +++ b/public_included_ws_fallback/lang/zh-CN.json @@ -0,0 +1,140 @@ +{ + "header": { + "about_title": "关于 PairDrop", + "about_aria-label": "打开 关于 PairDrop", + "theme-light_title": "总是使用明亮主题", + "install_title": "安装 PairDrop", + "pair-device_title": "配对新设备", + "theme-auto_title": "主题适应系统", + "theme-dark_title": "总是使用暗黑主题", + "notification_title": "开启通知", + "edit-paired-devices_title": "管理已配对设备", + "cancel-paste-mode": "完成" + }, + "instructions": { + "x-instructions_data-drop-peer": "释放以发送到此设备", + "no-peers_data-drop-bg": "释放来选择接收者", + "no-peers-subtitle": "配对新设备使在其他网络上可见", + "no-peers-title": "在其他设备上打开 PairDrop 来发送文件", + "x-instructions_desktop": "点击以发送文件 或 右键来发送信息", + "x-instructions_mobile": "轻触以发送文件 或 长按来发送信息", + "x-instructions_data-drop-bg": "释放来选择接收者", + "click-to-send": "点击发送", + "tap-to-send": "轻触发送" + }, + "footer": { + "routed": "途径服务器", + "webrtc": "如果 WebRTC 不可用。", + "known-as": "你的名字是:", + "display-name_placeholder": "加载中…", + "and-by": "和", + "display-name_title": "长久修改你的设备名", + "discovery-everyone": "你对所有人可见", + "on-this-network": "在此网络上", + "paired-devices": "已配对的设备", + "traffic": "流量将" + }, + "dialogs": { + "activate-paste-mode-base": "在其他设备上打开 PairDrop 来发送", + "activate-paste-mode-and-other-files": "和 {{count}} 个其他的文件", + "activate-paste-mode-activate-paste-mode-shared-text": "分享文本", + "pair-devices-title": "配对新设备", + "input-key-on-this-device": "在另一个设备上输入这串数字", + "base64-text": "信息", + "enter-key-from-another-device": "输入从另一个设备上获得的数字以继续。", + "edit-paired-devices-title": "管理已配对的设备", + "pair": "配对", + "cancel": "取消", + "scan-qr-code": "或者 扫描二维码。", + "paired-devices-wrapper_data-empty": "无已配对设备。", + "auto-accept-instructions-1": "启用", + "auto-accept": "自动接收", + "decline": "拒绝", + "base64-processing": "处理中...", + "base64-tap-to-paste": "轻触此处粘贴{{type}}", + "base64-paste-to-send": "粘贴到此处以发送 {{type}}", + "auto-accept-instructions-2": "以无需同意而自动接收从那个设备上发送的所有文件。", + "would-like-to-share": "想要分享", + "accept": "接收", + "close": "关闭", + "share": "分享", + "download": "保存", + "send": "发送", + "receive-text-title": "收到信息", + "copy": "复制", + "send-message-title": "发送信息", + "send-message-to": "发了一条信息给", + "has-sent": "发送了:", + "base64-files": "文件", + "file-other-description-file": "和 1 个其他的文件", + "file-other-description-image": "和 1 个其他的图片", + "file-other-description-image-plural": "和 {{count}} 个其他的图片", + "file-other-description-file-plural": "和 {{count}} 个其他的文件", + "title-image-plural": "图片", + "receive-title": "收到 {{descriptor}}", + "title-image": "图片", + "title-file": "文件", + "title-file-plural": "文件", + "download-again": "再次保存" + }, + "about": { + "faq_title": "常见问题", + "close-about_aria-label": "关闭 关于 PairDrop", + "github_title": "PairDrop 在 GitHub 上开源", + "claim": "最简单的跨设备传输方案", + "buy-me-a-coffee_title": "帮我买杯咖啡!", + "tweet_title": "关于 PairDrop 的推特" + }, + "notifications": { + "display-name-changed-permanently": "展示的名字已经长久变更。", + "display-name-changed-temporarily": "展示的名字已经变更 仅在此会话中。", + "display-name-random-again": "展示的名字再次随机生成。", + "download-successful": "{{descriptor}} 已下载", + "pairing-tabs-error": "无法配对两个浏览器标签页。", + "pairing-success": "新设备已配对。", + "pairing-not-persistent": "配对的设备不是持久的。", + "pairing-key-invalid": "无效配对码", + "pairing-key-invalidated": "配对码 {{key}} 已失效。", + "text-content-incorrect": "文本内容不合法。", + "file-content-incorrect": "文件内容不合法。", + "clipboard-content-incorrect": "剪贴板内容不合法。", + "link-received": "收到来自 {{name}} 的链接 - 点击打开", + "message-received": "收到来自 {{name}} 的信息 - 点击复制", + "request-title": "{{name}} 想要发送 {{count}} 个 {{descriptor}}", + "click-to-show": "点击展示", + "copied-text": "复制到剪贴板", + "selected-peer-left": "选择的设备已离开。", + "pairing-cleared": "所有设备已解除配对。", + "copied-to-clipboard": "已复制到剪贴板", + "notifications-enabled": "通知已启用。", + "copied-text-error": "写入剪贴板失败。请手动复制!", + "click-to-download": "点击以保存", + "unfinished-transfers-warning": "还有未完成的传输任务。你确定要关闭吗?", + "message-transfer-completed": "信息传输已完成。", + "offline": "你未连接到网络", + "online": "你已重新连接到网络", + "connected": "已连接。", + "online-requirement": "你需要连接网络来配对新设备。", + "files-incorrect": "文件不合法。", + "file-transfer-completed": "文件传输已完成。", + "connecting": "连接中…", + "ios-memory-limit": "向 iOS 发送文件 一次最多只能发送 200 MB", + "rate-limit-join-key": "已达连接限制。请等待 10秒 后再试。" + }, + "document-titles": { + "message-received": "收到信息", + "message-received-plural": "收到 {{count}} 条信息", + "file-transfer-requested": "文件传输请求", + "file-received-plural": "收到 {{count}} 个文件", + "file-received": "收到文件" + }, + "peer-ui": { + "click-to-send-paste-mode": "点击发送 {{descriptor}}", + "click-to-send": "点击以发送文件 或 右键来发送信息", + "connection-hash": "若要验证端到端加密的安全性,请在两个设备上比较此安全编号", + "preparing": "准备中…", + "waiting": "请等待…", + "transferring": "传输中…", + "processing": "处理中…" + } +} From c2a746d69cb16f1e790a9a9031034fc3788dd436 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Tue, 29 Aug 2023 02:30:01 +0200 Subject: [PATCH 108/568] fix html attribute translation --- public/scripts/localization.js | 2 +- public_included_ws_fallback/scripts/localization.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/scripts/localization.js b/public/scripts/localization.js index 1619676..8f30f26 100644 --- a/public/scripts/localization.js +++ b/public/scripts/localization.js @@ -63,7 +63,7 @@ class Localization { if (attr === "text") { element.innerText = Localization.getTranslation(key); } else { - element.attr = Localization.getTranslation(key, attr); + element.setAttribute(attr, Localization.getTranslation(key, attr)); } } diff --git a/public_included_ws_fallback/scripts/localization.js b/public_included_ws_fallback/scripts/localization.js index 1619676..8f30f26 100644 --- a/public_included_ws_fallback/scripts/localization.js +++ b/public_included_ws_fallback/scripts/localization.js @@ -63,7 +63,7 @@ class Localization { if (attr === "text") { element.innerText = Localization.getTranslation(key); } else { - element.attr = Localization.getTranslation(key, attr); + element.setAttribute(attr, Localization.getTranslation(key, attr)); } } From abc06fcc21bb73fede96e6cae3487bbf315d3dd6 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Tue, 29 Aug 2023 02:32:54 +0200 Subject: [PATCH 109/568] fix translation fallback for sparely translated languages when complete categories are missing --- public/scripts/localization.js | 22 ++++++++++++------- .../scripts/localization.js | 22 ++++++++++++------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/public/scripts/localization.js b/public/scripts/localization.js index 8f30f26..a59f30a 100644 --- a/public/scripts/localization.js +++ b/public/scripts/localization.js @@ -76,18 +76,24 @@ class Localization { ? Localization.defaultTranslations : Localization.translations; - for (let i=0; i Date: Tue, 29 Aug 2023 02:33:54 +0200 Subject: [PATCH 110/568] enable Norwegian, Russian, and Chinese --- public/scripts/localization.js | 2 +- public_included_ws_fallback/scripts/localization.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/scripts/localization.js b/public/scripts/localization.js index a59f30a..a833993 100644 --- a/public/scripts/localization.js +++ b/public/scripts/localization.js @@ -1,7 +1,7 @@ class Localization { constructor() { Localization.defaultLocale = "en"; - Localization.supportedLocales = ["en"]; + Localization.supportedLocales = ["en", "nb", "ru", "zh-CN"]; Localization.translations = {}; Localization.defaultTranslations = {}; diff --git a/public_included_ws_fallback/scripts/localization.js b/public_included_ws_fallback/scripts/localization.js index a59f30a..a833993 100644 --- a/public_included_ws_fallback/scripts/localization.js +++ b/public_included_ws_fallback/scripts/localization.js @@ -1,7 +1,7 @@ class Localization { constructor() { Localization.defaultLocale = "en"; - Localization.supportedLocales = ["en"]; + Localization.supportedLocales = ["en", "nb", "ru", "zh-CN"]; Localization.translations = {}; Localization.defaultTranslations = {}; From d738258869169d9207a3a311b1756c2381479ec7 Mon Sep 17 00:00:00 2001 From: MeIchthys <10717998+meichthys@users.noreply.github.com> Date: Tue, 29 Aug 2023 09:10:05 -0400 Subject: [PATCH 111/568] Add alternate TURN server option Added note about using an alternate TURN server like OpenRelay. --- docs/host-your-own.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/host-your-own.md b/docs/host-your-own.md index e0e33f7..c81afa1 100644 --- a/docs/host-your-own.md +++ b/docs/host-your-own.md @@ -82,6 +82,7 @@ Set options by using the following flags in the `docker run` command: > You can use `pairdrop/rtc_config_example.json` as a starting point. > > To host your own TURN server you can follow this guide: https://gabrieltanner.org/blog/turn-server/ +> Alternatively, use a free, pre-configured TURN server like [OpenRelay]([url](https://www.metered.ca/tools/openrelay/)) > > Default configuration: > ```json From 17afa18d84f0bffdc3cf7b4b9b9de5dfd0a7461e Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 30 Aug 2023 14:57:40 +0200 Subject: [PATCH 112/568] add translation selector and fix translation of data-attributes --- public/index.html | 43 ++++++++++++--- public/lang/en.json | 7 ++- public/lang/nb.json | 2 +- public/lang/ru.json | 2 +- public/lang/tr.json | 2 +- public/lang/zh-CN.json | 2 +- public/scripts/localization.js | 49 +++++++++++++---- public/scripts/ui.js | 55 +++++++++++++++++++ public/styles.css | 24 +++++--- public_included_ws_fallback/index.html | 43 ++++++++++++--- public_included_ws_fallback/lang/en.json | 2 +- public_included_ws_fallback/lang/nb.json | 2 +- public_included_ws_fallback/lang/ru.json | 2 +- public_included_ws_fallback/lang/tr.json | 2 +- public_included_ws_fallback/lang/zh-CN.json | 2 +- .../scripts/localization.js | 49 +++++++++++++---- public_included_ws_fallback/scripts/ui.js | 55 +++++++++++++++++++ public_included_ws_fallback/styles.css | 37 +++++++++---- 18 files changed, 312 insertions(+), 68 deletions(-) diff --git a/public/index.html b/public/index.html index cd81663..9625637 100644 --- a/public/index.html +++ b/public/index.html @@ -44,6 +44,11 @@
+
+ + + +
@@ -109,7 +114,7 @@
You are known as: -
+
@@ -125,6 +130,25 @@
+ + + + +

Select Language

+
+
+ + + + + +
+
+ +
+
+
+
@@ -147,7 +171,7 @@
Enter key from another device to continue.
-
+
@@ -173,7 +197,7 @@

-
+
@@ -199,7 +223,7 @@
-
+
@@ -224,7 +248,7 @@
-
+
@@ -244,7 +268,7 @@
-
+
@@ -263,7 +287,7 @@
-
+
@@ -392,6 +416,11 @@ + + + + + diff --git a/public/lang/en.json b/public/lang/en.json index ff8294d..de26740 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -1,6 +1,7 @@ { "header": { "about_title": "About PairDrop", + "language-selector_title": "Select Language", "about_aria-label": "Open About PairDrop", "theme-auto_title": "Adapt Theme to System", "theme-light_title": "Always Use Light-Theme", @@ -24,7 +25,7 @@ }, "footer": { "known-as": "You are known as:", - "display-name_placeholder": "Loading…", + "display-name_data-placeholder": "Loading…", "display-name_title": "Edit your device name permanently", "discovery-everyone": "You can be discovered by everyone", "on-this-network": "on this network", @@ -75,7 +76,9 @@ "title-image-plural": "Images", "title-file-plural": "Files", "receive-title": "{{descriptor}} Received", - "download-again": "Download again" + "download-again": "Download again", + "language-selector-title": "Select Language", + "system-language": "System Language" }, "about": { "close-about_aria-label": "Close About PairDrop", diff --git a/public/lang/nb.json b/public/lang/nb.json index b11b664..ee2bd64 100644 --- a/public/lang/nb.json +++ b/public/lang/nb.json @@ -15,7 +15,7 @@ "discovery-everyone": "Du kan oppdages av alle", "and-by": "og av", "webrtc": "hvis WebRTC ikke er tilgjengelig.", - "display-name_placeholder": "Laster inn …", + "display-name_data-placeholder": "Laster inn…", "display-name_title": "Rediger det vedvarende enhetsnavnet ditt", "traffic": "Trafikken", "on-this-network": "på dette nettverket", diff --git a/public/lang/ru.json b/public/lang/ru.json index 8617ec2..1c67504 100644 --- a/public/lang/ru.json +++ b/public/lang/ru.json @@ -24,7 +24,7 @@ }, "footer": { "discovery-everyone": "О вас может узнать каждый", - "display-name_placeholder": "Загрузка…", + "display-name_data-placeholder": "Загрузка…", "routed": "направляется через сервер", "webrtc": ", если WebRTC недоступен.", "traffic": "Трафик", diff --git a/public/lang/tr.json b/public/lang/tr.json index 783e25b..87608f2 100644 --- a/public/lang/tr.json +++ b/public/lang/tr.json @@ -15,7 +15,7 @@ "no-peers_data-drop-bg": "Alıcıyı seçmek için bırakın" }, "footer": { - "display-name_placeholder": "Yükleniyor…", + "display-name_data-placeholder": "Yükleniyor…", "display-name_title": "Cihazının adını kalıcı olarak düzenle" }, "dialogs": { diff --git a/public/lang/zh-CN.json b/public/lang/zh-CN.json index 4191c7d..d6af684 100644 --- a/public/lang/zh-CN.json +++ b/public/lang/zh-CN.json @@ -26,7 +26,7 @@ "routed": "途径服务器", "webrtc": "如果 WebRTC 不可用。", "known-as": "你的名字是:", - "display-name_placeholder": "加载中…", + "display-name_data-placeholder": "加载中…", "and-by": "和", "display-name_title": "长久修改你的设备名", "discovery-everyone": "你对所有人可见", diff --git a/public/scripts/localization.js b/public/scripts/localization.js index a833993..4510682 100644 --- a/public/scripts/localization.js +++ b/public/scripts/localization.js @@ -5,12 +5,19 @@ class Localization { Localization.translations = {}; Localization.defaultTranslations = {}; - const initialLocale = Localization.supportedOrDefault(navigator.languages); + Localization.systemLocale = Localization.supportedOrDefault(navigator.languages); - Localization.setLocale(initialLocale) + let storedLanguageCode = localStorage.getItem("language-code"); + + Localization.initialLocale = storedLanguageCode && Localization.isSupported(storedLanguageCode) + ? storedLanguageCode + : Localization.systemLocale; + + Localization.setTranslation(Localization.initialLocale) .then(_ => { - Localization.translatePage(); - }) + console.log("Initial translation successful."); + Events.fire("translation-loaded"); + }); } static isSupported(locale) { @@ -21,11 +28,21 @@ class Localization { return locales.find(Localization.isSupported) || Localization.defaultLocale; } + static async setTranslation(locale) { + if (!locale) locale = Localization.systemLocale; + + await Localization.setLocale(locale) + await Localization.translatePage(); + + console.log("Page successfully translated", + `System language: ${Localization.systemLocale}`, + `Selected language: ${locale}` + ); + } + static async setLocale(newLocale) { if (newLocale === Localization.locale) return false; - const isFirstTranslation = !Localization.locale - Localization.defaultTranslations = await Localization.fetchTranslationsFor(Localization.defaultLocale); const newTranslations = await Localization.fetchTranslationsFor(newLocale); @@ -34,10 +51,14 @@ class Localization { Localization.locale = newLocale; Localization.translations = newTranslations; + } - if (isFirstTranslation) { - Events.fire("translation-loaded"); - } + static getLocale() { + return Localization.locale; + } + + static isSystemLocale() { + return !localStorage.getItem('language-code'); } static async fetchTranslationsFor(newLocale) { @@ -48,7 +69,7 @@ class Localization { return await response.json(); } - static translatePage() { + static async translatePage() { document .querySelectorAll("[data-i18n-key]") .forEach(element => Localization.translateElement(element)); @@ -63,10 +84,14 @@ class Localization { if (attr === "text") { element.innerText = Localization.getTranslation(key); } else { - element.setAttribute(attr, Localization.getTranslation(key, attr)); + if (attr.startsWith("data-")) { + let dataAttr = attr.substring(5); + element.dataset.dataAttr = Localization.getTranslation(key, attr); + } { + element.setAttribute(attr, Localization.getTranslation(key, attr)); + } } } - } static getTranslation(key, attr, data, useDefault=false) { diff --git a/public/scripts/ui.js b/public/scripts/ui.js index f3d08d8..0ab425f 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -45,6 +45,8 @@ class PeersUI { this.$displayName = $('display-name'); + this.$displayName.setAttribute("placeholder", this.$displayName.dataset.placeholder); + this.$displayName.addEventListener('keydown', e => this._onKeyDownDisplayName(e)); this.$displayName.addEventListener('keyup', e => this._onKeyUpDisplayName(e)); this.$displayName.addEventListener('blur', e => this._saveDisplayName(e.target.innerText)); @@ -613,6 +615,58 @@ class Dialog { } } +class LanguageSelectDialog extends Dialog { + + constructor() { + super('language-select-dialog'); + + this.$languageSelectBtn = $('language-selector'); + this.$languageSelectBtn.addEventListener('click', _ => this.show()); + + this.$languageButtons = this.$el.querySelectorAll(".language-buttons button"); + this.$languageButtons.forEach($btn => { + $btn.addEventListener("click", e => this.selectLanguage(e)); + }) + Events.on('keydown', e => this._onKeyDown(e)); + } + + _onKeyDown(e) { + if (this.isShown() && e.code === "Escape") { + this.hide(); + } + } + + show() { + if (Localization.isSystemLocale()) { + this.$languageButtons[0].focus(); + } else { + let locale = Localization.getLocale(); + for (let i=0; i this.hide()); + } +} + class ReceiveDialog extends Dialog { constructor(id) { super(id); @@ -2255,6 +2309,7 @@ class PairDrop { const server = new ServerConnection(); const peers = new PeersManager(server); const peersUI = new PeersUI(); + const languageSelectDialog = new LanguageSelectDialog(); const receiveFileDialog = new ReceiveFileDialog(); const receiveRequestDialog = new ReceiveRequestDialog(); const sendTextDialog = new SendTextDialog(); diff --git a/public/styles.css b/public/styles.css index 1375b46..4b23974 100644 --- a/public/styles.css +++ b/public/styles.css @@ -23,6 +23,7 @@ body { -webkit-user-select: none; -moz-user-select: none; user-select: none; + transition: color 300ms; } body { @@ -40,6 +41,10 @@ html { min-height: fill-available; } +.fw { + width: 100%; +} + .row-reverse { display: flex; flex-direction: row-reverse; @@ -591,7 +596,6 @@ footer { align-items: center; padding: 0 0 16px 0; text-align: center; - transition: color 300ms; cursor: default; } @@ -683,7 +687,6 @@ x-dialog x-paper { top: max(50%, 350px); margin-top: -328.5px; width: calc(100vw - 20px); - height: 625px; } #pair-device-dialog ::-moz-selection, @@ -761,7 +764,7 @@ x-dialog a { } x-dialog hr { - margin: 40px -24px 30px -24px; + margin: 20px -24px 20px -24px; border: solid 1.25px var(--border-color); } @@ -868,18 +871,18 @@ x-dialog .row { } /* button row*/ -x-paper > div:last-child { - margin: auto -24px -15px; +x-paper > .button-row { + margin: 25px -24px -15px; border-top: solid 2.5px var(--border-color); height: 50px; } -x-paper > div:last-child > .button { +x-paper > .button-row > .button { height: 100%; width: 100%; } -x-paper > div:last-child > .button:not(:last-child) { +x-paper > .button-row > .button:not(:last-child) { border-left: solid 2.5px var(--border-color); } @@ -1044,6 +1047,11 @@ x-dialog .dialog-subheader { opacity: 0.1; } +.button[selected], +.icon-button[selected] { + opacity: 0.1; +} + #cancel-paste-mode { z-index: 2; margin: 0; @@ -1301,7 +1309,7 @@ x-peers:empty~x-instructions { x-dialog x-paper { padding: 15px; } - x-paper > div:last-child { + x-paper > .button-row { margin: auto -15px -15px; } } diff --git a/public_included_ws_fallback/index.html b/public_included_ws_fallback/index.html index a233aab..529bc1a 100644 --- a/public_included_ws_fallback/index.html +++ b/public_included_ws_fallback/index.html @@ -44,6 +44,11 @@ +
+ + + +
@@ -109,7 +114,7 @@
You are known as: -
+
@@ -130,6 +135,25 @@ if WebRTC is not available.
+ + + + +

Select Language

+
+
+ + + + + +
+
+ +
+
+
+
@@ -152,7 +176,7 @@
Enter key from another device to continue.
-
+
@@ -178,7 +202,7 @@

-
+
@@ -204,7 +228,7 @@
-
+
@@ -229,7 +253,7 @@
-
+
@@ -249,7 +273,7 @@
-
+
@@ -268,7 +292,7 @@
-
+
@@ -397,6 +421,11 @@ + + + + + diff --git a/public_included_ws_fallback/lang/en.json b/public_included_ws_fallback/lang/en.json index ff8294d..4c88dae 100644 --- a/public_included_ws_fallback/lang/en.json +++ b/public_included_ws_fallback/lang/en.json @@ -24,7 +24,7 @@ }, "footer": { "known-as": "You are known as:", - "display-name_placeholder": "Loading…", + "display-name_data-placeholder": "Loading…", "display-name_title": "Edit your device name permanently", "discovery-everyone": "You can be discovered by everyone", "on-this-network": "on this network", diff --git a/public_included_ws_fallback/lang/nb.json b/public_included_ws_fallback/lang/nb.json index b11b664..ee2bd64 100644 --- a/public_included_ws_fallback/lang/nb.json +++ b/public_included_ws_fallback/lang/nb.json @@ -15,7 +15,7 @@ "discovery-everyone": "Du kan oppdages av alle", "and-by": "og av", "webrtc": "hvis WebRTC ikke er tilgjengelig.", - "display-name_placeholder": "Laster inn …", + "display-name_data-placeholder": "Laster inn…", "display-name_title": "Rediger det vedvarende enhetsnavnet ditt", "traffic": "Trafikken", "on-this-network": "på dette nettverket", diff --git a/public_included_ws_fallback/lang/ru.json b/public_included_ws_fallback/lang/ru.json index 8617ec2..1c67504 100644 --- a/public_included_ws_fallback/lang/ru.json +++ b/public_included_ws_fallback/lang/ru.json @@ -24,7 +24,7 @@ }, "footer": { "discovery-everyone": "О вас может узнать каждый", - "display-name_placeholder": "Загрузка…", + "display-name_data-placeholder": "Загрузка…", "routed": "направляется через сервер", "webrtc": ", если WebRTC недоступен.", "traffic": "Трафик", diff --git a/public_included_ws_fallback/lang/tr.json b/public_included_ws_fallback/lang/tr.json index 783e25b..87608f2 100644 --- a/public_included_ws_fallback/lang/tr.json +++ b/public_included_ws_fallback/lang/tr.json @@ -15,7 +15,7 @@ "no-peers_data-drop-bg": "Alıcıyı seçmek için bırakın" }, "footer": { - "display-name_placeholder": "Yükleniyor…", + "display-name_data-placeholder": "Yükleniyor…", "display-name_title": "Cihazının adını kalıcı olarak düzenle" }, "dialogs": { diff --git a/public_included_ws_fallback/lang/zh-CN.json b/public_included_ws_fallback/lang/zh-CN.json index 4191c7d..d6af684 100644 --- a/public_included_ws_fallback/lang/zh-CN.json +++ b/public_included_ws_fallback/lang/zh-CN.json @@ -26,7 +26,7 @@ "routed": "途径服务器", "webrtc": "如果 WebRTC 不可用。", "known-as": "你的名字是:", - "display-name_placeholder": "加载中…", + "display-name_data-placeholder": "加载中…", "and-by": "和", "display-name_title": "长久修改你的设备名", "discovery-everyone": "你对所有人可见", diff --git a/public_included_ws_fallback/scripts/localization.js b/public_included_ws_fallback/scripts/localization.js index a833993..a447669 100644 --- a/public_included_ws_fallback/scripts/localization.js +++ b/public_included_ws_fallback/scripts/localization.js @@ -5,12 +5,19 @@ class Localization { Localization.translations = {}; Localization.defaultTranslations = {}; - const initialLocale = Localization.supportedOrDefault(navigator.languages); + Localization.systemLocale = Localization.supportedOrDefault(navigator.languages); - Localization.setLocale(initialLocale) + let storedLanguageCode = localStorage.getItem("language-code"); + + Localization.initialLocale = storedLanguageCode && Localization.isSupported(storedLanguageCode) + ? storedLanguageCode + : Localization.systemLocale; + + Localization.setTranslation(Localization.initialLocale) .then(_ => { - Localization.translatePage(); - }) + console.log("Initial translation successful."); + Events.fire("translation-loaded"); + }); } static isSupported(locale) { @@ -21,10 +28,20 @@ class Localization { return locales.find(Localization.isSupported) || Localization.defaultLocale; } + static async setTranslation(locale) { + if (!locale) locale = Localization.systemLocale; + + await Localization.setLocale(locale) + await Localization.translatePage(); + + console.log("Page successfully translated", + `System language: ${Localization.systemLocale}`, + `Selected language: ${locale}` + ); + } + static async setLocale(newLocale) { if (newLocale === Localization.locale) return false; - - const isFirstTranslation = !Localization.locale Localization.defaultTranslations = await Localization.fetchTranslationsFor(Localization.defaultLocale); @@ -34,10 +51,14 @@ class Localization { Localization.locale = newLocale; Localization.translations = newTranslations; + } - if (isFirstTranslation) { - Events.fire("translation-loaded"); - } + static getLocale() { + return Localization.locale; + } + + static isSystemLocale() { + return !localStorage.getItem('language-code'); } static async fetchTranslationsFor(newLocale) { @@ -48,7 +69,7 @@ class Localization { return await response.json(); } - static translatePage() { + static async translatePage() { document .querySelectorAll("[data-i18n-key]") .forEach(element => Localization.translateElement(element)); @@ -63,10 +84,14 @@ class Localization { if (attr === "text") { element.innerText = Localization.getTranslation(key); } else { - element.setAttribute(attr, Localization.getTranslation(key, attr)); + if (attr.startsWith("data-")) { + let dataAttr = attr.substring(5); + element.dataset.dataAttr = Localization.getTranslation(key, attr); + } { + element.setAttribute(attr, Localization.getTranslation(key, attr)); + } } } - } static getTranslation(key, attr, data, useDefault=false) { diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index b3afac4..edba81e 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -45,6 +45,8 @@ class PeersUI { this.$displayName = $('display-name'); + this.$displayName.setAttribute("placeholder", this.$displayName.dataset.placeholder); + this.$displayName.addEventListener('keydown', e => this._onKeyDownDisplayName(e)); this.$displayName.addEventListener('keyup', e => this._onKeyUpDisplayName(e)); this.$displayName.addEventListener('blur', e => this._saveDisplayName(e.target.innerText)); @@ -614,6 +616,58 @@ class Dialog { } } +class LanguageSelectDialog extends Dialog { + + constructor() { + super('language-select-dialog'); + + this.$languageSelectBtn = $('language-selector'); + this.$languageSelectBtn.addEventListener('click', _ => this.show()); + + this.$languageButtons = this.$el.querySelectorAll(".language-buttons button"); + this.$languageButtons.forEach($btn => { + $btn.addEventListener("click", e => this.selectLanguage(e)); + }) + Events.on('keydown', e => this._onKeyDown(e)); + } + + _onKeyDown(e) { + if (this.isShown() && e.code === "Escape") { + this.hide(); + } + } + + show() { + if (Localization.isSystemLocale()) { + this.$languageButtons[0].focus(); + } else { + let locale = Localization.getLocale(); + for (let i=0; i this.hide()); + } +} + class ReceiveDialog extends Dialog { constructor(id) { super(id); @@ -2256,6 +2310,7 @@ class PairDrop { const server = new ServerConnection(); const peers = new PeersManager(server); const peersUI = new PeersUI(); + const languageSelectDialog = new LanguageSelectDialog(); const receiveFileDialog = new ReceiveFileDialog(); const receiveRequestDialog = new ReceiveRequestDialog(); const sendTextDialog = new SendTextDialog(); diff --git a/public_included_ws_fallback/styles.css b/public_included_ws_fallback/styles.css index 2e8fbb8..b36dd69 100644 --- a/public_included_ws_fallback/styles.css +++ b/public_included_ws_fallback/styles.css @@ -24,6 +24,7 @@ body { -webkit-user-select: none; -moz-user-select: none; user-select: none; + transition: color 300ms; } body { @@ -41,6 +42,10 @@ html { min-height: fill-available; } +.fw { + width: 100%; +} + .row-reverse { display: flex; flex-direction: row-reverse; @@ -452,7 +457,7 @@ x-no-peers::before { } x-no-peers[drop-bg]::before { - content: "Release to select recipient"; + content: attr(data-drop-bg); } x-no-peers[drop-bg] * { @@ -652,11 +657,13 @@ footer .font-body2 { #on-this-network { border-bottom: solid 4px var(--primary-color); padding-bottom: 1px; + word-break: keep-all; } #paired-devices { border-bottom: solid 4px var(--paired-device-color); padding-bottom: 1px; + word-break: keep-all; } #display-name { @@ -723,7 +730,6 @@ x-dialog x-paper { top: max(50%, 350px); margin-top: -328.5px; width: calc(100vw - 20px); - height: 625px; } #pair-device-dialog ::-moz-selection, @@ -800,8 +806,12 @@ x-dialog .font-subheading { margin: 16px; } +#pair-instructions { + flex-direction: column; +} + x-dialog hr { - margin: 40px -24px 30px -24px; + margin: 20px -24px 20px -24px; border: solid 1.25px var(--border-color); } @@ -811,7 +821,7 @@ x-dialog hr { /* Edit Paired Devices Dialog */ .paired-devices-wrapper:empty:before { - content: "No paired devices."; + content: attr(data-empty); } .paired-devices-wrapper:empty { @@ -908,18 +918,18 @@ x-dialog .row { } /* button row*/ -x-paper > div:last-child { - margin: auto -24px -15px; +x-paper > .button-row { + margin: 25px -24px -15px; border-top: solid 2.5px var(--border-color); height: 50px; } -x-paper > div:last-child > .button { +x-paper > .button-row > .button { height: 100%; width: 100%; } -x-paper > div:last-child > .button:not(:last-child) { +x-paper > .button-row > .button:not(:last-child) { border-left: solid 2.5px var(--border-color); } @@ -1084,6 +1094,11 @@ x-dialog .dialog-subheader { opacity: 0.1; } +.button[selected], +.icon-button[selected] { + opacity: 0.1; +} + #cancel-paste-mode { z-index: 2; margin: 0; @@ -1314,11 +1329,11 @@ x-instructions:not([drop-peer]):not([drop-bg]):before { } x-instructions[drop-peer]:before { - content: "Release to send to peer"; + content: attr(data-drop-peer); } x-instructions[drop-bg]:not([drop-peer]):before { - content: "Release to select recipient"; + content: attr(data-drop-bg); } x-instructions p { @@ -1358,7 +1373,7 @@ x-peers:empty~x-instructions { x-dialog x-paper { padding: 15px; } - x-paper > div:last-child { + x-paper > .button-row { margin: auto -15px -15px; } } From 1c946fc78ef6014c443c54f4e9432f12eb6007e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 04:32:13 +0000 Subject: [PATCH 113/568] Bump ws from 8.13.0 to 8.14.1 Bumps [ws](https://github.com/websockets/ws) from 8.13.0 to 8.14.1. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/8.13.0...8.14.1) --- updated-dependencies: - dependency-name: ws dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ea5f15..b35dc56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "express-rate-limit": "^6.9.0", "ua-parser-js": "^1.0.35", "unique-names-generator": "^4.3.0", - "ws": "^8.13.0" + "ws": "^8.14.1" }, "engines": { "node": ">=15" @@ -633,9 +633,9 @@ } }, "node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.1.tgz", + "integrity": "sha512-4OOseMUq8AzRBI/7SLMUwO+FEDnguetSk7KMb1sHwvF2w2Wv5Hoj0nlifx8vtGsftE/jWHojPy8sMMzYLJ2G/A==", "engines": { "node": ">=10.0.0" }, @@ -1095,9 +1095,9 @@ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.1.tgz", + "integrity": "sha512-4OOseMUq8AzRBI/7SLMUwO+FEDnguetSk7KMb1sHwvF2w2Wv5Hoj0nlifx8vtGsftE/jWHojPy8sMMzYLJ2G/A==", "requires": {} } } diff --git a/package.json b/package.json index baca71c..a7df332 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "express-rate-limit": "^6.9.0", "ua-parser-js": "^1.0.35", "unique-names-generator": "^4.3.0", - "ws": "^8.13.0" + "ws": "^8.14.1" }, "engines": { "node": ">=15" From d689fe28e54cc2636c3bd61231e9db51b1c43686 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 13 Sep 2023 18:31:42 +0200 Subject: [PATCH 114/568] add English language names next to native language names to language select dialog --- public/index.html | 6 +++--- public_included_ws_fallback/index.html | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/public/index.html b/public/index.html index 9625637..155f511 100644 --- a/public/index.html +++ b/public/index.html @@ -139,9 +139,9 @@
- - - + + +
diff --git a/public_included_ws_fallback/index.html b/public_included_ws_fallback/index.html index 529bc1a..9a1523b 100644 --- a/public_included_ws_fallback/index.html +++ b/public_included_ws_fallback/index.html @@ -144,9 +144,9 @@
- - - + + +
From 02911804cb696a73e36254b9806e39551c2254cd Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 13 Sep 2023 17:08:57 +0200 Subject: [PATCH 115/568] add default values to Localization.getTranslation function --- public/scripts/localization.js | 4 ++-- public_included_ws_fallback/scripts/localization.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/scripts/localization.js b/public/scripts/localization.js index 4510682..879deae 100644 --- a/public/scripts/localization.js +++ b/public/scripts/localization.js @@ -42,7 +42,7 @@ class Localization { static async setLocale(newLocale) { if (newLocale === Localization.locale) return false; - + Localization.defaultTranslations = await Localization.fetchTranslationsFor(Localization.defaultLocale); const newTranslations = await Localization.fetchTranslationsFor(newLocale); @@ -94,7 +94,7 @@ class Localization { } } - static getTranslation(key, attr, data, useDefault=false) { + static getTranslation(key, attr=null, data={}, useDefault=false) { const keys = key.split("."); let translationCandidates = useDefault diff --git a/public_included_ws_fallback/scripts/localization.js b/public_included_ws_fallback/scripts/localization.js index a447669..879deae 100644 --- a/public_included_ws_fallback/scripts/localization.js +++ b/public_included_ws_fallback/scripts/localization.js @@ -94,7 +94,7 @@ class Localization { } } - static getTranslation(key, attr, data, useDefault=false) { + static getTranslation(key, attr=null, data={}, useDefault=false) { const keys = key.split("."); let translationCandidates = useDefault From 6679ef75293be94e0a40175a57e5e8dd97e586a5 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 13 Sep 2023 17:23:47 +0200 Subject: [PATCH 116/568] fix keepAliveTimers not correctly cleared on reconnect --- index.js | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index 029a955..176519e 100644 --- a/index.js +++ b/index.js @@ -130,8 +130,10 @@ class PairDropServer { this._wss = new WebSocket.Server({ server }); this._wss.on('connection', (socket, request) => this._onConnection(new Peer(socket, request))); - this._rooms = {}; - this._roomSecrets = {}; + this._rooms = {}; // { roomId: peers[] } + this._roomSecrets = {}; // { pairKey: roomSecret } + + this._keepAliveTimers = {}; console.log('PairDrop is running on port', port); } @@ -139,7 +141,9 @@ class PairDropServer { _onConnection(peer) { peer.socket.on('message', message => this._onMessage(peer, message)); peer.socket.onerror = e => console.error(e); + this._keepAlive(peer); + this._send(peer, { type: 'rtc-config', config: rtcConfig @@ -170,7 +174,7 @@ class PairDropServer { this._onDisconnect(sender); break; case 'pong': - sender.lastBeat = Date.now(); + this._keepAliveTimers[sender.id].lastBeat = Date.now(); break; case 'join-ip-room': this._joinRoom(sender); @@ -223,10 +227,15 @@ class PairDropServer { } _disconnect(sender) { - this._leaveRoom(sender, 'ip', '', true); - this._leaveAllSecretRooms(sender, true); this._removeRoomKey(sender.roomKey); sender.roomKey = null; + + this._cancelKeepAlive(sender); + delete this._keepAliveTimers[sender.id]; + + this._leaveRoom(sender, 'ip', '', true); + this._leaveAllSecretRooms(sender, true); + sender.socket.terminate(); } @@ -465,23 +474,29 @@ class PairDropServer { _keepAlive(peer) { this._cancelKeepAlive(peer); - let timeout = 500; - if (!peer.lastBeat) { - peer.lastBeat = Date.now(); + let timeout = 1000; + + if (!this._keepAliveTimers[peer.id]) { + this._keepAliveTimers[peer.id] = { + timer: 0, + lastBeat: Date.now() + }; } - if (Date.now() - peer.lastBeat > 2 * timeout) { + + if (Date.now() - this._keepAliveTimers[peer.id].lastBeat > 2 * timeout) { + // Disconnect peer if unresponsive for 10s this._disconnect(peer); return; } this._send(peer, { type: 'ping' }); - peer.timerId = setTimeout(() => this._keepAlive(peer), timeout); + this._keepAliveTimers[peer.id].timer = setTimeout(() => this._keepAlive(peer), timeout); } _cancelKeepAlive(peer) { - if (peer && peer.timerId) { - clearTimeout(peer.timerId); + if (this._keepAliveTimers[peer.id]?.timer) { + clearTimeout(this._keepAliveTimers[peer.id].timer); } } } @@ -506,10 +521,6 @@ class Peer { // set name this._setName(request); - // for keepalive - this.timerId = 0; - this.lastBeat = Date.now(); - this.roomSecrets = []; this.roomKey = null; this.roomKeyRate = 0; From c71bf456e3a71d6539f0ade58c9e0cf5342979c6 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 13 Sep 2023 17:44:49 +0200 Subject: [PATCH 117/568] fix "and 2 other files" div not cleared properly --- public/scripts/ui.js | 24 +++++++++++------------ public_included_ws_fallback/scripts/ui.js | 24 +++++++++++------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 0ab425f..69cc23a 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -695,20 +695,20 @@ class ReceiveDialog extends Dialog { } _parseFileData(displayName, connectionHash, files, imagesOnly, totalSize) { - if (files.length > 1) { - let fileOther; - if (files.length === 2) { - fileOther = imagesOnly - ? Localization.getTranslation("dialogs.file-other-description-image") - : Localization.getTranslation("dialogs.file-other-description-file"); - } else { - fileOther = imagesOnly - ? Localization.getTranslation("dialogs.file-other-description-image-plural", null, {count: files.length - 1}) - : Localization.getTranslation("dialogs.file-other-description-file-plural", null, {count: files.length - 1}); - } - this.$fileOther.innerText = fileOther; + let fileOther = ""; + + if (files.length === 2) { + fileOther = imagesOnly + ? Localization.getTranslation("dialogs.file-other-description-image") + : Localization.getTranslation("dialogs.file-other-description-file"); + } else if (files.length >= 2) { + fileOther = imagesOnly + ? Localization.getTranslation("dialogs.file-other-description-image-plural", null, {count: files.length - 1}) + : Localization.getTranslation("dialogs.file-other-description-file-plural", null, {count: files.length - 1}); } + this.$fileOther.innerText = fileOther; + const fileName = files[0].name; const fileNameSplit = fileName.split('.'); const fileExtension = '.' + fileNameSplit[fileNameSplit.length - 1]; diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index edba81e..c5eb462 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -696,20 +696,20 @@ class ReceiveDialog extends Dialog { } _parseFileData(displayName, connectionHash, files, imagesOnly, totalSize) { - if (files.length > 1) { - let fileOther; - if (files.length === 2) { - fileOther = imagesOnly - ? Localization.getTranslation("dialogs.file-other-description-image") - : Localization.getTranslation("dialogs.file-other-description-file"); - } else { - fileOther = imagesOnly - ? Localization.getTranslation("dialogs.file-other-description-image-plural", null, {count: files.length - 1}) - : Localization.getTranslation("dialogs.file-other-description-file-plural", null, {count: files.length - 1}); - } - this.$fileOther.innerText = fileOther; + let fileOther = ""; + + if (files.length === 2) { + fileOther = imagesOnly + ? Localization.getTranslation("dialogs.file-other-description-image") + : Localization.getTranslation("dialogs.file-other-description-file"); + } else if (files.length >= 2) { + fileOther = imagesOnly + ? Localization.getTranslation("dialogs.file-other-description-image-plural", null, {count: files.length - 1}) + : Localization.getTranslation("dialogs.file-other-description-file-plural", null, {count: files.length - 1}); } + this.$fileOther.innerText = fileOther; + const fileName = files[0].name; const fileNameSplit = fileName.split('.'); const fileExtension = '.' + fileNameSplit[fileNameSplit.length - 1]; From bd7b3c6d28d23004bd2181b112a3c55922e6c220 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 13 Sep 2023 17:50:46 +0200 Subject: [PATCH 118/568] show warning to user if navigator.clipboard.writeText fails --- public/lang/en.json | 1 + public/scripts/ui.js | 11 ++++++++--- public_included_ws_fallback/lang/en.json | 1 + public_included_ws_fallback/scripts/ui.js | 11 ++++++++--- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/public/lang/en.json b/public/lang/en.json index de26740..75a4d60 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -100,6 +100,7 @@ "pairing-key-invalidated": "Key {{key}} invalidated.", "pairing-cleared": "All Devices unpaired.", "copied-to-clipboard": "Copied to clipboard", + "copied-to-clipboard-error": "Copying not possible. Copy manually.", "text-content-incorrect": "Text content is incorrect.", "file-content-incorrect": "File content is incorrect.", "clipboard-content-incorrect": "Clipboard content is incorrect.", diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 69cc23a..4cebb96 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -1497,9 +1497,14 @@ class ReceiveTextDialog extends Dialog { async _onCopy() { const sanitizedText = this.$text.innerText.replace(/\u00A0/gm, ' '); - await navigator.clipboard.writeText(sanitizedText); - Events.fire('notify-user', Localization.getTranslation("notifications.copied-to-clipboard")); - this.hide(); + navigator.clipboard.writeText(sanitizedText) + .then(_ => { + Events.fire('notify-user', Localization.getTranslation("notifications.copied-to-clipboard")); + this.hide(); + }) + .catch(_ => { + Events.fire('notify-user', Localization.getTranslation("notifications.copied-to-clipboard-error")); + }); } hide() { diff --git a/public_included_ws_fallback/lang/en.json b/public_included_ws_fallback/lang/en.json index 4c88dae..a607219 100644 --- a/public_included_ws_fallback/lang/en.json +++ b/public_included_ws_fallback/lang/en.json @@ -97,6 +97,7 @@ "pairing-key-invalidated": "Key {{key}} invalidated.", "pairing-cleared": "All Devices unpaired.", "copied-to-clipboard": "Copied to clipboard", + "copied-to-clipboard-error": "Copying not possible. Copy manually.", "text-content-incorrect": "Text content is incorrect.", "file-content-incorrect": "File content is incorrect.", "clipboard-content-incorrect": "Clipboard content is incorrect.", diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index c5eb462..460f30f 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -1498,9 +1498,14 @@ class ReceiveTextDialog extends Dialog { async _onCopy() { const sanitizedText = this.$text.innerText.replace(/\u00A0/gm, ' '); - await navigator.clipboard.writeText(sanitizedText); - Events.fire('notify-user', Localization.getTranslation("notifications.copied-to-clipboard")); - this.hide(); + navigator.clipboard.writeText(sanitizedText) + .then(_ => { + Events.fire('notify-user', Localization.getTranslation("notifications.copied-to-clipboard")); + this.hide(); + }) + .catch(_ => { + Events.fire('notify-user', Localization.getTranslation("notifications.copied-to-clipboard-error")); + }); } hide() { From 8d2584fa6933c3209bb7096f18b686e4a60b7e29 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 13 Sep 2023 18:15:01 +0200 Subject: [PATCH 119/568] implement temporary public rooms, tidy up index.js, rework UI dialogs and change colors slightly --- index.js | 283 ++++++++++------ public/index.html | 255 ++++++++++---- public/lang/en.json | 30 +- public/scripts/network.js | 235 +++++++++---- public/scripts/ui.js | 677 ++++++++++++++++++++++++++++---------- public/styles.css | 253 +++++++++----- 6 files changed, 1237 insertions(+), 496 deletions(-) diff --git a/index.js b/index.js index 176519e..1d08d7d 100644 --- a/index.js +++ b/index.js @@ -177,7 +177,7 @@ class PairDropServer { this._keepAliveTimers[sender.id].lastBeat = Date.now(); break; case 'join-ip-room': - this._joinRoom(sender); + this._joinIpRoom(sender); break; case 'room-secrets': this._onRoomSecrets(sender, message); @@ -196,9 +196,15 @@ class PairDropServer { break; case 'regenerate-room-secret': this._onRegenerateRoomSecret(sender, message); - break - case 'resend-peers': - this._notifyPeers(sender); + break; + case 'create-public-room': + this._onCreatePublicRoom(sender); + break; + case 'join-public-room': + this._onJoinPublicRoom(sender, message); + break; + case 'leave-public-room': + this._onLeavePublicRoom(sender); break; case 'signal': default: @@ -207,7 +213,9 @@ class PairDropServer { } _signalAndRelay(sender, message) { - const room = message.roomType === 'ip' ? sender.ip : message.roomSecret; + const room = message.roomType === 'ip' + ? sender.ip + : message.roomId; // relay message to recipient if (message.to && Peer.isValidUuid(message.to) && this._rooms[room]) { @@ -227,14 +235,15 @@ class PairDropServer { } _disconnect(sender) { - this._removeRoomKey(sender.roomKey); - sender.roomKey = null; + this._removePairKey(sender.pairKey); + sender.pairKey = null; this._cancelKeepAlive(sender); delete this._keepAliveTimers[sender.id]; - this._leaveRoom(sender, 'ip', '', true); + this._leaveIpRoom(sender, true); this._leaveAllSecretRooms(sender, true); + this._leavePublicRoom(sender, true); sender.socket.terminate(); } @@ -264,7 +273,7 @@ class PairDropServer { for (const peerId in room) { const peer = room[peerId]; - this._leaveRoom(peer, 'secret', roomSecret); + this._leaveSecretRoom(peer, roomSecret, true); this._send(peer, { type: 'secret-room-deleted', @@ -275,34 +284,35 @@ class PairDropServer { _onPairDeviceInitiate(sender) { let roomSecret = randomizer.getRandomString(256); - let roomKey = this._createRoomKey(sender, roomSecret); - if (sender.roomKey) this._removeRoomKey(sender.roomKey); - sender.roomKey = roomKey; + let pairKey = this._createPairKey(sender, roomSecret); + + if (sender.pairKey) { + this._removePairKey(sender.pairKey); + } + sender.pairKey = pairKey; + this._send(sender, { type: 'pair-device-initiated', roomSecret: roomSecret, - roomKey: roomKey + pairKey: pairKey }); - this._joinRoom(sender, 'secret', roomSecret); + this._joinSecretRoom(sender, roomSecret); } _onPairDeviceJoin(sender, message) { - // rate limit implementation: max 10 attempts every 10s - if (sender.roomKeyRate >= 10) { - this._send(sender, { type: 'pair-device-join-key-rate-limit' }); + if (sender.rateLimitReached()) { + this._send(sender, { type: 'join-key-rate-limit' }); return; } - sender.roomKeyRate += 1; - setTimeout(_ => sender.roomKeyRate -= 1, 10000); - if (!this._roomSecrets[message.roomKey] || sender.id === this._roomSecrets[message.roomKey].creator.id) { + if (!this._roomSecrets[message.pairKey] || sender.id === this._roomSecrets[message.pairKey].creator.id) { this._send(sender, { type: 'pair-device-join-key-invalid' }); return; } - const roomSecret = this._roomSecrets[message.roomKey].roomSecret; - const creator = this._roomSecrets[message.roomKey].creator; - this._removeRoomKey(message.roomKey); + const roomSecret = this._roomSecrets[message.pairKey].roomSecret; + const creator = this._roomSecrets[message.pairKey].creator; + this._removePairKey(message.pairKey); this._send(sender, { type: 'pair-device-joined', roomSecret: roomSecret, @@ -313,22 +323,53 @@ class PairDropServer { roomSecret: roomSecret, peerId: sender.id }); - this._joinRoom(sender, 'secret', roomSecret); - this._removeRoomKey(sender.roomKey); + this._joinSecretRoom(sender, roomSecret); + this._removePairKey(sender.pairKey); } _onPairDeviceCancel(sender) { - const roomKey = sender.roomKey + const pairKey = sender.pairKey - if (!roomKey) return; + if (!pairKey) return; - this._removeRoomKey(roomKey); + this._removePairKey(pairKey); this._send(sender, { type: 'pair-device-canceled', - roomKey: roomKey, + pairKey: pairKey, }); } + _onCreatePublicRoom(sender) { + let publicRoomId = randomizer.getRandomString(5, true).toLowerCase(); + + this._send(sender, { + type: 'public-room-created', + roomId: publicRoomId + }); + + this._joinPublicRoom(sender, publicRoomId); + } + + _onJoinPublicRoom(sender, message) { + if (sender.rateLimitReached()) { + this._send(sender, { type: 'join-key-rate-limit' }); + return; + } + + if (!this._rooms[message.publicRoomId] && !message.createIfInvalid) { + this._send(sender, { type: 'public-room-id-invalid', publicRoomId: message.publicRoomId }); + return; + } + + this._leavePublicRoom(sender); + this._joinPublicRoom(sender, message.publicRoomId); + } + + _onLeavePublicRoom(sender) { + this._leavePublicRoom(sender, true); + this._send(sender, { type: 'public-room-left' }); + } + _onRegenerateRoomSecret(sender, message) { const oldRoomSecret = message.roomSecret; const newRoomSecret = randomizer.getRandomString(256); @@ -346,122 +387,158 @@ class PairDropServer { delete this._rooms[oldRoomSecret]; } - _createRoomKey(creator, roomSecret) { - let roomKey; + _createPairKey(creator, roomSecret) { + let pairKey; do { // get randomInt until keyRoom not occupied - roomKey = crypto.randomInt(1000000, 1999999).toString().substring(1); // include numbers with leading 0s - } while (roomKey in this._roomSecrets) + pairKey = crypto.randomInt(1000000, 1999999).toString().substring(1); // include numbers with leading 0s + } while (pairKey in this._roomSecrets) - this._roomSecrets[roomKey] = { + this._roomSecrets[pairKey] = { roomSecret: roomSecret, creator: creator } - return roomKey; + return pairKey; } - _removeRoomKey(roomKey) { + _removePairKey(roomKey) { if (roomKey in this._roomSecrets) { this._roomSecrets[roomKey].creator.roomKey = null delete this._roomSecrets[roomKey]; } } - _joinRoom(peer, roomType = 'ip', roomSecret = '') { - const room = roomType === 'ip' ? peer.ip : roomSecret; + _joinIpRoom(peer) { + this._joinRoom(peer, 'ip', peer.ip); + } - if (this._rooms[room] && this._rooms[room][peer.id]) { + _joinSecretRoom(peer, roomSecret) { + this._joinRoom(peer, 'secret', roomSecret); + + // add secret to peer + peer.addRoomSecret(roomSecret); + } + + _joinPublicRoom(peer, publicRoomId) { + // prevent joining of 2 public rooms simultaneously + this._leavePublicRoom(peer); + + this._joinRoom(peer, 'public-id', publicRoomId); + + peer.publicRoomId = publicRoomId; + } + + _joinRoom(peer, roomType, roomId) { + // roomType: 'ip', 'secret' or 'public-id' + if (this._rooms[roomId] && this._rooms[roomId][peer.id]) { // ensures that otherPeers never receive `peer-left` after `peer-joined` on reconnect. - this._leaveRoom(peer, roomType, roomSecret); + this._leaveRoom(peer, roomType, roomId); } // if room doesn't exist, create it - if (!this._rooms[room]) { - this._rooms[room] = {}; + if (!this._rooms[roomId]) { + this._rooms[roomId] = {}; } - this._notifyPeers(peer, roomType, roomSecret); + this._notifyPeers(peer, roomType, roomId); // add peer to room - this._rooms[room][peer.id] = peer; - // add secret to peer - if (roomType === 'secret') { - peer.addRoomSecret(roomSecret); - } + this._rooms[roomId][peer.id] = peer; } - _leaveRoom(peer, roomType = 'ip', roomSecret = '', disconnect = false) { - const room = roomType === 'ip' ? peer.ip : roomSecret; - if (!this._rooms[room] || !this._rooms[room][peer.id]) return; - this._cancelKeepAlive(this._rooms[room][peer.id]); + _leaveIpRoom(peer, disconnect = false) { + this._leaveRoom(peer, 'ip', peer.ip, disconnect); + } - // delete the peer - delete this._rooms[room][peer.id]; + _leaveSecretRoom(peer, roomSecret, disconnect = false) { + this._leaveRoom(peer, 'secret', roomSecret, disconnect) - //if room is empty, delete the room - if (!Object.keys(this._rooms[room]).length) { - delete this._rooms[room]; - } else { - // notify all other peers - for (const otherPeerId in this._rooms[room]) { - const otherPeer = this._rooms[room][otherPeerId]; - this._send(otherPeer, { - type: 'peer-left', - peerId: peer.id, - roomType: roomType, - roomSecret: roomSecret, - disconnect: disconnect - }); - } - } //remove secret from peer - if (roomType === 'secret') { - peer.removeRoomSecret(roomSecret); + peer.removeRoomSecret(roomSecret); + } + + _leavePublicRoom(peer, disconnect = false) { + if (!peer.publicRoomId) return; + + this._leaveRoom(peer, 'public-id', peer.publicRoomId, disconnect); + + peer.publicRoomId = null; + } + + _leaveRoom(peer, roomType, roomId, disconnect = false) { + if (!this._rooms[roomId] || !this._rooms[roomId][peer.id]) return; + + // remove peer from room + delete this._rooms[roomId][peer.id]; + + // delete room if empty and abort + if (!Object.keys(this._rooms[roomId]).length) { + delete this._rooms[roomId]; + return; + } + + // notify all other peers that remain in room that peer left + for (const otherPeerId in this._rooms[roomId]) { + const otherPeer = this._rooms[roomId][otherPeerId]; + + let msg = { + type: 'peer-left', + peerId: peer.id, + roomType: roomType, + roomId: roomId, + disconnect: disconnect + }; + + this._send(otherPeer, msg); } } - _notifyPeers(peer, roomType = 'ip', roomSecret = '') { - const room = roomType === 'ip' ? peer.ip : roomSecret; - if (!this._rooms[room]) return; + _notifyPeers(peer, roomType, roomId) { + if (!this._rooms[roomId]) return; - // notify all other peers - for (const otherPeerId in this._rooms[room]) { + // notify all other peers that peer joined + for (const otherPeerId in this._rooms[roomId]) { if (otherPeerId === peer.id) continue; - const otherPeer = this._rooms[room][otherPeerId]; - this._send(otherPeer, { + const otherPeer = this._rooms[roomId][otherPeerId]; + + let msg = { type: 'peer-joined', peer: peer.getInfo(), roomType: roomType, - roomSecret: roomSecret - }); + roomId: roomId + }; + + this._send(otherPeer, msg); } - // notify peer about the other peers + // notify peer about peers already in the room const otherPeers = []; - for (const otherPeerId in this._rooms[room]) { + for (const otherPeerId in this._rooms[roomId]) { if (otherPeerId === peer.id) continue; - otherPeers.push(this._rooms[room][otherPeerId].getInfo()); + otherPeers.push(this._rooms[roomId][otherPeerId].getInfo()); } - this._send(peer, { + let msg = { type: 'peers', peers: otherPeers, roomType: roomType, - roomSecret: roomSecret - }); + roomId: roomId + }; + + this._send(peer, msg); } _joinSecretRooms(peer, roomSecrets) { for (let i=0; i 2 * timeout) { + if (Date.now() - this._keepAliveTimers[peer.id].lastBeat > 5 * timeout) { // Disconnect peer if unresponsive for 10s this._disconnect(peer); return; @@ -521,9 +598,22 @@ class Peer { // set name this._setName(request); + this.requestRate = 0; + this.roomSecrets = []; this.roomKey = null; - this.roomKeyRate = 0; + + this.publicRoomId = null; + } + + rateLimitReached() { + // rate limit implementation: max 10 attempts every 10s + if (this.requestRate >= 10) { + return true; + } + this.requestRate += 1; + setTimeout(_ => this.requestRate -= 1, 10000); + return false; } _setIP(request) { @@ -699,8 +789,15 @@ const hasher = (() => { })() const randomizer = (() => { + let charCodeLettersOnly = r => 65 <= r && r <= 90; + let charCodeAllPrintableChars = r => r === 45 || 47 <= r && r <= 57 || 64 <= r && r <= 90 || 97 <= r && r <= 122; + return { - getRandomString(length) { + getRandomString(length, lettersOnly = false) { + const charCodeCondition = lettersOnly + ? charCodeLettersOnly + : charCodeAllPrintableChars; + let string = ""; while (string.length < length) { let arr = new Uint16Array(length); @@ -711,7 +808,7 @@ const randomizer = (() => { }) arr = arr.filter(function (r) { /* strip non-printables: if we transform into desirable range we have a probability bias, so I suppose we better skip this character */ - return r === 45 || r >= 47 && r <= 57 || r >= 64 && r <= 90 || r >= 97 && r <= 122; + return charCodeCondition(r); }); string += String.fromCharCode.apply(String, arr); } diff --git a/public/index.html b/public/index.html index 155f511..0ea2fd1 100644 --- a/public/index.html +++ b/public/index.html @@ -78,7 +78,7 @@
-