Compare commits

..

12 Commits

Author SHA1 Message Date
schlagmichdoch
67a1b04da2 increase version to v1.3.0 2023-03-03 19:45:04 +01:00
schlagmichdoch
8b2eb67266 fix position of close btn on about page 2023-03-03 19:43:31 +01:00
schlagmichdoch
827b10219d Merge pull request #58 from schlagmichdoch/define_turn_config_dynamically
STUN/TURN config can now be changed dynamically via environment variable 🎉
2023-03-03 19:42:52 +01:00
schlagmichdoch
e7ab5e26cc Add dynamic stun/turn config as new feature to README.md 2023-03-03 19:41:55 +01:00
schlagmichdoch
8bcaa3f60f Fix header hierarchy for dynamic stun/turn in docs 2023-03-03 18:28:49 +01:00
schlagmichdoch
c0a4224a59 merge master into branch 2023-03-03 18:01:24 +01:00
schlagmichdoch
1e35bab327 increase version to v1.2.2 2023-03-03 17:07:02 +01:00
schlagmichdoch
bb0493d071 Make user notifications and document titles more concise. 2023-03-03 17:03:10 +01:00
schlagmichdoch
bfb5aa8546 fix overwrite method _onMessage of class RTCPeer 2023-03-03 16:36:55 +01:00
schlagmichdoch
de76da52fe merge master into branch 2023-03-01 21:55:50 +01:00
schlagmichdoch
66359da2ca get rtcConfig dynamically from the server 2023-02-24 18:08:48 +01:00
schlagmichdoch
74b88c2e7d fix dialog heights 2023-02-24 16:53:13 +01:00
15 changed files with 198 additions and 99 deletions

View File

