From 750f54301ddac627a41f3364b961d2064a23c25a Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Sat, 28 Oct 2023 02:17:59 +0200 Subject: [PATCH 01/97] Conform the use of docker images by implementing the use of environment variables for all options available to Docker images and start node on docker run automatically. --- .github/ISSUE_TEMPLATE/bug-report.md | 2 +- Dockerfile | 5 + docker-compose-coturn.yml | 20 +- docker-compose.yml | 20 +- docs/host-your-own.md | 561 +++++++++++++++------------ index.js | 118 ++++-- 6 files changed, 419 insertions(+), 307 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 40b424b6..1ffe8f32 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -43,7 +43,7 @@ No | Yes **Self-Hosted Setup** Proxy: Nginx | Apache2 -Deployment: docker run | docker-compose | npm run start:prod +Deployment: docker run | docker compose | npm run start:prod Version: v1.9.4 **Additional context** diff --git a/Dockerfile b/Dockerfile index a307a456..74142abc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,12 @@ RUN npm ci COPY . . +# environment settings +ENV NODE_ENV="production" + EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD wget --quiet --tries=1 --spider http://localhost:3000 || exit 1 + +ENTRYPOINT ["node", "index.js"] \ No newline at end of file diff --git a/docker-compose-coturn.yml b/docker-compose-coturn.yml index e9a05b4b..67fcf2f7 100644 --- a/docker-compose-coturn.yml +++ b/docker-compose-coturn.yml @@ -1,15 +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" + pairdrop: + image: "lscr.io/linuxserver/pairdrop:latest" + container_name: pairdrop restart: unless-stopped + environment: + - PUID=1000 # UID to run the application as + - PGID=1000 # GID to run the application as + - WS_FALLBACK=false # Set to true to enable websocket fallback if the peer to peer WebRTC connection is not available to the client. + - RATE_LIMIT=false # Set to true to limit clients to 1000 requests per 5 min. + - RTC_CONFIG=false # Set to the path of a file that specifies the STUN/TURN servers. + - DEBUG_MODE=false # Set to true to debug container and peer connections. + - TZ=Etc/UTC # Time Zone ports: - - "3000:3000" + - "127.0.0.1:3000:3000" # Web UI. Change the port number before the last colon e.g. `127.0.0.1:9000:3000` coturn_server: image: "coturn/coturn" restart: always diff --git a/docker-compose.yml b/docker-compose.yml index 5ff9305c..357ed855 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,16 @@ 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" + pairdrop: + image: "lscr.io/linuxserver/pairdrop:latest" + container_name: pairdrop restart: unless-stopped + environment: + - PUID=1000 # UID to run the application as + - PGID=1000 # GID to run the application as + - WS_FALLBACK=false # Set to true to enable websocket fallback if the peer to peer WebRTC connection is not available to the client. + - RATE_LIMIT=false # Set to true to limit clients to 1000 requests per 5 min. + - RTC_CONFIG=false # Set to the path of a file that specifies the STUN/TURN servers. + - DEBUG_MODE=false # Set to true to debug container and peer connections. + - TZ=Etc/UTC # Time Zone ports: - - "3000:3000" + - "127.0.0.1:3000:3000" # Web UI. Change the port number before the last colon e.g. `127.0.0.1:9000:3000` diff --git a/docs/host-your-own.md b/docs/host-your-own.md index c81afa1e..626cc9cb 100644 --- a/docs/host-your-own.md +++ b/docs/host-your-own.md @@ -1,181 +1,129 @@ # 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 to enable transfers between different networks. -> -> 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). +## TURN server for Internet Transfer -> 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. +Beware that you have to host your own TURN server to enable transfers between different networks. + +Follow [this guide](https://gabrieltanner.org/blog/turn-server/) to either install coturn directly on your system (Step 1) +or deploy it via Docker (Step 5). + +Alternatively, use a free, pre-configured TURN server like [OpenRelay](https://www.metered.ca/tools/openrelay/) + +
+ +## PairDrop via HTTPS + +On some browsers PairDrop must be served over TLS in order for some features to work properly. +These may include: +- Copying an incoming message via the 'copy' button +- Installing PairDrop as PWA +- Persistent pairing of devices +- Changing of the display name +- Notifications + +Naturally, this is also recommended to increase security. + +
## Deployment with Docker +The easiest way to get PairDrop up and running is by using Docker. + ### Docker Image from Docker Hub ```bash docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 lscr.io/linuxserver/pairdrop ``` +> This image is hosted by [linuxserver.io](https://linuxserver.io). For more information visit https://hub.docker.com/r/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)). -> -> 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: +
-##### Port -```bash --p 127.0.0.1:8080:3000 -``` -> Specify the port used by the docker image -> - 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 +### Docker Image from GitHub Container Registry (ghcr.io) -##### IPv6 Localization ```bash --e IPV6_LOCALIZE=4 +docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 ghcr.io/schlagmichdoch/pairdrop ``` -> 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. -##### 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. -> -> 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. \ -> Additionally, beware that all traffic using this fallback debits the servers data plan. -##### Specify STUN/TURN Servers +
+ +### Docker Image self-built + +#### Build the image + ```bash --e RTC_CONFIG="rtc_config.json" +docker build --pull . -f Dockerfile -t pairdrop ``` -> 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/ -> Alternatively, use a free, pre-configured TURN server like [OpenRelay]([url](https://www.metered.ca/tools/openrelay/)) +> A GitHub action is set up to do this step automatically at the release of new versions. > -> Default configuration: -> ```json -> { -> "sdpSemantics": "unified-plan", -> "iceServers": [ -> { -> "urls": "stun:stun.l.google.com:19302" -> } -> ] -> } -> ``` +> `--pull` ensures always the latest node image is used. + +#### Run the image -##### Debug Mode ```bash --e DEBUG_MODE="true" +docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 -it pairdrop ``` -> 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 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!** +> You must use a server proxy to set the `X-Forwarded-For` header +> 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.
-### Docker Image from GHCR -```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)). -> -> 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) +### Flags -> The Docker Image includes a healthcheck. \ -> Read more about [Docker Swarm Usage](docker-swarm-usage.md#docker-swarm-usage). +Set options by using the following flags in the `docker run` command: + +#### Port -### Docker Image self-built -#### Build the image ```bash -docker build --pull . -f Dockerfile -t pairdrop +-p 127.0.0.1:8080:3000 ``` -> A GitHub action is set up to do this step automatically. + +> Specify the port used by the docker image > -> `--pull` ensures always the latest node image is used. +> - 3000 -> `-p 127.0.0.1:3000:3000` +> - 8080 -> `-p 127.0.0.1:8080:3000` -#### Run the image +#### Set Environment Variables via Docker + +Environment Variables are set directly in the `docker run` command: \ +e.g. `docker run -p 127.0.0.1:3000:3000 -it pairdrop -e DEBUG_MODE="true"` + +Overview of available Environment Variables are found [here](#environment-variables). + +Example: ```bash -docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 -it pairdrop npm run start:prod +docker run -d \ + --name=pairdrop \ + --restart=unless-stopped \ + -p 127.0.0.1:3000:3000 \ + -e PUID=1000 \ + -e PGID=1000 \ + -e WS_FALLBACK=false \ + -e RTC_CONFIG=false \ + -e RATE_LIMIT=false \ + -e DEBUG_MODE=false \ + -e TZ=Etc/UTC \ + 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)). -> -> 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) - -> The Docker Image includes a Healthcheck. \ -Read more about [Docker Swarm Usage](docker-swarm-usage.md#docker-swarm-usage).
## Deployment with Docker Compose -Here's an example docker-compose file: + +Here's an example docker compose file: ```yaml -version: "2" +version: "3" services: pairdrop: - image: lscr.io/linuxserver/pairdrop:latest + image: "lscr.io/linuxserver/pairdrop:latest" container_name: pairdrop restart: unless-stopped environment: @@ -183,22 +131,26 @@ services: - PGID=1000 # GID to run the application as - WS_FALLBACK=false # Set to true to enable websocket fallback if the peer to peer WebRTC connection is not available to the client. - RATE_LIMIT=false # Set to true to limit clients to 1000 requests per 5 min. + - RTC_CONFIG=false # Set to the path of a file that specifies the STUN/TURN servers. + - DEBUG_MODE=false # Set to true to debug container and peer connections. - TZ=Etc/UTC # Time Zone ports: - - 127.0.0.1:3000:3000 # Web UI + - "127.0.0.1:3000:3000" # Web UI ``` Run the compose file with `docker compose up -d`. -> You must use a server proxy to set the X-Forwarded-For \ +> You must use a server proxy to set the `X-Forwarded-For` header > 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 `ports` argument.
-## Deployment with node +## Deployment with Node.js + +Clone this repository and enter the folder ```bash git clone https://github.com/schlagmichdoch/PairDrop.git && cd PairDrop @@ -212,84 +164,110 @@ npm install Start the server with: -```bash -node index.js -``` -or ```bash npm start ``` -> 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. +
-### Environment variables +### Options / Flags + +These are some flags only reasonable when deploying via Node.js + #### Port -On Unix based systems + ```bash -PORT=3010 npm start +PORT=3000 ``` -On Windows + +> Default: `3000` +> +> Environment variable to specify the port used by the Node.js server \ +> e.g. `PORT=3010 npm start` + +#### Local Run + ```bash -$env:PORT=3010; npm start +npm start -- --localhost-only ``` -> Specify the port PairDrop is running on. (Default: 3000) -#### IPv6 Localization +> Only allow connections from localhost. +> +> You must use a server proxy to set the `X-Forwarded-For` header +> to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). +> +> Use this when deploying PairDrop with node to prevent +> bypassing the reverse proxy by reaching the Node.js server directly. + +#### Automatic restart on error + ```bash -IPV6_LOCALIZE=4 +npm start -- --auto-restart ``` -> 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 +> Restarts server automatically on error + +#### Production (autostart and rate-limit) + ```bash -RTC_CONFIG="rtc_config.json" npm start +npm run start:prod ``` -On Windows + +> shortcut for `RATE_LIMIT=5 npm start -- --auto-restart` + +#### Production (autostart, rate-limit, localhost-only) + ```bash -$env:RTC_CONFIG="rtc_config.json"; npm start +npm run start:prod -- --localhost-only ``` -> 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 -> { -> "sdpSemantics": "unified-plan", -> "iceServers": [ -> { -> "urls": "stun:stun.l.google.com:19302" -> } -> ] -> } -> ``` -#### Debug Mode +> To prevent connections to the node server from bypassing \ +> the proxy server you should always use "--localhost-only" on production. + +#### Set Environment Variables via Node.js + +To specify environment variables set them in the run command in front of `npm start`. +The syntax is different on Unix and Windows. + On Unix based systems + ```bash -DEBUG_MODE="true" npm start +PORT=3000 RTC_CONFIG="rtc_config.json" npm start ``` + On Windows + +```bash +$env:PORT=3000 RTC_CONFIG="rtc_config.json"; npm start +``` + +Overview of available Environment Variables are found [here](#environment-variables). + +
+ +## Environment Variables + +### Debug Mode + ```bash -$env:DEBUG_MODE="true"; npm start +DEBUG_MODE="true" ``` -> 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. \ +> Default: `false` +> +> Logs the used environment variables for debugging. +> +> Prints 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: +> 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 @@ -300,82 +278,141 @@ $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 set up correctly. \ ->Find your devices public IP by visiting https://www.whatismyip.com/. > -> Preserve your clients' privacy. **Never use this flag in production!** +> If the IP address "PairDrop uses" matches the public IP address of the client device, everything is set up correctly. \ +> To find out the public IP address of the client device visit https://whatsmyip.com/. +> +> To preserve your clients' privacy: \ +> **Never use this environment variable in production!** -### Options / Flags -#### Local Run +
+ +### Rate limiting requests + ```bash -npm start -- --localhost-only +RATE_LIMIT=1 ``` -> 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)). + +> Default: `false` +> +> Limits clients to 1000 requests per 5 min > -> Use this when deploying PairDrop with node to prevent \ -> bypassing the proxy by reaching the Docker container directly. +> "If you are behind a proxy/load balancer (usually the case with most hosting services, e.g. Heroku, Bluemix, AWS ELB, +> Render, Nginx, Cloudflare, Akamai, Fastly, Firebase Hosting, Rackspace LB, Riverbed Stingray, etc.), the IP address of +> the request might be the IP of the load balancer/reverse proxy (making the rate limiter effectively a global one and +> blocking all requests once the limit is reached) or undefined." +> (See: https://express-rate-limit.mintlify.app/guides/troubleshooting-proxy-issues) +> +> To find the correct number to use for this setting: +> +> 1. Start PairDrop with `DEBUG_MODE=True` and `RATE_LIMIT=1` +> 2. Make a `get` request to `/ip` of the PairDrop instance (e.g. `https://pairdrop-example.net/ip`) +> 3. Check if the IP address returned in the response matches your public IP address (find out by visiting e.g. https://whatsmyip.com/) +> 4. You have found the correct number if the IP addresses match. If not, then increase `RATE_LIMIT` by one and redo 1. - 4. +> +> e.g. on Render you must use RATE_LIMIT=5 -#### Automatic restart on error -```bash -npm start -- --auto-restart -``` -> Restarts server automatically on error
-#### Rate limiting requests +### IPv6 Localization + ```bash -npm start -- --rate-limit +IPV6_LOCALIZE=4 ``` -> Limits clients to 1000 requests per 5 min + +> Default: `false` +> +> To enable Peer Auto-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. +
-#### Websocket Fallback (for VPN) +### Websocket Fallback (for VPN) + ```bash -npm start -- --include-ws-fallback +WS_FALLBACK=true ``` + +> Default: `false` +> > 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! > -> **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. \ +> Only ever use this on instances you can trust. +> > Additionally, beware that all traffic using this fallback debits the servers data plan. +
-#### Production (autostart and rate-limit) -```bash -npm run start:prod -``` +### Specify STUN/TURN Servers -#### Production (autostart, rate-limit, localhost-only and websocket fallback for VPN) ```bash -npm run start:prod -- --localhost-only --include-ws-fallback +RTC_CONFIG="rtc_config.json" ``` -> To prevent connections to the node server from bypassing \ -> the proxy server you should always use "--localhost-only" on production. + +> Default: `false` +> +> Specify the STUN/TURN servers PairDrop clients use by setting \ +> `RTC_CONFIG` to a JSON file including the configuration. \ +> You can use `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 +> { +> "sdpSemantics": "unified-plan", +> "iceServers": [ +> { +> "urls": "stun:stun.l.google.com:19302" +> } +> ] +> } +> ``` + +
+ +## Healthcheck + +> The Docker Image hosted on `ghcr.io` and the self-built Docker Image include a healthcheck. +> +> Read more about [Docker Swarm Usage](docker-swarm-usage.md#docker-swarm-usage). + +
## 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 + ``` server { listen 80; @@ -409,6 +446,7 @@ server { ``` #### Automatic http to https redirect: + ``` server { listen 80; @@ -437,14 +475,21 @@ server { } ``` + +
+ ### Using Apache + install modules `proxy`, `proxy_http`, `mod_proxy_wstunnel` + ```bash a2enmod proxy ``` + ```bash a2enmod proxy_http ``` + ```bash a2enmod proxy_wstunnel ``` @@ -454,16 +499,18 @@ a2enmod proxy_wstunnel Create a new configuration file under `/etc/apache2/sites-available` (on Debian) **pairdrop.conf** + #### Allow HTTP and HTTPS requests + ```apacheconf - + ProxyPass / http://127.0.0.1:3000/ RewriteEngine on RewriteCond %{HTTP:Upgrade} websocket [NC] RewriteCond %{HTTP:Connection} upgrade [NC] RewriteRule ^/?(.*) "ws://127.0.0.1:3000/$1" [P,L] - + ProxyPass / https://127.0.0.1:3000/ RewriteEngine on RewriteCond %{HTTP:Upgrade} websocket [NC] @@ -471,12 +518,14 @@ 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: + ```apacheconf - + Redirect permanent / https://127.0.0.1:3000/ - + ProxyPass / https://127.0.0.1:3000/ RewriteEngine on RewriteCond %{HTTP:Upgrade} websocket [NC] @@ -484,62 +533,72 @@ 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: + ```bash a2ensite pairdrop ``` + ```bash service apache2 reload ``` -# Local Development -## Install +
+ +## Local Development + +### Install + All files needed for developing are available on the branch `dev`. First, [Install docker with docker-compose.](https://docs.docker.com/compose/install/) Then, clone the repository and run docker-compose: -```bash - git clone https://github.com/schlagmichdoch/PairDrop.git - cd PairDrop - - git checkout dev - - docker-compose up -d +```bash +git clone https://github.com/schlagmichdoch/PairDrop.git && cd PairDrop +``` +```bash +git checkout dev +``` +```bash +docker compose -f docker-compose-dev.yml up -d ``` + 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 Node.js server, run `docker logs pairdrop`.
-## Testing PWA related features +### Testing PWA related features + 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` \ +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). + +- 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`. diff --git a/index.js b/index.js index ae50931f..7309d6f6 100644 --- a/index.js +++ b/index.js @@ -35,7 +35,53 @@ process.on('unhandledRejection', (reason, promise) => { console.log(reason) }) -if (process.argv.includes('--auto-restart')) { +// Arguments for deployment with Docker and Node.js +const DEBUG_MODE = process.env.DEBUG_MODE === "true"; +const PORT = process.env.PORT || 3000; +const WS_FALLBACK = process.argv.includes('--include-ws-fallback') || process.env.WS_FALLBACK === "true"; +const IPV6_LOCALIZE = parseInt(process.env.IPV6_LOCALIZE) || false; +const RTC_CONFIG = process.env.RTC_CONFIG + ? JSON.parse(fs.readFileSync(process.env.RTC_CONFIG, 'utf8')) + : { + "sdpSemantics": "unified-plan", + "iceServers": [ + { + "urls": "stun:stun.l.google.com:19302" + } + ] + }; + +let rateLimit = false; +if (process.argv.includes('--rate-limit') || process.env.RATE_LIMIT === "true") { + rateLimit = 5; +} else { + let envRateLimit = parseInt(process.env.RATE_LIMIT); + if (!isNaN(envRateLimit)) { + rateLimit = envRateLimit; + } +} +const RATE_LIMIT = rateLimit; + +// Arguments for deployment with Node.js only +const AUTO_START = process.argv.includes('--auto-restart'); +const LOCALHOST_ONLY = process.argv.includes('--localhost-only'); + +if (DEBUG_MODE) { + console.log("DEBUG_MODE is active. To protect privacy, do not use in production."); + console.debug("\n"); + console.debug("----DEBUG ENVIRONMENT VARIABLES----") + console.debug("DEBUG_MODE", DEBUG_MODE); + console.debug("PORT", PORT); + console.debug("WS_FALLBACK", WS_FALLBACK); + console.debug("IPV6_LOCALIZE", IPV6_LOCALIZE); + console.debug("RTC_CONFIG", RTC_CONFIG); + console.debug("RATE_LIMIT", RATE_LIMIT); + console.debug("AUTO_START", AUTO_START); + console.debug("LOCALHOST_ONLY", LOCALHOST_ONLY); + console.debug("\n"); +} + +if (AUTO_START) { process.on( 'uncaughtException', () => { @@ -56,20 +102,9 @@ if (process.argv.includes('--auto-restart')) { ); } -const rtcConfig = process.env.RTC_CONFIG - ? JSON.parse(fs.readFileSync(process.env.RTC_CONFIG, 'utf8')) - : { - "sdpSemantics": "unified-plan", - "iceServers": [ - { - "urls": "stun:stun.l.google.com:19302" - } - ] - }; - const app = express(); -if (process.argv.includes('--rate-limit')) { +if (RATE_LIMIT) { const limiter = RateLimit({ windowMs: 5 * 60 * 1000, // 5 minutes max: 1000, // Limit each IP to 1000 requests per `window` (here, per 5 minutes) @@ -79,35 +114,40 @@ if (process.argv.includes('--rate-limit')) { }) app.use(limiter); - // ensure correct client ip and not the ip of the reverse proxy is used for rate limiting on render.com - // see https://github.com/express-rate-limit/express-rate-limit#troubleshooting-proxy-issues - app.set('trust proxy', 5); + + // ensure correct client ip and not the ip of the reverse proxy is used for rate limiting + // see https://express-rate-limit.mintlify.app/guides/troubleshooting-proxy-issues + app.set('trust proxy', RATE_LIMIT); + + if (!DEBUG_MODE) { + console.log("Use DEBUG_MODE=true to find correct number for RATE_LIMIT."); + } } -if (process.argv.includes('--include-ws-fallback')) { +if (WS_FALLBACK) { app.use(express.static('public_included_ws_fallback')); } else { 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.") -} - -let ipv6_lcl; -if (process.env.IPV6_LOCALIZE) { - ipv6_lcl = parseInt(process.env.IPV6_LOCALIZE); - if (!ipv6_lcl || !(0 < ipv6_lcl && ipv6_lcl < 8)) { +if (IPV6_LOCALIZE) { + if (!(0 < IPV6_LOCALIZE && IPV6_LOCALIZE < 8)) { console.error("IPV6_LOCALIZE must be an integer between 1 and 7"); return; } - console.log("IPv6 client IPs will be localized to", ipv6_lcl, ipv6_lcl === 1 ? "segment" : "segments"); + console.log("IPv6 client IPs will be localized to", IPV6_LOCALIZE, IPV6_LOCALIZE === 1 ? "segment" : "segments"); } app.use(function(req, res) { + if (DEBUG_MODE && RATE_LIMIT && req.path === "/ip") { + console.debug("----DEBUG RATE_LIMIT----") + console.debug("To find out the correct value for RATE_LIMIT go to '/ip' and ensure the returned IP-address is the IP-address of your client.") + console.debug("See https://github.com/express-rate-limit/express-rate-limit#troubleshooting-proxy-issues for more info") + console.debug("\n"); + res.send(req.ip); + } + res.redirect('/'); }); @@ -116,12 +156,11 @@ app.get('/', (req, res) => { }); const server = http.createServer(app); -const port = process.env.PORT || 3000; -if (process.argv.includes('--localhost-only')) { - server.listen(port, '127.0.0.1'); +if (LOCALHOST_ONLY) { + server.listen(PORT, '127.0.0.1'); } else { - server.listen(port); + server.listen(PORT); } server.on('error', (err) => { @@ -143,7 +182,7 @@ class PairDropServer { this._keepAliveTimers = {}; - console.log('PairDrop is running on port', port); + console.log('PairDrop is running on port', PORT); } _onConnection(peer) { @@ -154,7 +193,7 @@ class PairDropServer { this._send(peer, { type: 'rtc-config', - config: rtcConfig + config: RTC_CONFIG }); // send displayName @@ -644,18 +683,19 @@ class Peer { this.ip = this.ip.substring(7); let ipv6_was_localized = false; - if (ipv6_lcl && this.ip.includes(':')) { - this.ip = this.ip.split(':',ipv6_lcl).join(':'); + if (IPV6_LOCALIZE && this.ip.includes(':')) { + this.ip = this.ip.split(':',IPV6_LOCALIZE).join(':'); ipv6_was_localized = true; } - if (debugMode) { + if (DEBUG_MODE) { 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']); - if (ipv6_was_localized) - console.debug("IPv6 client IP was localized to", ipv6_lcl, ipv6_lcl > 1 ? "segments" : "segment"); + if (ipv6_was_localized) { + console.debug("IPv6 client IP was localized to", IPV6_LOCALIZE, IPV6_LOCALIZE > 1 ? "segments" : "segment"); + } 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"); From 1c79290ad6a9bfe89659808664f0b5b63b47e0ef Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Sat, 28 Oct 2023 04:23:59 +0200 Subject: [PATCH 02/97] - add guide to documentation and example files to repository to run coturn alongside PairDrop via Docker Compose - enable TURN over TLS - modified the .gitignore to ignore files with user-data - should fix #105 - partly cherry-picked from #106 Co-authored-by: xundeenergie --- .gitignore | 3 +++ docker-compose-coturn.yml | 16 +++++++++---- docs/host-your-own.md | 50 +++++++++++++++++++++++++++++++++++++++ rtc_config_example.json | 4 ++-- turnserver_example.conf | 42 ++++++++++++++++++++++---------- 5 files changed, 97 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index bd15e979..ba0f1e53 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ node_modules fqdn.env /docker/certs qrcode-svg/ +turnserver.conf +rtc_config.json +ssl/ diff --git a/docker-compose-coturn.yml b/docker-compose-coturn.yml index 67fcf2f7..9d0b0a85 100644 --- a/docker-compose-coturn.yml +++ b/docker-compose-coturn.yml @@ -4,20 +4,28 @@ services: image: "lscr.io/linuxserver/pairdrop:latest" container_name: pairdrop restart: unless-stopped + volumes: + - ./rtc_config.json:/home/node/app/rtc_config.json environment: - PUID=1000 # UID to run the application as - PGID=1000 # GID to run the application as - WS_FALLBACK=false # Set to true to enable websocket fallback if the peer to peer WebRTC connection is not available to the client. - RATE_LIMIT=false # Set to true to limit clients to 1000 requests per 5 min. - - RTC_CONFIG=false # Set to the path of a file that specifies the STUN/TURN servers. + - RTC_CONFIG=/home/node/app/rtc_config.json # Set to the path of a file that specifies the STUN/TURN servers. - DEBUG_MODE=false # Set to true to debug container and peer connections. - TZ=Etc/UTC # Time Zone ports: - "127.0.0.1:3000:3000" # Web UI. Change the port number before the last colon e.g. `127.0.0.1:9000:3000` coturn_server: image: "coturn/coturn" - restart: always - network_mode: "host" + restart: unless-stopped 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 + - ./ssl/:/etc/coturn/ssl/ + ports: + - "3478:3478" + - "3478:3478/udp" + - "5349:5349" + - "5349:5349/udp" + - "10000-20000:10000-20000/udp" + # see guide at docs/host-your-own.md#coturn-and-pairdrop-via-docker-compose \ No newline at end of file diff --git a/docs/host-your-own.md b/docs/host-your-own.md index 626cc9cb..ffa03c1f 100644 --- a/docs/host-your-own.md +++ b/docs/host-your-own.md @@ -6,6 +6,8 @@ Beware that you have to host your own TURN server to enable transfers between di Follow [this guide](https://gabrieltanner.org/blog/turn-server/) to either install coturn directly on your system (Step 1) or deploy it via Docker (Step 5). + +You can use the `docker-compose-coturn.yml` in this repository. See [Coturn and PairDrop via Docker Compose](#coturn-and-pairdrop-via-docker-compose). Alternatively, use a free, pre-configured TURN server like [OpenRelay](https://www.metered.ca/tools/openrelay/) @@ -546,6 +548,54 @@ service apache2 reload
+## Coturn and PairDrop via Docker Compose + +### Setup container +To run coturn and PairDrop at once by using the `docker-compose-coturn.yml` with TURN over TLS enabled +you need to follow these steps: + +1. Generate or retrieve certificates for your `` (e.g. letsencrypt / certbot) +2. Create `./ssl` folder: `mkdir ssl` +3. Copy your ssl-certificates and the privkey to `./ssl` +4. Restrict access to `./ssl`: `chown -R nobody:nogroup ./ssl` +5. Create a dh-params file: `openssl dhparam -out ./ssl/dhparams.pem 4096` +6. Copy `rtc_config_example.json` to `rtc_config.json` +7. Copy `turnserver_example.conf` to `turnserver.conf` +8. Change `` in both files to the domain where your PairDrop instance is running +9. Change `username` and `password` in `turnserver.conf` and `rtc-config.json` +10. To start the container including coturn run: \ + `docker compose -f docker-compose-coturn.yml up -d` + +
+ +#### Setup container +To restart the container including coturn run: \ + `docker compose -f docker-compose-coturn.yml restart` + +
+ +#### Setup container +To stop the container including coturn run: \ + `docker compose -f docker-compose-coturn.yml stop` + +
+ +### Firewall +To run PairDrop including its own coturn-server you need to punch holes in the firewall. These ports must be opened additionally: +- 3478 tcp/udp +- 5349 tcp/udp +- 10000:20000 tcp/udp + +
+ +### Firewall +To run PairDrop including its own coturn-server you need to punch holes in the firewall. These ports must be opened additionally: +- 3478 tcp/udp +- 5349 tcp/udp +- 10000:20000 tcp/udp + +
+ ## Local Development ### Install diff --git a/rtc_config_example.json b/rtc_config_example.json index d7e48e82..fe34c25e 100644 --- a/rtc_config_example.json +++ b/rtc_config_example.json @@ -2,10 +2,10 @@ "sdpSemantics": "unified-plan", "iceServers": [ { - "urls": "stun:stun.l.google.com:19302" + "urls": "stun::3478" }, { - "urls": "turn:example.com:3478", + "urls": "turns::5349", "username": "username", "credential": "password" } diff --git a/turnserver_example.conf b/turnserver_example.conf index 09e79867..04bc82de 100644 --- a/turnserver_example.conf +++ b/turnserver_example.conf @@ -6,11 +6,16 @@ server-name=pairdrop listening-ip=0.0.0.0 # External IP-Address of the TURN server -external-ip= +# only needed, if coturn is behind a NAT +# external-ip= -# Main listening port +# Main listening port for STUN and TURN listening-port=3478 +# Main listening port for TURN over TLS (TURNS) +# Use port 443 to bypass some firewalls +tls-listening-port=5349 + # Further ports that are open for communication min-port=10000 max-port=20000 @@ -18,21 +23,34 @@ max-port=20000 # Use fingerprint in TURN message fingerprint -# Log file path -log-file=/var/log/turnserver.log - # Enable verbose logging -verbose +# verbose + +# Log file path +# - is logging to STDOUT, so it's visible in docker-compose logs +log-file=- # Specify the user for the TURN authentification -user=user:password +user=username: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 +cert=/etc/coturn/ssl/cert.crt +pkey=/etc/coturn/ssl/pkey.pem +dh-file=/etc/coturn/ssl/dhparam.pem + +# For security-reasons disable old ssl and tls-protocols +# and other recommended options: see https://github.com/coturn/coturn/blob/master/examples/etc/turnserver.conf +no-sslv3 +no-tlsv1 +no-tlsv1_1 +no-tlsv1_2 +no-rfc5780 +no-stun-backward-compatibility +response-origin-only-with-rfc5780 +no-cli +no-multicast-peers +no-software-attribute +check-origin-consistency \ No newline at end of file From cb8d6448f513aeda8fca371222d7de30fae91f5b Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Tue, 31 Oct 2023 18:32:23 +0100 Subject: [PATCH 03/97] =?UTF-8?q?Delay=20loading=20of=20saved=20displayNam?= =?UTF-8?q?e=20until=20after=20the=20websocket=20is=20open=20to=20display?= =?UTF-8?q?=20"Loading=E2=80=A6"=20until=20then?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.js | 10 +-- public/scripts/network.js | 6 +- public/scripts/ui.js | 79 ++++++++++--------- .../scripts/network.js | 6 +- public_included_ws_fallback/scripts/ui.js | 79 ++++++++++--------- 5 files changed, 90 insertions(+), 90 deletions(-) diff --git a/index.js b/index.js index 7309d6f6..02b3c5f9 100644 --- a/index.js +++ b/index.js @@ -199,12 +199,10 @@ class PairDropServer { // send displayName this._send(peer, { type: 'display-name', - message: { - displayName: peer.name.displayName, - deviceName: peer.name.deviceName, - peerId: peer.id, - peerIdHash: hasher.hashCodeSalted(peer.id) - } + displayName: peer.name.displayName, + deviceName: peer.name.deviceName, + peerId: peer.id, + peerIdHash: hasher.hashCodeSalted(peer.id) }); } diff --git a/public/scripts/network.js b/public/scripts/network.js index e8958f04..5af4a21e 100644 --- a/public/scripts/network.js +++ b/public/scripts/network.js @@ -175,8 +175,8 @@ class ServerConnection { _onDisplayName(msg) { // Add peerId and peerIdHash to sessionStorage to authenticate as the same device on page reload - sessionStorage.setItem('peer_id', msg.message.peerId); - sessionStorage.setItem('peer_id_hash', msg.message.peerIdHash); + sessionStorage.setItem('peer_id', msg.peerId); + sessionStorage.setItem('peer_id_hash', msg.peerIdHash); // Add peerId to localStorage to mark it for other PairDrop tabs on the same browser BrowserTabsConnector.addPeerIdToLocalStorage().then(peerId => { @@ -902,7 +902,7 @@ class PeersManager { Events.on('secret-room-deleted', e => this._onSecretRoomDeleted(e.detail)); Events.on('room-secret-regenerated', e => this._onRoomSecretRegenerated(e.detail)); - Events.on('display-name', e => this._onDisplayName(e.detail.message.displayName)); + Events.on('display-name', e => this._onDisplayName(e.detail.displayName)); Events.on('self-display-name-changed', e => this._notifyPeersDisplayNameChanged(e.detail)); Events.on('notify-peer-display-name-changed', e => this._notifyPeerDisplayNameChanged(e.detail)); Events.on('auto-accept-updated', e => this._onAutoAcceptUpdated(e.detail.roomSecret, e.detail.autoAccept)); diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 089d6870..cd1d7e84 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -6,48 +6,46 @@ window.isMobile = window.iOS || window.android; window.pasteMode = {}; window.pasteMode.activated = false; -// set display name -Events.on('display-name', e => { - const me = e.detail.message; - const $displayName = $('display-name'); - $displayName.setAttribute('placeholder', me.displayName); -}); - class PeersUI { constructor() { + this.$cancelPasteModeBtn = $('cancel-paste-mode'); + this.$xPeers = $$('x-peers'); + this.$xNoPeers = $$('x-no-peers'); + this.$xInstructions = $$('x-instructions'); + this.$center = $$('#center'); + this.$footer = $$('footer'); + this.$discoveryWrapper = $$('footer .discovery-wrapper'); + this.$displayName = $('display-name'); + this.$header = $$('header.opacity-0'); + + this.evaluateHeader = ["notification", "edit-paired-devices"]; + this.fadedIn = false; + this.peers = {}; + + Events.on('display-name', e => this._onDisplayName(e.detail.displayName)); Events.on('peer-joined', e => this._onPeerJoined(e.detail)); + Events.on('peer-added', _ => this._evaluateOverflowing()); Events.on('peer-connected', e => this._onPeerConnected(e.detail.peerId, e.detail.connectionHash)); Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail)); Events.on('peers', e => this._onPeers(e.detail)); Events.on('set-progress', e => this._onSetProgress(e.detail)); - Events.on('paste', e => this._onPaste(e)); - Events.on('room-type-removed', e => this._onRoomTypeRemoved(e.detail.peerId, e.detail.roomType)); - Events.on('activate-paste-mode', e => this._activatePasteMode(e.detail.files, e.detail.text)); - this.peers = {}; - - this.$cancelPasteModeBtn = $('cancel-paste-mode'); - this.$cancelPasteModeBtn.addEventListener('click', _ => this._cancelPasteMode()); + Events.on('drop', e => this._onDrop(e)); + Events.on('keydown', e => this._onKeyDown(e)); Events.on('dragover', e => this._onDragOver(e)); Events.on('dragleave', _ => this._onDragEnd()); Events.on('dragend', _ => this._onDragEnd()); + Events.on('bg-resize', _ => this._evaluateOverflowing()); - Events.on('drop', e => this._onDrop(e)); - Events.on('keydown', e => this._onKeyDown(e)); - - this.$xPeers = $$('x-peers'); - this.$xNoPeers = $$('x-no-peers'); - this.$xInstructions = $$('x-instructions'); - this.$center = $$('#center'); - this.$footer = $$('footer'); - this.$discoveryWrapper = $$('footer .discovery-wrapper'); + Events.on('paste', e => this._onPaste(e)); + Events.on('activate-paste-mode', e => this._activatePasteMode(e.detail.files, e.detail.text)); + Events.on('room-type-removed', e => this._onRoomTypeRemoved(e.detail.peerId, e.detail.roomType)); - Events.on('peer-added', _ => this._evaluateOverflowing()); - Events.on('bg-resize', _ => this._evaluateOverflowing()); - this.$displayName = $('display-name'); + this.$cancelPasteModeBtn.addEventListener('click', _ => this._cancelPasteMode()); + // Show "Loading…" this.$displayName.setAttribute("placeholder", this.$displayName.dataset.placeholder); this.$displayName.addEventListener('keydown', e => this._onKeyDownDisplayName(e)); @@ -56,24 +54,27 @@ class PeersUI { Events.on('self-display-name-changed', e => this._insertDisplayName(e.detail)); Events.on('peer-display-name-changed', e => this._onPeerDisplayNameChanged(e)); + Events.on('evaluate-footer-badges', _ => this._evaluateFooterBadges()) + + if (!('Notification' in window)) this.evaluateHeader.splice(this.evaluateHeader.indexOf("notification"), 1); + + // wait for evaluation of notification and edit-paired-devices buttons + Events.on('header-evaluated', e => this._fadeInHeader(e.detail)); // Load saved display name on page load + Events.on('ws-connected', _ => this._loadSavedDisplayName()); + } + + _loadSavedDisplayName() { this._getSavedDisplayName().then(displayName => { console.log("Retrieved edited display name:", displayName) if (displayName) Events.fire('self-display-name-changed', displayName); }); + } - Events.on('evaluate-footer-badges', _ => this._evaluateFooterBadges()) - - this.fadedIn = false; - - this.$header = document.querySelector('header.opacity-0'); - Events.on('header-evaluated', e => this._fadeInHeader(e.detail)); - - // wait for evaluation of notification and edit-paired-devices buttons - this.evaluateHeader = ["notification", "edit-paired-devices"]; - - if (!('Notification' in window)) this.evaluateHeader.splice(this.evaluateHeader.indexOf("notification"), 1); + _onDisplayName(displayName){ + // set display name + this.$displayName.setAttribute('placeholder', displayName); } _fadeInHeader(id) { @@ -2789,8 +2790,6 @@ class BackgroundCanvas { class PairDrop { constructor() { Events.on('initial-translation-loaded', _ => { - const server = new ServerConnection(); - const peers = new PeersManager(server); const peersUI = new PeersUI(); const backgroundCanvas = new BackgroundCanvas(); const languageSelectDialog = new LanguageSelectDialog(); @@ -2809,6 +2808,8 @@ class PairDrop { const webFileHandlersUI = new WebFileHandlersUI(); const noSleepUI = new NoSleepUI(); const broadCast = new BrowserTabsConnector(); + const server = new ServerConnection(); + const peers = new PeersManager(server); }); } } diff --git a/public_included_ws_fallback/scripts/network.js b/public_included_ws_fallback/scripts/network.js index 6e680355..a133bceb 100644 --- a/public_included_ws_fallback/scripts/network.js +++ b/public_included_ws_fallback/scripts/network.js @@ -186,8 +186,8 @@ class ServerConnection { _onDisplayName(msg) { // Add peerId and peerIdHash to sessionStorage to authenticate as the same device on page reload - sessionStorage.setItem('peer_id', msg.message.peerId); - sessionStorage.setItem('peer_id_hash', msg.message.peerIdHash); + sessionStorage.setItem('peer_id', msg.peerId); + sessionStorage.setItem('peer_id_hash', msg.peerIdHash); // Add peerId to localStorage to mark it for other PairDrop tabs on the same browser BrowserTabsConnector.addPeerIdToLocalStorage().then(peerId => { @@ -953,7 +953,7 @@ class PeersManager { Events.on('secret-room-deleted', e => this._onSecretRoomDeleted(e.detail)); Events.on('room-secret-regenerated', e => this._onRoomSecretRegenerated(e.detail)); - Events.on('display-name', e => this._onDisplayName(e.detail.message.displayName)); + Events.on('display-name', e => this._onDisplayName(e.detail.displayName)); Events.on('self-display-name-changed', e => this._notifyPeersDisplayNameChanged(e.detail)); Events.on('notify-peer-display-name-changed', e => this._notifyPeerDisplayNameChanged(e.detail)); Events.on('auto-accept-updated', e => this._onAutoAcceptUpdated(e.detail.roomSecret, e.detail.autoAccept)); diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index 9fe21c71..ad8042c8 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -6,48 +6,46 @@ window.isMobile = window.iOS || window.android; window.pasteMode = {}; window.pasteMode.activated = false; -// set display name -Events.on('display-name', e => { - const me = e.detail.message; - const $displayName = $('display-name'); - $displayName.setAttribute('placeholder', me.displayName); -}); - class PeersUI { constructor() { + this.$cancelPasteModeBtn = $('cancel-paste-mode'); + this.$xPeers = $$('x-peers'); + this.$xNoPeers = $$('x-no-peers'); + this.$xInstructions = $$('x-instructions'); + this.$center = $$('#center'); + this.$footer = $$('footer'); + this.$discoveryWrapper = $$('footer .discovery-wrapper'); + this.$displayName = $('display-name'); + this.$header = $$('header.opacity-0'); + + this.evaluateHeader = ["notification", "edit-paired-devices"]; + this.fadedIn = false; + this.peers = {}; + + Events.on('display-name', e => this._onDisplayName(e.detail.displayName)); Events.on('peer-joined', e => this._onPeerJoined(e.detail)); + Events.on('peer-added', _ => this._evaluateOverflowing()); Events.on('peer-connected', e => this._onPeerConnected(e.detail.peerId, e.detail.connectionHash)); Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail)); Events.on('peers', e => this._onPeers(e.detail)); Events.on('set-progress', e => this._onSetProgress(e.detail)); - Events.on('paste', e => this._onPaste(e)); - Events.on('room-type-removed', e => this._onRoomTypeRemoved(e.detail.peerId, e.detail.roomType)); - Events.on('activate-paste-mode', e => this._activatePasteMode(e.detail.files, e.detail.text)); - this.peers = {}; - - this.$cancelPasteModeBtn = $('cancel-paste-mode'); - this.$cancelPasteModeBtn.addEventListener('click', _ => this._cancelPasteMode()); + Events.on('drop', e => this._onDrop(e)); + Events.on('keydown', e => this._onKeyDown(e)); Events.on('dragover', e => this._onDragOver(e)); Events.on('dragleave', _ => this._onDragEnd()); Events.on('dragend', _ => this._onDragEnd()); + Events.on('bg-resize', _ => this._evaluateOverflowing()); - Events.on('drop', e => this._onDrop(e)); - Events.on('keydown', e => this._onKeyDown(e)); - - this.$xPeers = $$('x-peers'); - this.$xNoPeers = $$('x-no-peers'); - this.$xInstructions = $$('x-instructions'); - this.$center = $$('#center'); - this.$footer = $$('footer'); - this.$discoveryWrapper = $$('footer .discovery-wrapper'); + Events.on('paste', e => this._onPaste(e)); + Events.on('activate-paste-mode', e => this._activatePasteMode(e.detail.files, e.detail.text)); + Events.on('room-type-removed', e => this._onRoomTypeRemoved(e.detail.peerId, e.detail.roomType)); - Events.on('peer-added', _ => this._evaluateOverflowing()); - Events.on('bg-resize', _ => this._evaluateOverflowing()); - this.$displayName = $('display-name'); + this.$cancelPasteModeBtn.addEventListener('click', _ => this._cancelPasteMode()); + // Show "Loading…" this.$displayName.setAttribute("placeholder", this.$displayName.dataset.placeholder); this.$displayName.addEventListener('keydown', e => this._onKeyDownDisplayName(e)); @@ -56,24 +54,27 @@ class PeersUI { Events.on('self-display-name-changed', e => this._insertDisplayName(e.detail)); Events.on('peer-display-name-changed', e => this._onPeerDisplayNameChanged(e)); + Events.on('evaluate-footer-badges', _ => this._evaluateFooterBadges()) + + if (!('Notification' in window)) this.evaluateHeader.splice(this.evaluateHeader.indexOf("notification"), 1); + + // wait for evaluation of notification and edit-paired-devices buttons + Events.on('header-evaluated', e => this._fadeInHeader(e.detail)); // Load saved display name on page load + Events.on('ws-connected', _ => this._loadSavedDisplayName()); + } + + _loadSavedDisplayName() { this._getSavedDisplayName().then(displayName => { console.log("Retrieved edited display name:", displayName) if (displayName) Events.fire('self-display-name-changed', displayName); }); + } - Events.on('evaluate-footer-badges', _ => this._evaluateFooterBadges()) - - this.fadedIn = false; - - this.$header = document.querySelector('header.opacity-0'); - Events.on('header-evaluated', e => this._fadeInHeader(e.detail)); - - // wait for evaluation of notification and edit-paired-devices buttons - this.evaluateHeader = ["notification", "edit-paired-devices"]; - - if (!('Notification' in window)) this.evaluateHeader.splice(this.evaluateHeader.indexOf("notification"), 1); + _onDisplayName(displayName){ + // set display name + this.$displayName.setAttribute('placeholder', displayName); } _fadeInHeader(id) { @@ -2791,8 +2792,6 @@ class BackgroundCanvas { class PairDrop { constructor() { Events.on('initial-translation-loaded', _ => { - const server = new ServerConnection(); - const peers = new PeersManager(server); const peersUI = new PeersUI(); const backgroundCanvas = new BackgroundCanvas(); const languageSelectDialog = new LanguageSelectDialog(); @@ -2811,6 +2810,8 @@ class PairDrop { const webFileHandlersUI = new WebFileHandlersUI(); const noSleepUI = new NoSleepUI(); const broadCast = new BrowserTabsConnector(); + const server = new ServerConnection(); + const peers = new PeersManager(server); }); } } From d84c7d1f84b33953e54e39777e3e5c09085eb1bf Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Tue, 31 Oct 2023 19:08:20 +0100 Subject: [PATCH 04/97] Tidy up Javascript files --- public/index.html | 2 +- public/scripts/network.js | 34 ++++++++++++------ public/scripts/ui.js | 2 -- public/scripts/util.js | 3 ++ public_included_ws_fallback/index.html | 2 +- .../scripts/network.js | 36 +++++++++++++------ public_included_ws_fallback/scripts/ui.js | 2 -- public_included_ws_fallback/scripts/util.js | 3 ++ 8 files changed, 56 insertions(+), 28 deletions(-) diff --git a/public/index.html b/public/index.html index 1cdbf571..20c78623 100644 --- a/public/index.html +++ b/public/index.html @@ -584,11 +584,11 @@

