diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 40b424b..1ffe8f3 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/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index bf479b3..9d706b6 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,3 +1,14 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# GitHub recommends pinning actions to a commit SHA. +# To get a newer version, you will need to update the SHA. +# You can also reference a tag or branch, but the action may change without warning. + +# Build a Docker image whenever it is pushed to master + name: Docker Image CI on: @@ -13,6 +24,6 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Build the Docker image run: docker build --pull . -f Dockerfile -t pairdrop diff --git a/.github/workflows/github-image.yml b/.github/workflows/github-image.yml index fb6d30d..05dcffc 100644 --- a/.github/workflows/github-image.yml +++ b/.github/workflows/github-image.yml @@ -7,6 +7,8 @@ # To get a newer version, you will need to update the SHA. # You can also reference a tag or branch, but the action may change without warning. +# Create a Docker image and push it to ghcr.io whenever a new version tag is pushed + name: GHCR Image CI on: @@ -27,16 +29,16 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup qemu - uses: docker/setup-qemu-action@v2.1.0 + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v2.5.0 + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 - name: Log in to the Container registry - uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -44,12 +46,12 @@ jobs: - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + uses: docker/metadata-action@31cebacef4805868f9ce9a0cb03ee36c32df2ac4 # v5.3.0 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - name: Build and push Docker image - uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc + uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 with: context: . platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/zip-release.yml b/.github/workflows/zip-release.yml new file mode 100644 index 0000000..115b08c --- /dev/null +++ b/.github/workflows/zip-release.yml @@ -0,0 +1,35 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# GitHub recommends pinning actions to a commit SHA. +# To get a newer version, you will need to update the SHA. +# You can also reference a tag or branch, but the action may change without warning. + +# Create a new zip file from pairdrop-cli whenever a new version tag is pushed + +name: Zip Release + +on: + push: + tags: + - "v*.*.*" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Archive Release + uses: thedoctor0/zip-release@b57d897cb5d60cb78b51a507f63fa184cfe35554 # v0.7.6 + with: + type: 'zip' + filename: 'pairdrop-cli.zip' + path: 'pairdrop-cli' + exclusions: '*.git* /*node_modules/* .editorconfig' + - name: Upload Release + uses: ncipollo/release-action@6c75be85e571768fa31b40abf38de58ba0397db5 # v1.13.0 + with: + artifacts: "pairdrop-cli.zip" + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index bd15e97..ba0f1e5 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/Dockerfile b/Dockerfile index a307a45..43c97ac 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 ["npm", "start"] \ No newline at end of file diff --git a/README.md b/README.md index 578b514..11f5250 100644 --- a/README.md +++ b/README.md @@ -67,10 +67,11 @@ Developed based on [Snapdrop](https://github.com/RobinLinus/snapdrop) * Multiple files are transferred at once with an overall progress indicator ### Send Files or Text Directly From Share Menu, Context Menu or CLI -* [Send files directly from context menu on Windows](docs/how-to.md#send-files-directly-from-context-menu-on-windows) -* [Send directly from share menu on iOS](docs/how-to.md#send-directly-from-share-menu-on-ios) -* [Send directly from share menu on Android](docs/how-to.md#send-directly-from-share-menu-on-android) -* [Send directly via command-line interface](docs/how-to.md#send-directly-via-command-line-interface) +* [Send files directly from context menu on Windows](docs/how-to.md#send-multiple-files-and-directories-directly-from-context-menu-on-windows) +* [Send files directly from context menu on Ubuntu (using Nautilus)](/docs/how-to.md#send-multiple-files-and-directories-directly-from-context-menu-on-ubuntu-using-nautilus) +* [Send files directly from share menu on iOS](docs/how-to.md#send-directly-from-share-menu-on-ios) +* [Send files directly from share menu on Android](docs/how-to.md#send-directly-from-share-menu-on-android) +* [Send files directly via command-line interface](docs/how-to.md#send-directly-via-command-line-interface) ### Other changes * Change your display name permanently to easily differentiate your devices @@ -85,7 +86,8 @@ Developed based on [Snapdrop](https://github.com/RobinLinus/snapdrop) * Lots of stability fixes (Thanks [@MWY001](https://github.com/MWY001) [@skiby7](https://github.com/skiby7) and [@willstott101](https://github.com/willstott101)) * To host PairDrop on your local network (e.g. on Raspberry Pi): [All peers connected with private IPs are discoverable by each other](https://github.com/RobinLinus/snapdrop/pull/558) * When hosting PairDrop yourself you can [set your own STUN/TURN servers](docs/host-your-own.md#specify-stunturn-servers) -* Built-in translations +* Built-in translations via [Weblate](https://hosted.weblate.org/engage/pairdrop/) +* Airy design (Thanks [@Avieshek](https://linktr.ee/avieshek/)) @@ -98,9 +100,11 @@ Developed based on [Snapdrop](https://github.com/RobinLinus/snapdrop) * [NodeJS](https://nodejs.org/en/) backend * [Progressive Web App](https://wikipedia.org/wiki/Progressive_Web_App) * [IndexedDB API](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) -* [zip.js](https://gildas-lormeau.github.io/zip.js/) -* [cyrb53](https://github.com/bryc) super fast hash function * [Weblate](https://weblate.org/) Web based localization tool +* [zip.js](https://github.com/gildas-lormeau/zip.js) JavaScript library to zip and unzip files ([BSD 3-Clause License](licenses/BSD_3-Clause-zip-js)) +* [NoSleep](https://github.com/richtr/NoSleep.js) JavaScript library to prevent display sleep and enable wake lock in any Android or iOS web browser ([MIT License](licenses/MIT-NoSleep)) +* [heic2any](https://github.com/alexcorvi/heic2any) JavaScript library to convert HEIC/HEIF images to PNG/GIF/JPEG ([MIT License](licenses/MIT-heic2any)) +* [cyrb53](https://github.com/bryc) Super fast hash function Have any questions? Read our [FAQ](docs/faq.md). diff --git a/docker-compose-coturn.yml b/docker-compose-coturn.yml index e9a05b4..9d0b0a8 100644 --- a/docker-compose-coturn.yml +++ b/docker-compose-coturn.yml @@ -1,19 +1,31 @@ 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 + 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=/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: - - "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 - 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/docker-compose.yml b/docker-compose.yml index 5ff9305..357ed85 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 c81afa1..32385a2 100644 --- a/docs/host-your-own.md +++ b/docs/host-your-own.md @@ -1,181 +1,132 @@ # 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). + +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/) + +
+ +## 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 ``` - -> 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 - -##### IPv6 Localization -```bash --e IPV6_LOCALIZE=4 -``` -> To enable Peer Discovery among IPv6 peers, you can specify a reduced number of segments \ -> of the client IPv6 address to be evaluated as the peer's IP. \ -> This can be especially useful when using Cloudflare as a proxy. -> -> 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 -```bash --e RTC_CONFIG="rtc_config.json" -``` - -> Specify the STUN/TURN servers PairDrop clients use by setting \ -> `RTC_CONFIG` to a JSON file including the configuration. \ -> You can use `pairdrop/rtc_config_example.json` as a starting point. -> -> To host your own TURN server you can follow this guide: https://gabrieltanner.org/blog/turn-server/ -> Alternatively, use a free, pre-configured TURN server like [OpenRelay]([url](https://www.metered.ca/tools/openrelay/)) -> -> Default configuration: -> ```json -> { -> "sdpSemantics": "unified-plan", -> "iceServers": [ -> { -> "urls": "stun:stun.l.google.com:19302" -> } -> ] -> } -> ``` - -##### Debug Mode -```bash --e DEBUG_MODE="true" -``` - -> Use this flag to enable debugging information about the connecting peers IP addresses. \ -> This is quite useful to check whether the [#HTTP-Server](#http-server) \ -> is configured correctly, so the auto-discovery feature works correctly. \ -> Otherwise, all clients discover each other mutually, independently of their network status. -> -> If this flag is set to `"true"` each peer that connects to the PairDrop server will produce a log to STDOUT like this: -> ``` -> ----DEBUGGING-PEER-IP-START---- -> remoteAddress: ::ffff:172.17.0.1 -> x-forwarded-for: 19.117.63.126 -> cf-connecting-ip: undefined -> PairDrop uses: 19.117.63.126 -> IP is private: false -> if IP is private, '127.0.0.1' is used instead -> ----DEBUGGING-PEER-IP-END---- -> ``` -> If the IP PairDrop uses is the public IP of your device, everything is 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!** +> This image is hosted by [linuxserver.io](https://linuxserver.io). For more information visit https://hub.docker.com/r/linuxserver/pairdrop
-### 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) +### Docker Image from GitHub Container Registry (ghcr.io) -> The Docker Image includes a healthcheck. \ -> Read more about [Docker Swarm Usage](docker-swarm-usage.md#docker-swarm-usage). +```bash +docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 ghcr.io/schlagmichdoch/pairdrop +``` + + +
### Docker Image self-built + #### Build the image + ```bash docker build --pull . -f Dockerfile -t pairdrop ``` -> A GitHub action is set up to do this step automatically. + +> A GitHub action is set up to do this step automatically at the release of new versions. > > `--pull` ensures always the latest node image is used. #### Run the image + ```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 --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 -it pairdrop ``` -> 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 specify options replace `npm run start:prod` \ -> according to [the documentation below.](#options--flags-1) +> To prevent bypassing the proxy by reaching the docker container directly, +> `127.0.0.1` is specified in the run command. -> The Docker Image includes a Healthcheck. \ -Read more about [Docker Swarm Usage](docker-swarm-usage.md#docker-swarm-usage). + +
+ +### 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` + +#### 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 \ + --name=pairdrop \ + --restart=unless-stopped \ + -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 \ + -e DEBUG_MODE=false \ + -e TZ=Etc/UTC \ + lscr.io/linuxserver/pairdrop +```
## 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 +134,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,56 +167,223 @@ 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 -#### Port -On Unix based systems -```bash -PORT=3010 npm start -``` -On Windows -```bash -$env:PORT=3010; npm start -``` -> Specify the port PairDrop is running on. (Default: 3000) +### Options / Flags + +These are some flags only reasonable when deploying via Node.js + +#### Port + +```bash +PORT=3000 +``` + +> Default: `3000` +> +> Environment variable to specify the port used by the Node.js server \ +> e.g. `PORT=3010 npm start` + +#### Local Run + +```bash +npm start -- --localhost-only +``` + +> 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 +npm start -- --auto-restart +``` + +> Restarts server automatically on error + +#### Production (autostart and rate-limit) + +```bash +npm run start:prod +``` + +> shortcut for `RATE_LIMIT=5 npm start -- --auto-restart` + +#### Production (autostart, rate-limit, localhost-only) + +```bash +npm run start:prod -- --localhost-only +``` + +> 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 +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 +DEBUG_MODE="true" +``` + +> 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: +> +> ``` +> ----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 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!** + + +
+ +### Rate limiting requests + +```bash +RATE_LIMIT=1 +``` + +> Default: `false` +> +> Limits clients to 1000 requests per 5 min +> +> "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 + + +
+ +### IPv6 Localization -#### IPv6 Localization ```bash IPV6_LOCALIZE=4 ``` -> 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 +> 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) + ```bash -RTC_CONFIG="rtc_config.json" npm start +WS_FALLBACK=true ``` -On Windows -```bash -$env:RTC_CONFIG="rtc_config.json"; npm start -``` -> Specify the STUN/TURN servers PairDrop clients use by setting `RTC_CONFIG` \ -> to a JSON file including the configuration. \ -> You can use `pairdrop/rtc_config_example.json` as a starting point. + +> 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 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! > -> To host your own TURN server you can follow this guide: \ -> https://gabrieltanner.org/blog/turn-server/ +> 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 + +```bash +RTC_CONFIG="rtc_config.json" +``` + +> 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", @@ -273,109 +395,51 @@ $env:RTC_CONFIG="rtc_config.json"; npm start > } > ``` -#### Debug Mode -On Unix based systems +
+ +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 -DEBUG_MODE="true" npm start -``` -On Windows -```bash -$env:DEBUG_MODE="true"; npm start +SIGNALING_SERVER="pairdrop.net" ``` -> Use this flag to enable debugging info about the connecting peers IP addresses. \ -> This is quite useful to check whether the [#HTTP-Server](#http-server) \ -> is configured correctly, so the auto discovery feature works correctly. \ -> Otherwise, all clients discover each other mutually, independently of their network status. +> Default: `false` > -> 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. \ ->Find your devices public IP by visiting https://www.whatismyip.com/. -> -> Preserve your clients' privacy. **Never use this flag in production!** - - -### Options / Flags -#### Local Run -```bash -npm start -- --localhost-only -``` -> Only allow connections from localhost. +> By default, clients connecting to your instance use the signaling server of your instance to connect to other devices. > -> You must use a server proxy to set the X-Forwarded-For \ -> to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). -> -> Use this when deploying PairDrop with node to prevent \ -> bypassing the proxy by reaching the Docker container directly. - -#### Automatic restart on error -```bash -npm start -- --auto-restart -``` -> Restarts server automatically on error - -
- -#### Rate limiting requests -```bash -npm start -- --rate-limit -``` -> Limits clients to 1000 requests per 5 min - -
- -#### Websocket Fallback (for VPN) -```bash -npm start -- --include-ws-fallback -``` -> Provides PairDrop to clients with an included websocket fallback \ -> if the peer to peer WebRTC connection is not available to the client. -> -> 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/)). +> By using `SIGNALING_SERVER`, you can host an instance that uses another signaling server. > -> **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. - +> 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`
-#### Production (autostart and rate-limit) -```bash -npm run start:prod -``` +## Healthcheck -#### Production (autostart, rate-limit, localhost-only and websocket fallback for VPN) -```bash -npm run start:prod -- --localhost-only --include-ws-fallback -``` -> To prevent connections to the node server from bypassing \ -> the proxy server you should always use "--localhost-only" on production. +> 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 +473,7 @@ server { ``` #### Automatic http to https redirect: + ``` server { listen 80; @@ -437,14 +502,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 +526,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 +545,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 +560,120 @@ 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 +
+ +## 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 + 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 +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/docs/how-to.md b/docs/how-to.md index f38d5f2..7db17fc 100644 --- a/docs/how-to.md +++ b/docs/how-to.md @@ -1,84 +1,120 @@ # How-To -## Send files directly from context menu on Windows -### Registering to open files with PairDrop -The [File Handling API](https://learn.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/handle-files) is implemented - -This is still experimental and must be enabled via a flag **before** the PWA is installed to Windows. -1. [Enabled feature in Edge](https://learn.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/handle-files#enable-the-file-handling-api) -2. Install PairDrop by visiting https://pairdrop.net/ with the Edge web browser and install it as described [here](faq.md#help--i-cant-install-the-pwa-). -3. You are done! You can now send most files one at a time via PairDrop: - - _context menu > Open with > PairDrop_ - -[//]: # (Todo: add screenshots) - -### Sending multiple files to PairDrop -Outstandingly, it is also possible to send multiple files to PairDrop \ -via the context menu by adding PairDrop to the `Send to` menu: -1. [Register PairDrop as file handler](#registering-to-open-files-with-pairdrop) -2. Hit Windows Key+R, type: `shell:programs` and hit Enter. -3. Copy the PairDrop shortcut from the directory -4. Hit Windows Key+R, type: `shell:sendto` and hit Enter. -5. Paste the copied shortcut into the directory -6. You are done! You can now send multiple files (but no directories) directly via PairDrop: - - _context menu > Send to > PairDrop_ - -[//]: # (Todo: add screenshots) ## Send directly from share menu on iOS I created an iOS shortcut to send images, files, folder, URLs \ or text directly from the share-menu https://routinehub.co/shortcut/13990/ -[//]: # (Todo: add doku with screenshots) +[//]: # (Todo: Add screenshots) +
## Send directly from share menu on Android The [Web Share Target API](https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target) is implemented. When the PWA is installed, it will register itself to the share-menu of the device automatically. +
## Send directly via command-line interface -Send files or text with PairDrop via command-line interface. - +Send files or text with PairDrop via command-line interface. \ This opens PairDrop in the default browser where you can choose the receiver. ### Usage ```bash -$ pairdrop -h -Current domain: https://pairdrop.net/ +pairdrop -h +``` +```bash +Send files or text with PairDrop via command-line interface. +Current domain: https://pairdrop-dev.onrender.com/ Usage: -Open PairDrop: pairdrop -Send files: pairdrop file/directory -Send text: pairdrop -t "text" -Specify domain: pairdrop -d "https://pairdrop.net/" -Show this help text: pairdrop (-h|--help) +Open PairDrop: pairdrop +Send files: pairdrop file1/directory1 (file2/directory2 file3/directory3 ...) +Send text: pairdrop -t "text" +Specify domain: pairdrop -d "https://pairdrop.net/" +Show this help text: pairdrop (-h|--help) + +This pairdrop-cli version was released alongside v1.10.0 ``` -On Windows Command Prompt you need to use bash: `bash pairdrop -h` - +
### Setup Download the bash file: [pairdrop-cli/pairdrop](/pairdrop-cli/pairdrop). #### Linux -1. Put the file in a preferred folder e.g. `/usr/local/bin` -2. Make sure the bash file is executable. Otherwise, use `chmod +x pairdrop` -3. Add absolute path of the folder to PATH variable to make `pairdrop` available globally by executing - `export PATH=$PATH:/opt/pairdrop-cli` +1. Download the latest _pairdrop-cli.zip_ from the [releases page](https://github.com/schlagmichdoch/PairDrop/releases) +2. Unzip the archive to a folder of your choice e.g. `/usr/local/bin/pairdrop-cli/` +3. Make sure the bash file `/usr/local/bin/pairdrop-cli/pairdrop` is executable. Otherwise, use `chmod +x pairdrop` +4. Add absolute path of the folder to PATH variable to make `pairdrop` available globally by executing + `export PATH=$PATH:/usr/local/bin/pairdrop-cli/` + +
#### Mac 1. add bash file to `/usr/local/bin` +
+ #### Windows -1. Put file in a preferred folder e.g. `C:\Users\Public\pairdrop-cli` -2. Search for and open `Edit environment variables for your account` -3. Click `Environment Variables…` -4. Under *System Variables* select `Path` and click *Edit...* -5. Click *New*, insert the preferred folder (`C:\Users\Public\pairdrop-cli`), click *OK* until all windows are closed -6. Reopen Command prompt window +1. Download the latest _pairdrop-cli.zip_ from the [releases page](https://github.com/schlagmichdoch/PairDrop/releases) +2. Put file in a preferred folder e.g. `C:\Program Files\pairdrop-cli` +3. Search for and open `Edit environment variables for your account` +4. Click `Environment Variables…` +5. Under *System Variables* select `Path` and click *Edit...* +6. Click *New*, insert the preferred folder (`C:\Program Files\pairdrop-cli`), click *OK* until all windows are closed +7. Reopen Command prompt window + +
+ +### Requirements +As Windows cannot execute bash scripts natively, you need to install [Git Bash](https://gitforwindows.org/). +Then, you can also use pairdrop-cli from the default Windows Command Prompt \ +by using the shell file instead of the bash file: `pairdrop.sh -h` which then itself executes \ +pairdrop-cli (the bash file) via the Git Bash. + +
+ +## Send multiple files and directories directly from context menu on Windows + +### Registering to open files with PairDrop +It is possible to send multiple files with PairDrop via the context menu by adding pairdrop-cli to Windows `Send to` menu: +1. Download the latest _pairdrop-cli.zip_ from the [releases page](https://github.com/schlagmichdoch/PairDrop/releases) +2. Unzip the archive to a folder of your choice e.g. `C:\Program Files\pairdrop-cli\` +3. Copy the shortcut _send with PairDrop.lnk_ +4. Hit Windows Key+R, type: `shell:sendto` and hit Enter. +5. Paste the copied shortcut into the directory +6. Open the properties window of the shortcut and edit the link field to point to _send-with-pairdrop.ps1_ located in the folder you used in step 2: \ + `"C:\Program Files\PowerShell\7\pwsh.exe" -File "C:\Program Files\pairdrop-cli\send-with-pairdrop.ps1"` +7. You are done! You can now send multiple files and directories directly via PairDrop: + +> _context menu > Send to > PairDrop_ + +##### Requirements +As Windows cannot execute bash scripts natively, you need to install [Git Bash](https://gitforwindows.org/). + +
+ +## Send multiple files and directories directly from context menu on Ubuntu using Nautilus + +### Registering to open files with PairDrop +It is possible to send multiple files with PairDrop via the context menu by adding pairdrop-cli to Nautilus `Scripts` menu: +1. Download the latest _pairdrop-cli.zip_ from the [releases page](https://github.com/schlagmichdoch/PairDrop/releases) +2. Unzip the archive to a folder of your choice e.g. `/usr/local/bin/pairdrop-cli/` +3. Copy the shell file _send-with-pairdrop.sh_ to `/home//.local/share/nautilus/scripts/` +4. Edit the shell file and edit the variable `pathToPairDropCli` to point to the pairdrop-cli executable from step 2 (e.g. `/usr/local/bin/pairdrop-cli/pairdrop`) +5. Make sure the shell file `/home//.local/share/nautilus/scripts/send-with-pairdrop.sh` is executable. Otherwise, use `chmod +x send-with-pairdrop.sh` +6. You are done! You can now send multiple files and directories directly via PairDrop: + +> _context menu > Scripts > send-with-pairdrop.sh_ + +
+ +## File Handling API +The [File Handling API](https://learn.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/handle-files) +was implemented, but it was removed as default file associations were overwritten ([#17](https://github.com/schlagmichdoch/PairDrop/issues/17), +[#116](https://github.com/schlagmichdoch/PairDrop/issues/116) [#190](https://github.com/schlagmichdoch/PairDrop/issues/190)) +and it only worked with explicitly specified file types and not with directories at all. [< Back](/README.md) diff --git a/docs/pairdrop_screenshot_mobile.gif b/docs/pairdrop_screenshot_mobile.gif index 1813720..1eb6eb1 100644 Binary files a/docs/pairdrop_screenshot_mobile.gif and b/docs/pairdrop_screenshot_mobile.gif differ diff --git a/index.js b/index.js deleted file mode 100644 index ae50931..0000000 --- a/index.js +++ /dev/null @@ -1,852 +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) -}) - -if (process.argv.includes('--auto-restart')) { - process.on( - 'uncaughtException', - () => { - process.once( - 'exit', - () => spawn( - process.argv.shift(), - process.argv, - { - cwd: process.cwd(), - detached: true, - stdio: 'inherit' - } - ) - ); - process.exit(); - } - ); -} - -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')) { - 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 on render.com - // see https://github.com/express-rate-limit/express-rate-limit#troubleshooting-proxy-issues - app.set('trust proxy', 5); -} - -if (process.argv.includes('--include-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)) { - 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"); -} - -app.use(function(req, res) { - res.redirect('/'); -}); - -app.get('/', (req, res) => { - res.sendFile('index.html'); -}); - -const server = http.createServer(app); -const port = process.env.PORT || 3000; - -if (process.argv.includes('--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: rtcConfig - }); - - // 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) - } - }); - } - - _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_lcl && this.ip.includes(':')) { - this.ip = this.ip.split(':',ipv6_lcl).join(':'); - ipv6_was_localized = true; - } - - if (debugMode) { - console.debug("----DEBUGGING-PEER-IP-START----"); - console.debug("remoteAddress:", request.connection.remoteAddress); - console.debug("x-forwarded-for:", request.headers['x-forwarded-for']); - console.debug("cf-connecting-ip:", request.headers['cf-connecting-ip']); - if (ipv6_was_localized) - console.debug("IPv6 client IP was localized to", ipv6_lcl, ipv6_lcl > 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/licenses/BSD_3-Clause-zip-js b/licenses/BSD_3-Clause-zip-js new file mode 100644 index 0000000..4f2de22 --- /dev/null +++ b/licenses/BSD_3-Clause-zip-js @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2023, Gildas Lormeau + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/licenses/MIT-NoSleep b/licenses/MIT-NoSleep new file mode 100644 index 0000000..fa1f83a --- /dev/null +++ b/licenses/MIT-NoSleep @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) Rich Tibbett + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/licenses/MIT-heic2any b/licenses/MIT-heic2any new file mode 100644 index 0000000..5d07689 --- /dev/null +++ b/licenses/MIT-heic2any @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2020 Alex Corvi + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/package.json b/package.json index b1f029a..5ac4f10 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/pairdrop-cli/.gitignore b/pairdrop-cli/.gitignore new file mode 100644 index 0000000..e75a6be --- /dev/null +++ b/pairdrop-cli/.gitignore @@ -0,0 +1 @@ +.pairdrop-cli-config \ No newline at end of file diff --git a/pairdrop-cli/pairdrop b/pairdrop-cli/pairdrop index b5e80bd..d9edacb 100644 --- a/pairdrop-cli/pairdrop +++ b/pairdrop-cli/pairdrop @@ -12,10 +12,12 @@ help() echo echo "Usage:" echo -e "Open PairDrop:\t\t$(basename "$0")" - echo -e "Send files:\t\t$(basename "$0") file/directory" + echo -e "Send files:\t\t$(basename "$0") file1/directory1 (file2/directory2 file3/directory3 ...)" echo -e "Send text:\t\t$(basename "$0") -t \"text\"" echo -e "Specify domain:\t\t$(basename "$0") -d \"https://pairdrop.net/\"" echo -e "Show this help text:\t$(basename "$0") (-h|--help)" + echo + echo "This pairdrop-cli version was released alongside v1.10.0" } openPairDrop() @@ -36,7 +38,7 @@ openPairDrop() elif [[ $OS == "WSL" || $OS == "WSL2" ]];then powershell.exe /c "Start-Process ${url}" else - xdg-open "$url" + xdg-open "$url" > /dev/null 2>&1 fi @@ -62,7 +64,7 @@ setOs() specifyDomain() { [[ ! $1 = http* ]] || [[ ! $1 = */ ]] && echo "Incorrect format. Specify domain like https://pairdrop.net/" && exit - echo "DOMAIN=${1}" > "$CONFIGPATH" + echo "DOMAIN=${1}" > "$config_path" echo -e "Domain is now set to:\n$1\n" } @@ -87,75 +89,228 @@ sendText() exit } +escapePSPath() +{ + local path=$1 + + # escape '[' and ']' with grave accent (`) character + pathPS=${path//[/\`[} + pathPS=${pathPS//]/\`]} + # escape single quote (') with another single quote (') + pathPS=${pathPS//\'/\'\'} + + # Convert GitHub bash path "/i/path" to Windows path "I:/path" + if [[ $pathPS == /* ]]; then + # Remove preceding slash + pathPS="${pathPS#/}" + # Convert drive letter to uppercase + driveLetter=$(echo "${pathPS::1}" | tr '[:lower:]' '[:upper:]') + # Put together absolute path as used in Windows + pathPS="${driveLetter}:${pathPS:1}" + fi + + echo "$pathPS" +} + sendFiles() { params="base64zip=hash" - if [[ $1 == */ ]]; then - path="${1::-1}" - else - path=$1 - fi - zipPath="${path}_pairdrop.zip" - zipPath=${zipPath// /_} + workingDir="$(pwd)" + tmpDir="/tmp/pairdrop-cli-temp/" + tmpDirPS="\$env:TEMP/pairdrop-cli-temp/" + + index=0 + directoryBaseNamesUnix=() + directoryPathsUnix=() + filePathsUnix=() + directoryCount=0 + fileCount=0 + pathsPS="" - [[ -a "$zipPath" ]] && echo "Cannot overwrite $zipPath. Please remove first." && exit - - if [[ -d $path ]]; then - zipPathTemp="${path}_pairdrop_temp.zip" - [[ -a "$zipPathTemp" ]] && echo "Cannot overwrite $zipPathTemp. Please remove first." && exit - echo "Processing directory..." - - # Create zip files temporarily to send directory - if [[ $OS == "Windows" ]];then - powershell.exe -Command "Compress-Archive -Path ${path} -DestinationPath ${zipPath}" - echo "Compress-Archive -Path ${zipPath} -DestinationPath ${zipPathTemp}" - powershell.exe -Command "Compress-Archive -Path ${zipPath} -DestinationPath ${zipPathTemp}" - else - zip -q -b /tmp/ -r "$zipPath" "$path" - zip -q -b /tmp/ "$zipPathTemp" "$zipPath" - fi - - if [[ $OS == "Mac" ]];then - hash=$(base64 -i "$zipPathTemp") - else - hash=$(base64 -w 0 "$zipPathTemp") - fi - - # remove temporary temp file - rm "$zipPathTemp" - else - echo "Processing file..." - - # Create zip file temporarily to send file - - if [[ $OS == "Windows" ]];then - powershell.exe -Command "Compress-Archive -Path ${path} -DestinationPath ${zipPath} -CompressionLevel Optimal" - else - zip -q -b /tmp/ "$zipPath" "$path" - fi - if [[ $OS == "Mac" ]];then - hash=$(base64 -i "$zipPath") - else - hash=$(base64 -w 0 "$zipPath") - fi + #create tmp folder if it does not exist already + if [[ ! -d "$tmpDir" ]]; then + mkdir "$tmpDir" fi - # remove temporary temp file - rm "$zipPath" + for arg in "$@"; do + echo "$arg" + [[ ! -e "$arg" ]] && echo "The given path $arg does not exist." && exit - if [[ $(echo -n "$hash" | wc -m) -gt 32600 ]];then + # Remove trailing slash from directory + arg="${arg%/}" + + # get absolute path and basename of file/directory + absolutePath=$(realpath "$arg") + baseName=$(basename "$absolutePath") + directoryPath=$(dirname "$absolutePath") + + if [[ -d $absolutePath ]]; then + # is directory + ((directoryCount+=1)) + # add basename and directory path to arrays + directoryBaseNamesUnix+=("$baseName") + directoryPathsUnix+=("$directoryPath") + else + # is file + ((fileCount+=1)) + absolutePathUnix=$absolutePath + # append new path and separate paths with space + filePathsUnix+=("$absolutePathUnix") + fi + + # Prepare paths for PowerShell on Windows + if [[ $OS == "Windows" ]];then + absolutePathPS=$(escapePSPath "$absolutePath") + + # append new path and separate paths with commas + pathsPS+="'${absolutePathPS}', " + fi + + # set fileNames on first loop + if [[ $index == 0 ]]; then + baseNameU=${baseName// /_} + + # Prevent baseNameU being empty for hidden files by removing the preceding dot + if [[ $baseNameU == .* ]]; then + baseNameU=${baseNameU#.*} + fi + + # only use trunk of basename "document.txt" -> "document" + baseNameTrunk=${baseNameU%.*} + + # remove all special characters + zipName=${baseNameTrunk//[^a-zA-Z0-9_]/} + + zipToSendAbs="${tmpDir}${zipName}_pairdrop.zip" + wrapperZipAbs="${tmpDir}${zipName}_pairdrop_wrapper.zip" + + if [[ $OS == "Windows" ]];then + zipToSendAbsPS="${tmpDirPS}${zipName}_pairdrop.zip" + wrapperZipAbsPS="${tmpDirPS}${zipName}_pairdrop_wrapper.zip" + fi + fi + + ((index+=1)) # somehow ((index++)) stops the script + done + + # Prepare paths for PowerShell on Windows + if [[ $OS == "Windows" ]];then + # remove trailing comma + pathsPS=${pathsPS%??} + fi + + echo "Preparing ${fileCount} files and ${directoryCount} directories..." + + # if arguments include files only -> zip files once so files it is unzipped by sending browser + # if arguments include directories -> wrap first zip in a second wrapper zip so that after unzip by sending browser a zip file is sent to receiver + # + # Preferred zip structure: + # pairdrop "d1/d2/d3/f1" "../../d4/d5/d6/f2" "d7/" "../d8/" "f5" + # zip structure: pairdrop.zip + # |-f1 + # |-f2 + # |-d7/ + # |-d8/ + # |-f5 + # -> truncate (relative) paths but keep directories + + [[ -e "$zipToSendAbs" ]] && echo "Cannot overwrite $zipToSendAbs. Please remove first." && exit + + if [[ $OS == "Windows" ]];then + # Powershell does preferred zip structure natively + powershell.exe -Command "Compress-Archive -Path ${pathsPS} -DestinationPath ${zipToSendAbsPS}" + else + # Workaround needed to create preferred zip structure on unix systems + # Create zip file with all single files by junking the path + if [[ $fileCount != 0 ]]; then + zip -q -b /tmp/ -j -0 -r "$zipToSendAbs" "${filePathsUnix[@]}" + fi + + # Add directories recursively to zip file + index=0 + while [[ $index < $directoryCount ]]; do + # workaround to keep directory name but junk the rest of the paths + + # cd to path above directory + cd "${directoryPathsUnix[index]}" + + # add directory to zip without junking the path + zip -q -b /tmp/ -0 -u -r "$zipToSendAbs" "${directoryBaseNamesUnix[index]}" + + # cd back to working directory + cd "$workingDir" + + ((index+=1)) # somehow ((index++)) stops the script + done + fi + + # If directories are included send as zip + # -> Create additional zip wrapper which will be unzipped by the sending browser + if [[ "$directoryCount" != 0 ]]; then + echo "Bundle as ZIP file..." + + # Prevent filename from being absolute zip path by "cd"ing to directory before zipping + zipToSendDirectory=$(dirname "$zipToSendAbs") + zipToSendBaseName=$(basename "$zipToSendAbs") + + cd "$zipToSendDirectory" + + [[ -e "$wrapperZipAbs" ]] && echo "Cannot overwrite $wrapperZipAbs. Please remove first." && exit + + if [[ $OS == "Windows" ]];then + powershell.exe -Command "Compress-Archive -Path ${zipToSendBaseName} -DestinationPath ${wrapperZipAbsPS} -CompressionLevel Optimal" + else + zip -q -b /tmp/ -0 "$wrapperZipAbs" "$zipToSendBaseName" + fi + cd "$workingDir" + + # remove inner zip file and set wrapper as zipToSend (do not differentiate between OS as this is done via Git Bash on Windows) + rm "$zipToSendAbs" + + zipToSendAbs=$wrapperZipAbs + fi + + # base64 encode zip file + if [[ $OS == "Mac" ]];then + hash=$(base64 -i "$zipToSendAbs") + else + hash=$(base64 -w 0 "$zipToSendAbs") + fi + + # remove zip file (do not differentiate between OS as this is done via Git Bash on Windows) + rm "$zipToSendAbs" + + if [[ $(echo -n "$hash" | wc -m) -gt 1000 ]];then params="base64zip=paste" + + # Copy $hash to clipboard if [[ $OS == "Windows" || $OS == "WSL" || $OS == "WSL2" ]];then echo -n "$hash" | clip.exe elif [[ $OS == "Mac" ]];then echo -n "$hash" | pbcopy + elif [ -n "$WAYLAND_DISPLAY" ]; then + # Wayland + if ! command -v wl-copy &> /dev/null; then + echo -e "You need to install 'wl-copy' to send bigger filePathsUnix from cli" + echo "Try: sudo apt install wl-clipboard" + exit 1 + fi + # Workaround to prevent use of Pipe which has a max letter limit + echo -n "$hash" > /tmp/pairdrop-cli-temp/pairdrop_hash_temp + wl-copy < /tmp/pairdrop-cli-temp/pairdrop_hash_temp + rm /tmp/pairdrop-cli-temp/pairdrop_hash_temp else - (echo -n "$hash" | xclip) || echo "You need to install xclip for sending bigger files from cli" + # X11 + if ! command -v xclip &> /dev/null; then + echo -e "You need to install 'xclip' to send bigger filePathsUnix from cli" + echo "Try: sudo apt install xclip" + exit 1 + fi + echo -n "$hash" | xclip -sel c fi hash= fi - openPairDrop exit } @@ -165,31 +320,32 @@ sendFiles() # Main program # ############################################################ ############################################################ -SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +script_path="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" pushd . > '/dev/null'; -SCRIPTPATH="${BASH_SOURCE[0]:-$0}"; +script_path="${BASH_SOURCE[0]:-$0}"; -while [ -h "$SCRIPTPATH" ]; +while [ -h "$script_path" ]; do - cd "$( dirname -- "$SCRIPTPATH"; )"; - SCRIPTPATH="$( readlink -f -- "$SCRIPTPATH"; )"; + cd "$( dirname -- "$script_path"; )"; + script_path="$( readlink -f -- "$script_path"; )"; done -cd "$( dirname -- "$SCRIPTPATH"; )" > '/dev/null'; -SCRIPTPATH="$( pwd; )"; +cd "$( dirname -- "$script_path"; )" > '/dev/null'; +script_path="$( pwd; )"; popd > '/dev/null'; -CONFIGPATH="${SCRIPTPATH}/.pairdrop-cli-config" +config_path="${script_path}/.pairdrop-cli-config" -[ ! -f "$CONFIGPATH" ] && - specifyDomain "https://pairdrop.net/" && - [ ! -f "$CONFIGPATH" ] && - echo "Could not create config file. Add 'DOMAIN=https://pairdrop.net/' to a file called .pairdrop-cli-config in the same file as this 'pairdrop' bash file" +[ ! -f "$config_path" ] && + specifyDomain "https://pairdrop.net/" && + [ ! -f "$config_path" ] && + echo "Could not create config file. Add 'DOMAIN=https://pairdrop.net/' to a file called .pairdrop-cli-config in the same file as this 'pairdrop' bash file" -[ ! -f "$CONFIGPATH" ] || export "$(grep -v '^#' "$CONFIGPATH" | xargs)" +[ ! -f "$config_path" ] || export "$(grep -v '^#' "$config_path" | xargs)" setOs + ############################################################ # Process the input options. Add options as needed. # ############################################################ @@ -198,24 +354,23 @@ setOs [[ $# -eq 0 ]] && openPairDrop && exit # display help and exit if first argument is "--help" or more than 2 arguments are given -[ "$1" == "--help" ] || [[ $# -gt 2 ]] && help && exit +[ "$1" == "--help" ] && help && exit while getopts "d:ht:*" option; do - case $option in - d) # specify domain - specifyDomain "$2" - exit;; - t) # Send text - sendText - exit;; - h | ?) # display help and exit - help - exit;; - esac + case $option in + d) # specify domain - show help and exit if too many arguments + [[ $# -gt 2 ]] && help && exit + specifyDomain "$2" + exit;; + t) # Send text - show help and exit if too many arguments + [[ $# -gt 2 ]] && help && exit + sendText + exit;; + h | ?) # display help and exit + help + exit;; + esac done # Send file(s) -# display help and exit if 2 arguments are given or if file does not exist -[[ $# -eq 2 ]] || [[ ! -a $1 ]] && help && exit - -sendFiles "$1" +sendFiles "$@" diff --git a/pairdrop-cli/pairdrop.sh b/pairdrop-cli/pairdrop.sh new file mode 100644 index 0000000..17d3672 --- /dev/null +++ b/pairdrop-cli/pairdrop.sh @@ -0,0 +1,6 @@ +#!/bin/bash +parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" || exit ; pwd -P ) + +cd "$parent_path" || exit + +./pairdrop "$@" \ No newline at end of file diff --git a/pairdrop-cli/send with PairDrop.lnk b/pairdrop-cli/send with PairDrop.lnk new file mode 100644 index 0000000..14d8fa4 Binary files /dev/null and b/pairdrop-cli/send with PairDrop.lnk differ diff --git a/pairdrop-cli/send-with-pairdrop.ps1 b/pairdrop-cli/send-with-pairdrop.ps1 new file mode 100644 index 0000000..cfb11b0 --- /dev/null +++ b/pairdrop-cli/send-with-pairdrop.ps1 @@ -0,0 +1,3 @@ +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path + +& "$scriptDir\pairdrop.sh" $args \ No newline at end of file diff --git a/pairdrop-cli/send-with-pairdrop.sh b/pairdrop-cli/send-with-pairdrop.sh new file mode 100644 index 0000000..194beac --- /dev/null +++ b/pairdrop-cli/send-with-pairdrop.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# edit this to point to the pairdrop-cli executable +pathToPairDropCli="/usr/local/bin/pairdrop-cli/pairdrop" + +# Initialize an array +lines=() + +# Read each line into the array +while IFS= read -r line; do + lines+=("$line") +done <<< "$NAUTILUS_SCRIPT_SELECTED_FILE_PATHS" + +# Get the length of the array +length=${#lines[@]} + +# Remove the last entry +unset 'lines[length-1]' + +$pathToPairDropCli "${lines[@]}" \ No newline at end of file diff --git a/public/fonts/OpenSans/OFL.txt b/public/fonts/OpenSans/OFL.txt new file mode 100644 index 0000000..9b448d4 --- /dev/null +++ b/public/fonts/OpenSans/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/public/fonts/OpenSans/OpenSans-Italic-VariableFont_wdth,wght.ttf b/public/fonts/OpenSans/OpenSans-Italic-VariableFont_wdth,wght.ttf new file mode 100644 index 0000000..5bda9cc Binary files /dev/null and b/public/fonts/OpenSans/OpenSans-Italic-VariableFont_wdth,wght.ttf differ diff --git a/public/fonts/OpenSans/OpenSans-VariableFont_wdth,wght.ttf b/public/fonts/OpenSans/OpenSans-VariableFont_wdth,wght.ttf new file mode 100644 index 0000000..e4142bf Binary files /dev/null and b/public/fonts/OpenSans/OpenSans-VariableFont_wdth,wght.ttf differ diff --git a/public/fonts/OpenSans/README.txt b/public/fonts/OpenSans/README.txt new file mode 100644 index 0000000..2548322 --- /dev/null +++ b/public/fonts/OpenSans/README.txt @@ -0,0 +1,100 @@ +Open Sans Variable Font +======================= + +This download contains Open Sans as both variable fonts and static fonts. + +Open Sans is a variable font with these axes: + wdth + wght + +This means all the styles are contained in these files: + OpenSans-VariableFont_wdth,wght.ttf + OpenSans-Italic-VariableFont_wdth,wght.ttf + +If your app fully supports variable fonts, you can now pick intermediate styles +that aren’t available as static fonts. Not all apps support variable fonts, and +in those cases you can use the static font files for Open Sans: + static/OpenSans_Condensed-Light.ttf + static/OpenSans_Condensed-Regular.ttf + static/OpenSans_Condensed-Medium.ttf + static/OpenSans_Condensed-SemiBold.ttf + static/OpenSans_Condensed-Bold.ttf + static/OpenSans_Condensed-ExtraBold.ttf + static/OpenSans_SemiCondensed-Light.ttf + static/OpenSans_SemiCondensed-Regular.ttf + static/OpenSans_SemiCondensed-Medium.ttf + static/OpenSans_SemiCondensed-SemiBold.ttf + static/OpenSans_SemiCondensed-Bold.ttf + static/OpenSans_SemiCondensed-ExtraBold.ttf + static/OpenSans-Light.ttf + static/OpenSans-Regular.ttf + static/OpenSans-Medium.ttf + static/OpenSans-SemiBold.ttf + static/OpenSans-Bold.ttf + static/OpenSans-ExtraBold.ttf + static/OpenSans_Condensed-LightItalic.ttf + static/OpenSans_Condensed-Italic.ttf + static/OpenSans_Condensed-MediumItalic.ttf + static/OpenSans_Condensed-SemiBoldItalic.ttf + static/OpenSans_Condensed-BoldItalic.ttf + static/OpenSans_Condensed-ExtraBoldItalic.ttf + static/OpenSans_SemiCondensed-LightItalic.ttf + static/OpenSans_SemiCondensed-Italic.ttf + static/OpenSans_SemiCondensed-MediumItalic.ttf + static/OpenSans_SemiCondensed-SemiBoldItalic.ttf + static/OpenSans_SemiCondensed-BoldItalic.ttf + static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf + static/OpenSans-LightItalic.ttf + static/OpenSans-Italic.ttf + static/OpenSans-MediumItalic.ttf + static/OpenSans-SemiBoldItalic.ttf + static/OpenSans-BoldItalic.ttf + static/OpenSans-ExtraBoldItalic.ttf + +Get started +----------- + +1. Install the font files you want to use + +2. Use your app's font picker to view the font family and all the +available styles + +Learn more about variable fonts +------------------------------- + + https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts + https://variablefonts.typenetwork.com + https://medium.com/variable-fonts + +In desktop apps + + https://theblog.adobe.com/can-variable-fonts-illustrator-cc + https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts + +Online + + https://developers.google.com/fonts/docs/getting_started + https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide + https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts + +Installing fonts + + MacOS: https://support.apple.com/en-us/HT201749 + Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux + Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows + +Android Apps + + https://developers.google.com/fonts/docs/android + https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts + +License +------- +Please read the full license text (OFL.txt) to understand the permissions, +restrictions and requirements for usage, redistribution, and modification. + +You can use them in your products & projects – print or digital, +commercial or otherwise. + +This isn't legal advice, please consider consulting a lawyer and see the full +license for all details. diff --git a/public/fonts/OpenSans/static/OpenSans-Bold.ttf b/public/fonts/OpenSans/static/OpenSans-Bold.ttf new file mode 100644 index 0000000..4a5bc39 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans-Bold.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans-BoldItalic.ttf b/public/fonts/OpenSans/static/OpenSans-BoldItalic.ttf new file mode 100644 index 0000000..8878a3e Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans-BoldItalic.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans-ExtraBold.ttf b/public/fonts/OpenSans/static/OpenSans-ExtraBold.ttf new file mode 100644 index 0000000..5dfb66c Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans-ExtraBold.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans-ExtraBoldItalic.ttf b/public/fonts/OpenSans/static/OpenSans-ExtraBoldItalic.ttf new file mode 100644 index 0000000..d266998 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans-ExtraBoldItalic.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans-Italic.ttf b/public/fonts/OpenSans/static/OpenSans-Italic.ttf new file mode 100644 index 0000000..e84f9ee Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans-Italic.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans-Light.ttf b/public/fonts/OpenSans/static/OpenSans-Light.ttf new file mode 100644 index 0000000..cf8e0c7 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans-Light.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans-LightItalic.ttf b/public/fonts/OpenSans/static/OpenSans-LightItalic.ttf new file mode 100644 index 0000000..d913f35 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans-LightItalic.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans-Medium.ttf b/public/fonts/OpenSans/static/OpenSans-Medium.ttf new file mode 100644 index 0000000..a76d4ce Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans-Medium.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans-MediumItalic.ttf b/public/fonts/OpenSans/static/OpenSans-MediumItalic.ttf new file mode 100644 index 0000000..5599691 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans-MediumItalic.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans-Regular.ttf b/public/fonts/OpenSans/static/OpenSans-Regular.ttf new file mode 100644 index 0000000..29e9e60 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans-Regular.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans-SemiBold.ttf b/public/fonts/OpenSans/static/OpenSans-SemiBold.ttf new file mode 100644 index 0000000..a571167 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans-SemiBold.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans-SemiBoldItalic.ttf b/public/fonts/OpenSans/static/OpenSans-SemiBoldItalic.ttf new file mode 100644 index 0000000..a7d2323 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans-SemiBoldItalic.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_Condensed-Bold.ttf b/public/fonts/OpenSans/static/OpenSans_Condensed-Bold.ttf new file mode 100644 index 0000000..90d25e5 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_Condensed-Bold.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_Condensed-BoldItalic.ttf b/public/fonts/OpenSans/static/OpenSans_Condensed-BoldItalic.ttf new file mode 100644 index 0000000..9fefa96 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_Condensed-BoldItalic.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_Condensed-ExtraBold.ttf b/public/fonts/OpenSans/static/OpenSans_Condensed-ExtraBold.ttf new file mode 100644 index 0000000..ec9e308 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_Condensed-ExtraBold.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_Condensed-ExtraBoldItalic.ttf b/public/fonts/OpenSans/static/OpenSans_Condensed-ExtraBoldItalic.ttf new file mode 100644 index 0000000..f4a2648 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_Condensed-ExtraBoldItalic.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_Condensed-Italic.ttf b/public/fonts/OpenSans/static/OpenSans_Condensed-Italic.ttf new file mode 100644 index 0000000..451059e Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_Condensed-Italic.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_Condensed-Light.ttf b/public/fonts/OpenSans/static/OpenSans_Condensed-Light.ttf new file mode 100644 index 0000000..9823525 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_Condensed-Light.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_Condensed-LightItalic.ttf b/public/fonts/OpenSans/static/OpenSans_Condensed-LightItalic.ttf new file mode 100644 index 0000000..d1f9808 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_Condensed-LightItalic.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_Condensed-Medium.ttf b/public/fonts/OpenSans/static/OpenSans_Condensed-Medium.ttf new file mode 100644 index 0000000..50a9f70 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_Condensed-Medium.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_Condensed-MediumItalic.ttf b/public/fonts/OpenSans/static/OpenSans_Condensed-MediumItalic.ttf new file mode 100644 index 0000000..e24fdca Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_Condensed-MediumItalic.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_Condensed-Regular.ttf b/public/fonts/OpenSans/static/OpenSans_Condensed-Regular.ttf new file mode 100644 index 0000000..3aa5d46 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_Condensed-Regular.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_Condensed-SemiBold.ttf b/public/fonts/OpenSans/static/OpenSans_Condensed-SemiBold.ttf new file mode 100644 index 0000000..1b98bc4 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_Condensed-SemiBold.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_Condensed-SemiBoldItalic.ttf b/public/fonts/OpenSans/static/OpenSans_Condensed-SemiBoldItalic.ttf new file mode 100644 index 0000000..318828d Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_Condensed-SemiBoldItalic.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_SemiCondensed-Bold.ttf b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-Bold.ttf new file mode 100644 index 0000000..dc2168f Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-Bold.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_SemiCondensed-BoldItalic.ttf b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-BoldItalic.ttf new file mode 100644 index 0000000..36818ec Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-BoldItalic.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_SemiCondensed-ExtraBold.ttf b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-ExtraBold.ttf new file mode 100644 index 0000000..64b8c41 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-ExtraBold.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf new file mode 100644 index 0000000..09f3851 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_SemiCondensed-Italic.ttf b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-Italic.ttf new file mode 100644 index 0000000..690ce39 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-Italic.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_SemiCondensed-Light.ttf b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-Light.ttf new file mode 100644 index 0000000..443bc12 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-Light.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_SemiCondensed-LightItalic.ttf b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-LightItalic.ttf new file mode 100644 index 0000000..b804514 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-LightItalic.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_SemiCondensed-Medium.ttf b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-Medium.ttf new file mode 100644 index 0000000..8c143cd Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-Medium.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_SemiCondensed-MediumItalic.ttf b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-MediumItalic.ttf new file mode 100644 index 0000000..d4564c9 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-MediumItalic.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_SemiCondensed-Regular.ttf b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-Regular.ttf new file mode 100644 index 0000000..8130446 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-Regular.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_SemiCondensed-SemiBold.ttf b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-SemiBold.ttf new file mode 100644 index 0000000..99b6069 Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-SemiBold.ttf differ diff --git a/public/fonts/OpenSans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf new file mode 100644 index 0000000..c89a1cf Binary files /dev/null and b/public/fonts/OpenSans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf differ diff --git a/public/images/android-chrome-192x192-maskable.png b/public/images/android-chrome-192x192-maskable.png index f0e9245..7a70b20 100644 Binary files a/public/images/android-chrome-192x192-maskable.png and b/public/images/android-chrome-192x192-maskable.png differ diff --git a/public/images/android-chrome-192x192.png b/public/images/android-chrome-192x192.png index 0bdca51..217e355 100644 Binary files a/public/images/android-chrome-192x192.png and b/public/images/android-chrome-192x192.png differ diff --git a/public/images/android-chrome-512x512-maskable.png b/public/images/android-chrome-512x512-maskable.png index cdda606..b0e9ee5 100644 Binary files a/public/images/android-chrome-512x512-maskable.png and b/public/images/android-chrome-512x512-maskable.png differ diff --git a/public/images/android-chrome-512x512.png b/public/images/android-chrome-512x512.png index b01e679..0dc0647 100644 Binary files a/public/images/android-chrome-512x512.png and b/public/images/android-chrome-512x512.png differ diff --git a/public/images/apple-touch-icon.png b/public/images/apple-touch-icon.png index 0a32878..6a96c53 100644 Binary files a/public/images/apple-touch-icon.png and b/public/images/apple-touch-icon.png differ diff --git a/public/images/favicon-96x96-notification.png b/public/images/favicon-96x96-notification.png index d3407c3..54cbbf6 100644 Binary files a/public/images/favicon-96x96-notification.png and b/public/images/favicon-96x96-notification.png differ diff --git a/public/images/favicon-96x96.png b/public/images/favicon-96x96.png index b8a5746..3ed2446 100644 Binary files a/public/images/favicon-96x96.png and b/public/images/favicon-96x96.png differ diff --git a/public/images/logo_blue_512x512.png b/public/images/logo_blue_512x512.png index 41d13fc..b0e9ee5 100644 Binary files a/public/images/logo_blue_512x512.png and b/public/images/logo_blue_512x512.png differ diff --git a/public/images/logo_transparent_128x128.png b/public/images/logo_transparent_128x128.png deleted file mode 100644 index c276efe..0000000 Binary files a/public/images/logo_transparent_128x128.png and /dev/null differ diff --git a/public/images/logo_transparent_512x512.png b/public/images/logo_transparent_512x512.png deleted file mode 100644 index 367e24f..0000000 Binary files a/public/images/logo_transparent_512x512.png and /dev/null differ diff --git a/public/images/logo_transparent_white_512x512.png b/public/images/logo_transparent_white_512x512.png index 37589b6..2c9d046 100644 Binary files a/public/images/logo_transparent_white_512x512.png and b/public/images/logo_transparent_white_512x512.png differ diff --git a/public/images/logo_white_512x512.png b/public/images/logo_white_512x512.png deleted file mode 100644 index d7750d9..0000000 Binary files a/public/images/logo_white_512x512.png and /dev/null differ diff --git a/public/images/mstile-150x150.png b/public/images/mstile-150x150.png index 6380e32..945d874 100644 Binary files a/public/images/mstile-150x150.png and b/public/images/mstile-150x150.png differ diff --git a/public/images/pairdrop_screenshot_mobile_1.png b/public/images/pairdrop_screenshot_mobile_1.png index d93aafb..42aae56 100644 Binary files a/public/images/pairdrop_screenshot_mobile_1.png and b/public/images/pairdrop_screenshot_mobile_1.png differ diff --git a/public/images/pairdrop_screenshot_mobile_2.png b/public/images/pairdrop_screenshot_mobile_2.png index 51ace10..c472c33 100644 Binary files a/public/images/pairdrop_screenshot_mobile_2.png and b/public/images/pairdrop_screenshot_mobile_2.png differ diff --git a/public/images/pairdrop_screenshot_mobile_3.png b/public/images/pairdrop_screenshot_mobile_3.png index 57ad15a..0a61028 100644 Binary files a/public/images/pairdrop_screenshot_mobile_3.png and b/public/images/pairdrop_screenshot_mobile_3.png differ diff --git a/public/images/pairdrop_screenshot_mobile_4.png b/public/images/pairdrop_screenshot_mobile_4.png index d5811ad..4b442d5 100644 Binary files a/public/images/pairdrop_screenshot_mobile_4.png and b/public/images/pairdrop_screenshot_mobile_4.png differ diff --git a/public/images/pairdrop_screenshot_mobile_5.png b/public/images/pairdrop_screenshot_mobile_5.png index d205fd9..5d0682f 100644 Binary files a/public/images/pairdrop_screenshot_mobile_5.png and b/public/images/pairdrop_screenshot_mobile_5.png differ diff --git a/public/images/pairdrop_screenshot_mobile_6.png b/public/images/pairdrop_screenshot_mobile_6.png index 23c06ae..c4a1e1d 100644 Binary files a/public/images/pairdrop_screenshot_mobile_6.png and b/public/images/pairdrop_screenshot_mobile_6.png differ diff --git a/public/images/pairdrop_screenshot_mobile_7.png b/public/images/pairdrop_screenshot_mobile_7.png index c32980a..b1dc8ad 100644 Binary files a/public/images/pairdrop_screenshot_mobile_7.png and b/public/images/pairdrop_screenshot_mobile_7.png differ diff --git a/public/images/pairdrop_screenshot_mobile_8.png b/public/images/pairdrop_screenshot_mobile_8.png new file mode 100644 index 0000000..7532227 Binary files /dev/null and b/public/images/pairdrop_screenshot_mobile_8.png differ diff --git a/public/index.html b/public/index.html index 9f6504b..ffbee78 100644 --- a/public/index.html +++ b/public/index.html @@ -5,17 +5,17 @@ - PairDrop + PairDrop | Transfer Files Cross-Platform. No Setup, No Signup. - + - + @@ -28,18 +28,19 @@ + - + -
+
@@ -94,39 +95,79 @@ - +
-
- - + +

- -

-
+ + +