@@ -63,6 +63,7 @@ Developed based on [Snapdrop](https://github.com/RobinLinus/snapdrop)
* Automatic restart on error (Thanks [@KaKi87](https://github.com/KaKi87))
* 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)
## Screenshots
<div align="center">

View File

@@ -17,7 +17,7 @@ docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 ls
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
@@ -30,7 +30,7 @@ Set options by using the following flags in the `docker run` command:
> Limits clients to 1000 requests per 5 min
##### 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.
@@ -42,6 +42,36 @@ Set options by using the following flags in the `docker run` command:
> 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.
>
> Default configuration:
> ```json
> {
> "sdpSemantics": "unified-plan",
> "iceServers": [
> {
> "urls": "stun:stun.l.google.com:19302"
> },
> {
> "urls": "stun:openrelay.metered.ca:80"
> },
> {
> "urls": "turn:openrelay.metered.ca:443",
> "username": "openrelayproject",
> "credential": "openrelayproject"
> }
> ]
> }
> ```
<br>
### 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
@@ -141,6 +171,38 @@ $env:PORT=3010; npm start
```
> Specify the port PairDrop is running on. (Default: 3000)
#### Specify STUN/TURN Server
On Unix based systems
```bash
RTC_CONFIG="rtc_config.json" npm start
```
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 configuration:
> ```json
> {
> "sdpSemantics": "unified-plan",
> "iceServers": [
> {
> "urls": "stun:stun.l.google.com:19302"
> },
> {
> "urls": "stun:openrelay.metered.ca:80"
> },
> {
> "urls": "turn:openrelay.metered.ca:443",
> "username": "openrelayproject",
> "credential": "openrelayproject"
> }
> ]
> }
> ```
### Options / Flags
#### Local Run
```bash
@@ -262,13 +324,13 @@ server {
### Using Apache
install modules `proxy`, `proxy_http`, `mod_proxy_wstunnel`
```shell
```bash
a2enmod proxy
```
```shell
```bash
a2enmod proxy_http
```
```shell
```bash
a2enmod proxy_wstunnel
```
@@ -278,7 +340,7 @@ Create a new configuration file under `/etc/apache2/sites-available` (on debian)
**pairdrop.conf**
#### Allow http and https requests
```
```apacheconf
<VirtualHost *:80>
ProxyPass / http://127.0.0.1:3000/
RewriteEngine on
@@ -295,7 +357,7 @@ Create a new configuration file under `/etc/apache2/sites-available` (on debian)
</VirtualHost>
```
#### Automatic http to https redirect:
```
```apacheconf
<VirtualHost *:80>
Redirect permanent / https://127.0.0.1:3000/
</VirtualHost>
@@ -308,10 +370,10 @@ Create a new configuration file under `/etc/apache2/sites-available` (on debian)
</VirtualHost>
```
Activate the new virtual host and reload apache:
```shell
```bash
a2ensite pairdrop
```
```shell
```bash
service apache2 reload
```
@@ -322,7 +384,7 @@ 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:
```shell
```bash
git clone https://github.com/schlagmichdoch/PairDrop.git
cd PairDrop
@@ -347,7 +409,7 @@ The nginx container creates a CA certificate and a website certificate for you.
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://<Your FQDN>: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.
- 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://<Your FQDN>: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).

View File

@@ -2,6 +2,7 @@ const process = require('process')
const crypto = require('crypto')
const {spawn} = require('child_process')
const WebSocket = require('ws');
const fs = require('fs');
// Handle SIGINT
process.on('SIGINT', () => {
@@ -50,6 +51,25 @@ if (process.argv.includes('--auto-restart')) {
);
}
const rtcConfig = process.env.RTC_CONFIG
? fs.readFileSync(process.env.RTC_CONFIG, 'utf8')
: {
"sdpSemantics": "unified-plan",
"iceServers": [
{
"urls": "stun:stun.l.google.com:19302"
},
{
"urls": "stun:openrelay.metered.ca:80"
},
{
"urls": "turn:openrelay.metered.ca:443",
"username": "openrelayproject",
"credential": "openrelayproject"
}
]
};
const express = require('express');
const RateLimit = require('express-rate-limit');
const http = require('http');
@@ -113,6 +133,10 @@ class PairDropServer {
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
});
this._joinRoom(peer);
// send displayName

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "pairdrop",
"version": "1.2.1",
"version": "1.3.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "pairdrop",
"version": "1.2.1",
"version": "1.3.0",
"license": "ISC",
"dependencies": {
"express": "^4.18.2",

View File

@@ -1,6 +1,6 @@
{
"name": "pairdrop",
"version": "1.2.1",
"version": "1.3.0",
"description": "",
"main": "index.js",
"scripts": {

View File

@@ -242,14 +242,14 @@
</div>
<!-- About Page -->
<x-about id="about" class="full center column">
<header class="row-reverse fade-in">
<a href="#" class="close icon-button">
<svg class="icon">
<use xlink:href="#close-icon" />
</svg>
</a>
</header>
<section class="center column fade-in">
<header class="row-reverse">
<a href="#" class="close icon-button">
<svg class="icon">
<use xlink:href="#close-icon" />
</svg>
</a>
</header>
<svg class="icon logo">
<use xlink:href="#wifi-tethering" />
</svg>

View File

@@ -36,6 +36,7 @@ class ServerConnection {
_onOpen() {
console.log('WS: server connected');
Events.fire('ws-connected');
if (this._isReconnect) Events.fire('notify-user', 'Connected.');
}
_sendRoomSecrets(roomSecrets) {
@@ -58,10 +59,17 @@ class ServerConnection {
this.send({ type: 'pair-device-join', roomKey: roomKey })
}
_setRtcConfig(config) {
window.rtcConfig = config;
}
_onMessage(msg) {
msg = JSON.parse(msg);
if (msg.type !== 'ping') console.log('WS:', msg);
switch (msg.type) {
case 'rtc-config':
this._setRtcConfig(msg.config);
break;
case 'peers':
Events.fire('peers', msg);
break;
@@ -145,15 +153,17 @@ class ServerConnection {
this._socket.close();
this._socket = null;
Events.fire('ws-disconnected');
this._isReconnect = true;
}
}
_onDisconnect() {
console.log('WS: server disconnected');
Events.fire('notify-user', 'No server connection. Retry in 5s...');
Events.fire('notify-user', 'Connecting..');
clearTimeout(this._reconnectTimer);
this._reconnectTimer = setTimeout(_ => this._connect(), 5000);
Events.fire('ws-disconnected');
this._isReconnect = true;
}
_onVisibilityChange() {
@@ -319,25 +329,25 @@ class Peer {
this._onChunkReceived(message);
return;
}
message = JSON.parse(message);
switch (message.type) {
const messageJSON = JSON.parse(message);
switch (messageJSON.type) {
case 'request':
this._onFilesTransferRequest(message);
this._onFilesTransferRequest(messageJSON);
break;
case 'header':
this._onFilesHeader(message);
this._onFilesHeader(messageJSON);
break;
case 'partition':
this._onReceivedPartitionEnd(message);
this._onReceivedPartitionEnd(messageJSON);
break;
case 'partition-received':
this._sendNextPartition();
break;
case 'progress':
this._onDownloadProgress(message.progress);
this._onDownloadProgress(messageJSON.progress);
break;
case 'files-transfer-response':
this._onFileTransferRequestResponded(message);
this._onFileTransferRequestResponded(messageJSON);
break;
case 'file-transfer-complete':
this._onFileTransferCompleted();
@@ -346,7 +356,7 @@ class Peer {
this._onMessageTransferCompleted();
break;
case 'text':
this._onTextReceived(message);
this._onTextReceived(messageJSON);
break;
}
}
@@ -509,7 +519,7 @@ class RTCPeer extends Peer {
_openConnection(peerId, isCaller) {
this._isCaller = isCaller;
this._peerId = peerId;
this._conn = new RTCPeerConnection(RTCPeer.config);
this._conn = new RTCPeerConnection(window.rtcConfig);
this._conn.onicecandidate = e => this._onIceCandidate(e);
this._conn.onconnectionstatechange = _ => this._onConnectionStateChange();
this._conn.oniceconnectionstatechange = e => this._onIceConnectionStateChange(e);
@@ -570,8 +580,7 @@ class RTCPeer extends Peer {
_onMessage(message) {
if (typeof message === 'string') {
message = JSON.parse(message);
console.log('RTC:', message);
console.log('RTC:', JSON.parse(message));
}
super._onMessage(message);
}
@@ -860,20 +869,3 @@ class Events {
return window.removeEventListener(type, callback, false);
}
}
RTCPeer.config = {
'sdpSemantics': 'unified-plan',
'iceServers': [
{
urls: 'stun:stun.l.google.com:19302'
},
{
urls: 'stun:openrelay.metered.ca:80'
},
{
urls: 'turn:openrelay.metered.ca:443',
username: 'openrelayproject',
credential: 'openrelayproject',
},
]
}

View File

@@ -662,7 +662,7 @@ class ReceiveFileDialog extends ReceiveDialog {
this.createPreviewElement(files[0]).finally(_ => {
document.title = files.length === 1
? 'File received - PairDrop'
: `(${files.length}) Files received - PairDrop`;
: `${files.length} Files received - PairDrop`;
document.changeFavicon("images/favicon-96x96-notification.png");
Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'})
this.show();
@@ -1143,7 +1143,7 @@ class ReceiveTextDialog extends Dialog {
_setDocumentTitleMessages() {
document.title = !this._receiveTextQueue.length
? 'Message Received - PairDrop'
: `(${this._receiveTextQueue.length + 1}) Messages Received - PairDrop`;
: `${this._receiveTextQueue.length + 1} Messages Received - PairDrop`;
}
async _onCopy() {
@@ -1424,7 +1424,7 @@ class NetworkStatusUI {
constructor() {
Events.on('offline', _ => this._showOfflineMessage());
Events.on('online', _ => this._showOnlineMessage());
Events.on('ws-connected', _ => this._showOnlineMessage());
Events.on('ws-connected', _ => this._onWsConnected());
Events.on('ws-disconnected', _ => this._onWsDisconnected());
if (!navigator.onLine) this._showOfflineMessage();
}
@@ -1435,17 +1435,16 @@ class NetworkStatusUI {
}
_showOnlineMessage() {
window.animateBackground(true);
if (!this.firstConnect) {
this.firstConnect = true;
return;
}
Events.fire('notify-user', 'You are back online');
window.animateBackground(true);
}
_onWsConnected() {
window.animateBackground(true);
}
_onWsDisconnected() {
window.animateBackground(false);
if (!this.firstConnect) this.firstConnect = true;
}
}

View File

@@ -1,4 +1,4 @@
const cacheVersion = 'v1.2.1';
const cacheVersion = 'v1.3.0';
const cacheTitle = `pairdrop-cache-${cacheVersion}`;
const urlsToCache = [
'index.html',

View File

@@ -939,6 +939,13 @@ button::-moz-focus-inner {
margin: 8px 8px -16px;
}
#about section {
flex-grow: 1;
}
#about header {
align-self: end;
}
/* Loading Indicator */

View File

@@ -34,6 +34,7 @@ class ServerConnection {
_onOpen() {
console.log('WS: server connected');
Events.fire('ws-connected');
if (this._isReconnect) Events.fire('notify-user', 'Connected.');
}
_sendRoomSecrets(roomSecrets) {
@@ -56,10 +57,17 @@ class ServerConnection {
this.send({ type: 'pair-device-join', roomKey: roomKey })
}
_setRtcConfig(config) {
window.rtcConfig = config;
}
_onMessage(msg) {
msg = JSON.parse(msg);
if (msg.type !== 'ping') console.log('WS:', msg);
switch (msg.type) {
case 'rtc-config':
this._setRtcConfig(msg.config);
break;
case 'peers':
Events.fire('peers', msg);
break;
@@ -155,15 +163,17 @@ class ServerConnection {
this._socket.close();
this._socket = null;
Events.fire('ws-disconnected');
this._isReconnect = true;
}
}
_onDisconnect() {
console.log('WS: server disconnected');
Events.fire('notify-user', 'No server connection. Retry in 5s...');
Events.fire('notify-user', 'Connecting..');
clearTimeout(this._reconnectTimer);
this._reconnectTimer = setTimeout(_ => this._connect(), 5000);
Events.fire('ws-disconnected');
this._isReconnect = true;
}
_onVisibilityChange() {
@@ -329,25 +339,25 @@ class Peer {
this._onChunkReceived(message);
return;
}
message = JSON.parse(message);
switch (message.type) {
const messageJSON = JSON.parse(message);
switch (messageJSON.type) {
case 'request':
this._onFilesTransferRequest(message);
this._onFilesTransferRequest(messageJSON);
break;
case 'header':
this._onFilesHeader(message);
this._onFilesHeader(messageJSON);
break;
case 'partition':
this._onReceivedPartitionEnd(message);
this._onReceivedPartitionEnd(messageJSON);
break;
case 'partition-received':
this._sendNextPartition();
break;
case 'progress':
this._onDownloadProgress(message.progress);
this._onDownloadProgress(messageJSON.progress);
break;
case 'files-transfer-response':
this._onFileTransferRequestResponded(message);
this._onFileTransferRequestResponded(messageJSON);
break;
case 'file-transfer-complete':
this._onFileTransferCompleted();
@@ -356,7 +366,7 @@ class Peer {
this._onMessageTransferCompleted();
break;
case 'text':
this._onTextReceived(message);
this._onTextReceived(messageJSON);
break;
}
}
@@ -519,7 +529,7 @@ class RTCPeer extends Peer {
_openConnection(peerId, isCaller) {
this._isCaller = isCaller;
this._peerId = peerId;
this._conn = new RTCPeerConnection(RTCPeer.config);
this._conn = new RTCPeerConnection(window.rtcConfig);
this._conn.onicecandidate = e => this._onIceCandidate(e);
this._conn.onconnectionstatechange = _ => this._onConnectionStateChange();
this._conn.oniceconnectionstatechange = e => this._onIceConnectionStateChange(e);
@@ -580,8 +590,7 @@ class RTCPeer extends Peer {
_onMessage(message) {
if (typeof message === 'string') {
message = JSON.parse(message);
console.log('RTC:', message);
console.log('RTC:', JSON.parse(message));
}
super._onMessage(message);
}
@@ -938,20 +947,3 @@ class Events {
return window.removeEventListener(type, callback, false);
}
}
RTCPeer.config = {
'sdpSemantics': 'unified-plan',
'iceServers': [
{
urls: 'stun:stun.l.google.com:19302'
},
{
urls: 'stun:openrelay.metered.ca:80'
},
{
urls: 'turn:openrelay.metered.ca:443',
username: 'openrelayproject',
credential: 'openrelayproject',
},
]
}

View File

@@ -663,7 +663,7 @@ class ReceiveFileDialog extends ReceiveDialog {
this.createPreviewElement(files[0]).finally(_ => {
document.title = files.length === 1
? 'File received - PairDrop'
: `(${files.length}) Files received - PairDrop`;
: `${files.length} Files received - PairDrop`;
document.changeFavicon("images/favicon-96x96-notification.png");
Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'})
this.show();
@@ -1144,7 +1144,7 @@ class ReceiveTextDialog extends Dialog {
_setDocumentTitleMessages() {
document.title = !this._receiveTextQueue.length
? 'Message Received - PairDrop'
: `(${this._receiveTextQueue.length + 1}) Messages Received - PairDrop`;
: `${this._receiveTextQueue.length + 1} Messages Received - PairDrop`;
}
async _onCopy() {
@@ -1425,7 +1425,7 @@ class NetworkStatusUI {
constructor() {
Events.on('offline', _ => this._showOfflineMessage());
Events.on('online', _ => this._showOnlineMessage());
Events.on('ws-connected', _ => this._showOnlineMessage());
Events.on('ws-connected', _ => this._onWsConnected());
Events.on('ws-disconnected', _ => this._onWsDisconnected());
if (!navigator.onLine) this._showOfflineMessage();
}
@@ -1436,17 +1436,16 @@ class NetworkStatusUI {
}
_showOnlineMessage() {
window.animateBackground(true);
if (!this.firstConnect) {
this.firstConnect = true;
return;
}
Events.fire('notify-user', 'You are back online');
window.animateBackground(true);
}
_onWsConnected() {
window.animateBackground(true);
}
_onWsDisconnected() {
window.animateBackground(false);
if (!this.firstConnect) this.firstConnect = true;
}
}

View File

@@ -1,4 +1,4 @@
const cacheVersion = 'v1.2.1';
const cacheVersion = 'v1.3.0';
const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`;
const urlsToCache = [
'index.html',

View File

@@ -965,6 +965,13 @@ button::-moz-focus-inner {
margin: 8px 8px -16px;
}
#about section {
flex-grow: 1;
}
#about header {
align-self: end;
}
/* Loading Indicator */

16
rtc_config_example.json Normal file
View File

@@ -0,0 +1,16 @@
{
"sdpSemantics": "unified-plan",
"iceServers": [
{
"urls": "stun:stun.l.google.com:19302"
},
{
"urls": "stun:openrelay.metered.ca:80"
},
{
"urls": "turn:openrelay.metered.ca:443",
"username": "openrelayproject",
"credential": "openrelayproject"
}
]
}