PairDrop

+ - diff --git a/public/scripts/network.js b/public/scripts/network.js index 5af4a21e..fa60c917 100644 --- a/public/scripts/network.js +++ b/public/scripts/network.js @@ -3,22 +3,32 @@ window.isRtcSupported = !!(window.RTCPeerConnection || window.mozRTCPeerConnecti if (!window.isRtcSupported) alert("WebRTC must be enabled for PairDrop to work"); -window.hiddenProperty = 'hidden' in document ? 'hidden' : - 'webkitHidden' in document ? 'webkitHidden' : - 'mozHidden' in document ? 'mozHidden' : - null; -window.visibilityChangeEvent = 'visibilitychange' in document ? 'visibilitychange' : - 'webkitvisibilitychange' in document ? 'webkitvisibilitychange' : - 'mozvisibilitychange' in document ? 'mozvisibilitychange' : - null; +window.hiddenProperty = 'hidden' in document + ? 'hidden' + : 'webkitHidden' in document + ? 'webkitHidden' + : 'mozHidden' in document + ? 'mozHidden' + : null; + +window.visibilityChangeEvent = 'visibilitychange' in document + ? 'visibilitychange' + : 'webkitvisibilitychange' in document + ? 'webkitvisibilitychange' + : 'mozvisibilitychange' in document + ? 'mozvisibilitychange' + : null; class ServerConnection { constructor() { - this._connect(); Events.on('pagehide', _ => this._disconnect()); - document.addEventListener(window.visibilityChangeEvent, _ => this._onVisibilityChange()); - if (navigator.connection) navigator.connection.addEventListener('change', _ => this._reconnect()); + Events.on(window.visibilityChangeEvent, _ => this._onVisibilityChange()); + + if (navigator.connection) { + navigator.connection.addEventListener('change', _ => this._reconnect()); + } + Events.on('room-secrets', e => this.send({ type: 'room-secrets', roomSecrets: e.detail })); Events.on('join-ip-room', e => this.send({ type: 'join-ip-room'})); Events.on('room-secrets-deleted', e => this.send({ type: 'room-secrets-deleted', roomSecrets: e.detail})); @@ -33,6 +43,8 @@ class ServerConnection { Events.on('offline', _ => clearTimeout(this._reconnectTimer)); Events.on('online', _ => this._connect()); + + this._connect(); } _connect() { diff --git a/public/scripts/ui.js b/public/scripts/ui.js index cd1d7e84..9874529a 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -1,5 +1,3 @@ -const $ = query => document.getElementById(query); -const $$ = query => document.body.querySelector(query); window.iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; window.android = /android/i.test(navigator.userAgent); window.isMobile = window.iOS || window.android; diff --git a/public/scripts/util.js b/public/scripts/util.js index eada792d..921094ba 100644 --- a/public/scripts/util.js +++ b/public/scripts/util.js @@ -37,6 +37,9 @@ if (!navigator.clipboard) { } } +const $ = query => document.getElementById(query); +const $$ = query => document.querySelector(query); + const zipper = (() => { let zipWriter; diff --git a/public_included_ws_fallback/index.html b/public_included_ws_fallback/index.html index fd2dbb31..449a9f97 100644 --- a/public_included_ws_fallback/index.html +++ b/public_included_ws_fallback/index.html @@ -589,11 +589,11 @@

PairDrop

+ - diff --git a/public_included_ws_fallback/scripts/network.js b/public_included_ws_fallback/scripts/network.js index a133bceb..c8157419 100644 --- a/public_included_ws_fallback/scripts/network.js +++ b/public_included_ws_fallback/scripts/network.js @@ -1,22 +1,34 @@ window.URL = window.URL || window.webkitURL; window.isRtcSupported = !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection); -window.hiddenProperty = 'hidden' in document ? 'hidden' : - 'webkitHidden' in document ? 'webkitHidden' : - 'mozHidden' in document ? 'mozHidden' : - null; -window.visibilityChangeEvent = 'visibilitychange' in document ? 'visibilitychange' : - 'webkitvisibilitychange' in document ? 'webkitvisibilitychange' : - 'mozvisibilitychange' in document ? 'mozvisibilitychange' : - null; +if (!window.isRtcSupported) alert("WebRTC must be enabled for PairDrop to work"); + +window.hiddenProperty = 'hidden' in document + ? 'hidden' + : 'webkitHidden' in document + ? 'webkitHidden' + : 'mozHidden' in document + ? 'mozHidden' + : null; + +window.visibilityChangeEvent = 'visibilitychange' in document + ? 'visibilitychange' + : 'webkitvisibilitychange' in document + ? 'webkitvisibilitychange' + : 'mozvisibilitychange' in document + ? 'mozvisibilitychange' + : null; class ServerConnection { constructor() { - this._connect(); Events.on('pagehide', _ => this._disconnect()); - document.addEventListener(window.visibilityChangeEvent, _ => this._onVisibilityChange()); - if (navigator.connection) navigator.connection.addEventListener('change', _ => this._reconnect()); + Events.on(window.visibilityChangeEvent, _ => this._onVisibilityChange()); + + if (navigator.connection) { + navigator.connection.addEventListener('change', _ => this._reconnect()); + } + Events.on('room-secrets', e => this.send({ type: 'room-secrets', roomSecrets: e.detail })); Events.on('join-ip-room', e => this.send({ type: 'join-ip-room'})); Events.on('room-secrets-deleted', e => this.send({ type: 'room-secrets-deleted', roomSecrets: e.detail})); @@ -31,6 +43,8 @@ class ServerConnection { Events.on('offline', _ => clearTimeout(this._reconnectTimer)); Events.on('online', _ => this._connect()); + + this._connect(); } _connect() { diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index ad8042c8..e6e5b81c 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -1,5 +1,3 @@ -const $ = query => document.getElementById(query); -const $$ = query => document.body.querySelector(query); window.iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; window.android = /android/i.test(navigator.userAgent); window.isMobile = window.iOS || window.android; diff --git a/public_included_ws_fallback/scripts/util.js b/public_included_ws_fallback/scripts/util.js index 00d3883f..740325bf 100644 --- a/public_included_ws_fallback/scripts/util.js +++ b/public_included_ws_fallback/scripts/util.js @@ -37,6 +37,9 @@ if (!navigator.clipboard) { } } +const $ = query => document.getElementById(query); +const $$ = query => document.querySelector(query); + const zipper = (() => { let zipWriter; From d388f7e3cd7b6c970cb6b7c9e1cd25959875e3d4 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 2 Nov 2023 01:22:41 +0100 Subject: [PATCH 05/97] Partly replace `_ =>` with `() =>` globally to conform with Googles JavaScript style guide if no parameters are expected in arrow functions --- index.js | 2 +- public/scripts/network.js | 12 ++-- public/scripts/ui.js | 70 +++++++++---------- public/service-worker.js | 4 +- .../scripts/network.js | 12 ++-- public_included_ws_fallback/scripts/ui.js | 70 +++++++++---------- public_included_ws_fallback/service-worker.js | 4 +- 7 files changed, 87 insertions(+), 87 deletions(-) diff --git a/index.js b/index.js index 02b3c5f9..f894c042 100644 --- a/index.js +++ b/index.js @@ -663,7 +663,7 @@ class Peer { return true; } this.requestRate += 1; - setTimeout(_ => this.requestRate -= 1, 10000); + setTimeout(() => this.requestRate -= 1, 10000); return false; } diff --git a/public/scripts/network.js b/public/scripts/network.js index fa60c917..53bff2f2 100644 --- a/public/scripts/network.js +++ b/public/scripts/network.js @@ -30,7 +30,7 @@ class ServerConnection { } Events.on('room-secrets', e => this.send({ type: 'room-secrets', roomSecrets: e.detail })); - Events.on('join-ip-room', e => this.send({ type: 'join-ip-room'})); + Events.on('join-ip-room', _ => this.send({ type: 'join-ip-room'})); Events.on('room-secrets-deleted', e => this.send({ type: 'room-secrets-deleted', roomSecrets: e.detail})); Events.on('regenerate-room-secret', e => this.send({ type: 'regenerate-room-secret', roomSecret: e.detail})); Events.on('pair-device-initiate', _ => this._onPairDeviceInitiate()); @@ -81,7 +81,7 @@ class ServerConnection { _onPairDeviceJoin(pairKey) { if (!this._isConnected()) { - setTimeout(_ => this._onPairDeviceJoin(pairKey), 1000); + setTimeout(() => this._onPairDeviceJoin(pairKey), 1000); return; } this.send({ type: 'pair-device-join', pairKey: pairKey }); @@ -97,7 +97,7 @@ class ServerConnection { _onJoinPublicRoom(roomId, createIfInvalid) { if (!this._isConnected()) { - setTimeout(_ => this._onJoinPublicRoom(roomId), 1000); + setTimeout(() => this._onJoinPublicRoom(roomId), 1000); return; } this.send({ type: 'join-public-room', publicRoomId: roomId, createIfInvalid: createIfInvalid }); @@ -105,7 +105,7 @@ class ServerConnection { _onLeavePublicRoom() { if (!this._isConnected()) { - setTimeout(_ => this._onLeavePublicRoom(), 1000); + setTimeout(() => this._onLeavePublicRoom(), 1000); return; } this.send({ type: 'leave-public-room' }); @@ -241,7 +241,7 @@ class ServerConnection { setTimeout(() => { this._isReconnect = true; Events.fire('ws-disconnected'); - this._reconnectTimer = setTimeout(_ => this._connect(), 1000); + this._reconnectTimer = setTimeout(() => this._connect(), 1000); }, 100); //delay for 100ms to prevent flickering on page reload } @@ -742,7 +742,7 @@ class RTCPeer extends Peer { if (message.sdp) { this._conn.setRemoteDescription(message.sdp) - .then( _ => { + .then(_ => { if (message.sdp.type === 'offer') { return this._conn.createAnswer() .then(d => this._onDescription(d)); diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 9874529a..079494b5 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -93,7 +93,7 @@ class PeersUI { this.$footer.classList.remove('opacity-0'); // Prevent flickering on load - setTimeout(_ => { + setTimeout(() => { this.$xNoPeers.classList.remove('no-animation-on-load'); }, 600); @@ -143,7 +143,7 @@ class PeersUI { localStorage.setItem('editedDisplayName', newDisplayName); Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-temporarily")); }) - .finally(_ => { + .finally(() => { Events.fire('self-display-name-changed', newDisplayName); Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: newDisplayName}); }); @@ -153,7 +153,7 @@ class PeersUI { console.log("This browser does not support IndexedDB. Use localStorage instead.") localStorage.removeItem('editedDisplayName'); }) - .finally(_ => { + .finally(() => { Events.fire('notify-user', Localization.getTranslation("notifications.display-name-random-again")); Events.fire('self-display-name-changed', ''); Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: ''}); @@ -212,7 +212,7 @@ class PeersUI { return; } - peer._isSameBrowser = _ => BrowserTabsConnector.peerIsSameBrowser(peer.id); + peer._isSameBrowser = () => BrowserTabsConnector.peerIsSameBrowser(peer.id); peer._roomIds = {}; peer._roomIds[roomType] = roomId; @@ -621,7 +621,7 @@ class PeerUI { _onTouchStart(e) { this._touchStart = Date.now(); - this._touchTimer = setTimeout(_ => this._onTouchEnd(e), 610); + this._touchTimer = setTimeout(() => this._onTouchEnd(e), 610); } _onTouchEnd(e) { @@ -827,7 +827,7 @@ class ReceiveFileDialog extends ReceiveDialog { return; } // dequeue next file - setTimeout(_ => { + setTimeout(() => { this._busy = false; this._nextFiles(); }, 300); @@ -946,7 +946,7 @@ class ReceiveFileDialog extends ReceiveDialog { } Events.fire('notify-user', Localization.getTranslation("notifications.download-successful", null, {descriptor: descriptor})); this.$downloadBtn.style.pointerEvents = "none"; - setTimeout(_ => this.$downloadBtn.style.pointerEvents = "unset", 2000); + setTimeout(() => this.$downloadBtn.style.pointerEvents = "unset", 2000); }; document.title = files.length === 1 @@ -957,7 +957,7 @@ class ReceiveFileDialog extends ReceiveDialog { Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'}) this.show(); - setTimeout(_ => { + setTimeout(() => { if (canShare) { this.$shareBtn.click(); } else { @@ -1067,12 +1067,12 @@ class ReceiveRequestDialog extends ReceiveDialog { hide() { // clear previewBox after dialog is closed - setTimeout(_ => this.$previewBox.innerHTML = '', 300); + setTimeout(() => this.$previewBox.innerHTML = '', 300); super.hide(); // show next request - setTimeout(_ => this._dequeueRequests(), 500); + setTimeout(() => this._dequeueRequests(), 500); } } @@ -1235,7 +1235,7 @@ class PairDeviceDialog extends Dialog { _onKeyDown(e) { if (this.isShown() && e.code === "Escape") { // Timeout to prevent paste mode from getting cancelled simultaneously - setTimeout(_ => this._close(), 50); + setTimeout(() => this._close(), 50); } } @@ -1369,7 +1369,7 @@ class PairDeviceDialog extends Dialog { Events.fire('notify-user', Localization.getTranslation("notifications.pairing-success")); this._evaluateNumberRoomSecrets(); }) - .finally(_ => { + .finally(() => { this._cleanUp(); this.hide(); }) @@ -1493,7 +1493,7 @@ class EditPairedDevicesDialog extends Dialog { hide() { super.hide(); - setTimeout(_ => { + setTimeout(() => { this.$pairedDevicesWrapper.innerHTML = "" }, 300); } @@ -1505,7 +1505,7 @@ class EditPairedDevicesDialog extends Dialog { _clearRoomSecrets() { PersistentStorage.getAllRoomSecrets() .then(roomSecrets => { - PersistentStorage.clearRoomSecrets().finally(_ => { + PersistentStorage.clearRoomSecrets().finally(() => { Events.fire('room-secrets-deleted', roomSecrets); Events.fire('evaluate-number-room-secrets'); Events.fire('notify-user', Localization.getTranslation("notifications.pairing-cleared")); @@ -1915,7 +1915,7 @@ class ReceiveTextDialog extends Dialog { hide() { super.hide(); - setTimeout(_ => this._dequeueRequests(), 500); + setTimeout(() => this._dequeueRequests(), 500); } } @@ -1944,7 +1944,7 @@ class Base64ZipDialog extends Dialog { .catch(_ => { Events.fire('notify-user', Localization.getTranslation("notifications.text-content-incorrect")); console.log("Text content incorrect."); - }).finally(_ => { + }).finally(() => { this.hide(); }); } else { @@ -1954,7 +1954,7 @@ class Base64ZipDialog extends Dialog { .catch(_ => { Events.fire('notify-user', Localization.getTranslation("notifications.text-content-incorrect")); console.log("Text content incorrect."); - }).finally(_ => { + }).finally(() => { this.hide(); }); } @@ -1967,7 +1967,7 @@ class Base64ZipDialog extends Dialog { .catch(_ => { Events.fire('notify-user', Localization.getTranslation("notifications.file-content-incorrect")); console.log("File content incorrect."); - }).finally(_ => { + }).finally(() => { this.hide(); }); } else { @@ -2354,7 +2354,7 @@ class NoSleepUI { static enable() { if (!this._interval) { NoSleepUI._nosleep.enable(); - NoSleepUI._interval = setInterval(_ => NoSleepUI.disable(), 10000); + NoSleepUI._interval = setInterval(() => NoSleepUI.disable(), 10000); } } @@ -2373,15 +2373,15 @@ class PersistentStorage { return; } const DBOpenRequest = window.indexedDB.open('pairdrop_store', 4); - DBOpenRequest.onerror = (e) => { + DBOpenRequest.onerror = e => { PersistentStorage.logBrowserNotCapable(); console.log('Error initializing database: '); console.log(e) }; - DBOpenRequest.onsuccess = () => { + DBOpenRequest.onsuccess = _ => { console.log('Database initialised.'); }; - DBOpenRequest.onupgradeneeded = (e) => { + DBOpenRequest.onupgradeneeded = e => { const db = e.target.result; const txn = e.target.transaction; @@ -2420,7 +2420,7 @@ class PersistentStorage { static set(key, value) { return new Promise((resolve, reject) => { const DBOpenRequest = window.indexedDB.open('pairdrop_store'); - DBOpenRequest.onsuccess = (e) => { + DBOpenRequest.onsuccess = e => { const db = e.target.result; const transaction = db.transaction('keyval', 'readwrite'); const objectStore = transaction.objectStore('keyval'); @@ -2430,7 +2430,7 @@ class PersistentStorage { resolve(value); }; } - DBOpenRequest.onerror = (e) => { + DBOpenRequest.onerror = e => { reject(e); } }) @@ -2439,7 +2439,7 @@ class PersistentStorage { static get(key) { return new Promise((resolve, reject) => { const DBOpenRequest = window.indexedDB.open('pairdrop_store'); - DBOpenRequest.onsuccess = (e) => { + DBOpenRequest.onsuccess = e => { const db = e.target.result; const transaction = db.transaction('keyval', 'readonly'); const objectStore = transaction.objectStore('keyval'); @@ -2449,7 +2449,7 @@ class PersistentStorage { resolve(objectStoreRequest.result); } } - DBOpenRequest.onerror = (e) => { + DBOpenRequest.onerror = e => { reject(e); } }); @@ -2458,7 +2458,7 @@ class PersistentStorage { static delete(key) { return new Promise((resolve, reject) => { const DBOpenRequest = window.indexedDB.open('pairdrop_store'); - DBOpenRequest.onsuccess = (e) => { + DBOpenRequest.onsuccess = e => { const db = e.target.result; const transaction = db.transaction('keyval', 'readwrite'); const objectStore = transaction.objectStore('keyval'); @@ -2468,7 +2468,7 @@ class PersistentStorage { resolve(); }; } - DBOpenRequest.onerror = (e) => { + DBOpenRequest.onerror = e => { reject(e); } }) @@ -2477,7 +2477,7 @@ class PersistentStorage { static addRoomSecret(roomSecret, displayName, deviceName) { return new Promise((resolve, reject) => { const DBOpenRequest = window.indexedDB.open('pairdrop_store'); - DBOpenRequest.onsuccess = (e) => { + DBOpenRequest.onsuccess = e => { const db = e.target.result; const transaction = db.transaction('room_secrets', 'readwrite'); const objectStore = transaction.objectStore('room_secrets'); @@ -2492,7 +2492,7 @@ class PersistentStorage { resolve(); } } - DBOpenRequest.onerror = (e) => { + DBOpenRequest.onerror = e => { reject(e); } }) @@ -2534,7 +2534,7 @@ class PersistentStorage { static getRoomSecretEntry(roomSecret) { return new Promise((resolve, reject) => { const DBOpenRequest = window.indexedDB.open('pairdrop_store'); - DBOpenRequest.onsuccess = (e) => { + DBOpenRequest.onsuccess = e => { const db = e.target.result; const transaction = db.transaction('room_secrets', 'readonly'); const objectStore = transaction.objectStore('room_secrets'); @@ -2585,12 +2585,12 @@ class PersistentStorage { console.log(`Request successful. Deleted room_secret: ${key}`); resolve(roomSecret); } - objectStoreRequestDeletion.onerror = (e) => { + objectStoreRequestDeletion.onerror = e => { reject(e); } }; } - DBOpenRequest.onerror = (e) => { + DBOpenRequest.onerror = e => { reject(e); } }) @@ -2609,7 +2609,7 @@ class PersistentStorage { resolve(); }; } - DBOpenRequest.onerror = (e) => { + DBOpenRequest.onerror = e => { reject(e); } }) @@ -2626,7 +2626,7 @@ class PersistentStorage { static updateRoomSecret(roomSecret, updatedRoomSecret = undefined, updatedDisplayName = undefined, updatedDeviceName = undefined, updatedAutoAccept = undefined) { return new Promise((resolve, reject) => { const DBOpenRequest = window.indexedDB.open('pairdrop_store'); - DBOpenRequest.onsuccess = (e) => { + DBOpenRequest.onsuccess = e => { const db = e.target.result; this.getRoomSecretEntry(roomSecret) .then(roomSecretEntry => { diff --git a/public/service-worker.js b/public/service-worker.js index f84d49dd..81a1b5f5 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -56,7 +56,7 @@ const fromNetwork = (request, timeout) => fetch(request).then(response => { clearTimeout(timeoutId); fulfill(response); - update(request).then(() => console.log("Cache successfully updated for", request.url)); + update(request).then(_ => console.log("Cache successfully updated for", request.url)); }, reject); }); @@ -77,7 +77,7 @@ const update = request => .then(async response => { await cache.put(request, response); }) - .catch(() => console.log(`Cache could not be updated. ${request.url}`)) + .catch(_ => console.log(`Cache could not be updated. ${request.url}`)) ); // general strategy when making a request (eg if online try to fetch it diff --git a/public_included_ws_fallback/scripts/network.js b/public_included_ws_fallback/scripts/network.js index c8157419..5a279a15 100644 --- a/public_included_ws_fallback/scripts/network.js +++ b/public_included_ws_fallback/scripts/network.js @@ -30,7 +30,7 @@ class ServerConnection { } Events.on('room-secrets', e => this.send({ type: 'room-secrets', roomSecrets: e.detail })); - Events.on('join-ip-room', e => this.send({ type: 'join-ip-room'})); + Events.on('join-ip-room', _ => this.send({ type: 'join-ip-room'})); Events.on('room-secrets-deleted', e => this.send({ type: 'room-secrets-deleted', roomSecrets: e.detail})); Events.on('regenerate-room-secret', e => this.send({ type: 'regenerate-room-secret', roomSecret: e.detail})); Events.on('pair-device-initiate', _ => this._onPairDeviceInitiate()); @@ -81,7 +81,7 @@ class ServerConnection { _onPairDeviceJoin(pairKey) { if (!this._isConnected()) { - setTimeout(_ => this._onPairDeviceJoin(pairKey), 1000); + setTimeout(() => this._onPairDeviceJoin(pairKey), 1000); return; } this.send({ type: 'pair-device-join', pairKey: pairKey }); @@ -97,7 +97,7 @@ class ServerConnection { _onJoinPublicRoom(roomId, createIfInvalid) { if (!this._isConnected()) { - setTimeout(_ => this._onJoinPublicRoom(roomId), 1000); + setTimeout(() => this._onJoinPublicRoom(roomId), 1000); return; } this.send({ type: 'join-public-room', publicRoomId: roomId, createIfInvalid: createIfInvalid }); @@ -105,7 +105,7 @@ class ServerConnection { _onLeavePublicRoom() { if (!this._isConnected()) { - setTimeout(_ => this._onLeavePublicRoom(), 1000); + setTimeout(() => this._onLeavePublicRoom(), 1000); return; } this.send({ type: 'leave-public-room' }); @@ -254,7 +254,7 @@ class ServerConnection { setTimeout(() => { this._isReconnect = true; Events.fire('ws-disconnected'); - this._reconnectTimer = setTimeout(_ => this._connect(), 1000); + this._reconnectTimer = setTimeout(() => this._connect(), 1000); }, 100); //delay for 100ms to prevent flickering on page reload } @@ -755,7 +755,7 @@ class RTCPeer extends Peer { if (message.sdp) { this._conn.setRemoteDescription(message.sdp) - .then( _ => { + .then(_ => { if (message.sdp.type === 'offer') { return this._conn.createAnswer() .then(d => this._onDescription(d)); diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index e6e5b81c..98fb5628 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -93,7 +93,7 @@ class PeersUI { this.$footer.classList.remove('opacity-0'); // Prevent flickering on load - setTimeout(_ => { + setTimeout(() => { this.$xNoPeers.classList.remove('no-animation-on-load'); }, 600); @@ -143,7 +143,7 @@ class PeersUI { localStorage.setItem('editedDisplayName', newDisplayName); Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-temporarily")); }) - .finally(_ => { + .finally(() => { Events.fire('self-display-name-changed', newDisplayName); Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: newDisplayName}); }); @@ -153,7 +153,7 @@ class PeersUI { console.log("This browser does not support IndexedDB. Use localStorage instead.") localStorage.removeItem('editedDisplayName'); }) - .finally(_ => { + .finally(() => { Events.fire('notify-user', Localization.getTranslation("notifications.display-name-random-again")); Events.fire('self-display-name-changed', ''); Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: ''}); @@ -212,7 +212,7 @@ class PeersUI { return; } - peer._isSameBrowser = _ => BrowserTabsConnector.peerIsSameBrowser(peer.id); + peer._isSameBrowser = () => BrowserTabsConnector.peerIsSameBrowser(peer.id); peer._roomIds = {}; peer._roomIds[roomType] = roomId; @@ -623,7 +623,7 @@ class PeerUI { _onTouchStart(e) { this._touchStart = Date.now(); - this._touchTimer = setTimeout(_ => this._onTouchEnd(e), 610); + this._touchTimer = setTimeout(() => this._onTouchEnd(e), 610); } _onTouchEnd(e) { @@ -829,7 +829,7 @@ class ReceiveFileDialog extends ReceiveDialog { return; } // dequeue next file - setTimeout(_ => { + setTimeout(() => { this._busy = false; this._nextFiles(); }, 300); @@ -948,7 +948,7 @@ class ReceiveFileDialog extends ReceiveDialog { } Events.fire('notify-user', Localization.getTranslation("notifications.download-successful", null, {descriptor: descriptor})); this.$downloadBtn.style.pointerEvents = "none"; - setTimeout(_ => this.$downloadBtn.style.pointerEvents = "unset", 2000); + setTimeout(() => this.$downloadBtn.style.pointerEvents = "unset", 2000); }; document.title = files.length === 1 @@ -959,7 +959,7 @@ class ReceiveFileDialog extends ReceiveDialog { Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'}) this.show(); - setTimeout(_ => { + setTimeout(() => { if (canShare) { this.$shareBtn.click(); } else { @@ -1069,12 +1069,12 @@ class ReceiveRequestDialog extends ReceiveDialog { hide() { // clear previewBox after dialog is closed - setTimeout(_ => this.$previewBox.innerHTML = '', 300); + setTimeout(() => this.$previewBox.innerHTML = '', 300); super.hide(); // show next request - setTimeout(_ => this._dequeueRequests(), 500); + setTimeout(() => this._dequeueRequests(), 500); } } @@ -1237,7 +1237,7 @@ class PairDeviceDialog extends Dialog { _onKeyDown(e) { if (this.isShown() && e.code === "Escape") { // Timeout to prevent paste mode from getting cancelled simultaneously - setTimeout(_ => this._close(), 50); + setTimeout(() => this._close(), 50); } } @@ -1371,7 +1371,7 @@ class PairDeviceDialog extends Dialog { Events.fire('notify-user', Localization.getTranslation("notifications.pairing-success")); this._evaluateNumberRoomSecrets(); }) - .finally(_ => { + .finally(() => { this._cleanUp(); this.hide(); }) @@ -1495,7 +1495,7 @@ class EditPairedDevicesDialog extends Dialog { hide() { super.hide(); - setTimeout(_ => { + setTimeout(() => { this.$pairedDevicesWrapper.innerHTML = "" }, 300); } @@ -1507,7 +1507,7 @@ class EditPairedDevicesDialog extends Dialog { _clearRoomSecrets() { PersistentStorage.getAllRoomSecrets() .then(roomSecrets => { - PersistentStorage.clearRoomSecrets().finally(_ => { + PersistentStorage.clearRoomSecrets().finally(() => { Events.fire('room-secrets-deleted', roomSecrets); Events.fire('evaluate-number-room-secrets'); Events.fire('notify-user', Localization.getTranslation("notifications.pairing-cleared")); @@ -1917,7 +1917,7 @@ class ReceiveTextDialog extends Dialog { hide() { super.hide(); - setTimeout(_ => this._dequeueRequests(), 500); + setTimeout(() => this._dequeueRequests(), 500); } } @@ -1946,7 +1946,7 @@ class Base64ZipDialog extends Dialog { .catch(_ => { Events.fire('notify-user', Localization.getTranslation("notifications.text-content-incorrect")); console.log("Text content incorrect."); - }).finally(_ => { + }).finally(() => { this.hide(); }); } else { @@ -1956,7 +1956,7 @@ class Base64ZipDialog extends Dialog { .catch(_ => { Events.fire('notify-user', Localization.getTranslation("notifications.text-content-incorrect")); console.log("Text content incorrect."); - }).finally(_ => { + }).finally(() => { this.hide(); }); } @@ -1969,7 +1969,7 @@ class Base64ZipDialog extends Dialog { .catch(_ => { Events.fire('notify-user', Localization.getTranslation("notifications.file-content-incorrect")); console.log("File content incorrect."); - }).finally(_ => { + }).finally(() => { this.hide(); }); } else { @@ -2356,7 +2356,7 @@ class NoSleepUI { static enable() { if (!this._interval) { NoSleepUI._nosleep.enable(); - NoSleepUI._interval = setInterval(_ => NoSleepUI.disable(), 10000); + NoSleepUI._interval = setInterval(() => NoSleepUI.disable(), 10000); } } @@ -2375,15 +2375,15 @@ class PersistentStorage { return; } const DBOpenRequest = window.indexedDB.open('pairdrop_store', 4); - DBOpenRequest.onerror = (e) => { + DBOpenRequest.onerror = e => { PersistentStorage.logBrowserNotCapable(); console.log('Error initializing database: '); console.log(e) }; - DBOpenRequest.onsuccess = () => { + DBOpenRequest.onsuccess = _ => { console.log('Database initialised.'); }; - DBOpenRequest.onupgradeneeded = (e) => { + DBOpenRequest.onupgradeneeded = e => { const db = e.target.result; const txn = e.target.transaction; @@ -2422,7 +2422,7 @@ class PersistentStorage { static set(key, value) { return new Promise((resolve, reject) => { const DBOpenRequest = window.indexedDB.open('pairdrop_store'); - DBOpenRequest.onsuccess = (e) => { + DBOpenRequest.onsuccess = e => { const db = e.target.result; const transaction = db.transaction('keyval', 'readwrite'); const objectStore = transaction.objectStore('keyval'); @@ -2432,7 +2432,7 @@ class PersistentStorage { resolve(value); }; } - DBOpenRequest.onerror = (e) => { + DBOpenRequest.onerror = e => { reject(e); } }) @@ -2441,7 +2441,7 @@ class PersistentStorage { static get(key) { return new Promise((resolve, reject) => { const DBOpenRequest = window.indexedDB.open('pairdrop_store'); - DBOpenRequest.onsuccess = (e) => { + DBOpenRequest.onsuccess = e => { const db = e.target.result; const transaction = db.transaction('keyval', 'readonly'); const objectStore = transaction.objectStore('keyval'); @@ -2451,7 +2451,7 @@ class PersistentStorage { resolve(objectStoreRequest.result); } } - DBOpenRequest.onerror = (e) => { + DBOpenRequest.onerror = e => { reject(e); } }); @@ -2460,7 +2460,7 @@ class PersistentStorage { static delete(key) { return new Promise((resolve, reject) => { const DBOpenRequest = window.indexedDB.open('pairdrop_store'); - DBOpenRequest.onsuccess = (e) => { + DBOpenRequest.onsuccess = e => { const db = e.target.result; const transaction = db.transaction('keyval', 'readwrite'); const objectStore = transaction.objectStore('keyval'); @@ -2470,7 +2470,7 @@ class PersistentStorage { resolve(); }; } - DBOpenRequest.onerror = (e) => { + DBOpenRequest.onerror = e => { reject(e); } }) @@ -2479,7 +2479,7 @@ class PersistentStorage { static addRoomSecret(roomSecret, displayName, deviceName) { return new Promise((resolve, reject) => { const DBOpenRequest = window.indexedDB.open('pairdrop_store'); - DBOpenRequest.onsuccess = (e) => { + DBOpenRequest.onsuccess = e => { const db = e.target.result; const transaction = db.transaction('room_secrets', 'readwrite'); const objectStore = transaction.objectStore('room_secrets'); @@ -2494,7 +2494,7 @@ class PersistentStorage { resolve(); } } - DBOpenRequest.onerror = (e) => { + DBOpenRequest.onerror = e => { reject(e); } }) @@ -2536,7 +2536,7 @@ class PersistentStorage { static getRoomSecretEntry(roomSecret) { return new Promise((resolve, reject) => { const DBOpenRequest = window.indexedDB.open('pairdrop_store'); - DBOpenRequest.onsuccess = (e) => { + DBOpenRequest.onsuccess = e => { const db = e.target.result; const transaction = db.transaction('room_secrets', 'readonly'); const objectStore = transaction.objectStore('room_secrets'); @@ -2587,12 +2587,12 @@ class PersistentStorage { console.log(`Request successful. Deleted room_secret: ${key}`); resolve(roomSecret); } - objectStoreRequestDeletion.onerror = (e) => { + objectStoreRequestDeletion.onerror = e => { reject(e); } }; } - DBOpenRequest.onerror = (e) => { + DBOpenRequest.onerror = e => { reject(e); } }) @@ -2611,7 +2611,7 @@ class PersistentStorage { resolve(); }; } - DBOpenRequest.onerror = (e) => { + DBOpenRequest.onerror = e => { reject(e); } }) @@ -2628,7 +2628,7 @@ class PersistentStorage { static updateRoomSecret(roomSecret, updatedRoomSecret = undefined, updatedDisplayName = undefined, updatedDeviceName = undefined, updatedAutoAccept = undefined) { return new Promise((resolve, reject) => { const DBOpenRequest = window.indexedDB.open('pairdrop_store'); - DBOpenRequest.onsuccess = (e) => { + DBOpenRequest.onsuccess = e => { const db = e.target.result; this.getRoomSecretEntry(roomSecret) .then(roomSecretEntry => { diff --git a/public_included_ws_fallback/service-worker.js b/public_included_ws_fallback/service-worker.js index 1de39739..d344408a 100644 --- a/public_included_ws_fallback/service-worker.js +++ b/public_included_ws_fallback/service-worker.js @@ -56,7 +56,7 @@ const fromNetwork = (request, timeout) => fetch(request).then(response => { clearTimeout(timeoutId); fulfill(response); - update(request).then(() => console.log("Cache successfully updated for", request.url)); + update(request).then(_ => console.log("Cache successfully updated for", request.url)); }, reject); }); @@ -77,7 +77,7 @@ const update = request => .then(async response => { await cache.put(request, response); }) - .catch(() => console.log(`Cache could not be updated. ${request.url}`)) + .catch(_ => console.log(`Cache could not be updated. ${request.url}`)) ); // general strategy when making a request (eg if online try to fetch it From c068a2e32909379db17b68b5e0105e37f9185c1c Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 2 Nov 2023 01:31:31 +0100 Subject: [PATCH 06/97] Fix missing async in Notification class; Fix too many arguments for window.open function --- public/scripts/ui.js | 25 ++++++++++++----------- public_included_ws_fallback/scripts/ui.js | 25 ++++++++++++----------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 079494b5..6b8a0fab 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -2117,15 +2117,16 @@ class Notifications { Events.on('files-transfer-request', e => this._requestNotification(e.detail.request, e.detail.peerId)); } - _requestPermission() { - Notification.requestPermission(permission => { - if (permission !== 'granted') { - Events.fire('notify-user', Localization.getTranslation("notifications.notifications-permissions-error")); - return; - } - Events.fire('notify-user', Localization.getTranslation("notifications.notifications-enabled")); - this.$headerNotificationButton.setAttribute('hidden', ""); - }); + async _requestPermission() { + await Notification. + requestPermission(permission => { + if (permission !== 'granted') { + Events.fire('notify-user', Localization.getTranslation("notifications.notifications-permissions-error")); + return; + } + Events.fire('notify-user', Localization.getTranslation("notifications.notifications-enabled")); + this.$headerNotificationButton.setAttribute('hidden', ""); + }); } _notify(title, body) { @@ -2159,7 +2160,7 @@ class Notifications { const peerDisplayName = $(peerId).ui._displayName(); if (/^((https?:\/\/|www)[abcdefghijklmnopqrstuvwxyz0123456789\-._~:\/?#\[\]@!$&'()*+,;=]+)$/.test(message.toLowerCase())) { const notification = this._notify(Localization.getTranslation("notifications.link-received", null, {name: peerDisplayName}), message); - this._bind(notification, _ => window.open(message, '_blank', null, true)); + this._bind(notification, _ => window.open(message, '_blank', "noreferrer")); } else { const notification = this._notify(Localization.getTranslation("notifications.message-received", null, {name: peerDisplayName}), message); this._bind(notification, _ => this._copyText(message, notification)); @@ -2235,8 +2236,8 @@ class Notifications { notification.close(); } - _copyText(message, notification) { - if (navigator.clipboard.writeText(message)) { + async _copyText(message, notification) { + if (await navigator.clipboard.writeText(message)) { notification.close(); this._notify(Localization.getTranslation("notifications.copied-text")); } else { diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index 98fb5628..705d398e 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -2119,15 +2119,16 @@ class Notifications { Events.on('files-transfer-request', e => this._requestNotification(e.detail.request, e.detail.peerId)); } - _requestPermission() { - Notification.requestPermission(permission => { - if (permission !== 'granted') { - Events.fire('notify-user', Localization.getTranslation("notifications.notifications-permissions-error")); - return; - } - Events.fire('notify-user', Localization.getTranslation("notifications.notifications-enabled")); - this.$headerNotificationButton.setAttribute('hidden', ""); - }); + async _requestPermission() { + await Notification. + requestPermission(permission => { + if (permission !== 'granted') { + Events.fire('notify-user', Localization.getTranslation("notifications.notifications-permissions-error")); + return; + } + Events.fire('notify-user', Localization.getTranslation("notifications.notifications-enabled")); + this.$headerNotificationButton.setAttribute('hidden', ""); + }); } _notify(title, body) { @@ -2161,7 +2162,7 @@ class Notifications { const peerDisplayName = $(peerId).ui._displayName(); if (/^((https?:\/\/|www)[abcdefghijklmnopqrstuvwxyz0123456789\-._~:\/?#\[\]@!$&'()*+,;=]+)$/.test(message.toLowerCase())) { const notification = this._notify(Localization.getTranslation("notifications.link-received", null, {name: peerDisplayName}), message); - this._bind(notification, _ => window.open(message, '_blank', null, true)); + this._bind(notification, _ => window.open(message, '_blank', "noreferrer")); } else { const notification = this._notify(Localization.getTranslation("notifications.message-received", null, {name: peerDisplayName}), message); this._bind(notification, _ => this._copyText(message, notification)); @@ -2237,8 +2238,8 @@ class Notifications { notification.close(); } - _copyText(message, notification) { - if (navigator.clipboard.writeText(message)) { + async _copyText(message, notification) { + if (await navigator.clipboard.writeText(message)) { notification.close(); this._notify(Localization.getTranslation("notifications.copied-text")); } else { From d9270a5560eed92ce5856854cc4506dba8c251e0 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 2 Nov 2023 02:56:01 +0100 Subject: [PATCH 07/97] Try to beautify code by adding line breaks --- index.js | 21 +- public/scripts/localization.js | 12 +- public/scripts/network.js | 93 ++++-- public/scripts/theme.js | 129 ++++---- public/scripts/ui.js | 305 +++++++++++------- public/scripts/util.js | 6 +- public/service-worker.js | 37 ++- .../scripts/localization.js | 12 +- .../scripts/network.js | 96 +++--- public_included_ws_fallback/scripts/theme.js | 129 ++++---- public_included_ws_fallback/scripts/ui.js | 305 +++++++++++------- public_included_ws_fallback/scripts/util.js | 6 +- public_included_ws_fallback/service-worker.js | 37 ++- 13 files changed, 716 insertions(+), 472 deletions(-) diff --git a/index.js b/index.js index f894c042..0a665353 100644 --- a/index.js +++ b/index.js @@ -54,7 +54,8 @@ const RTC_CONFIG = process.env.RTC_CONFIG let rateLimit = false; if (process.argv.includes('--rate-limit') || process.env.RATE_LIMIT === "true") { rateLimit = 5; -} else { +} +else { let envRateLimit = parseInt(process.env.RATE_LIMIT); if (!isNaN(envRateLimit)) { rateLimit = envRateLimit; @@ -126,7 +127,8 @@ if (RATE_LIMIT) { if (WS_FALLBACK) { app.use(express.static('public_included_ws_fallback')); -} else { +} +else { app.use(express.static('public')); } @@ -159,7 +161,8 @@ const server = http.createServer(app); if (LOCALHOST_ONLY) { server.listen(PORT, '127.0.0.1'); -} else { +} +else { server.listen(PORT); } @@ -670,9 +673,11 @@ class Peer { _setIP(request) { if (request.headers['cf-connecting-ip']) { this.ip = request.headers['cf-connecting-ip'].split(/\s*,\s*/)[0]; - } else if (request.headers['x-forwarded-for']) { + } + else if (request.headers['x-forwarded-for']) { this.ip = request.headers['x-forwarded-for'].split(/\s*,\s*/)[0]; - } else { + } + else { this.ip = request.connection.remoteAddress; } @@ -748,7 +753,8 @@ class Peer { let peerIdHash = searchParams.get("peer_id_hash"); if (peerId && Peer.isValidUuid(peerId) && this.isPeerIdHashValid(peerId, peerIdHash)) { this.id = peerId; - } else { + } + else { this.id = crypto.randomUUID(); } } @@ -769,7 +775,8 @@ class Peer { if (ua.device.model) { deviceName += ua.device.model; - } else { + } + else { deviceName += ua.browser.name; } diff --git a/public/scripts/localization.js b/public/scripts/localization.js index 62b11aa1..877cfb02 100644 --- a/public/scripts/localization.js +++ b/public/scripts/localization.js @@ -15,7 +15,8 @@ class Localization { ? storedLanguageCode : Localization.systemLocale; - Localization.setTranslation(Localization.initialLocale) + Localization + .setTranslation(Localization.initialLocale) .then(_ => { console.log("Initial translation successful."); Events.fire("initial-translation-loaded"); @@ -50,7 +51,8 @@ class Localization { if (Localization.isRTLLanguage(locale)) { htmlRootNode.setAttribute('dir', 'rtl'); - } else { + } + else { htmlRootNode.removeAttribute('dir'); } @@ -112,7 +114,8 @@ class Localization { let attr = attrs[i]; if (attr === "text") { element.innerText = Localization.getTranslation(key); - } else { + } + else { if (attr.startsWith("data-")) { let dataAttr = attr.substring(5); element.dataset.dataAttr = Localization.getTranslation(key, attr); @@ -156,7 +159,8 @@ class Localization { console.warn(`Missing translation entry for your language ${Localization.locale.toUpperCase()}. Using ${Localization.defaultLocale.toUpperCase()} instead.`, key, attr); console.warn(`Translate this string here: https://hosted.weblate.org/browse/pairdrop/pairdrop-spa/${Localization.locale.toLowerCase()}/?q=${key}`) console.log("Help translating PairDrop: https://hosted.weblate.org/engage/pairdrop/"); - } else { + } + else { console.warn("Missing translation in default language:", key, attr); } } diff --git a/public/scripts/network.js b/public/scripts/network.js index 53bff2f2..629c9988 100644 --- a/public/scripts/network.js +++ b/public/scripts/network.js @@ -191,16 +191,19 @@ class ServerConnection { sessionStorage.setItem('peer_id_hash', msg.peerIdHash); // Add peerId to localStorage to mark it for other PairDrop tabs on the same browser - BrowserTabsConnector.addPeerIdToLocalStorage().then(peerId => { - if (!peerId) return; - console.log("successfully added peerId to localStorage"); - - // Only now join rooms - Events.fire('join-ip-room'); - PersistentStorage.getAllRoomSecrets().then(roomSecrets => { - Events.fire('room-secrets', roomSecrets); + BrowserTabsConnector + .addPeerIdToLocalStorage() + .then(peerId => { + if (!peerId) return; + console.log("successfully added peerId to localStorage"); + + // Only now join rooms + Events.fire('join-ip-room'); + PersistentStorage.getAllRoomSecrets() + .then(roomSecrets => { + Events.fire('room-secrets', roomSecrets); + }); }); - }); Events.fire('display-name', msg); } @@ -223,9 +226,11 @@ class ServerConnection { this.send({ type: 'disconnect' }); const peerId = sessionStorage.getItem('peer_id'); - BrowserTabsConnector.removePeerIdFromLocalStorage(peerId).then(_ => { - console.log("successfully removed peerId from localStorage"); - }); + BrowserTabsConnector + .removePeerIdFromLocalStorage(peerId) + .then(_ => { + console.log("successfully removed peerId from localStorage"); + }); if (!this._socket) return; @@ -318,7 +323,8 @@ class Peer { // -> do not delete duplicates and do not regenerate room secrets if (!this._isSameBrowser() && roomType === "secret" && this._isPaired() && this._getPairSecret() !== roomId) { // multiple roomSecrets with same peer -> delete old roomSecret - PersistentStorage.deleteRoomSecret(this._getPairSecret()) + PersistentStorage + .deleteRoomSecret(this._getPairSecret()) .then(deletedRoomSecret => { if (deletedRoomSecret) console.log("Successfully deleted duplicate room secret with same peer: ", deletedRoomSecret); }); @@ -348,7 +354,8 @@ class Peer { return; } - PersistentStorage.getRoomSecretEntry(this._getPairSecret()) + PersistentStorage + .getRoomSecretEntry(this._getPairSecret()) .then(roomSecretEntry => { const autoAccept = roomSecretEntry ? roomSecretEntry.entry.auto_accept @@ -379,13 +386,16 @@ class Peer { if (width && height) { canvas.width = width; canvas.height = height; - } else if (width) { + } + else if (width) { canvas.width = width; canvas.height = Math.floor(imageHeight * width / imageWidth) - } else if (height) { + } + else if (height) { canvas.width = Math.floor(imageWidth * height / imageHeight); canvas.height = height; - } else { + } + else { canvas.width = imageWidth; canvas.height = imageHeight } @@ -397,9 +407,11 @@ class Peer { resolve(dataUrl); } image.onerror = _ => reject(`Could not create an image thumbnail from type ${file.type}`); - }).then(dataUrl => { + }) + .then(dataUrl => { return dataUrl; - }).catch(e => console.error(e)); + }) + .catch(e => console.error(e)); } async requestFileTransfer(files) { @@ -634,7 +646,8 @@ class Peer { this._busy = false; Events.fire('notify-user', Localization.getTranslation("notifications.file-transfer-completed")); Events.fire('files-sent'); // used by 'Snapdrop & PairDrop for Android' app - } else { + } + else { this._dequeueFile(); } } @@ -697,7 +710,8 @@ class RTCPeer extends Peer { if (this._isCaller) { this._openChannel(); - } else { + } + else { this._conn.ondatachannel = e => this._onChannelOpened(e); } } @@ -727,7 +741,8 @@ class RTCPeer extends Peer { _onDescription(description) { // description.sdp = description.sdp.replace('b=AS:30', 'b=AS:1638400'); - this._conn.setLocalDescription(description) + this._conn + .setLocalDescription(description) .then(_ => this._sendSignal({ sdp: description })) .catch(e => this._onError(e)); } @@ -741,16 +756,20 @@ class RTCPeer extends Peer { if (!this._conn) this._connect(); if (message.sdp) { - this._conn.setRemoteDescription(message.sdp) + this._conn + .setRemoteDescription(message.sdp) .then(_ => { if (message.sdp.type === 'offer') { - return this._conn.createAnswer() + return this._conn + .createAnswer() .then(d => this._onDescription(d)); } }) .catch(e => this._onError(e)); - } else if (message.ice) { - this._conn.addIceCandidate(new RTCIceCandidate(message.ice)) + } + else if (message.ice) { + this._conn + .addIceCandidate(new RTCIceCandidate(message.ice)) .catch(e => this._onError(e)); } } @@ -997,10 +1016,11 @@ class PeersManager { // If no peers are connected anymore, we can safely assume that no other tab on the same browser is connected: // Tidy up peerIds in localStorage if (Object.keys(this.peers).length === 0) { - BrowserTabsConnector.removeOtherPeerIdsFromLocalStorage().then(peerIds => { - if (!peerIds) return; - console.log("successfully removed other peerIds from localStorage"); - }); + BrowserTabsConnector.removeOtherPeerIdsFromLocalStorage() + .then(peerIds => { + if (!peerIds) return; + console.log("successfully removed other peerIds from localStorage"); + }); } } } @@ -1050,16 +1070,19 @@ class PeersManager { if (peer._getRoomTypes().length > 1) { peer._removeRoomType(roomType); - } else { + } + else { Events.fire('peer-disconnected', peerId); } } _onRoomSecretRegenerated(message) { - PersistentStorage.updateRoomSecret(message.oldRoomSecret, message.newRoomSecret).then(_ => { - console.log("successfully regenerated room secret"); - Events.fire("room-secrets", [message.newRoomSecret]); - }) + PersistentStorage + .updateRoomSecret(message.oldRoomSecret, message.newRoomSecret) + .then(_ => { + console.log("successfully regenerated room secret"); + Events.fire("room-secrets", [message.newRoomSecret]); + }) } _notifyPeersDisplayNameChanged(newDisplayName) { diff --git a/public/scripts/theme.js b/public/scripts/theme.js index 81cf0b23..33ea0b3a 100644 --- a/public/scripts/theme.js +++ b/public/scripts/theme.js @@ -1,78 +1,83 @@ (function(){ - const prefersDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches; - const prefersLightTheme = window.matchMedia('(prefers-color-scheme: light)').matches; + const prefersDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches; + const prefersLightTheme = window.matchMedia('(prefers-color-scheme: light)').matches; - const $themeAuto = document.getElementById('theme-auto'); - const $themeLight = document.getElementById('theme-light'); - const $themeDark = document.getElementById('theme-dark'); + const $themeAuto = document.getElementById('theme-auto'); + const $themeLight = document.getElementById('theme-light'); + const $themeDark = document.getElementById('theme-dark'); - let currentTheme = localStorage.getItem('theme'); + let currentTheme = localStorage.getItem('theme'); - if (currentTheme === 'dark') { - setModeToDark(); - } else if (currentTheme === 'light') { - setModeToLight(); - } - - $themeAuto.addEventListener('click', _ => { - if (currentTheme) { - setModeToAuto(); - } else { - setModeToDark(); - } - }); - $themeLight.addEventListener('click', _ => { - if (currentTheme !== 'light') { - setModeToLight(); - } else { - setModeToAuto(); + if (currentTheme === 'dark') { + setModeToDark(); } - }); - $themeDark.addEventListener('click', _ => { - if (currentTheme !== 'dark') { - setModeToDark(); - } else { - setModeToLight(); + else if (currentTheme === 'light') { + setModeToLight(); } - }); - function setModeToDark() { - document.body.classList.remove('light-theme'); - document.body.classList.add('dark-theme'); - localStorage.setItem('theme', 'dark'); - currentTheme = 'dark'; + $themeAuto.addEventListener('click', _ => { + if (currentTheme) { + setModeToAuto(); + } + else { + setModeToDark(); + } + }); + $themeLight.addEventListener('click', _ => { + if (currentTheme !== 'light') { + setModeToLight(); + } + else { + setModeToAuto(); + } + }); + $themeDark.addEventListener('click', _ => { + if (currentTheme !== 'dark') { + setModeToDark(); + } + else { + setModeToLight(); + } + }); - $themeAuto.classList.remove("selected"); - $themeLight.classList.remove("selected"); - $themeDark.classList.add("selected"); - } + function setModeToDark() { + document.body.classList.remove('light-theme'); + document.body.classList.add('dark-theme'); + localStorage.setItem('theme', 'dark'); + currentTheme = 'dark'; - function setModeToLight() { - document.body.classList.remove('dark-theme'); - document.body.classList.add('light-theme'); - localStorage.setItem('theme', 'light'); - currentTheme = 'light'; + $themeAuto.classList.remove("selected"); + $themeLight.classList.remove("selected"); + $themeDark.classList.add("selected"); + } - $themeAuto.classList.remove("selected"); - $themeLight.classList.add("selected"); - $themeDark.classList.remove("selected"); - } + function setModeToLight() { + document.body.classList.remove('dark-theme'); + document.body.classList.add('light-theme'); + localStorage.setItem('theme', 'light'); + currentTheme = 'light'; - function setModeToAuto() { - document.body.classList.remove('dark-theme'); - document.body.classList.remove('light-theme'); - if (prefersDarkTheme) { - document.body.classList.add('dark-theme'); - } else if (prefersLightTheme) { - document.body.classList.add('light-theme'); + $themeAuto.classList.remove("selected"); + $themeLight.classList.add("selected"); + $themeDark.classList.remove("selected"); } - localStorage.removeItem('theme'); - currentTheme = undefined; - $themeAuto.classList.add("selected"); - $themeLight.classList.remove("selected"); - $themeDark.classList.remove("selected"); - } + function setModeToAuto() { + document.body.classList.remove('dark-theme'); + document.body.classList.remove('light-theme'); + if (prefersDarkTheme) { + document.body.classList.add('dark-theme'); + } + else if (prefersLightTheme) { + document.body.classList.add('light-theme'); + } + localStorage.removeItem('theme'); + currentTheme = undefined; + + $themeAuto.classList.add("selected"); + $themeLight.classList.remove("selected"); + $themeDark.classList.remove("selected"); + } })(); diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 6b8a0fab..65459668 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -64,10 +64,13 @@ class PeersUI { } _loadSavedDisplayName() { - this._getSavedDisplayName().then(displayName => { - console.log("Retrieved edited display name:", displayName) - if (displayName) Events.fire('self-display-name-changed', displayName); - }); + this._getSavedDisplayName() + .then(displayName => { + console.log("Retrieved edited display name:", displayName) + if (displayName) { + Events.fire('self-display-name-changed', displayName); + } + }); } _onDisplayName(displayName){ @@ -104,7 +107,8 @@ class PeersUI { if (this.$discoveryWrapper.querySelectorAll('div:last-of-type > span[hidden]').length < 2) { this.$discoveryWrapper.classList.remove('row'); this.$discoveryWrapper.classList.add('column'); - } else { + } + else { this.$discoveryWrapper.classList.remove('column'); this.$discoveryWrapper.classList.add('row'); } @@ -147,7 +151,8 @@ class PeersUI { Events.fire('self-display-name-changed', newDisplayName); Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: newDisplayName}); }); - } else { + } + else { PersistentStorage.delete('editedDisplayName') .catch(_ => { console.log("This browser does not support IndexedDB. Use localStorage instead.") @@ -188,8 +193,12 @@ class PeersUI { this._changePeerDisplayName(e.detail.peerId, e.detail.displayName); } + _noDialogShown() { + return document.querySelectorAll('x-dialog[show]').length === 0; + } + _onKeyDown(e) { - if (document.querySelectorAll('x-dialog[show]').length === 0 && window.pasteMode.activated && e.code === "Escape") { + if (this._noDialogShown() && window.pasteMode.activated && e.code === "Escape") { Events.fire('deactivate-paste-mode'); } @@ -245,7 +254,8 @@ class PeersUI { _evaluateOverflowing() { if (this.$xPeers.clientHeight < this.$xPeers.scrollHeight) { this.$xPeers.classList.add('overflowing'); - } else { + } + else { this.$xPeers.classList.remove('overflowing'); } } @@ -319,9 +329,11 @@ class PeersUI { if (files.length === 1) { descriptor = `${files[0].name}`; - } else if (files.length > 1) { + } + else if (files.length > 1) { descriptor = `${files[0].name}
${andOtherFiles}`; - } else { + } + else { descriptor = sharedText; } @@ -380,7 +392,8 @@ class PeersUI { files: files, to: peerId }); - } else if (text.length > 0) { + } + else if (text.length > 0) { Events.fire('send-text', { text: text, to: peerId @@ -408,7 +421,8 @@ class PeerUI { let input = ''; if (window.pasteMode.activated) { title = Localization.getTranslation("peer-ui.click-to-send-paste-mode", null, {descriptor: window.pasteMode.descriptor}); - } else { + } + else { title = Localization.getTranslation("peer-ui.click-to-send"); input = ''; } @@ -498,7 +512,8 @@ class PeerUI { this.$el.addEventListener('contextmenu', this._callbackContextMenu); this.$el.addEventListener('touchstart', this._callbackTouchStart); this.$el.addEventListener('touchend', this._callbackTouchEnd); - } else { + } + else { // Remove Events Normal Mode this.$el.removeEventListener('click', this._callbackClickSleep); this.$el.removeEventListener('touchstart', this._callbackTouchStartSleep); @@ -566,7 +581,8 @@ class PeerUI { const $progress = this.$el.querySelector('.progress'); if (0.5 < progress && progress < 1) { $progress.classList.add('over50'); - } else { + } + else { $progress.classList.remove('over50'); } if (progress < 1) { @@ -582,7 +598,8 @@ class PeerUI { this.$el.querySelector('.status').innerText = statusName; this.currentStatus = status; } - } else { + } + else { this.$el.removeAttribute('status'); this.$el.querySelector('.status').innerHTML = ''; progress = 0; @@ -627,7 +644,8 @@ class PeerUI { _onTouchEnd(e) { if (Date.now() - this._touchStart < 500) { clearTimeout(this._touchTimer); - } else if (this._touchTimer) { // this was a long tap + } + else if (this._touchTimer) { // this was a long tap e.preventDefault(); Events.fire('text-recipient', { peerId: this._peer.id, @@ -641,7 +659,9 @@ class PeerUI { class Dialog { constructor(id) { this.$el = $(id); - this.$el.querySelectorAll('[close]').forEach(el => el.addEventListener('click', _ => this.hide())); + this.$el.querySelectorAll('[close]').forEach(el => { + el.addEventListener('click', _ => this.hide()) + }); this.$autoFocus = this.$el.querySelector('[autofocus]'); Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail)); @@ -699,7 +719,8 @@ class LanguageSelectDialog extends Dialog { show() { if (Localization.isSystemLocale()) { this.$languageButtons[0].focus(); - } else { + } + else { let locale = Localization.getLocale(); for (let i=0; i= 1073741824) { return Math.round(10 * bytes / 1073741824) / 10 + ' GB'; - } else if (bytes >= 1048576) { + } + else if (bytes >= 1048576) { return Math.round(bytes / 1048576) + ' MB'; - } else if (bytes > 1024) { + } + else if (bytes > 1024) { return Math.round(bytes / 1024) + ' KB'; - } else { + } + else { return bytes + ' Bytes'; } } @@ -761,7 +786,8 @@ class ReceiveDialog extends Dialog { fileOther = imagesOnly ? Localization.getTranslation("dialogs.file-other-description-image") : Localization.getTranslation("dialogs.file-other-description-file"); - } else if (files.length >= 2) { + } + 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}); @@ -845,7 +871,8 @@ class ReceiveFileDialog extends ReceiveDialog { if (Object.keys(previewElement).indexOf(mime) === -1) { resolve(false); - } else { + } + else { let element = document.createElement(previewElement[mime]); element.controls = true; element.onload = _ => { @@ -875,7 +902,8 @@ class ReceiveFileDialog extends ReceiveDialog { descriptor = imagesOnly ? Localization.getTranslation("dialogs.title-image") : Localization.getTranslation("dialogs.title-file"); - } else { + } + else { descriptor = imagesOnly ? Localization.getTranslation("dialogs.title-image-plural") : Localization.getTranslation("dialogs.title-file-plural"); @@ -937,7 +965,8 @@ class ReceiveFileDialog extends ReceiveDialog { tmpZipBtn.download = filenameDownload; tmpZipBtn.href = url; tmpZipBtn.click(); - } else { + } + else { this._downloadFilesIndividually(files); } @@ -960,7 +989,8 @@ class ReceiveFileDialog extends ReceiveDialog { setTimeout(() => { if (canShare) { this.$shareBtn.click(); - } else { + } + else { this.$downloadBtn.click(); } }, 500); @@ -969,7 +999,8 @@ class ReceiveFileDialog extends ReceiveDialog { .then(canPreview => { if (canPreview) { console.log('the file is able to preview'); - } else { + } + else { console.log('the file is not able to preview'); } }) @@ -1132,10 +1163,12 @@ class InputKeyContainer { if (e.key === "Backspace" && previousSibling && !e.target.value) { previousSibling.value = ''; previousSibling.focus(); - } else if (e.key === "ArrowRight" && nextSibling) { + } + else if (e.key === "ArrowRight" && nextSibling) { e.preventDefault(); nextSibling.focus(); - } else if (e.key === "ArrowLeft" && previousSibling) { + } + else if (e.key === "ArrowLeft" && previousSibling) { e.preventDefault(); previousSibling.focus(); } @@ -1171,7 +1204,8 @@ class InputKeyContainer { _evaluateKeyChars() { if (this.$inputKeyContainer.querySelectorAll('input:placeholder-shown').length > 0) { this._onNotAllCharsFilled(); - } else { + } + else { this._onAllCharsFilled(); const lastCharFocused = document.activeElement === this.$inputKeyChars[this.$inputKeyChars.length - 1]; @@ -1241,7 +1275,10 @@ class PairDeviceDialog extends Dialog { _onPaste(e) { e.preventDefault(); - let pastedKey = e.clipboardData.getData("Text").replace(/\D/g,'').substring(0, 6); + let pastedKey = e.clipboardData + .getData("Text") + .replace(/\D/g,'') + .substring(0, 6); this.inputKeyContainer._onPaste(pastedKey); } @@ -1364,7 +1401,8 @@ class PairDeviceDialog extends Dialog { deviceName = $peer.ui._peer.name.deviceName; } - PersistentStorage.addRoomSecret(roomSecret, displayName, deviceName) + PersistentStorage + .addRoomSecret(roomSecret, displayName, deviceName) .then(_ => { Events.fire('notify-user', Localization.getTranslation("notifications.pairing-success")); this._evaluateNumberRoomSecrets(); @@ -1405,18 +1443,22 @@ class PairDeviceDialog extends Dialog { } _onSecretRoomDeleted(roomSecret) { - PersistentStorage.deleteRoomSecret(roomSecret).then(_ => { - this._evaluateNumberRoomSecrets(); - }); + PersistentStorage + .deleteRoomSecret(roomSecret) + .then(_ => { + this._evaluateNumberRoomSecrets(); + }); } _evaluateNumberRoomSecrets() { - PersistentStorage.getAllRoomSecrets() + PersistentStorage + .getAllRoomSecrets() .then(roomSecrets => { if (roomSecrets.length > 0) { this.$editPairedDevicesHeaderBtn.removeAttribute('hidden'); this.$footerInstructionsPairedDevices.removeAttribute('hidden'); - } else { + } + else { this.$editPairedDevicesHeaderBtn.setAttribute('hidden', ''); this.$footerInstructionsPairedDevices.setAttribute('hidden', ''); } @@ -1450,45 +1492,51 @@ class EditPairedDevicesDialog extends Dialog { const autoAcceptString = Localization.getTranslation("dialogs.auto-accept").toLowerCase(); const roomSecretsEntries = await PersistentStorage.getAllRoomSecretEntries(); - roomSecretsEntries.forEach(roomSecretsEntry => { - let $pairedDevice = document.createElement('div'); - $pairedDevice.classList = ["paired-device"]; - - $pairedDevice.innerHTML = ` -
- ${roomSecretsEntry.display_name} -
-
- ${roomSecretsEntry.device_name} -
-
- - -
` - - $pairedDevice.querySelector('input[type="checkbox"]').addEventListener('click', e => { - PersistentStorage.updateRoomSecretAutoAccept(roomSecretsEntry.secret, e.target.checked).then(roomSecretsEntry => { - Events.fire('auto-accept-updated', { - 'roomSecret': roomSecretsEntry.entry.secret, - 'autoAccept': e.target.checked - }); - }); - }); + roomSecretsEntries + .forEach(roomSecretsEntry => { + let $pairedDevice = document.createElement('div'); + $pairedDevice.classList = ["paired-device"]; - $pairedDevice.querySelector('button').addEventListener('click', e => { - PersistentStorage.deleteRoomSecret(roomSecretsEntry.secret).then(roomSecret => { - Events.fire('room-secrets-deleted', [roomSecret]); - Events.fire('evaluate-number-room-secrets'); - e.target.parentNode.parentNode.remove(); - }); - }) + $pairedDevice.innerHTML = ` +
+ ${roomSecretsEntry.display_name} +
+
+ ${roomSecretsEntry.device_name} +
+
+ + +
` + + $pairedDevice + .querySelector('input[type="checkbox"]') + .addEventListener('click', e => { + PersistentStorage.updateRoomSecretAutoAccept(roomSecretsEntry.secret, e.target.checked) + .then(roomSecretsEntry => { + Events.fire('auto-accept-updated', { + 'roomSecret': roomSecretsEntry.entry.secret, + 'autoAccept': e.target.checked + }); + }); + }); - this.$pairedDevicesWrapper.html = ""; - this.$pairedDevicesWrapper.appendChild($pairedDevice) - }) + $pairedDevice + .querySelector('button') + .addEventListener('click', e => { + PersistentStorage.deleteRoomSecret(roomSecretsEntry.secret) + .then(roomSecret => { + Events.fire('room-secrets-deleted', [roomSecret]); + Events.fire('evaluate-number-room-secrets'); + e.target.parentNode.parentNode.remove(); + }); + }) + this.$pairedDevicesWrapper.html = ""; + this.$pairedDevicesWrapper.appendChild($pairedDevice) + }) } hide() { @@ -1503,14 +1551,17 @@ class EditPairedDevicesDialog extends Dialog { } _clearRoomSecrets() { - PersistentStorage.getAllRoomSecrets() + PersistentStorage + .getAllRoomSecrets() .then(roomSecrets => { - PersistentStorage.clearRoomSecrets().finally(() => { - Events.fire('room-secrets-deleted', roomSecrets); - Events.fire('evaluate-number-room-secrets'); - Events.fire('notify-user', Localization.getTranslation("notifications.pairing-cleared")); - this.hide(); - }) + PersistentStorage + .clearRoomSecrets() + .finally(() => { + Events.fire('room-secrets-deleted', roomSecrets); + Events.fire('evaluate-number-room-secrets'); + Events.fire('notify-user', Localization.getTranslation("notifications.pairing-cleared")); + this.hide(); + }) }); } @@ -1524,9 +1575,11 @@ class EditPairedDevicesDialog extends Dialog { if (!peer || !peer._roomIds["secret"]) return; - PersistentStorage.updateRoomSecretNames(peer._roomIds["secret"], peer.name.displayName, peer.name.deviceName).then(roomSecretEntry => { - console.log(`Successfully updated DisplayName and DeviceName for roomSecretEntry ${roomSecretEntry.key}`); - }) + PersistentStorage + .updateRoomSecretNames(peer._roomIds["secret"], peer.name.displayName, peer.name.deviceName) + .then(roomSecretEntry => { + console.log(`Successfully updated DisplayName and DeviceName for roomSecretEntry ${roomSecretEntry.key}`); + }) } } @@ -1589,7 +1642,8 @@ class PublicRoomDialog extends Dialog { _onHeaderBtnClick() { if (this.roomId) { this.show(); - } else { + } + else { this._createPublicRoom(); } } @@ -1782,7 +1836,8 @@ class SendTextDialog extends Dialog { if (e.code === "Escape") { this.hide(); - } else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) { + } + else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) { if (this._textInputEmpty()) return; this._send(); } @@ -1795,7 +1850,8 @@ class SendTextDialog extends Dialog { _onChange(e) { if (this._textInputEmpty()) { this.$submit.setAttribute('disabled', ''); - } else { + } + else { this.$submit.removeAttribute('disabled'); } } @@ -1853,7 +1909,8 @@ class ReceiveTextDialog extends Dialog { if (e.code === "KeyC" && (e.ctrlKey || e.metaKey)) { await this._onCopy() this.hide(); - } else if (e.code === "Escape") { + } + else if (e.code === "Escape") { this.hide(); } } @@ -1903,7 +1960,8 @@ class ReceiveTextDialog extends Dialog { async _onCopy() { const sanitizedText = this.$text.innerText.replace(/\u00A0/gm, ' '); - navigator.clipboard.writeText(sanitizedText) + navigator.clipboard + .writeText(sanitizedText) .then(_ => { Events.fire('notify-user', Localization.getTranslation("notifications.copied-to-clipboard")); this.hide(); @@ -1937,7 +1995,8 @@ class Base64ZipDialog extends Dialog { // ?base64text=paste // base64 encoded string is ready to be pasted from clipboard this.preparePasting('text'); - } else if (base64Text === 'hash') { + } + else if (base64Text === 'hash') { // ?base64text=hash#BASE64ENCODED // base64 encoded string is url hash which is never sent to server and faster (recommended) this.processBase64Text(base64Hash) @@ -1947,7 +2006,8 @@ class Base64ZipDialog extends Dialog { }).finally(() => { this.hide(); }); - } else { + } + else { // ?base64text=BASE64ENCODED // base64 encoded string was part of url param (not recommended) this.processBase64Text(base64Text) @@ -1958,7 +2018,8 @@ class Base64ZipDialog extends Dialog { this.hide(); }); } - } else if (base64Zip) { + } + else if (base64Zip) { this.show(); if (base64Zip === "hash") { // ?base64zip=hash#BASE64ENCODED @@ -1970,7 +2031,8 @@ class Base64ZipDialog extends Dialog { }).finally(() => { this.hide(); }); - } else { + } + else { // ?base64zip=paste || ?base64zip=true this.preparePasting('files'); } @@ -1991,7 +2053,8 @@ class Base64ZipDialog extends Dialog { this.$pasteBtn.innerText = Localization.getTranslation("dialogs.base64-tap-to-paste", null, {type: translateType}); this._clickCallback = _ => this.processClipboard(type); this.$pasteBtn.addEventListener('click', _ => this._clickCallback()); - } else { + } + else { console.log("`navigator.clipboard.readText()` is not available on your browser.\nOn Firefox you can set `dom.events.asyncClipboard.readText` to true under `about:config` for convenience.") this.$pasteBtn.setAttribute('hidden', ''); this.$fallbackTextarea.setAttribute('placeholder', Localization.getTranslation("dialogs.base64-paste-to-send", null, {type: translateType})); @@ -2030,7 +2093,8 @@ class Base64ZipDialog extends Dialog { try { if (type === 'text') { await this.processBase64Text(base64); - } else { + } + else { await this.processBase64Zip(base64); } } catch(_) { @@ -2161,7 +2225,8 @@ class Notifications { if (/^((https?:\/\/|www)[abcdefghijklmnopqrstuvwxyz0123456789\-._~:\/?#\[\]@!$&'()*+,;=]+)$/.test(message.toLowerCase())) { const notification = this._notify(Localization.getTranslation("notifications.link-received", null, {name: peerDisplayName}), message); this._bind(notification, _ => window.open(message, '_blank', "noreferrer")); - } else { + } + else { const notification = this._notify(Localization.getTranslation("notifications.message-received", null, {name: peerDisplayName}), message); this._bind(notification, _ => this._copyText(message, notification)); } @@ -2180,13 +2245,15 @@ class Notifications { let title; if (files.length === 1) { title = `${files[0].name}`; - } else { + } + else { let fileOther; if (files.length === 2) { fileOther = imagesOnly ? Localization.getTranslation("dialogs.file-other-description-image") : Localization.getTranslation("dialogs.file-other-description-file"); - } else { + } + 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}); @@ -2215,17 +2282,19 @@ class Notifications { descriptor = imagesOnly ? Localization.getTranslation("dialogs.title-image") : Localization.getTranslation("dialogs.title-file"); - } else { + } + else { descriptor = imagesOnly ? Localization.getTranslation("dialogs.title-image-plural") : Localization.getTranslation("dialogs.title-file-plural"); } - let title = Localization.getTranslation("notifications.request-title", null, { - name: displayName, - count: request.header.length, - descriptor: descriptor.toLowerCase() - }); + let title = Localization + .getTranslation("notifications.request-title", null, { + name: displayName, + count: request.header.length, + descriptor: descriptor.toLowerCase() + }); const notification = this._notify(title, Localization.getTranslation("notifications.click-to-show")); } @@ -2240,17 +2309,23 @@ class Notifications { if (await navigator.clipboard.writeText(message)) { notification.close(); this._notify(Localization.getTranslation("notifications.copied-text")); - } else { + } + else { this._notify(Localization.getTranslation("notifications.copied-text-error")); } } _bind(notification, handler) { if (notification.then) { - notification.then(_ => serviceWorker.getNotifications().then(_ => { - serviceWorker.addEventListener('notificationclick', handler); - })); - } else { + notification.then(_ => { + serviceWorker + .getNotifications() + .then(_ => { + serviceWorker.addEventListener('notificationclick', handler); + }) + }); + } + else { notification.onclick = handler; } } @@ -2289,14 +2364,17 @@ class WebShareTargetUI { if (url) { shareTargetText = url; // we share only the link - no text. - } else if (title && text) { + } + else if (title && text) { shareTargetText = title + '\r\n' + text; - } else { + } + else { shareTargetText = title + text; } Events.fire('activate-paste-mode', {files: [], text: shareTargetText}) - } else if (share_target_type === "files") { + } + else if (share_target_type === "files") { let openRequest = window.indexedDB.open('pairdrop_store') openRequest.onsuccess = e => { const db = e.target.result; @@ -2818,7 +2896,8 @@ const pairDrop = new PairDrop(); const localization = new Localization(); if ('serviceWorker' in navigator) { - navigator.serviceWorker.register('/service-worker.js') + navigator.serviceWorker + .register('/service-worker.js') .then(serviceWorker => { console.log('Service Worker registered'); window.serviceWorker = serviceWorker diff --git a/public/scripts/util.js b/public/scripts/util.js index 921094ba..7272d1b1 100644 --- a/public/scripts/util.js +++ b/public/scripts/util.js @@ -55,7 +55,8 @@ const zipper = (() => { const blobURL = URL.createObjectURL(await zipWriter.close()); zipWriter = null; return blobURL; - } else { + } + else { throw new Error("Zip file closed"); } }, @@ -64,7 +65,8 @@ const zipper = (() => { const file = new File([await zipWriter.close()], filename, {type: "application/zip"}); zipWriter = null; return file; - } else { + } + else { throw new Error("Zip file closed"); } }, diff --git a/public/service-worker.js b/public/service-worker.js index 81a1b5f5..752cb46a 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -90,16 +90,19 @@ self.addEventListener('fetch', function(event) { const share_url = await evaluateRequestData(event.request); return Response.redirect(encodeURI(share_url), 302); })()); - } else { + } + else { // Regular requests not related to Web Share Target. if (forceFetch) { event.respondWith(fromNetwork(event.request, 10000)); - } else { + } + else { event.respondWith( - fromCache(event.request).then(rsp => { - // if fromCache resolves to undefined fetch from network instead - return rsp || fromNetwork(event.request, 10000); - }) + fromCache(event.request) + .then(rsp => { + // if fromCache resolves to undefined fetch from network instead + return rsp || fromNetwork(event.request, 10000); + }) ); } } @@ -109,15 +112,16 @@ self.addEventListener('fetch', function(event) { // on activation, we clean up the previously registered service workers self.addEventListener('activate', evt => { return evt.waitUntil( - caches.keys().then(cacheNames => { - return Promise.all( - cacheNames.map(cacheName => { - if (cacheName !== cacheTitle) { - return caches.delete(cacheName); - } - }) - ); - }) + caches.keys() + .then(cacheNames => { + return Promise.all( + cacheNames.map(cacheName => { + if (cacheName !== cacheTitle) { + return caches.delete(cacheName); + } + }) + ); + }) ) } ); @@ -157,7 +161,8 @@ const evaluateRequestData = function (request) { DBOpenRequest.onerror = _ => { resolve(pairDropUrl); } - } else { + } + else { let urlArgument = '?share-target=text'; if (title) urlArgument += `&title=${title}`; diff --git a/public_included_ws_fallback/scripts/localization.js b/public_included_ws_fallback/scripts/localization.js index 62b11aa1..877cfb02 100644 --- a/public_included_ws_fallback/scripts/localization.js +++ b/public_included_ws_fallback/scripts/localization.js @@ -15,7 +15,8 @@ class Localization { ? storedLanguageCode : Localization.systemLocale; - Localization.setTranslation(Localization.initialLocale) + Localization + .setTranslation(Localization.initialLocale) .then(_ => { console.log("Initial translation successful."); Events.fire("initial-translation-loaded"); @@ -50,7 +51,8 @@ class Localization { if (Localization.isRTLLanguage(locale)) { htmlRootNode.setAttribute('dir', 'rtl'); - } else { + } + else { htmlRootNode.removeAttribute('dir'); } @@ -112,7 +114,8 @@ class Localization { let attr = attrs[i]; if (attr === "text") { element.innerText = Localization.getTranslation(key); - } else { + } + else { if (attr.startsWith("data-")) { let dataAttr = attr.substring(5); element.dataset.dataAttr = Localization.getTranslation(key, attr); @@ -156,7 +159,8 @@ class Localization { console.warn(`Missing translation entry for your language ${Localization.locale.toUpperCase()}. Using ${Localization.defaultLocale.toUpperCase()} instead.`, key, attr); console.warn(`Translate this string here: https://hosted.weblate.org/browse/pairdrop/pairdrop-spa/${Localization.locale.toLowerCase()}/?q=${key}`) console.log("Help translating PairDrop: https://hosted.weblate.org/engage/pairdrop/"); - } else { + } + else { console.warn("Missing translation in default language:", key, attr); } } diff --git a/public_included_ws_fallback/scripts/network.js b/public_included_ws_fallback/scripts/network.js index 5a279a15..719a478c 100644 --- a/public_included_ws_fallback/scripts/network.js +++ b/public_included_ws_fallback/scripts/network.js @@ -204,16 +204,19 @@ class ServerConnection { sessionStorage.setItem('peer_id_hash', msg.peerIdHash); // Add peerId to localStorage to mark it for other PairDrop tabs on the same browser - BrowserTabsConnector.addPeerIdToLocalStorage().then(peerId => { - if (!peerId) return; - console.log("successfully added peerId to localStorage"); - - // Only now join rooms - Events.fire('join-ip-room'); - PersistentStorage.getAllRoomSecrets().then(roomSecrets => { - Events.fire('room-secrets', roomSecrets); + BrowserTabsConnector + .addPeerIdToLocalStorage() + .then(peerId => { + if (!peerId) return; + console.log("successfully added peerId to localStorage"); + + // Only now join rooms + Events.fire('join-ip-room'); + PersistentStorage.getAllRoomSecrets() + .then(roomSecrets => { + Events.fire('room-secrets', roomSecrets); + }); }); - }); Events.fire('display-name', msg); } @@ -236,9 +239,11 @@ class ServerConnection { this.send({ type: 'disconnect' }); const peerId = sessionStorage.getItem('peer_id'); - BrowserTabsConnector.removePeerIdFromLocalStorage(peerId).then(_ => { - console.log("successfully removed peerId from localStorage"); - }); + BrowserTabsConnector + .removePeerIdFromLocalStorage(peerId) + .then(_ => { + console.log("successfully removed peerId from localStorage"); + }); if (!this._socket) return; @@ -331,7 +336,8 @@ class Peer { // -> do not delete duplicates and do not regenerate room secrets if (!this._isSameBrowser() && roomType === "secret" && this._isPaired() && this._getPairSecret() !== roomId) { // multiple roomSecrets with same peer -> delete old roomSecret - PersistentStorage.deleteRoomSecret(this._getPairSecret()) + PersistentStorage + .deleteRoomSecret(this._getPairSecret()) .then(deletedRoomSecret => { if (deletedRoomSecret) console.log("Successfully deleted duplicate room secret with same peer: ", deletedRoomSecret); }); @@ -361,7 +367,8 @@ class Peer { return; } - PersistentStorage.getRoomSecretEntry(this._getPairSecret()) + PersistentStorage + .getRoomSecretEntry(this._getPairSecret()) .then(roomSecretEntry => { const autoAccept = roomSecretEntry ? roomSecretEntry.entry.auto_accept @@ -392,13 +399,16 @@ class Peer { if (width && height) { canvas.width = width; canvas.height = height; - } else if (width) { + } + else if (width) { canvas.width = width; canvas.height = Math.floor(imageHeight * width / imageWidth) - } else if (height) { + } + else if (height) { canvas.width = Math.floor(imageWidth * height / imageHeight); canvas.height = height; - } else { + } + else { canvas.width = imageWidth; canvas.height = imageHeight } @@ -410,9 +420,11 @@ class Peer { resolve(dataUrl); } image.onerror = _ => reject(`Could not create an image thumbnail from type ${file.type}`); - }).then(dataUrl => { + }) + .then(dataUrl => { return dataUrl; - }).catch(e => console.error(e)); + }) + .catch(e => console.error(e)); } async requestFileTransfer(files) { @@ -647,7 +659,8 @@ class Peer { this._busy = false; Events.fire('notify-user', Localization.getTranslation("notifications.file-transfer-completed")); Events.fire('files-sent'); // used by 'Snapdrop & PairDrop for Android' app - } else { + } + else { this._dequeueFile(); } } @@ -710,7 +723,8 @@ class RTCPeer extends Peer { if (this._isCaller) { this._openChannel(); - } else { + } + else { this._conn.ondatachannel = e => this._onChannelOpened(e); } } @@ -740,7 +754,8 @@ class RTCPeer extends Peer { _onDescription(description) { // description.sdp = description.sdp.replace('b=AS:30', 'b=AS:1638400'); - this._conn.setLocalDescription(description) + this._conn + .setLocalDescription(description) .then(_ => this._sendSignal({ sdp: description })) .catch(e => this._onError(e)); } @@ -754,16 +769,20 @@ class RTCPeer extends Peer { if (!this._conn) this._connect(); if (message.sdp) { - this._conn.setRemoteDescription(message.sdp) + this._conn + .setRemoteDescription(message.sdp) .then(_ => { if (message.sdp.type === 'offer') { - return this._conn.createAnswer() + return this._conn + .createAnswer() .then(d => this._onDescription(d)); } }) .catch(e => this._onError(e)); - } else if (message.ice) { - this._conn.addIceCandidate(new RTCIceCandidate(message.ice)) + } + else if (message.ice) { + this._conn + .addIceCandidate(new RTCIceCandidate(message.ice)) .catch(e => this._onError(e)); } } @@ -1007,7 +1026,8 @@ class PeersManager { if (window.isRtcSupported && rtcSupported) { this.peers[peerId] = new RTCPeer(this._server,isCaller, peerId, roomType, roomId); - } else { + } + else { this.peers[peerId] = new WSPeer(this._server, isCaller, peerId, roomType, roomId); } } @@ -1063,10 +1083,11 @@ class PeersManager { // If no peers are connected anymore, we can safely assume that no other tab on the same browser is connected: // Tidy up peerIds in localStorage if (Object.keys(this.peers).length === 0) { - BrowserTabsConnector.removeOtherPeerIdsFromLocalStorage().then(peerIds => { - if (!peerIds) return; - console.log("successfully removed other peerIds from localStorage"); - }); + BrowserTabsConnector.removeOtherPeerIdsFromLocalStorage() + .then(peerIds => { + if (!peerIds) return; + console.log("successfully removed other peerIds from localStorage"); + }); } } } @@ -1124,16 +1145,19 @@ class PeersManager { if (peer._getRoomTypes().length > 1) { peer._removeRoomType(roomType); - } else { + } + else { Events.fire('peer-disconnected', peerId); } } _onRoomSecretRegenerated(message) { - PersistentStorage.updateRoomSecret(message.oldRoomSecret, message.newRoomSecret).then(_ => { - console.log("successfully regenerated room secret"); - Events.fire("room-secrets", [message.newRoomSecret]); - }) + PersistentStorage + .updateRoomSecret(message.oldRoomSecret, message.newRoomSecret) + .then(_ => { + console.log("successfully regenerated room secret"); + Events.fire("room-secrets", [message.newRoomSecret]); + }) } _notifyPeersDisplayNameChanged(newDisplayName) { diff --git a/public_included_ws_fallback/scripts/theme.js b/public_included_ws_fallback/scripts/theme.js index 81cf0b23..33ea0b3a 100644 --- a/public_included_ws_fallback/scripts/theme.js +++ b/public_included_ws_fallback/scripts/theme.js @@ -1,78 +1,83 @@ (function(){ - const prefersDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches; - const prefersLightTheme = window.matchMedia('(prefers-color-scheme: light)').matches; + const prefersDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches; + const prefersLightTheme = window.matchMedia('(prefers-color-scheme: light)').matches; - const $themeAuto = document.getElementById('theme-auto'); - const $themeLight = document.getElementById('theme-light'); - const $themeDark = document.getElementById('theme-dark'); + const $themeAuto = document.getElementById('theme-auto'); + const $themeLight = document.getElementById('theme-light'); + const $themeDark = document.getElementById('theme-dark'); - let currentTheme = localStorage.getItem('theme'); + let currentTheme = localStorage.getItem('theme'); - if (currentTheme === 'dark') { - setModeToDark(); - } else if (currentTheme === 'light') { - setModeToLight(); - } - - $themeAuto.addEventListener('click', _ => { - if (currentTheme) { - setModeToAuto(); - } else { - setModeToDark(); - } - }); - $themeLight.addEventListener('click', _ => { - if (currentTheme !== 'light') { - setModeToLight(); - } else { - setModeToAuto(); + if (currentTheme === 'dark') { + setModeToDark(); } - }); - $themeDark.addEventListener('click', _ => { - if (currentTheme !== 'dark') { - setModeToDark(); - } else { - setModeToLight(); + else if (currentTheme === 'light') { + setModeToLight(); } - }); - function setModeToDark() { - document.body.classList.remove('light-theme'); - document.body.classList.add('dark-theme'); - localStorage.setItem('theme', 'dark'); - currentTheme = 'dark'; + $themeAuto.addEventListener('click', _ => { + if (currentTheme) { + setModeToAuto(); + } + else { + setModeToDark(); + } + }); + $themeLight.addEventListener('click', _ => { + if (currentTheme !== 'light') { + setModeToLight(); + } + else { + setModeToAuto(); + } + }); + $themeDark.addEventListener('click', _ => { + if (currentTheme !== 'dark') { + setModeToDark(); + } + else { + setModeToLight(); + } + }); - $themeAuto.classList.remove("selected"); - $themeLight.classList.remove("selected"); - $themeDark.classList.add("selected"); - } + function setModeToDark() { + document.body.classList.remove('light-theme'); + document.body.classList.add('dark-theme'); + localStorage.setItem('theme', 'dark'); + currentTheme = 'dark'; - function setModeToLight() { - document.body.classList.remove('dark-theme'); - document.body.classList.add('light-theme'); - localStorage.setItem('theme', 'light'); - currentTheme = 'light'; + $themeAuto.classList.remove("selected"); + $themeLight.classList.remove("selected"); + $themeDark.classList.add("selected"); + } - $themeAuto.classList.remove("selected"); - $themeLight.classList.add("selected"); - $themeDark.classList.remove("selected"); - } + function setModeToLight() { + document.body.classList.remove('dark-theme'); + document.body.classList.add('light-theme'); + localStorage.setItem('theme', 'light'); + currentTheme = 'light'; - function setModeToAuto() { - document.body.classList.remove('dark-theme'); - document.body.classList.remove('light-theme'); - if (prefersDarkTheme) { - document.body.classList.add('dark-theme'); - } else if (prefersLightTheme) { - document.body.classList.add('light-theme'); + $themeAuto.classList.remove("selected"); + $themeLight.classList.add("selected"); + $themeDark.classList.remove("selected"); } - localStorage.removeItem('theme'); - currentTheme = undefined; - $themeAuto.classList.add("selected"); - $themeLight.classList.remove("selected"); - $themeDark.classList.remove("selected"); - } + function setModeToAuto() { + document.body.classList.remove('dark-theme'); + document.body.classList.remove('light-theme'); + if (prefersDarkTheme) { + document.body.classList.add('dark-theme'); + } + else if (prefersLightTheme) { + document.body.classList.add('light-theme'); + } + localStorage.removeItem('theme'); + currentTheme = undefined; + + $themeAuto.classList.add("selected"); + $themeLight.classList.remove("selected"); + $themeDark.classList.remove("selected"); + } })(); diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index 705d398e..3cde8dad 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -64,10 +64,13 @@ class PeersUI { } _loadSavedDisplayName() { - this._getSavedDisplayName().then(displayName => { - console.log("Retrieved edited display name:", displayName) - if (displayName) Events.fire('self-display-name-changed', displayName); - }); + this._getSavedDisplayName() + .then(displayName => { + console.log("Retrieved edited display name:", displayName) + if (displayName) { + Events.fire('self-display-name-changed', displayName); + } + }); } _onDisplayName(displayName){ @@ -104,7 +107,8 @@ class PeersUI { if (this.$discoveryWrapper.querySelectorAll('div:last-of-type > span[hidden]').length < 2) { this.$discoveryWrapper.classList.remove('row'); this.$discoveryWrapper.classList.add('column'); - } else { + } + else { this.$discoveryWrapper.classList.remove('column'); this.$discoveryWrapper.classList.add('row'); } @@ -147,7 +151,8 @@ class PeersUI { Events.fire('self-display-name-changed', newDisplayName); Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: newDisplayName}); }); - } else { + } + else { PersistentStorage.delete('editedDisplayName') .catch(_ => { console.log("This browser does not support IndexedDB. Use localStorage instead.") @@ -188,8 +193,12 @@ class PeersUI { this._changePeerDisplayName(e.detail.peerId, e.detail.displayName); } + _isAnyDialogShown() { + return document.querySelectorAll('x-dialog[show]').length === 0; + } + _onKeyDown(e) { - if (document.querySelectorAll('x-dialog[show]').length === 0 && window.pasteMode.activated && e.code === "Escape") { + if (this._isAnyDialogShown() && window.pasteMode.activated && e.code === "Escape") { Events.fire('deactivate-paste-mode'); } @@ -245,7 +254,8 @@ class PeersUI { _evaluateOverflowing() { if (this.$xPeers.clientHeight < this.$xPeers.scrollHeight) { this.$xPeers.classList.add('overflowing'); - } else { + } + else { this.$xPeers.classList.remove('overflowing'); } } @@ -319,9 +329,11 @@ class PeersUI { if (files.length === 1) { descriptor = `${files[0].name}`; - } else if (files.length > 1) { + } + else if (files.length > 1) { descriptor = `${files[0].name}
${andOtherFiles}`; - } else { + } + else { descriptor = sharedText; } @@ -380,7 +392,8 @@ class PeersUI { files: files, to: peerId }); - } else if (text.length > 0) { + } + else if (text.length > 0) { Events.fire('send-text', { text: text, to: peerId @@ -408,7 +421,8 @@ class PeerUI { let input = ''; if (window.pasteMode.activated) { title = Localization.getTranslation("peer-ui.click-to-send-paste-mode", null, {descriptor: window.pasteMode.descriptor}); - } else { + } + else { title = Localization.getTranslation("peer-ui.click-to-send"); input = ''; } @@ -500,7 +514,8 @@ class PeerUI { this.$el.addEventListener('contextmenu', this._callbackContextMenu); this.$el.addEventListener('touchstart', this._callbackTouchStart); this.$el.addEventListener('touchend', this._callbackTouchEnd); - } else { + } + else { // Remove Events Normal Mode this.$el.removeEventListener('click', this._callbackClickSleep); this.$el.removeEventListener('touchstart', this._callbackTouchStartSleep); @@ -568,7 +583,8 @@ class PeerUI { const $progress = this.$el.querySelector('.progress'); if (0.5 < progress && progress < 1) { $progress.classList.add('over50'); - } else { + } + else { $progress.classList.remove('over50'); } if (progress < 1) { @@ -584,7 +600,8 @@ class PeerUI { this.$el.querySelector('.status').innerText = statusName; this.currentStatus = status; } - } else { + } + else { this.$el.removeAttribute('status'); this.$el.querySelector('.status').innerHTML = ''; progress = 0; @@ -629,7 +646,8 @@ class PeerUI { _onTouchEnd(e) { if (Date.now() - this._touchStart < 500) { clearTimeout(this._touchTimer); - } else if (this._touchTimer) { // this was a long tap + } + else if (this._touchTimer) { // this was a long tap e.preventDefault(); Events.fire('text-recipient', { peerId: this._peer.id, @@ -643,7 +661,9 @@ class PeerUI { class Dialog { constructor(id) { this.$el = $(id); - this.$el.querySelectorAll('[close]').forEach(el => el.addEventListener('click', _ => this.hide())); + this.$el.querySelectorAll('[close]').forEach(el => { + el.addEventListener('click', _ => this.hide()) + }); this.$autoFocus = this.$el.querySelector('[autofocus]'); Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail)); @@ -701,7 +721,8 @@ class LanguageSelectDialog extends Dialog { show() { if (Localization.isSystemLocale()) { this.$languageButtons[0].focus(); - } else { + } + else { let locale = Localization.getLocale(); for (let i=0; i= 1073741824) { return Math.round(10 * bytes / 1073741824) / 10 + ' GB'; - } else if (bytes >= 1048576) { + } + else if (bytes >= 1048576) { return Math.round(bytes / 1048576) + ' MB'; - } else if (bytes > 1024) { + } + else if (bytes > 1024) { return Math.round(bytes / 1024) + ' KB'; - } else { + } + else { return bytes + ' Bytes'; } } @@ -763,7 +788,8 @@ class ReceiveDialog extends Dialog { fileOther = imagesOnly ? Localization.getTranslation("dialogs.file-other-description-image") : Localization.getTranslation("dialogs.file-other-description-file"); - } else if (files.length >= 2) { + } + 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}); @@ -847,7 +873,8 @@ class ReceiveFileDialog extends ReceiveDialog { if (Object.keys(previewElement).indexOf(mime) === -1) { resolve(false); - } else { + } + else { let element = document.createElement(previewElement[mime]); element.controls = true; element.onload = _ => { @@ -877,7 +904,8 @@ class ReceiveFileDialog extends ReceiveDialog { descriptor = imagesOnly ? Localization.getTranslation("dialogs.title-image") : Localization.getTranslation("dialogs.title-file"); - } else { + } + else { descriptor = imagesOnly ? Localization.getTranslation("dialogs.title-image-plural") : Localization.getTranslation("dialogs.title-file-plural"); @@ -939,7 +967,8 @@ class ReceiveFileDialog extends ReceiveDialog { tmpZipBtn.download = filenameDownload; tmpZipBtn.href = url; tmpZipBtn.click(); - } else { + } + else { this._downloadFilesIndividually(files); } @@ -962,7 +991,8 @@ class ReceiveFileDialog extends ReceiveDialog { setTimeout(() => { if (canShare) { this.$shareBtn.click(); - } else { + } + else { this.$downloadBtn.click(); } }, 500); @@ -971,7 +1001,8 @@ class ReceiveFileDialog extends ReceiveDialog { .then(canPreview => { if (canPreview) { console.log('the file is able to preview'); - } else { + } + else { console.log('the file is not able to preview'); } }) @@ -1134,10 +1165,12 @@ class InputKeyContainer { if (e.key === "Backspace" && previousSibling && !e.target.value) { previousSibling.value = ''; previousSibling.focus(); - } else if (e.key === "ArrowRight" && nextSibling) { + } + else if (e.key === "ArrowRight" && nextSibling) { e.preventDefault(); nextSibling.focus(); - } else if (e.key === "ArrowLeft" && previousSibling) { + } + else if (e.key === "ArrowLeft" && previousSibling) { e.preventDefault(); previousSibling.focus(); } @@ -1173,7 +1206,8 @@ class InputKeyContainer { _evaluateKeyChars() { if (this.$inputKeyContainer.querySelectorAll('input:placeholder-shown').length > 0) { this._onNotAllCharsFilled(); - } else { + } + else { this._onAllCharsFilled(); const lastCharFocused = document.activeElement === this.$inputKeyChars[this.$inputKeyChars.length - 1]; @@ -1243,7 +1277,10 @@ class PairDeviceDialog extends Dialog { _onPaste(e) { e.preventDefault(); - let pastedKey = e.clipboardData.getData("Text").replace(/\D/g,'').substring(0, 6); + let pastedKey = e.clipboardData + .getData("Text") + .replace(/\D/g,'') + .substring(0, 6); this.inputKeyContainer._onPaste(pastedKey); } @@ -1366,7 +1403,8 @@ class PairDeviceDialog extends Dialog { deviceName = $peer.ui._peer.name.deviceName; } - PersistentStorage.addRoomSecret(roomSecret, displayName, deviceName) + PersistentStorage + .addRoomSecret(roomSecret, displayName, deviceName) .then(_ => { Events.fire('notify-user', Localization.getTranslation("notifications.pairing-success")); this._evaluateNumberRoomSecrets(); @@ -1407,18 +1445,22 @@ class PairDeviceDialog extends Dialog { } _onSecretRoomDeleted(roomSecret) { - PersistentStorage.deleteRoomSecret(roomSecret).then(_ => { - this._evaluateNumberRoomSecrets(); - }); + PersistentStorage + .deleteRoomSecret(roomSecret) + .then(_ => { + this._evaluateNumberRoomSecrets(); + }); } _evaluateNumberRoomSecrets() { - PersistentStorage.getAllRoomSecrets() + PersistentStorage + .getAllRoomSecrets() .then(roomSecrets => { if (roomSecrets.length > 0) { this.$editPairedDevicesHeaderBtn.removeAttribute('hidden'); this.$footerInstructionsPairedDevices.removeAttribute('hidden'); - } else { + } + else { this.$editPairedDevicesHeaderBtn.setAttribute('hidden', ''); this.$footerInstructionsPairedDevices.setAttribute('hidden', ''); } @@ -1452,45 +1494,51 @@ class EditPairedDevicesDialog extends Dialog { const autoAcceptString = Localization.getTranslation("dialogs.auto-accept").toLowerCase(); const roomSecretsEntries = await PersistentStorage.getAllRoomSecretEntries(); - roomSecretsEntries.forEach(roomSecretsEntry => { - let $pairedDevice = document.createElement('div'); - $pairedDevice.classList = ["paired-device"]; - - $pairedDevice.innerHTML = ` -
- ${roomSecretsEntry.display_name} -
-
- ${roomSecretsEntry.device_name} -
-
- - -
` - - $pairedDevice.querySelector('input[type="checkbox"]').addEventListener('click', e => { - PersistentStorage.updateRoomSecretAutoAccept(roomSecretsEntry.secret, e.target.checked).then(roomSecretsEntry => { - Events.fire('auto-accept-updated', { - 'roomSecret': roomSecretsEntry.entry.secret, - 'autoAccept': e.target.checked - }); - }); - }); + roomSecretsEntries + .forEach(roomSecretsEntry => { + let $pairedDevice = document.createElement('div'); + $pairedDevice.classList = ["paired-device"]; - $pairedDevice.querySelector('button').addEventListener('click', e => { - PersistentStorage.deleteRoomSecret(roomSecretsEntry.secret).then(roomSecret => { - Events.fire('room-secrets-deleted', [roomSecret]); - Events.fire('evaluate-number-room-secrets'); - e.target.parentNode.parentNode.remove(); - }); - }) + $pairedDevice.innerHTML = ` +
+ ${roomSecretsEntry.display_name} +
+
+ ${roomSecretsEntry.device_name} +
+
+ + +
` + + $pairedDevice + .querySelector('input[type="checkbox"]') + .addEventListener('click', e => { + PersistentStorage.updateRoomSecretAutoAccept(roomSecretsEntry.secret, e.target.checked) + .then(roomSecretsEntry => { + Events.fire('auto-accept-updated', { + 'roomSecret': roomSecretsEntry.entry.secret, + 'autoAccept': e.target.checked + }); + }); + }); - this.$pairedDevicesWrapper.html = ""; - this.$pairedDevicesWrapper.appendChild($pairedDevice) - }) + $pairedDevice + .querySelector('button') + .addEventListener('click', e => { + PersistentStorage.deleteRoomSecret(roomSecretsEntry.secret) + .then(roomSecret => { + Events.fire('room-secrets-deleted', [roomSecret]); + Events.fire('evaluate-number-room-secrets'); + e.target.parentNode.parentNode.remove(); + }); + }) + this.$pairedDevicesWrapper.html = ""; + this.$pairedDevicesWrapper.appendChild($pairedDevice) + }) } hide() { @@ -1505,14 +1553,17 @@ class EditPairedDevicesDialog extends Dialog { } _clearRoomSecrets() { - PersistentStorage.getAllRoomSecrets() + PersistentStorage + .getAllRoomSecrets() .then(roomSecrets => { - PersistentStorage.clearRoomSecrets().finally(() => { - Events.fire('room-secrets-deleted', roomSecrets); - Events.fire('evaluate-number-room-secrets'); - Events.fire('notify-user', Localization.getTranslation("notifications.pairing-cleared")); - this.hide(); - }) + PersistentStorage + .clearRoomSecrets() + .finally(() => { + Events.fire('room-secrets-deleted', roomSecrets); + Events.fire('evaluate-number-room-secrets'); + Events.fire('notify-user', Localization.getTranslation("notifications.pairing-cleared")); + this.hide(); + }) }); } @@ -1526,9 +1577,11 @@ class EditPairedDevicesDialog extends Dialog { if (!peer || !peer._roomIds["secret"]) return; - PersistentStorage.updateRoomSecretNames(peer._roomIds["secret"], peer.name.displayName, peer.name.deviceName).then(roomSecretEntry => { - console.log(`Successfully updated DisplayName and DeviceName for roomSecretEntry ${roomSecretEntry.key}`); - }) + PersistentStorage + .updateRoomSecretNames(peer._roomIds["secret"], peer.name.displayName, peer.name.deviceName) + .then(roomSecretEntry => { + console.log(`Successfully updated DisplayName and DeviceName for roomSecretEntry ${roomSecretEntry.key}`); + }) } } @@ -1591,7 +1644,8 @@ class PublicRoomDialog extends Dialog { _onHeaderBtnClick() { if (this.roomId) { this.show(); - } else { + } + else { this._createPublicRoom(); } } @@ -1784,7 +1838,8 @@ class SendTextDialog extends Dialog { if (e.code === "Escape") { this.hide(); - } else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) { + } + else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) { if (this._textInputEmpty()) return; this._send(); } @@ -1797,7 +1852,8 @@ class SendTextDialog extends Dialog { _onChange(e) { if (this._textInputEmpty()) { this.$submit.setAttribute('disabled', ''); - } else { + } + else { this.$submit.removeAttribute('disabled'); } } @@ -1855,7 +1911,8 @@ class ReceiveTextDialog extends Dialog { if (e.code === "KeyC" && (e.ctrlKey || e.metaKey)) { await this._onCopy() this.hide(); - } else if (e.code === "Escape") { + } + else if (e.code === "Escape") { this.hide(); } } @@ -1905,7 +1962,8 @@ class ReceiveTextDialog extends Dialog { async _onCopy() { const sanitizedText = this.$text.innerText.replace(/\u00A0/gm, ' '); - navigator.clipboard.writeText(sanitizedText) + navigator.clipboard + .writeText(sanitizedText) .then(_ => { Events.fire('notify-user', Localization.getTranslation("notifications.copied-to-clipboard")); this.hide(); @@ -1939,7 +1997,8 @@ class Base64ZipDialog extends Dialog { // ?base64text=paste // base64 encoded string is ready to be pasted from clipboard this.preparePasting('text'); - } else if (base64Text === 'hash') { + } + else if (base64Text === 'hash') { // ?base64text=hash#BASE64ENCODED // base64 encoded string is url hash which is never sent to server and faster (recommended) this.processBase64Text(base64Hash) @@ -1949,7 +2008,8 @@ class Base64ZipDialog extends Dialog { }).finally(() => { this.hide(); }); - } else { + } + else { // ?base64text=BASE64ENCODED // base64 encoded string was part of url param (not recommended) this.processBase64Text(base64Text) @@ -1960,7 +2020,8 @@ class Base64ZipDialog extends Dialog { this.hide(); }); } - } else if (base64Zip) { + } + else if (base64Zip) { this.show(); if (base64Zip === "hash") { // ?base64zip=hash#BASE64ENCODED @@ -1972,7 +2033,8 @@ class Base64ZipDialog extends Dialog { }).finally(() => { this.hide(); }); - } else { + } + else { // ?base64zip=paste || ?base64zip=true this.preparePasting('files'); } @@ -1993,7 +2055,8 @@ class Base64ZipDialog extends Dialog { this.$pasteBtn.innerText = Localization.getTranslation("dialogs.base64-tap-to-paste", null, {type: translateType}); this._clickCallback = _ => this.processClipboard(type); this.$pasteBtn.addEventListener('click', _ => this._clickCallback()); - } else { + } + else { console.log("`navigator.clipboard.readText()` is not available on your browser.\nOn Firefox you can set `dom.events.asyncClipboard.readText` to true under `about:config` for convenience.") this.$pasteBtn.setAttribute('hidden', ''); this.$fallbackTextarea.setAttribute('placeholder', Localization.getTranslation("dialogs.base64-paste-to-send", null, {type: translateType})); @@ -2032,7 +2095,8 @@ class Base64ZipDialog extends Dialog { try { if (type === 'text') { await this.processBase64Text(base64); - } else { + } + else { await this.processBase64Zip(base64); } } catch(_) { @@ -2163,7 +2227,8 @@ class Notifications { if (/^((https?:\/\/|www)[abcdefghijklmnopqrstuvwxyz0123456789\-._~:\/?#\[\]@!$&'()*+,;=]+)$/.test(message.toLowerCase())) { const notification = this._notify(Localization.getTranslation("notifications.link-received", null, {name: peerDisplayName}), message); this._bind(notification, _ => window.open(message, '_blank', "noreferrer")); - } else { + } + else { const notification = this._notify(Localization.getTranslation("notifications.message-received", null, {name: peerDisplayName}), message); this._bind(notification, _ => this._copyText(message, notification)); } @@ -2182,13 +2247,15 @@ class Notifications { let title; if (files.length === 1) { title = `${files[0].name}`; - } else { + } + else { let fileOther; if (files.length === 2) { fileOther = imagesOnly ? Localization.getTranslation("dialogs.file-other-description-image") : Localization.getTranslation("dialogs.file-other-description-file"); - } else { + } + 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}); @@ -2217,17 +2284,19 @@ class Notifications { descriptor = imagesOnly ? Localization.getTranslation("dialogs.title-image") : Localization.getTranslation("dialogs.title-file"); - } else { + } + else { descriptor = imagesOnly ? Localization.getTranslation("dialogs.title-image-plural") : Localization.getTranslation("dialogs.title-file-plural"); } - let title = Localization.getTranslation("notifications.request-title", null, { - name: displayName, - count: request.header.length, - descriptor: descriptor.toLowerCase() - }); + let title = Localization + .getTranslation("notifications.request-title", null, { + name: displayName, + count: request.header.length, + descriptor: descriptor.toLowerCase() + }); const notification = this._notify(title, Localization.getTranslation("notifications.click-to-show")); } @@ -2242,17 +2311,23 @@ class Notifications { if (await navigator.clipboard.writeText(message)) { notification.close(); this._notify(Localization.getTranslation("notifications.copied-text")); - } else { + } + else { this._notify(Localization.getTranslation("notifications.copied-text-error")); } } _bind(notification, handler) { if (notification.then) { - notification.then(_ => serviceWorker.getNotifications().then(_ => { - serviceWorker.addEventListener('notificationclick', handler); - })); - } else { + notification.then(_ => { + serviceWorker + .getNotifications() + .then(_ => { + serviceWorker.addEventListener('notificationclick', handler); + }) + }); + } + else { notification.onclick = handler; } } @@ -2291,14 +2366,17 @@ class WebShareTargetUI { if (url) { shareTargetText = url; // we share only the link - no text. - } else if (title && text) { + } + else if (title && text) { shareTargetText = title + '\r\n' + text; - } else { + } + else { shareTargetText = title + text; } Events.fire('activate-paste-mode', {files: [], text: shareTargetText}) - } else if (share_target_type === "files") { + } + else if (share_target_type === "files") { let openRequest = window.indexedDB.open('pairdrop_store') openRequest.onsuccess = e => { const db = e.target.result; @@ -2820,7 +2898,8 @@ const pairDrop = new PairDrop(); const localization = new Localization(); if ('serviceWorker' in navigator) { - navigator.serviceWorker.register('/service-worker.js') + navigator.serviceWorker + .register('/service-worker.js') .then(serviceWorker => { console.log('Service Worker registered'); window.serviceWorker = serviceWorker diff --git a/public_included_ws_fallback/scripts/util.js b/public_included_ws_fallback/scripts/util.js index 740325bf..a6cae64b 100644 --- a/public_included_ws_fallback/scripts/util.js +++ b/public_included_ws_fallback/scripts/util.js @@ -55,7 +55,8 @@ const zipper = (() => { const blobURL = URL.createObjectURL(await zipWriter.close()); zipWriter = null; return blobURL; - } else { + } + else { throw new Error("Zip file closed"); } }, @@ -64,7 +65,8 @@ const zipper = (() => { const file = new File([await zipWriter.close()], filename, {type: "application/zip"}); zipWriter = null; return file; - } else { + } + else { throw new Error("Zip file closed"); } }, diff --git a/public_included_ws_fallback/service-worker.js b/public_included_ws_fallback/service-worker.js index d344408a..54f4ebc5 100644 --- a/public_included_ws_fallback/service-worker.js +++ b/public_included_ws_fallback/service-worker.js @@ -90,16 +90,19 @@ self.addEventListener('fetch', function(event) { const share_url = await evaluateRequestData(event.request); return Response.redirect(encodeURI(share_url), 302); })()); - } else { + } + else { // Regular requests not related to Web Share Target. if (forceFetch) { event.respondWith(fromNetwork(event.request, 10000)); - } else { + } + else { event.respondWith( - fromCache(event.request).then(rsp => { - // if fromCache resolves to undefined fetch from network instead - return rsp || fromNetwork(event.request, 10000); - }) + fromCache(event.request) + .then(rsp => { + // if fromCache resolves to undefined fetch from network instead + return rsp || fromNetwork(event.request, 10000); + }) ); } } @@ -109,15 +112,16 @@ self.addEventListener('fetch', function(event) { // on activation, we clean up the previously registered service workers self.addEventListener('activate', evt => { return evt.waitUntil( - caches.keys().then(cacheNames => { - return Promise.all( - cacheNames.map(cacheName => { - if (cacheName !== cacheTitle) { - return caches.delete(cacheName); - } - }) - ); - }) + caches.keys() + .then(cacheNames => { + return Promise.all( + cacheNames.map(cacheName => { + if (cacheName !== cacheTitle) { + return caches.delete(cacheName); + } + }) + ); + }) ) } ); @@ -157,7 +161,8 @@ const evaluateRequestData = function (request) { DBOpenRequest.onerror = _ => { resolve(pairDropUrl); } - } else { + } + else { let urlArgument = '?share-target=text'; if (title) urlArgument += `&title=${title}`; From cb72edef20643bf5ff29d7bccbb9a6aa6b1ee712 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 2 Nov 2023 03:17:20 +0100 Subject: [PATCH 08/97] Modularize server into multiple files --- Dockerfile | 2 +- index.js | 897 -------------------------------------------- package.json | 7 +- server/helper.js | 65 ++++ server/index.js | 114 ++++++ server/peer.js | 200 ++++++++++ server/server.js | 73 ++++ server/ws-server.js | 466 +++++++++++++++++++++++ 8 files changed, 923 insertions(+), 901 deletions(-) delete mode 100644 index.js create mode 100644 server/helper.js create mode 100644 server/index.js create mode 100644 server/peer.js create mode 100644 server/server.js create mode 100644 server/ws-server.js diff --git a/Dockerfile b/Dockerfile index 74142abc..43c97acb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,4 +16,4 @@ EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD wget --quiet --tries=1 --spider http://localhost:3000 || exit 1 -ENTRYPOINT ["node", "index.js"] \ No newline at end of file +ENTRYPOINT ["npm", "start"] \ No newline at end of file diff --git a/index.js b/index.js deleted file mode 100644 index 0a665353..00000000 --- a/index.js +++ /dev/null @@ -1,897 +0,0 @@ -const process = require('process') -const crypto = require('crypto') -const {spawn} = require('child_process') -const WebSocket = require('ws'); -const fs = require('fs'); -const parser = require('ua-parser-js'); -const { uniqueNamesGenerator, animals, colors } = require('unique-names-generator'); -const express = require('express'); -const RateLimit = require('express-rate-limit'); -const http = require('http'); - -// Handle SIGINT -process.on('SIGINT', () => { - console.info("SIGINT Received, exiting...") - process.exit(0) -}) - -// Handle SIGTERM -process.on('SIGTERM', () => { - console.info("SIGTERM Received, exiting...") - process.exit(0) -}) - -// Handle APP ERRORS -process.on('uncaughtException', (error, origin) => { - console.log('----- Uncaught exception -----') - console.log(error) - console.log('----- Exception origin -----') - console.log(origin) -}) -process.on('unhandledRejection', (reason, promise) => { - console.log('----- Unhandled Rejection at -----') - console.log(promise) - console.log('----- Reason -----') - console.log(reason) -}) - -// Arguments for deployment with Docker and Node.js -const DEBUG_MODE = process.env.DEBUG_MODE === "true"; -const PORT = process.env.PORT || 3000; -const WS_FALLBACK = process.argv.includes('--include-ws-fallback') || process.env.WS_FALLBACK === "true"; -const IPV6_LOCALIZE = parseInt(process.env.IPV6_LOCALIZE) || false; -const RTC_CONFIG = process.env.RTC_CONFIG - ? JSON.parse(fs.readFileSync(process.env.RTC_CONFIG, 'utf8')) - : { - "sdpSemantics": "unified-plan", - "iceServers": [ - { - "urls": "stun:stun.l.google.com:19302" - } - ] - }; - -let rateLimit = false; -if (process.argv.includes('--rate-limit') || process.env.RATE_LIMIT === "true") { - rateLimit = 5; -} -else { - let envRateLimit = parseInt(process.env.RATE_LIMIT); - if (!isNaN(envRateLimit)) { - rateLimit = envRateLimit; - } -} -const RATE_LIMIT = rateLimit; - -// Arguments for deployment with Node.js only -const AUTO_START = process.argv.includes('--auto-restart'); -const LOCALHOST_ONLY = process.argv.includes('--localhost-only'); - -if (DEBUG_MODE) { - console.log("DEBUG_MODE is active. To protect privacy, do not use in production."); - console.debug("\n"); - console.debug("----DEBUG ENVIRONMENT VARIABLES----") - console.debug("DEBUG_MODE", DEBUG_MODE); - console.debug("PORT", PORT); - console.debug("WS_FALLBACK", WS_FALLBACK); - console.debug("IPV6_LOCALIZE", IPV6_LOCALIZE); - console.debug("RTC_CONFIG", RTC_CONFIG); - console.debug("RATE_LIMIT", RATE_LIMIT); - console.debug("AUTO_START", AUTO_START); - console.debug("LOCALHOST_ONLY", LOCALHOST_ONLY); - console.debug("\n"); -} - -if (AUTO_START) { - process.on( - 'uncaughtException', - () => { - process.once( - 'exit', - () => spawn( - process.argv.shift(), - process.argv, - { - cwd: process.cwd(), - detached: true, - stdio: 'inherit' - } - ) - ); - process.exit(); - } - ); -} - -const app = express(); - -if (RATE_LIMIT) { - const limiter = RateLimit({ - windowMs: 5 * 60 * 1000, // 5 minutes - max: 1000, // Limit each IP to 1000 requests per `window` (here, per 5 minutes) - message: 'Too many requests from this IP Address, please try again after 5 minutes.', - standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers - legacyHeaders: false, // Disable the `X-RateLimit-*` headers - }) - - app.use(limiter); - - // ensure correct client ip and not the ip of the reverse proxy is used for rate limiting - // see https://express-rate-limit.mintlify.app/guides/troubleshooting-proxy-issues - app.set('trust proxy', RATE_LIMIT); - - if (!DEBUG_MODE) { - console.log("Use DEBUG_MODE=true to find correct number for RATE_LIMIT."); - } -} - -if (WS_FALLBACK) { - app.use(express.static('public_included_ws_fallback')); -} -else { - app.use(express.static('public')); -} - -if (IPV6_LOCALIZE) { - if (!(0 < IPV6_LOCALIZE && IPV6_LOCALIZE < 8)) { - console.error("IPV6_LOCALIZE must be an integer between 1 and 7"); - return; - } - - console.log("IPv6 client IPs will be localized to", IPV6_LOCALIZE, IPV6_LOCALIZE === 1 ? "segment" : "segments"); -} - -app.use(function(req, res) { - if (DEBUG_MODE && RATE_LIMIT && req.path === "/ip") { - console.debug("----DEBUG RATE_LIMIT----") - console.debug("To find out the correct value for RATE_LIMIT go to '/ip' and ensure the returned IP-address is the IP-address of your client.") - console.debug("See https://github.com/express-rate-limit/express-rate-limit#troubleshooting-proxy-issues for more info") - console.debug("\n"); - res.send(req.ip); - } - - res.redirect('/'); -}); - -app.get('/', (req, res) => { - res.sendFile('index.html'); -}); - -const server = http.createServer(app); - -if (LOCALHOST_ONLY) { - server.listen(PORT, '127.0.0.1'); -} -else { - server.listen(PORT); -} - -server.on('error', (err) => { - if (err.code === 'EADDRINUSE') { - console.error(err); - console.info("Error EADDRINUSE received, exiting process without restarting process..."); - process.exit(0) - } -}); - -class PairDropServer { - - constructor() { - this._wss = new WebSocket.Server({ server }); - this._wss.on('connection', (socket, request) => this._onConnection(new Peer(socket, request))); - - this._rooms = {}; // { roomId: peers[] } - this._roomSecrets = {}; // { pairKey: roomSecret } - - this._keepAliveTimers = {}; - - console.log('PairDrop is running on port', PORT); - } - - _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: RTC_CONFIG - }); - - // send displayName - this._send(peer, { - type: 'display-name', - displayName: peer.name.displayName, - deviceName: peer.name.deviceName, - peerId: peer.id, - peerIdHash: hasher.hashCodeSalted(peer.id) - }); - } - - _onMessage(sender, message) { - // Try to parse message - try { - message = JSON.parse(message); - } catch (e) { - return; // TODO: handle malformed JSON - } - - switch (message.type) { - case 'disconnect': - this._onDisconnect(sender); - break; - case 'pong': - this._setKeepAliveTimerToNow(sender); - break; - case 'join-ip-room': - this._joinIpRoom(sender); - break; - case 'room-secrets': - this._onRoomSecrets(sender, message); - break; - case 'room-secrets-deleted': - this._onRoomSecretsDeleted(sender, message); - break; - case 'pair-device-initiate': - this._onPairDeviceInitiate(sender); - break; - case 'pair-device-join': - this._onPairDeviceJoin(sender, message); - break; - case 'pair-device-cancel': - this._onPairDeviceCancel(sender); - break; - case 'regenerate-room-secret': - this._onRegenerateRoomSecret(sender, message); - 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: - this._signalAndRelay(sender, message); - } - } - - _signalAndRelay(sender, message) { - const room = message.roomType === 'ip' - ? sender.ip - : message.roomId; - - // relay message to recipient - if (message.to && Peer.isValidUuid(message.to) && this._rooms[room]) { - const recipient = this._rooms[room][message.to]; - delete message.to; - // add sender - message.sender = { - id: sender.id, - rtcSupported: sender.rtcSupported - }; - this._send(recipient, message); - } - } - - _onDisconnect(sender) { - this._disconnect(sender); - } - - _disconnect(sender) { - this._removePairKey(sender.pairKey); - sender.pairKey = null; - - this._cancelKeepAlive(sender); - delete this._keepAliveTimers[sender.id]; - - this._leaveIpRoom(sender, true); - this._leaveAllSecretRooms(sender, true); - this._leavePublicRoom(sender, true); - - sender.socket.terminate(); - } - - _onRoomSecrets(sender, message) { - if (!message.roomSecrets) return; - - const roomSecrets = message.roomSecrets.filter(roomSecret => { - return /^[\x00-\x7F]{64,256}$/.test(roomSecret); - }) - - if (!roomSecrets) return; - - this._joinSecretRooms(sender, roomSecrets); - } - - _onRoomSecretsDeleted(sender, message) { - for (let i = 0; i 5 * timeout) { - // Disconnect peer if unresponsive for 10s - this._disconnect(peer); - return; - } - - this._send(peer, { type: 'ping' }); - - this._keepAliveTimers[peer.id].timer = setTimeout(() => this._keepAlive(peer), timeout); - } - - _cancelKeepAlive(peer) { - if (this._keepAliveTimers[peer.id]?.timer) { - clearTimeout(this._keepAliveTimers[peer.id].timer); - } - } - - _setKeepAliveTimerToNow(peer) { - if (this._keepAliveTimers[peer.id]?.lastBeat) { - this._keepAliveTimers[peer.id].lastBeat = Date.now(); - } - } -} - - - -class Peer { - - constructor(socket, request) { - // set socket - this.socket = socket; - - // set remote ip - this._setIP(request); - - // set peer id - this._setPeerId(request); - - // is WebRTC supported ? - this.rtcSupported = request.url.indexOf('webrtc') > -1; - - // set name - this._setName(request); - - this.requestRate = 0; - - this.roomSecrets = []; - this.roomKey = null; - - 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) { - if (request.headers['cf-connecting-ip']) { - this.ip = request.headers['cf-connecting-ip'].split(/\s*,\s*/)[0]; - } - else if (request.headers['x-forwarded-for']) { - this.ip = request.headers['x-forwarded-for'].split(/\s*,\s*/)[0]; - } - else { - this.ip = request.connection.remoteAddress; - } - - // remove the prefix used for IPv4-translated addresses - if (this.ip.substring(0,7) === "::ffff:") - this.ip = this.ip.substring(7); - - let ipv6_was_localized = false; - if (IPV6_LOCALIZE && this.ip.includes(':')) { - this.ip = this.ip.split(':',IPV6_LOCALIZE).join(':'); - ipv6_was_localized = true; - } - - if (DEBUG_MODE) { - 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']); - if (ipv6_was_localized) { - console.debug("IPv6 client IP was localized to", IPV6_LOCALIZE, IPV6_LOCALIZE > 1 ? "segments" : "segment"); - } - 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)) { - this.ip = '127.0.0.1'; - } - } - - ipIsPrivate(ip) { - // if ip is IPv4 - if (!ip.includes(":")) { - // 10.0.0.0 - 10.255.255.255 || 172.16.0.0 - 172.31.255.255 || 192.168.0.0 - 192.168.255.255 - return /^(10)\.(.*)\.(.*)\.(.*)$/.test(ip) || /^(172)\.(1[6-9]|2[0-9]|3[0-1])\.(.*)\.(.*)$/.test(ip) || /^(192)\.(168)\.(.*)\.(.*)$/.test(ip) - } - - // else: ip is IPv6 - const firstWord = ip.split(":").find(el => !!el); //get first not empty word - - // The original IPv6 Site Local addresses (fec0::/10) are deprecated. Range: fec0 - feff - if (/^fe[c-f][0-f]$/.test(firstWord)) - return true; - - // These days Unique Local Addresses (ULA) are used in place of Site Local. - // Range: fc00 - fcff - else if (/^fc[0-f]{2}$/.test(firstWord)) - return true; - - // Range: fd00 - fcff - else if (/^fd[0-f]{2}$/.test(firstWord)) - return true; - - // Link local addresses (prefixed with fe80) are not routable - else if (firstWord === "fe80") - return true; - - // Discard Prefix - else if (firstWord === "100") - return true; - - // Any other IP address is not Unique Local Address (ULA) - return false; - } - - _setPeerId(request) { - const searchParams = new URL(request.url, "http://server").searchParams; - let peerId = searchParams.get("peer_id"); - let peerIdHash = searchParams.get("peer_id_hash"); - if (peerId && Peer.isValidUuid(peerId) && this.isPeerIdHashValid(peerId, peerIdHash)) { - this.id = peerId; - } - else { - this.id = crypto.randomUUID(); - } - } - - toString() { - return `` - } - - _setName(req) { - let ua = parser(req.headers['user-agent']); - - - let deviceName = ''; - - if (ua.os && ua.os.name) { - deviceName = ua.os.name.replace('Mac OS', 'Mac') + ' '; - } - - if (ua.device.model) { - deviceName += ua.device.model; - } - else { - deviceName += ua.browser.name; - } - - if(!deviceName) - deviceName = 'Unknown Device'; - - const displayName = uniqueNamesGenerator({ - length: 2, - separator: ' ', - dictionaries: [colors, animals], - style: 'capital', - seed: cyrb53(this.id) - }) - - this.name = { - model: ua.device.model, - os: ua.os.name, - browser: ua.browser.name, - type: ua.device.type, - deviceName, - displayName - }; - } - - getInfo() { - return { - id: this.id, - name: this.name, - rtcSupported: this.rtcSupported - } - } - - static isValidUuid(uuid) { - return /^([0-9]|[a-f]){8}-(([0-9]|[a-f]){4}-){3}([0-9]|[a-f]){12}$/.test(uuid); - } - - isPeerIdHashValid(peerId, peerIdHash) { - return peerIdHash === hasher.hashCodeSalted(peerId); - } - - addRoomSecret(roomSecret) { - if (!(roomSecret in this.roomSecrets)) { - this.roomSecrets.push(roomSecret); - } - } - - removeRoomSecret(roomSecret) { - if (roomSecret in this.roomSecrets) { - delete this.roomSecrets[roomSecret]; - } - } -} - -const hasher = (() => { - let password; - return { - hashCodeSalted(salt) { - if (!password) { - // password is created on first call. - password = randomizer.getRandomString(128); - } - - return crypto.createHash("sha3-512") - .update(password) - .update(crypto.createHash("sha3-512").update(salt, "utf8").digest("hex")) - .digest("hex"); - } - } -})() - -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, lettersOnly = false) { - const charCodeCondition = lettersOnly - ? charCodeLettersOnly - : charCodeAllPrintableChars; - - 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 charCodeCondition(r); - }); - string += String.fromCharCode.apply(String, arr); - } - return string.substring(0, length) - } - } -})() - -/* - cyrb53 (c) 2018 bryc (github.com/bryc) - A fast and simple hash function with decent collision resistance. - Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity. - Public domain. Attribution appreciated. -*/ -const cyrb53 = function(str, seed = 0) { - let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; - for (let i = 0, ch; i < str.length; i++) { - ch = str.charCodeAt(i); - h1 = Math.imul(h1 ^ ch, 2654435761); - h2 = Math.imul(h2 ^ ch, 1597334677); - } - h1 = Math.imul(h1 ^ (h1>>>16), 2246822507) ^ Math.imul(h2 ^ (h2>>>13), 3266489909); - h2 = Math.imul(h2 ^ (h2>>>16), 2246822507) ^ Math.imul(h1 ^ (h1>>>13), 3266489909); - return 4294967296 * (2097151 & h2) + (h1>>>0); -}; - -new PairDropServer(); diff --git a/package.json b/package.json index ac41c5ef..01cc03da 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { "name": "pairdrop", "version": "1.9.4", + "type": "module", "description": "", - "main": "index.js", + "main": "server/index.js", "scripts": { - "start": "node index.js", - "start:prod": "node index.js --rate-limit --auto-restart" + "start": "node server/index.js", + "start:prod": "node server/index.js --rate-limit --auto-restart" }, "author": "", "license": "ISC", diff --git a/server/helper.js b/server/helper.js new file mode 100644 index 00000000..c8746d43 --- /dev/null +++ b/server/helper.js @@ -0,0 +1,65 @@ +import crypto from "crypto"; + +export const hasher = (() => { + let password; + return { + hashCodeSalted(salt) { + if (!password) { + // password is created on first call. + password = randomizer.getRandomString(128); + } + + return crypto.createHash("sha3-512") + .update(password) + .update(crypto.createHash("sha3-512").update(salt, "utf8").digest("hex")) + .digest("hex"); + } + } +})() + +export 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, lettersOnly = false) { + const charCodeCondition = lettersOnly + ? charCodeLettersOnly + : charCodeAllPrintableChars; + + 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 charCodeCondition(r); + }); + string += String.fromCharCode.apply(String, arr); + } + return string.substring(0, length) + } + } +})() + +/* + cyrb53 (c) 2018 bryc (github.com/bryc) + A fast and simple hash function with decent collision resistance. + Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity. + Public domain. Attribution appreciated. +*/ +export const cyrb53 = function(str, seed = 0) { + let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; + for (let i = 0, ch; i < str.length; i++) { + ch = str.charCodeAt(i); + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); + } + h1 = Math.imul(h1 ^ (h1>>>16), 2246822507) ^ Math.imul(h2 ^ (h2>>>13), 3266489909); + h2 = Math.imul(h2 ^ (h2>>>16), 2246822507) ^ Math.imul(h1 ^ (h1>>>13), 3266489909); + return 4294967296 * (2097151 & h2) + (h1>>>0); +}; \ No newline at end of file diff --git a/server/index.js b/server/index.js new file mode 100644 index 00000000..db6dd9b1 --- /dev/null +++ b/server/index.js @@ -0,0 +1,114 @@ +import {spawn} from "child_process"; +import fs from "fs"; + +import PairDropServer from "./server.js"; +import PairDropWsServer from "./ws-server.js"; + +// Handle SIGINT +process.on('SIGINT', () => { + console.info("SIGINT Received, exiting...") + process.exit(0) +}) + +// Handle SIGTERM +process.on('SIGTERM', () => { + console.info("SIGTERM Received, exiting...") + process.exit(0) +}) + +// Handle APP ERRORS +process.on('uncaughtException', (error, origin) => { + console.log('----- Uncaught exception -----') + console.log(error) + console.log('----- Exception origin -----') + console.log(origin) +}) +process.on('unhandledRejection', (reason, promise) => { + console.log('----- Unhandled Rejection at -----') + console.log(promise) + console.log('----- Reason -----') + console.log(reason) +}) + +// Evaluate arguments for deployment with Docker and Node.js +let conf = {}; +conf.debugMode = process.env.DEBUG_MODE === "true"; +conf.port = process.env.PORT || 3000; +conf.wsFallback = process.argv.includes('--include-ws-fallback') || process.env.WS_FALLBACK === "true"; +conf.rtcConfig = process.env.RTC_CONFIG + ? JSON.parse(fs.readFileSync(process.env.RTC_CONFIG, 'utf8')) + : { + "sdpSemantics": "unified-plan", + "iceServers": [ + { + "urls": "stun:stun.l.google.com:19302" + } + ] + }; + +let ipv6Localize = parseInt(process.env.IPV6_LOCALIZE) || false; +if (ipv6Localize) { + if (!(0 < ipv6Localize && ipv6Localize < 8)) { + console.error("ipv6Localize must be an integer between 1 and 7"); + process.exit(1); + } + + console.log("IPv6 client IPs will be localized to", + ipv6Localize, + ipv6Localize === 1 ? "segment" : "segments"); +} +conf.ipv6Localize = ipv6Localize; + +let rateLimit = false; +if (process.argv.includes('--rate-limit') || process.env.RATE_LIMIT === "true") { + rateLimit = 5; +} +else { + let envRateLimit = parseInt(process.env.RATE_LIMIT); + if (!isNaN(envRateLimit)) { + rateLimit = envRateLimit; + } +} +conf.rateLimit = rateLimit; + +// Evaluate arguments for deployment with Node.js only +conf.autoStart = process.argv.includes('--auto-restart'); +conf.localhostOnly = process.argv.includes('--localhost-only'); + +// Logs for debugging +if (conf.debugMode) { + console.log("DEBUG_MODE is active. To protect privacy, do not use in production."); + console.debug("\n"); + console.debug("----DEBUG ENVIRONMENT VARIABLES----") + console.debug(JSON.stringify(conf, null, 4)); + console.debug("\n"); +} + +// Start a new PairDrop instance when an uncaught exception occurs +if (conf.autoStart) { + process.on( + 'uncaughtException', + () => { + process.once( + 'exit', + () => spawn( + process.argv.shift(), + process.argv, + { + cwd: process.cwd(), + detached: true, + stdio: 'inherit' + } + ) + ); + process.exit(); + } + ); +} + +// Start server to serve client files +const pairDropServer = new PairDropServer(conf); + +// Start websocket Server +const pairDropWsServer = new PairDropWsServer(pairDropServer.server, conf); + diff --git a/server/peer.js b/server/peer.js new file mode 100644 index 00000000..5b556c5c --- /dev/null +++ b/server/peer.js @@ -0,0 +1,200 @@ +import crypto from "crypto"; +import parser from "ua-parser-js"; +import {animals, colors, uniqueNamesGenerator} from "unique-names-generator"; +import {cyrb53, hasher} from "./helper.js"; + +export default class Peer { + + constructor(socket, request, conf) { + this.conf = conf + + // set socket + this.socket = socket; + + // set remote ip + this._setIP(request); + + // set peer id + this._setPeerId(request); + + // is WebRTC supported ? + this.rtcSupported = request.url.indexOf('webrtc') > -1; + + // set name + this._setName(request); + + this.requestRate = 0; + + this.roomSecrets = []; + this.pairKey = null; + + 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) { + if (request.headers['cf-connecting-ip']) { + this.ip = request.headers['cf-connecting-ip'].split(/\s*,\s*/)[0]; + } else if (request.headers['x-forwarded-for']) { + this.ip = request.headers['x-forwarded-for'].split(/\s*,\s*/)[0]; + } else { + this.ip = request.connection.remoteAddress; + } + + // remove the prefix used for IPv4-translated addresses + if (this.ip.substring(0,7) === "::ffff:") + this.ip = this.ip.substring(7); + + let ipv6_was_localized = false; + if (this.conf.ipv6Localize && this.ip.includes(':')) { + this.ip = this.ip.split(':',this.conf.ipv6Localize).join(':'); + ipv6_was_localized = true; + } + + if (this.conf.debugMode) { + console.debug("\n"); + 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']); + if (ipv6_was_localized) { + console.debug("IPv6 client IP was localized to", this.conf.ipv6Localize, this.conf.ipv6Localize > 1 ? "segments" : "segment"); + } + 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)) { + this.ip = '127.0.0.1'; + } + } + + ipIsPrivate(ip) { + // if ip is IPv4 + if (!ip.includes(":")) { + // 10.0.0.0 - 10.255.255.255 || 172.16.0.0 - 172.31.255.255 || 192.168.0.0 - 192.168.255.255 + return /^(10)\.(.*)\.(.*)\.(.*)$/.test(ip) || /^(172)\.(1[6-9]|2[0-9]|3[0-1])\.(.*)\.(.*)$/.test(ip) || /^(192)\.(168)\.(.*)\.(.*)$/.test(ip) + } + + // else: ip is IPv6 + const firstWord = ip.split(":").find(el => !!el); //get first not empty word + + if (/^fe[c-f][0-f]$/.test(firstWord)) { + // The original IPv6 Site Local addresses (fec0::/10) are deprecated. Range: fec0 - feff + return true; + } + + // These days Unique Local Addresses (ULA) are used in place of Site Local. + // Range: fc00 - fcff + else if (/^fc[0-f]{2}$/.test(firstWord)) { + return true; + } + + // Range: fd00 - fcff + else if (/^fd[0-f]{2}$/.test(firstWord)) { + return true; + } + + // Link local addresses (prefixed with fe80) are not routable + else if (firstWord === "fe80") { + return true; + } + + // Discard Prefix + else if (firstWord === "100") { + return true; + } + + // Any other IP address is not Unique Local Address (ULA) + return false; + } + + _setPeerId(request) { + const searchParams = new URL(request.url, "http://server").searchParams; + let peerId = searchParams.get("peer_id"); + let peerIdHash = searchParams.get("peer_id_hash"); + if (peerId && Peer.isValidUuid(peerId) && this.isPeerIdHashValid(peerId, peerIdHash)) { + this.id = peerId; + } else { + this.id = crypto.randomUUID(); + } + } + + _setName(req) { + let ua = parser(req.headers['user-agent']); + + let deviceName = ''; + + if (ua.os && ua.os.name) { + deviceName = ua.os.name.replace('Mac OS', 'Mac') + ' '; + } + + if (ua.device.model) { + deviceName += ua.device.model; + } else { + deviceName += ua.browser.name; + } + + if (!deviceName) { + deviceName = 'Unknown Device'; + } + + const displayName = uniqueNamesGenerator({ + length: 2, + separator: ' ', + dictionaries: [colors, animals], + style: 'capital', + seed: cyrb53(this.id) + }) + + this.name = { + model: ua.device.model, + os: ua.os.name, + browser: ua.browser.name, + type: ua.device.type, + deviceName, + displayName + }; + } + + getInfo() { + return { + id: this.id, + name: this.name, + rtcSupported: this.rtcSupported + } + } + + static isValidUuid(uuid) { + return /^([0-9]|[a-f]){8}-(([0-9]|[a-f]){4}-){3}([0-9]|[a-f]){12}$/.test(uuid); + } + + isPeerIdHashValid(peerId, peerIdHash) { + return peerIdHash === hasher.hashCodeSalted(peerId); + } + + addRoomSecret(roomSecret) { + if (!(roomSecret in this.roomSecrets)) { + this.roomSecrets.push(roomSecret); + } + } + + removeRoomSecret(roomSecret) { + if (roomSecret in this.roomSecrets) { + delete this.roomSecrets[roomSecret]; + } + } +} \ No newline at end of file diff --git a/server/server.js b/server/server.js new file mode 100644 index 00000000..02c56432 --- /dev/null +++ b/server/server.js @@ -0,0 +1,73 @@ +import express from "express"; +import RateLimit from "express-rate-limit"; +import {fileURLToPath} from "url"; +import path, {dirname} from "path"; +import http from "http"; + +export default class PairDropServer { + + constructor(conf) { + const app = express(); + + if (conf.rateLimit) { + const limiter = RateLimit({ + windowMs: 5 * 60 * 1000, // 5 minutes + max: 1000, // Limit each IP to 1000 requests per `window` (here, per 5 minutes) + message: 'Too many requests from this IP Address, please try again after 5 minutes.', + standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers + legacyHeaders: false, // Disable the `X-RateLimit-*` headers + }) + + app.use(limiter); + // ensure correct client ip and not the ip of the reverse proxy is used for rate limiting + // see https://express-rate-limit.mintlify.app/guides/troubleshooting-proxy-issues + + app.set('trust proxy', conf.rateLimit); + + if (!conf.debugMode) { + console.log("Use DEBUG_MODE=true to find correct number for RATE_LIMIT."); + } + } + + const __filename = fileURLToPath(import.meta.url); + const __dirname = dirname(__filename); + + const publicPathAbs = conf.wsFallback + ? path.join(__dirname, '../public_included_ws_fallback') + : path.join(__dirname, '../public'); + + app.use(express.static(publicPathAbs)); + + app.use((req, res) => { + if (conf.debugMode && conf.rateLimit && req.path === "/ip") { + console.debug("\n"); + console.debug("----DEBUG RATE_LIMIT----") + console.debug("To find out the correct value for RATE_LIMIT go to '/ip' and ensure the returned IP-address is the IP-address of your client.") + console.debug("See https://github.com/express-rate-limit/express-rate-limit#troubleshooting-proxy-issues for more info") + res.send(req.ip); + } + + res.redirect(301, '/'); + }); + + app.get('/', (req, res) => { + res.sendFile('index.html'); + console.log(`Serving client files from:\n${publicPathAbs}`) + }); + + const hostname = conf.localhostOnly ? '127.0.0.1' : null; + const server = http.createServer(app); + + server.listen(conf.port, hostname); + + server.on('error', (err) => { + if (err.code === 'EADDRINUSE') { + console.error(err); + console.info("Error EADDRINUSE received, exiting process without restarting process..."); + process.exit(1) + } + }); + + this.server = server + } +} \ No newline at end of file diff --git a/server/ws-server.js b/server/ws-server.js new file mode 100644 index 00000000..3779f83e --- /dev/null +++ b/server/ws-server.js @@ -0,0 +1,466 @@ +import {WebSocketServer} from "ws"; +import crypto from "crypto" + +import Peer from "./peer.js"; +import {hasher, randomizer} from "./helper.js"; + +export default class PairDropWsServer { + + constructor(server, conf) { + this._conf = conf + + this._rooms = {}; // { roomId: peers[] } + + this._roomSecrets = {}; // { pairKey: roomSecret } + this._keepAliveTimers = {}; + + this._wss = new WebSocketServer({ server }); + this._wss.on('connection', (socket, request) => this._onConnection(new Peer(socket, request, conf))); + console.log('\nPairDrop is running on port', this._conf.port); + } + + _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: this._conf.rtcConfig + }); + + // send displayName + this._send(peer, { + type: 'display-name', + displayName: peer.name.displayName, + deviceName: peer.name.deviceName, + peerId: peer.id, + peerIdHash: hasher.hashCodeSalted(peer.id) + }); + } + + _onMessage(sender, message) { + // Try to parse message + try { + message = JSON.parse(message); + } catch (e) { + console.warn("WS: Received JSON is malformed"); + return; + } + + switch (message.type) { + case 'disconnect': + this._onDisconnect(sender); + break; + case 'pong': + this._setKeepAliveTimerToNow(sender); + break; + case 'join-ip-room': + this._joinIpRoom(sender); + break; + case 'room-secrets': + this._onRoomSecrets(sender, message); + break; + case 'room-secrets-deleted': + this._onRoomSecretsDeleted(sender, message); + break; + case 'pair-device-initiate': + this._onPairDeviceInitiate(sender); + break; + case 'pair-device-join': + this._onPairDeviceJoin(sender, message); + break; + case 'pair-device-cancel': + this._onPairDeviceCancel(sender); + break; + case 'regenerate-room-secret': + this._onRegenerateRoomSecret(sender, message); + 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: + this._signalAndRelay(sender, message); + } + } + + _signalAndRelay(sender, message) { + const room = message.roomType === 'ip' + ? sender.ip + : message.roomId; + + // relay message to recipient + if (message.to && Peer.isValidUuid(message.to) && this._rooms[room]) { + const recipient = this._rooms[room][message.to]; + delete message.to; + // add sender + message.sender = { + id: sender.id, + rtcSupported: sender.rtcSupported + }; + this._send(recipient, message); + } + } + + _onDisconnect(sender) { + this._disconnect(sender); + } + + _disconnect(sender) { + this._removePairKey(sender.pairKey); + sender.pairKey = null; + + this._cancelKeepAlive(sender); + delete this._keepAliveTimers[sender.id]; + + this._leaveIpRoom(sender, true); + this._leaveAllSecretRooms(sender, true); + this._leavePublicRoom(sender, true); + + sender.socket.terminate(); + } + + _onRoomSecrets(sender, message) { + if (!message.roomSecrets) return; + + const roomSecrets = message.roomSecrets.filter(roomSecret => { + return /^[\x00-\x7F]{64,256}$/.test(roomSecret); + }) + + if (!roomSecrets) return; + + this._joinSecretRooms(sender, roomSecrets); + } + + _onRoomSecretsDeleted(sender, message) { + for (let i = 0; i 5 * timeout) { + // Disconnect peer if unresponsive for 10s + this._disconnect(peer); + return; + } + + this._send(peer, { type: 'ping' }); + + this._keepAliveTimers[peer.id].timer = setTimeout(() => this._keepAlive(peer), timeout); + } + + _cancelKeepAlive(peer) { + if (this._keepAliveTimers[peer.id]?.timer) { + clearTimeout(this._keepAliveTimers[peer.id].timer); + } + } + + _setKeepAliveTimerToNow(peer) { + if (this._keepAliveTimers[peer.id]?.lastBeat) { + this._keepAliveTimers[peer.id].lastBeat = Date.now(); + } + } +} + From 3439e7f6d4b345c117f762aaf158e51a169726f8 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 8 Nov 2023 20:31:57 +0100 Subject: [PATCH 09/97] Decrease redundancy by changing the way the websocket fallback is included; Adding new env var SIGNALING_SERVER to host client files but use another server for signaling. --- docs/host-your-own.md | 25 + public/index.html | 5 + public/lang/en.json | 3 +- public/scripts/network.js | 231 +- public/scripts/ui.js | 80 +- public/scripts/util.js | 45 + public/styles.css | 36 +- .../android-chrome-192x192-maskable.png | Bin 8512 -> 0 bytes .../images/android-chrome-192x192.png | Bin 16490 -> 0 bytes .../android-chrome-512x512-maskable.png | Bin 31075 -> 0 bytes .../images/android-chrome-512x512.png | Bin 53321 -> 0 bytes .../images/apple-touch-icon.png | Bin 13729 -> 0 bytes .../images/favicon-96x96-notification.png | Bin 7181 -> 0 bytes .../images/favicon-96x96.png | Bin 7137 -> 0 bytes .../images/logo_blue_512x512.png | Bin 33906 -> 0 bytes .../images/logo_transparent_128x128.png | Bin 10568 -> 0 bytes .../images/logo_transparent_512x512.png | Bin 53321 -> 0 bytes .../images/logo_transparent_white_512x512.png | Bin 26049 -> 0 bytes .../images/logo_white_512x512.png | Bin 31289 -> 0 bytes .../images/mstile-150x150.png | Bin 3577 -> 0 bytes .../images/pairdrop_screenshot_mobile_1.png | Bin 237780 -> 0 bytes .../images/pairdrop_screenshot_mobile_2.png | Bin 237110 -> 0 bytes .../images/pairdrop_screenshot_mobile_3.png | Bin 1087772 -> 0 bytes .../images/pairdrop_screenshot_mobile_4.png | Bin 237059 -> 0 bytes .../images/pairdrop_screenshot_mobile_5.png | Bin 1832450 -> 0 bytes .../images/pairdrop_screenshot_mobile_6.png | Bin 184729 -> 0 bytes .../images/pairdrop_screenshot_mobile_7.png | Bin 206125 -> 0 bytes .../images/safari-pinned-tab.svg | 251 -- .../images/snapdrop-graphics.sketch | Bin 930794 -> 0 bytes public_included_ws_fallback/index.html | 613 ---- public_included_ws_fallback/lang/ar.json | 159 - public_included_ws_fallback/lang/de.json | 167 - public_included_ws_fallback/lang/en.json | 165 - public_included_ws_fallback/lang/es.json | 165 - public_included_ws_fallback/lang/fr.json | 160 - public_included_ws_fallback/lang/id.json | 165 - public_included_ws_fallback/lang/it.json | 165 - public_included_ws_fallback/lang/ja.json | 165 - public_included_ws_fallback/lang/nb.json | 138 - public_included_ws_fallback/lang/nl.json | 159 - public_included_ws_fallback/lang/ro.json | 165 - public_included_ws_fallback/lang/ru.json | 167 - public_included_ws_fallback/lang/tr.json | 25 - public_included_ws_fallback/lang/zh-CN.json | 166 - public_included_ws_fallback/manifest.json | 281 -- public_included_ws_fallback/robots.txt | 2 - .../scripts/NoSleep.min.js | 2 - .../scripts/QRCode.min.js | 2 - .../scripts/localization.js | 176 - .../scripts/network.js | 1299 -------- public_included_ws_fallback/scripts/theme.js | 83 - public_included_ws_fallback/scripts/ui.js | 2920 ----------------- public_included_ws_fallback/scripts/util.js | 438 --- .../scripts/zip.min.js | 1 - public_included_ws_fallback/service-worker.js | 175 - public_included_ws_fallback/sounds/blop.mp3 | Bin 1992 -> 0 bytes public_included_ws_fallback/sounds/blop.ogg | Bin 5802 -> 0 bytes public_included_ws_fallback/styles.css | 1619 --------- server/index.js | 56 +- server/peer.js | 9 +- server/server.js | 29 +- server/ws-server.js | 28 +- 62 files changed, 439 insertions(+), 10101 deletions(-) delete mode 100644 public_included_ws_fallback/images/android-chrome-192x192-maskable.png delete mode 100644 public_included_ws_fallback/images/android-chrome-192x192.png delete mode 100644 public_included_ws_fallback/images/android-chrome-512x512-maskable.png delete mode 100644 public_included_ws_fallback/images/android-chrome-512x512.png delete mode 100644 public_included_ws_fallback/images/apple-touch-icon.png delete mode 100644 public_included_ws_fallback/images/favicon-96x96-notification.png delete mode 100644 public_included_ws_fallback/images/favicon-96x96.png delete mode 100644 public_included_ws_fallback/images/logo_blue_512x512.png delete mode 100644 public_included_ws_fallback/images/logo_transparent_128x128.png delete mode 100644 public_included_ws_fallback/images/logo_transparent_512x512.png delete mode 100644 public_included_ws_fallback/images/logo_transparent_white_512x512.png delete mode 100644 public_included_ws_fallback/images/logo_white_512x512.png delete mode 100644 public_included_ws_fallback/images/mstile-150x150.png delete mode 100644 public_included_ws_fallback/images/pairdrop_screenshot_mobile_1.png delete mode 100644 public_included_ws_fallback/images/pairdrop_screenshot_mobile_2.png delete mode 100644 public_included_ws_fallback/images/pairdrop_screenshot_mobile_3.png delete mode 100644 public_included_ws_fallback/images/pairdrop_screenshot_mobile_4.png delete mode 100644 public_included_ws_fallback/images/pairdrop_screenshot_mobile_5.png delete mode 100644 public_included_ws_fallback/images/pairdrop_screenshot_mobile_6.png delete mode 100644 public_included_ws_fallback/images/pairdrop_screenshot_mobile_7.png delete mode 100644 public_included_ws_fallback/images/safari-pinned-tab.svg delete mode 100644 public_included_ws_fallback/images/snapdrop-graphics.sketch delete mode 100644 public_included_ws_fallback/index.html delete mode 100644 public_included_ws_fallback/lang/ar.json delete mode 100644 public_included_ws_fallback/lang/de.json delete mode 100644 public_included_ws_fallback/lang/en.json delete mode 100644 public_included_ws_fallback/lang/es.json delete mode 100644 public_included_ws_fallback/lang/fr.json delete mode 100644 public_included_ws_fallback/lang/id.json delete mode 100644 public_included_ws_fallback/lang/it.json delete mode 100644 public_included_ws_fallback/lang/ja.json delete mode 100644 public_included_ws_fallback/lang/nb.json delete mode 100644 public_included_ws_fallback/lang/nl.json delete mode 100644 public_included_ws_fallback/lang/ro.json delete mode 100644 public_included_ws_fallback/lang/ru.json delete mode 100644 public_included_ws_fallback/lang/tr.json delete mode 100644 public_included_ws_fallback/lang/zh-CN.json delete mode 100644 public_included_ws_fallback/manifest.json delete mode 100644 public_included_ws_fallback/robots.txt delete mode 100644 public_included_ws_fallback/scripts/NoSleep.min.js delete mode 100644 public_included_ws_fallback/scripts/QRCode.min.js delete mode 100644 public_included_ws_fallback/scripts/localization.js delete mode 100644 public_included_ws_fallback/scripts/network.js delete mode 100644 public_included_ws_fallback/scripts/theme.js delete mode 100644 public_included_ws_fallback/scripts/ui.js delete mode 100644 public_included_ws_fallback/scripts/util.js delete mode 100644 public_included_ws_fallback/scripts/zip.min.js delete mode 100644 public_included_ws_fallback/service-worker.js delete mode 100644 public_included_ws_fallback/sounds/blop.mp3 delete mode 100644 public_included_ws_fallback/sounds/blop.ogg delete mode 100644 public_included_ws_fallback/styles.css diff --git a/docs/host-your-own.md b/docs/host-your-own.md index ffa03c1f..32385a23 100644 --- a/docs/host-your-own.md +++ b/docs/host-your-own.md @@ -107,6 +107,7 @@ docker run -d \ -p 127.0.0.1:3000:3000 \ -e PUID=1000 \ -e PGID=1000 \ + -e WS_SERVER=false \ -e WS_FALLBACK=false \ -e RTC_CONFIG=false \ -e RATE_LIMIT=false \ @@ -396,6 +397,30 @@ RTC_CONFIG="rtc_config.json"
+You can host an instance that uses another signaling server +This can be useful if you don't want to trust the client files that are hosted on another instance but still want to connect to devices that use https://pairdrop.net. +### Host Websocket Server (for VPN) + +```bash +SIGNALING_SERVER="pairdrop.net" +``` + +> Default: `false` +> +> By default, clients connecting to your instance use the signaling server of your instance to connect to other devices. +> +> By using `SIGNALING_SERVER`, you can host an instance that uses another signaling server. +> +> This can be useful if you want to ensure the integrity of the client files and don't want to trust the client files that are hosted on another PairDrop instance but still want to connect to devices that use the other instance. +> E.g. host your own client files under *pairdrop.your-domain.com* but use the official signaling server under *pairdrop.net* +> This way devices connecting to *pairdrop.your-domain.com* and *pairdrop.net* can discover each other. +> +> Beware that the version of your PairDrop server is compatible with the version of the signaling server. +> +> `WS_SERVER` must be a valid url without the protocol prefix. +> Examples of valid values: `pairdrop.net`, `pairdrop.your-domain.com:3000`, `your-domain.com/pairdrop` +
+ ## Healthcheck > The Docker Image hosted on `ghcr.io` and the self-built Docker Image include a healthcheck. diff --git a/public/index.html b/public/index.html index 20c78623..671c4c92 100644 --- a/public/index.html +++ b/public/index.html @@ -108,6 +108,11 @@

+