implement complete WSPeer as fallback if WebRTC is deactivated. Only ever use on self-hosted instances as clients need to trust the server!

This commit is contained in:
schlagmichdoch 2023-02-10 20:22:36 +01:00
parent b8c78bccfa
commit 616f6a6799
38 changed files with 5162 additions and 14 deletions

View file

@ -76,18 +76,43 @@ npm start
> By default, the node server listens on port 3000.
<br>
#### Automatic restart on error
```bash
npm start -- --auto-restart
```
#### Rate limiting requests:
> Restarts server automatically on error
<br>
#### Rate limiting requests
```bash
npm start -- --rate-limit
```
> Limits clients to 100 requests per 5 min
<br>
#### 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 using this option.
> This is especially useful if you connect to your instance via a VPN as most VPN services block WebRTC completely in order to hide your real IP address ([read more](https://privacysavvy.com/security/safe-browsing/disable-webrtc-chrome-firefox-safari-opera-edge/)).
>
> **Warning:** All traffic sent between devices using this fallback is routed through the server and therefor not peer to peer!
> Beware that the traffic routed via this fallback is readable by the server. Only ever use this on instances you can trust.
> Additionally, beware that all traffic using this fallback debits the servers data plan.
>
<br>
#### Production (autostart and rate-limit)
```bash
npm start:prod
npm run start:prod
```
## HTTP-Server
@ -192,6 +217,8 @@ By default, docker listens on ports 8080 (http) and 8443 (https) (specified in `
When running PairDrop via Docker, the `X-Forwarded-For` header has to be set by a proxy. Otherwise, all clients will be mutually visible.
To run PairDrop with [the options listed above](#public-run) you have to edit the `npm start` command in the `docker-compose.yml` accordingly.
### Installation
[See Local Development > Install](#install)

View file

@ -68,7 +68,11 @@ if (process.argv.includes('--rate-limit')) {
app.use(limiter);
}
app.use(express.static('public'));
if (process.argv.includes('--include-ws-fallback')) {
app.use(express.static('public_included_ws_fallback'));
} else {
app.use(express.static('public'));
}
app.use(function(req, res) {
res.redirect('/');
@ -152,20 +156,23 @@ class PairDropServer {
this._notifyPeers(sender);
break;
case 'signal':
this._onSignal(sender, message);
default:
this._signalAndRelay(sender, message);
}
}
_onSignal(sender, message) {
_signalAndRelay(sender, message) {
const room = message.roomType === 'ip' ? sender.ip : message.roomSecret;
// relay message to recipient
if (message.to && Peer.isValidUuid(message.to) && this._rooms[room]) {
const recipientId = message.to;
const recipient = this._rooms[room][recipientId];
const recipient = this._rooms[room][message.to];
delete message.to;
// add sender id
message.sender = sender.id;
// add sender
message.sender = {
id: sender.id,
rtcSupported: sender.rtcSupported
};
this._send(recipient, message);
}
}

View file

@ -537,7 +537,7 @@ class RTCPeer extends Peer {
}
onServerMessage(message) {
if (!this._conn) this._connect(message.sender, false);
if (!this._conn) this._connect(message.sender.id, false);
if (message.sdp) {
this._conn.setRemoteDescription(message.sdp)
@ -651,11 +651,11 @@ class PeersManager {
_onMessage(message) {
// if different roomType -> abort
if (this.peers[message.sender] && this.peers[message.sender]._roomType !== message.roomType) return;
if (!this.peers[message.sender]) {
this.peers[message.sender] = new RTCPeer(this._server, undefined, message.roomType, message.roomSecret);
if (this.peers[message.sender.id] && this.peers[message.sender.id]._roomType !== message.roomType) return;
if (!this.peers[message.sender.id]) {
this.peers[message.sender.id] = new RTCPeer(this._server, undefined, message.roomType, message.roomSecret);
}
this.peers[message.sender].onServerMessage(message);
this.peers[message.sender.id].onServerMessage(message);
}
_onPeers(msg) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

View file

@ -0,0 +1,251 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M2325 4214 c-225 -34 -366 -76 -544 -161 -361 -172 -651 -455 -830
-809 -135 -268 -194 -532 -186 -839 2 -88 6 -173 9 -190 3 -16 8 -43 11 -60 3
-16 7 -41 10 -55 3 -14 7 -36 10 -50 9 -51 54 -188 86 -265 94 -229 217 -413
389 -585 111 -111 139 -136 212 -186 29 -20 60 -42 68 -49 8 -7 34 -22 57 -35
36 -20 43 -21 55 -9 7 8 14 20 15 26 2 7 20 38 40 70 21 32 38 63 38 68 0 6
10 9 22 9 12 -1 21 4 20 10 -1 6 3 10 8 9 6 -1 12 7 12 18 2 18 -1 17 -19 -6
-29 -38 -32 -25 -5 24 13 22 26 38 30 35 3 -3 4 2 2 13 -2 10 -4 20 -4 23 -1
3 -21 16 -46 29 -28 15 -44 29 -41 37 3 8 0 11 -10 7 -18 -7 -46 16 -37 31 3
6 3 8 -2 4 -5 -5 -24 4 -44 19 -55 40 -180 166 -181 181 0 7 -4 10 -10 7 -5
-3 -10 1 -10 9 0 9 -4 16 -9 16 -4 0 -27 26 -49 58 -29 42 -38 62 -30 70 7 7
5 10 -7 9 -10 -1 -20 4 -23 12 -3 9 0 11 9 6 8 -5 11 -4 6 1 -5 5 -14 9 -20 9
-7 0 -13 9 -15 19 -2 11 -10 26 -18 34 -8 7 -12 18 -9 23 4 5 2 9 -2 9 -5 0
-14 9 -21 21 -10 15 -10 24 -2 34 8 10 8 15 -1 21 -8 4 -9 3 -5 -4 4 -7 3 -12
-2 -12 -9 0 -39 69 -47 108 -2 11 -8 29 -13 39 -5 10 -8 28 -7 40 1 12 -2 20
-7 17 -4 -3 -9 4 -9 15 -2 23 -3 29 -12 51 -11 29 -29 161 -23 175 3 8 2 15
-2 15 -4 0 -7 24 -8 54 -1 48 1 54 17 51 10 -2 21 0 24 4 2 5 -5 8 -18 7 -19
-1 -22 4 -22 34 0 27 4 35 17 34 10 -1 15 3 12 8 -3 5 -12 7 -20 4 -17 -7 -18
6 -1 23 6 8 7 11 2 8 -7 -3 -9 9 -7 34 3 25 8 37 16 32 8 -4 8 -3 0 6 -8 9 -9
25 -2 54 5 23 12 51 14 62 2 11 6 30 9 43 3 12 7 32 10 43 11 53 28 95 43 107
10 7 11 12 4 12 -9 0 -9 5 -3 18 5 9 22 45 38 80 19 42 32 61 42 57 11 -4 11
-2 2 9 -7 8 -8 16 -4 18 4 1 18 23 31 48 24 47 50 85 92 130 14 15 30 36 36
46 6 11 16 19 22 19 6 0 14 4 19 9 5 5 3 6 -4 2 -20 -11 -15 1 11 29 14 15 29
24 34 21 6 -3 9 2 8 12 0 10 6 16 16 16 10 -1 15 3 12 8 -7 10 36 45 49 40 5
-1 6 2 3 7 -8 12 21 34 34 26 6 -4 9 -2 8 3 -4 12 52 62 70 62 6 0 12 4 12 8
0 4 15 16 33 27 17 10 34 21 37 25 12 16 101 51 111 44 8 -5 9 -3 4 6 -6 10
-4 12 9 7 10 -4 15 -3 11 3 -6 10 56 40 88 43 9 1 17 5 17 10 0 4 4 6 8 3 4
-2 14 -1 22 4 9 5 19 5 27 -2 11 -8 12 -7 7 7 -5 12 -4 16 4 11 6 -3 13 -2 16
3 4 5 14 8 24 7 9 -2 19 0 22 5 3 4 21 10 40 12 19 3 35 6 35 7 0 3 42 10 87
14 26 2 51 7 56 10 6 3 13 0 15 -6 4 -10 6 -10 6 0 1 16 151 17 151 1 0 -6 6
-4 14 3 17 17 89 12 79 -6 -4 -6 1 -5 10 3 9 7 17 10 17 5 0 -5 19 -9 43 -10
43 -1 91 -7 137 -19 14 -4 32 -8 40 -9 8 -1 22 -5 30 -8 8 -3 51 -17 95 -32
43 -15 82 -30 85 -34 3 -4 12 -8 20 -10 16 -3 121 -53 130 -62 3 -3 17 -11 32
-19 15 -8 36 -22 47 -32 10 -10 21 -15 25 -12 3 3 8 -2 12 -11 3 -9 11 -16 17
-16 33 0 237 -202 320 -317 180 -253 268 -536 262 -847 -1 -83 -5 -162 -9
-176 -3 -13 -8 -41 -11 -61 -3 -20 -10 -50 -15 -68 -5 -17 -9 -35 -9 -39 -1
-4 -13 -43 -27 -87 -35 -107 -110 -257 -180 -357 -78 -112 -258 -290 -366
-362 -49 -32 -88 -62 -88 -67 0 -4 10 -25 21 -46 33 -60 142 -247 146 -253 3
-2 18 3 34 12 16 10 37 15 47 12 14 -5 15 -4 2 5 -8 6 -12 11 -7 12 4 1 10 2
15 3 4 0 18 11 31 23 13 12 27 20 31 18 5 -3 11 2 14 11 4 9 13 14 22 10 8 -3
12 -2 9 3 -7 12 84 83 96 75 5 -3 6 2 3 10 -4 11 0 16 11 16 9 0 13 5 10 10
-6 10 9 15 30 10 5 -1 7 2 3 6 -11 10 12 35 25 27 5 -3 7 -2 4 4 -4 5 5 20 18
31 14 12 25 27 25 34 0 6 3 9 6 6 6 -7 33 18 51 48 6 10 16 18 23 16 6 -1 9 2
5 7 -3 6 2 18 12 28 10 10 30 38 46 61 15 23 32 42 37 42 6 0 9 6 8 13 -2 6 3
11 11 9 8 -2 11 3 8 11 -7 19 21 59 35 51 6 -4 8 1 3 15 -4 15 -2 21 9 21 9 0
13 6 10 14 -3 8 0 17 5 21 6 3 9 11 6 16 -4 5 -2 9 3 9 4 0 15 16 22 35 9 24
19 34 28 30 12 -4 13 -3 2 10 -9 11 -9 15 -1 15 7 0 10 4 7 9 -3 5 3 28 13 52
17 40 22 54 32 84 1 6 10 17 19 25 9 8 10 11 3 6 -10 -6 -10 1 1 32 8 23 20
68 27 101 6 34 15 59 18 56 4 -2 5 10 3 27 -3 17 -1 28 4 25 5 -3 9 11 10 31
1 97 5 133 12 129 4 -3 7 6 7 19 0 13 -3 24 -6 24 -4 0 -3 17 0 38 4 20 6 61
4 90 -1 29 2 50 7 47 5 -3 12 0 16 6 4 8 3 9 -4 5 -16 -10 -34 28 -20 42 9 9
8 12 -2 12 -13 0 -13 2 0 10 12 8 12 10 1 10 -11 0 -11 3 0 17 8 9 9 14 3 10
-12 -7 -29 97 -19 114 4 5 2 9 -2 9 -5 0 -10 14 -11 31 -1 17 -7 39 -12 49 -6
10 -5 21 1 28 5 7 6 10 2 7 -8 -6 -65 168 -67 202 -1 11 -5 19 -10 15 -5 -3
-7 2 -3 11 4 10 2 17 -4 17 -6 0 -8 7 -5 16 3 8 2 12 -4 9 -9 -6 -14 8 -11 28
0 4 -4 7 -10 7 -5 0 -8 4 -5 9 4 5 1 11 -4 13 -6 2 -12 10 -14 18 -1 8 -8 25
-14 38 -7 12 -9 22 -5 22 4 0 -1 6 -12 13 -11 9 -15 19 -10 27 5 9 4 11 -3 6
-7 -4 -12 -2 -12 4 0 6 -11 27 -25 47 -13 20 -21 41 -18 47 3 6 2 8 -2 3 -11
-9 -46 43 -38 57 3 6 3 8 -2 4 -4 -4 -24 14 -44 40 -45 59 -49 64 -89 107 -19
20 -30 40 -26 47 4 6 3 8 -4 5 -11 -8 -112 85 -112 103 0 6 -4 9 -9 5 -5 -3
-17 5 -25 17 -9 12 -16 18 -16 13 0 -6 -3 -6 -8 0 -11 16 -111 89 -182 134
-36 22 -69 44 -75 48 -5 5 -45 24 -87 44 -43 20 -74 40 -70 44 4 5 2 5 -4 2
-11 -6 -54 7 -109 33 -23 11 -128 43 -195 58 -84 20 -104 24 -230 41 -91 13
-339 10 -435 -5z"/>
<path d="M2470 3856 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21
13z"/>
<path d="M2333 3825 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/>
<path d="M2365 3820 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z"/>
<path d="M1993 3715 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/>
<path d="M1936 3658 c3 -5 10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/>
<path d="M1710 3555 c5 -7 6 -16 2 -21 -4 -5 -3 -5 2 -1 15 12 15 34 0 34 -9
0 -11 -4 -4 -12z"/>
<path d="M1650 3515 c5 -7 6 -16 2 -21 -4 -5 -3 -5 2 -1 15 12 15 34 0 34 -9
0 -11 -4 -4 -12z"/>
<path d="M2425 3508 c-152 -17 -331 -84 -468 -175 -88 -57 -238 -206 -292
-288 -86 -132 -146 -280 -170 -423 -16 -99 -14 -299 5 -377 6 -27 14 -61 16
-74 10 -48 65 -178 108 -254 48 -84 140 -202 181 -233 14 -10 25 -25 25 -32 0
-7 4 -11 8 -8 4 2 23 -9 42 -26 31 -27 94 -72 127 -91 7 -5 24 13 44 47 18 30
36 53 40 50 4 -2 6 4 5 14 0 9 4 16 11 15 7 -2 10 3 7 11 -3 7 2 21 11 30 9 9
13 16 9 16 -3 0 -2 6 3 13 22 28 75 130 65 123 -13 -8 -100 48 -109 70 -3 8
-9 14 -14 14 -17 0 -99 95 -94 108 3 8 0 10 -7 6 -8 -5 -9 -2 -5 10 4 10 3 15
-3 11 -12 -7 -44 41 -35 55 4 6 1 9 -5 8 -12 -3 -51 87 -42 96 3 4 1 6 -5 6
-18 0 -37 121 -36 225 2 84 7 140 13 140 2 0 5 16 14 59 3 16 13 38 23 49 10
11 13 17 6 14 -8 -5 -8 -1 0 18 8 16 17 23 27 19 11 -4 12 -2 3 7 -9 9 -6 20
10 49 34 58 79 110 90 103 6 -3 8 -2 4 3 -3 5 1 18 9 29 8 10 14 16 14 12 0
-4 13 7 29 24 17 17 35 28 41 24 6 -3 9 -1 8 7 -2 7 5 12 14 12 10 -1 16 3 15
9 -2 11 124 77 136 70 4 -2 7 1 7 7 0 7 6 10 14 7 8 -3 16 -2 18 3 2 4 19 11
38 15 19 3 51 9 70 12 46 9 186 9 230 0 19 -4 50 -10 67 -13 19 -4 30 -11 26
-17 -3 -6 -1 -7 6 -3 16 10 53 -3 45 -16 -4 -7 -2 -8 5 -4 16 11 83 -22 75
-36 -4 -6 -3 -8 4 -5 14 9 43 -3 36 -14 -3 -5 1 -7 8 -4 7 3 27 -8 44 -23 17
-15 47 -41 66 -58 53 -47 114 -133 152 -215 96 -207 83 -452 -34 -649 -43 -74
-145 -181 -213 -227 -53 -35 -60 -12 57 -210 l78 -132 24 15 c13 9 24 13 24 9
0 -5 8 2 19 13 10 12 21 19 25 15 3 -3 6 -1 6 6 0 8 6 11 16 7 8 -3 12 -2 9 4
-3 6 -1 10 6 10 7 0 9 3 6 7 -4 3 9 17 28 30 19 13 35 29 35 35 0 6 10 3 22
-8 16 -14 19 -14 10 -2 -11 14 -9 21 18 50 18 18 28 26 24 18 -4 -9 -3 -12 2
-7 5 5 9 16 9 25 0 9 11 22 25 29 14 7 19 13 12 13 -10 0 -9 4 2 16 9 8 21 24
27 35 9 17 13 18 27 7 15 -12 16 -11 4 4 -12 15 -11 22 7 47 12 16 21 32 21
36 0 4 9 20 21 36 11 16 17 29 13 29 -4 0 1 7 12 16 10 8 12 12 4 8 -12 -6
-13 -5 -4 7 6 8 17 35 25 62 7 26 16 47 20 47 4 0 6 6 6 13 -3 34 28 165 37
160 8 -5 8 -3 0 8 -9 13 -10 25 -4 47 6 21 4 172 -3 172 -4 0 -3 10 4 21 6 12
7 19 1 15 -5 -3 -12 15 -16 42 -3 26 -9 61 -12 77 -4 17 -7 36 -7 43 0 6 -4
12 -9 12 -5 0 -7 4 -3 9 3 5 1 12 -4 15 -5 4 -12 24 -16 46 -4 22 -10 40 -14
40 -5 0 -15 18 -23 39 -15 36 -14 39 1 35 10 -4 9 -1 -4 7 -29 18 -45 42 -39
59 3 8 2 11 -2 7 -4 -4 -16 5 -25 20 -14 21 -15 29 -6 36 8 6 6 7 -6 3 -11 -4
-20 1 -24 12 -4 9 -13 22 -21 28 -8 6 -12 16 -8 23 4 6 4 10 -1 9 -5 -2 -38
25 -73 59 -36 34 -89 80 -117 101 -29 21 -49 43 -45 47 4 5 2 5 -5 2 -6 -4
-19 0 -27 9 -9 8 -16 13 -16 10 0 -4 -17 4 -37 17 -162 99 -432 151 -658 125z"/>
<path d="M1435 3301 c-3 -5 -2 -12 3 -15 5 -3 9 1 9 9 0 17 -3 19 -12 6z"/>
<path d="M1461 3276 c-9 -11 -9 -16 1 -22 7 -4 10 -4 6 1 -4 4 -3 14 3 22 6 7
9 13 6 13 -2 0 -10 -6 -16 -14z"/>
<path d="M1389 3217 c6 -8 7 -18 3 -22 -4 -5 -1 -5 6 -1 10 6 10 11 1 22 -6 8
-14 14 -16 14 -3 0 0 -6 6 -13z"/>
<path d="M1350 3200 c0 -5 5 -10 11 -10 5 0 7 5 4 10 -3 6 -8 10 -11 10 -2 0
-4 -4 -4 -10z"/>
<path d="M2520 3140 c-9 -6 -10 -10 -3 -10 6 0 15 5 18 10 8 12 4 12 -15 0z"/>
<path d="M1339 3113 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17
-17z"/>
<path d="M2595 3120 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
-8 -4 -11 -10z"/>
<path d="M1336 3075 c-9 -26 -7 -32 5 -12 6 10 9 21 6 23 -2 3 -7 -2 -11 -11z"/>
<path d="M2330 3079 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M2239 3068 c-5 -18 -6 -38 -1 -34 7 8 12 36 6 36 -2 0 -4 -1 -5 -2z"/>
<path d="M1274 3049 c-3 -6 -2 -15 3 -20 5 -5 9 -1 9 11 0 23 -2 24 -12 9z"/>
<path d="M2185 3020 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z"/>
<path d="M2255 3019 c-3 -4 2 -6 10 -5 21 3 28 13 10 13 -9 0 -18 -4 -20 -8z"/>
<path d="M2153 2995 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/>
<path d="M2116 2982 c-3 -5 1 -9 9 -9 8 0 12 4 9 9 -3 4 -7 8 -9 8 -2 0 -6 -4
-9 -8z"/>
<path d="M2086 2962 c-3 -5 1 -9 9 -9 8 0 12 4 9 9 -3 4 -7 8 -9 8 -2 0 -6 -4
-9 -8z"/>
<path d="M1216 2922 c-3 -5 1 -9 9 -9 8 0 12 4 9 9 -3 4 -7 8 -9 8 -2 0 -6 -4
-9 -8z"/>
<path d="M2070 2919 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M1210 2896 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21
13z"/>
<path d="M1195 2860 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
-8 -4 -11 -10z"/>
<path d="M1256 2858 c3 -5 10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/>
<path d="M1993 2835 c0 -8 4 -12 9 -9 4 3 8 9 8 15 0 5 -4 9 -8 9 -5 0 -9 -7
-9 -15z"/>
<path d="M2030 2839 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M1190 2825 c0 -7 30 -13 34 -7 3 4 -4 9 -15 9 -10 1 -19 0 -19 -2z"/>
<path d="M1193 2803 c4 -3 1 -13 -6 -22 -11 -14 -10 -14 5 -2 16 12 16 31 1
31 -4 0 -3 -3 0 -7z"/>
<path d="M2493 2795 c-122 -27 -209 -94 -260 -202 -64 -138 -24 -318 92 -415
39 -33 101 -68 120 -68 8 0 15 -4 15 -8 0 -13 143 -14 196 -1 27 7 68 25 91
41 23 15 47 28 53 28 6 0 9 3 7 8 -3 4 1 13 9 20 8 7 14 9 14 5 1 -4 10 9 21
30 11 20 24 38 29 38 6 1 14 2 19 3 5 0 13 4 17 8 3 4 -3 6 -15 3 -23 -4 -29
10 -7 18 7 3 14 16 14 29 2 41 9 63 20 63 6 0 14 4 19 8 4 5 1 7 -7 5 -12 -2
-15 7 -15 45 0 26 -4 47 -9 47 -5 0 -2 8 5 17 10 11 10 14 2 9 -8 -4 -13 -2
-13 8 0 8 -5 27 -11 43 -6 15 -11 30 -12 33 -2 7 -24 34 -64 80 -40 45 -132
96 -193 105 -25 3 -52 8 -60 10 -8 2 -43 -3 -77 -10z"/>
<path d="M1953 2775 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/>
<path d="M1987 2779 c7 -7 15 -10 18 -7 3 3 -2 9 -12 12 -14 6 -15 5 -6 -5z"/>
<path d="M4375 2761 c-3 -5 -2 -12 3 -15 5 -3 9 1 9 9 0 17 -3 19 -12 6z"/>
<path d="M3630 2739 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M1929 2723 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17
-17z"/>
<path d="M1987 2719 c7 -7 15 -10 18 -7 3 3 -2 9 -12 12 -14 6 -15 5 -6 -5z"/>
<path d="M1949 2683 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17
-17z"/>
<path d="M1910 2681 c0 -6 4 -12 8 -15 5 -3 9 1 9 9 0 8 -4 15 -9 15 -4 0 -8
-4 -8 -9z"/>
<path d="M1155 2661 c-3 -5 -2 -12 3 -15 5 -3 9 1 9 9 0 17 -3 19 -12 6z"/>
<path d="M1894 2640 c0 -13 4 -16 10 -10 7 7 7 13 0 20 -6 6 -10 3 -10 -10z"/>
<path d="M3667 2639 c7 -7 15 -10 18 -7 3 3 -2 9 -12 12 -14 6 -15 5 -6 -5z"/>
<path d="M1879 2593 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17
-17z"/>
<path d="M4416 2542 c-3 -5 1 -9 9 -9 8 0 12 4 9 9 -3 4 -7 8 -9 8 -2 0 -6 -4
-9 -8z"/>
<path d="M1894 2476 c1 -8 5 -18 8 -22 4 -3 5 1 4 10 -1 8 -5 18 -8 22 -4 3
-5 -1 -4 -10z"/>
<path d="M2936 2447 c3 -10 9 -15 12 -12 3 3 0 11 -7 18 -10 9 -11 8 -5 -6z"/>
<path d="M4415 2441 c-3 -5 -2 -12 3 -15 5 -3 9 1 9 9 0 17 -3 19 -12 6z"/>
<path d="M1890 2421 c0 -6 4 -13 10 -16 6 -3 7 1 4 9 -7 18 -14 21 -14 7z"/>
<path d="M1186 2358 c3 -5 10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/>
<path d="M1865 2360 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z"/>
<path d="M2926 2258 c3 -5 10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/>
<path d="M1153 2235 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/>
<path d="M3633 2215 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/>
<path d="M2873 2175 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/>
<path d="M1186 2158 c3 -5 10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/>
<path d="M3653 2149 c-2 -23 3 -25 10 -4 4 8 3 16 -1 19 -4 3 -9 -4 -9 -15z"/>
<path d="M4385 2120 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z"/>
<path d="M2770 2099 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M1230 1939 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M3519 1833 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17
-17z"/>
<path d="M4260 1846 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21
13z"/>
<path d="M4251 1804 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M2210 1779 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M3467 1779 c7 -7 15 -10 18 -7 3 3 -2 9 -12 12 -14 6 -15 5 -6 -5z"/>
<path d="M3430 1739 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M4230 1746 c0 -2 7 -7 16 -10 8 -3 12 -2 9 4 -6 10 -25 14 -25 6z"/>
<path d="M3396 1713 c-6 -14 -5 -15 5 -6 7 7 10 15 7 18 -3 3 -9 -2 -12 -12z"/>
<path d="M2136 1679 c4 -8 30 -6 38 2 3 3 -5 5 -19 5 -13 0 -22 -3 -19 -7z"/>
<path d="M4255 1680 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
-8 -4 -11 -10z"/>
<path d="M3354 1662 c4 -3 14 -7 22 -8 9 -1 13 0 10 4 -4 3 -14 7 -22 8 -9 1
-13 0 -10 -4z"/>
<path d="M3296 1638 c3 -5 10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/>
<path d="M4236 1638 c3 -5 10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/>
<path d="M3294 1595 c0 -13 3 -22 7 -19 8 4 6 30 -2 38 -3 3 -5 -5 -5 -19z"/>
<path d="M1435 1600 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
-8 -4 -11 -10z"/>
<path d="M2076 1601 c-3 -5 2 -15 12 -22 15 -12 16 -12 5 2 -7 9 -10 19 -6 22
3 4 4 7 0 7 -3 0 -8 -4 -11 -9z"/>
<path d="M3253 1595 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/>
<path d="M2115 1580 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0
-7 -4 -4 -10z"/>
<path d="M3199 1553 c-13 -16 -12 -17 4 -4 9 7 17 15 17 17 0 8 -8 3 -21 -13z"/>
<path d="M3240 1520 c-9 -6 -10 -10 -3 -10 6 0 15 5 18 10 8 12 4 12 -15 0z"/>
<path d="M4105 1479 c-3 -4 2 -6 10 -5 21 3 28 13 10 13 -9 0 -18 -4 -20 -8z"/>
<path d="M4033 1355 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/>
<path d="M4006 1298 c3 -5 10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/>
<path d="M3940 1285 c0 -2 6 -8 13 -14 10 -8 14 -7 14 2 0 8 -6 14 -14 14 -7
0 -13 -1 -13 -2z"/>
<path d="M3827 1139 c7 -9 10 -19 6 -22 -3 -4 -1 -7 5 -7 17 0 15 16 -5 31
-16 12 -17 12 -6 -2z"/>
<path d="M1810 1059 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M3719 1053 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17
-17z"/>
<path d="M1774 1049 c-3 -6 -2 -15 3 -20 5 -5 9 -1 9 11 0 23 -2 24 -12 9z"/>
<path d="M3679 1028 c-5 -16 -4 -46 2 -42 4 2 7 13 6 24 -1 17 -5 26 -8 18z"/>
<path d="M1710 965 c0 -7 30 -13 34 -7 3 4 -4 9 -15 9 -10 1 -19 0 -19 -2z"/>
<path d="M3610 939 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M3570 879 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -0,0 +1,345 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<!-- Web App Config -->
<title>PairDrop</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta name="theme-color" content="#3367d6">
<meta name="color-scheme" content="dark light">
<meta name="apple-mobile-web-app-capable" content="no">
<meta name="apple-mobile-web-app-title" content="PairDrop">
<meta name="application-name" content="PairDrop">
<!-- Descriptions -->
<meta name="description" content="Instantly share images, videos, PDFs, and links with people nearby. Peer2Peer and Open Source. No Setup, No Signup.">
<meta name="keywords" content="File, Transfer, Share, Peer2Peer">
<meta name="author" content="RobinLinus">
<meta property="og:title" content="PairDrop">
<meta property="og:type" content="article">
<meta property="og:url" content="https://pairdrop.net/">
<meta property="og:author" content="https://github.com/schlagmichdoch">
<meta name="twitter:author" content="@schlagmichdoch">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:description" content="Instantly share images, videos, PDFs, and links with people nearby. Peer2Peer and Open Source. No Setup, No Signup.">
<meta name="og:description" content="Instantly share images, videos, PDFs, and links with people nearby. Peer2Peer and Open Source. No Setup, No Signup.">
<!-- Icons -->
<link rel="icon" sizes="96x96" href="images/favicon-96x96.png">
<link rel="shortcut icon" href="images/favicon-96x96.png">
<link rel="apple-touch-icon" href="images/apple-touch-icon.png">
<meta name="msapplication-TileImage" content="images/mstile-150x150.png">
<link rel="fluid-icon" type="image/png" href="images/android-chrome-192x192.png">
<meta name="twitter:image" content="images/logo_transparent_512x512.png">
<meta property="og:image" content="images/logo_transparent_512x512.png">
<!-- Resources -->
<link rel="stylesheet" type="text/css" href="styles.css">
<link rel="manifest" href="manifest.json">
</head>
<body translate="no">
<header class="row-reverse">
<a href="#about" class="icon-button" title="About PairDrop">
<svg class="icon">
<use xlink:href="#info-outline" />
</svg>
</a>
<a id="theme" class="icon-button" title="Switch Darkmode/Lightmode" >
<svg class="icon">
<use xlink:href="#icon-theme" />
</svg>
</a>
<a id="notification" class="icon-button" title="Enable Notifications" hidden>
<svg class="icon">
<use xlink:href="#notifications" />
</svg>
</a>
<a id="install" class="icon-button" title="Install PairDrop" hidden>
<svg class="icon">
<use xlink:href="#homescreen" />
</svg>
</a>
<a id="pair-device" class="icon-button" title="Pair Device" >
<svg class="icon">
<use xlink:href="#pair-device-icon" />
</svg>
</a>
<a id="clear-pair-devices" class="icon-button" title="Clear All Paired Devices" hidden>
<svg class="icon">
<use xlink:href="#clear-pair-devices-icon" />
</svg>
</a>
</header>
<a id="cancelPasteModeBtn" class="button" close hidden>Done</a>
<!-- Peers -->
<x-peers class="center"></x-peers>
<x-no-peers>
<h2>Open PairDrop on other devices to send files</h2>
<div>Pair devices to be discoverable on other networks</div>
<br>
<div>A <span class="websocket-fallback">websocket fallback</span> is implemented on this instance. Use only if you trust the server!</div>
</x-no-peers>
<x-instructions desktop="Click to send files or right click to send a message" mobile="Tap to send files or long tap to send a message">
<div>A <span class="websocket-fallback">websocket fallback</span> is implemented on this instance. Use only if you trust the server!</div>
<p id="pasteFilename"></p>
</x-instructions>
<!-- Footer -->
<footer class="column">
<svg class="icon logo">
<use xlink:href="#wifi-tethering" />
</svg>
<div id="displayName" placeholder="&nbsp;"></div>
<div class="font-body2">
You can be discovered by everyone <span id="on-this-network">on&nbsp;this&nbsp;network</span>
<span id="and-by-paired-devices" hidden> and&nbsp;by&nbsp;<span id="paired-devices">paired&nbsp;devices</span></span>
</div>
</footer>
<!-- Pair Device Dialog -->
<x-dialog id="pairDeviceDialog">
<form action="#">
<x-background class="full center text-center">
<x-paper shadow="2">
<h2 class="center">Pair Devices</h2>
<div class="center" id="roomKeyQrCode"></div>
<h1 class="center" id="roomKey">000 000</h1>
<div id="pairInstructions" class="font-subheading center text-center">Input this key on another device<br>or scan the QR-Code.</div>
<hr>
<div id="keyInputContainer">
<input type="tel" id="char0" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" autofocus contenteditable placeholder="" disabled>
<input type="tel" id="char1" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
<input type="tel" id="char2" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
<input type="tel" id="char3" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
<input type="tel" id="char4" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
<input type="tel" id="char5" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
</div>
<div class="font-subheading center text-center">Enter key from another device to continue.</div>
<div class="row-reverse space-between">
<button class="button" type="submit" disabled>Pair</button>
<div class="separator"></div>
<a class="button" close>Cancel</a>
</div>
</x-paper>
</x-background>
</form>
</x-dialog>
<!-- Clear Devices Dialog -->
<x-dialog id="clearDevicesDialog">
<form action="#">
<x-background class="full center text-center">
<x-paper shadow="2">
<h2 class="center">Unpair Devices</h2>
<div class="font-subheading center text-center">Are you sure to unpair all devices?</div>
<div class="row-reverse space-between">
<button class="button" type="submit">Unpair Devices</button>
<a class="button" close>Cancel</a>
</div>
</x-paper>
</x-background>
</form>
</x-dialog>
<!-- Receive Request Dialog -->
<x-dialog id="receiveRequestDialog">
<x-background class="full center">
<x-paper shadow="2">
<h2 class="center">PairDrop</h2>
<div class="text-center file-description">
<div>
<span id="requestingPeerDisplayName"></span>
<span>would like to share</span>
</div>
<div class="row" id="fileName">
<span id="fileStem"></span>
<span id="fileExtension"></span>
</div>
<div class="row">
<span id="fileOther"></span>
</div>
</div>
<div class="font-body2 text-center file-size"></div>
<div class="center file-preview"></div>
<div class="row-reverse space-between">
<button class="button" id="acceptRequest" title="ENTER" autofocus>Accept</button>
<div class="separator"></div>
<button class="button" id="declineRequest" title="ESCAPE">Decline</button>
</div>
</x-paper>
</x-background>
</x-dialog>
<!-- Receive File Dialog -->
<x-dialog id="receiveFileDialog">
<x-background class="full center">
<x-paper shadow="2">
<h2 class="center" id="receiveTitle"></h2>
<div class="text-center file-description"></div>
<div class="font-body2 text-center file-size"></div>
<div class="center file-preview"></div>
<div class="row-reverse space-between">
<a class="button" id="shareOrDownload" autofocus></a>
<div class="separator"></div>
<button class="button" close>Close</button>
</div>
</x-paper>
</x-background>
</x-dialog>
<!-- Send Text Dialog -->
<x-dialog id="sendTextDialog">
<form action="#">
<x-background class="full center">
<x-paper shadow="2">
<h2>PairDrop - Send a Message</h2>
<div id="textInput" class="textarea" role="textbox" placeholder="Send a message" autocapitalize="none" spellcheck="false" autofocus contenteditable></div>
<div class="row-reverse">
<button class="button" type="submit" title="STR + ENTER" disabled close>Send</button>
<div class="separator"></div>
<a class="button" title="ESCAPE" close>Cancel</a>
</div>
</x-paper>
</x-background>
</form>
</x-dialog>
<!-- Receive Text Dialog -->
<x-dialog id="receiveTextDialog">
<x-background class="full center">
<x-paper shadow="2">
<h2>PairDrop - Message Received</h2>
<div id="receiveTextDescriptionContainer">
<span id="receiveTextPeerDisplayName"></span>
<span>sent the following message:</span>
</div>
<div class="row-separator"></div>
<div id="text"></div>
<div class="row-reverse">
<button class="button" id="copy" title="CTRL/⌘ + C">Copy</button>
<div class="separator"></div>
<button class="button" id="close" title="ESCAPE">Close</button>
</div>
</x-paper>
</x-background>
</x-dialog>
<!-- base64ZipDialog Dialog -->
<x-dialog id="base64ZipDialog">
<x-background class="full center">
<x-paper shadow="2">
<button class="button center" id="base64ZipPasteBtn" title="Paste">Tap here to paste files</button>
<button class="button center" close>Close</button>
</x-paper>
</x-background>
</x-dialog>
<!-- Toast -->
<div class="toast-container full center">
<x-toast class="row" shadow="1" id="toast"></x-toast>
</div>
<!-- About Page -->
<x-about id="about" class="full center column">
<section class="center column fade-in">
<header class="row-reverse">
<a href="#" class="close icon-button">
<svg class="icon">
<use xlink:href="#close" />
</svg>
</a>
</header>
<svg class="icon logo">
<use xlink:href="#wifi-tethering" />
</svg>
<h1>PairDrop</h1>
<div class="font-subheading">The easiest way to transfer files across devices</div>
<div class="row">
<a class="icon-button" target="_blank" href="https://github.com/schlagmichdoch/pairdrop" title="PairDrop on Github" rel="noreferrer">
<svg class="icon">
<use xlink:href="#github" />
</svg>
</a>
<a class="icon-button" target="_blank" href="https://www.buymeacoffee.com/pairdrop" title="Buy me a coffee!" rel="noreferrer">
<svg class="icon">
<use xlink:href="#monetarization" />
</svg>
</a>
<a class="icon-button" target="_blank" href="https://twitter.com/intent/tweet?text=https%3A%2F%2Fpairdrop.net%20by%20https%3A%2F%2Fgithub.com%2Fschlagmichdoch%2F&" title="Tweet about PairDrop" rel="noreferrer">
<svg class="icon">
<use xlink:href="#twitter" />
</svg>
</a>
<a class="icon-button" target="_blank" href="https://github.com/schlagmichdoch/pairdrop/blob/master/docs/faq.md" title="Frequently asked questions" rel="noreferrer">
<svg class="icon">
<use xlink:href="#help-outline" />
</svg>
</a>
</div>
</section>
<x-background></x-background>
</x-about>
<!-- SVG Icon Library -->
<svg style="display: none;">
<symbol id=wifi-tethering viewBox="0 0 24 24">
<path d="M12 11c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 2c0-3.31-2.69-6-6-6s-6 2.69-6 6c0 2.22 1.21 4.15 3 5.19l1-1.74c-1.19-.7-2-1.97-2-3.45 0-2.21 1.79-4 4-4s4 1.79 4 4c0 1.48-.81 2.75-2 3.45l1 1.74c1.79-1.04 3-2.97 3-5.19zM12 3C6.48 3 2 7.48 2 13c0 3.7 2.01 6.92 4.99 8.65l1-1.73C5.61 18.53 4 15.96 4 13c0-4.42 3.58-8 8-8s8 3.58 8 8c0 2.96-1.61 5.53-4 6.92l1 1.73c2.99-1.73 5-4.95 5-8.65 0-5.52-4.48-10-10-10z"></path>
</symbol>
<symbol id=desktop-mac viewBox="0 0 24 24">
<path d="M21 2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7l-2 3v1h8v-1l-2-3h7c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 12H3V4h18v10z"></path>
</symbol>
<symbol id=phone-iphone viewBox="0 0 24 24">
<path d="M15.5 1h-8C6.12 1 5 2.12 5 3.5v17C5 21.88 6.12 23 7.5 23h8c1.38 0 2.5-1.12 2.5-2.5v-17C18 2.12 16.88 1 15.5 1zm-4 21c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm4.5-4H7V4h9v14z"></path>
</symbol>
<symbol id=tablet-mac viewBox="0 0 24 24">
<path d="M18.5 0h-14C3.12 0 2 1.12 2 2.5v19C2 22.88 3.12 24 4.5 24h14c1.38 0 2.5-1.12 2.5-2.5v-19C21 1.12 19.88 0 18.5 0zm-7 23c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm7.5-4H4V3h15v16z"></path>
</symbol>
<symbol id=info-outline viewBox="0 0 24 24">
<path d="M11 17h2v-6h-2v6zm1-15C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zM11 9h2V7h-2v2z"></path>
</symbol>
<symbol id=close viewBox="0 0 24 24">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path>
</symbol>
<symbol id=help-outline viewBox="0 0 24 24">
<path d="M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"></path>
</symbol>
<symbol id="twitter">
<path d="M23.954 4.569c-.885.389-1.83.654-2.825.775 1.014-.611 1.794-1.574 2.163-2.723-.951.555-2.005.959-3.127 1.184-.896-.959-2.173-1.559-3.591-1.559-2.717 0-4.92 2.203-4.92 4.917 0 .39.045.765.127 1.124C7.691 8.094 4.066 6.13 1.64 3.161c-.427.722-.666 1.561-.666 2.475 0 1.71.87 3.213 2.188 4.096-.807-.026-1.566-.248-2.228-.616v.061c0 2.385 1.693 4.374 3.946 4.827-.413.111-.849.171-1.296.171-.314 0-.615-.03-.916-.086.631 1.953 2.445 3.377 4.604 3.417-1.68 1.319-3.809 2.105-6.102 2.105-.39 0-.779-.023-1.17-.067 2.189 1.394 4.768 2.209 7.557 2.209 9.054 0 13.999-7.496 13.999-13.986 0-.209 0-.42-.015-.63.961-.689 1.8-1.56 2.46-2.548l-.047-.02z" />
</symbol>
<symbol id="github">
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
</symbol>
<g id="notifications">
<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z" />
</g>
<symbol id="homescreen">
<path fill="none" d="M0 0h24v24H0V0z" />
<path d="M18 1.01L8 1c-1.1 0-2 .9-2 2v3h2V5h10v14H8v-1H6v3c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM10 15h2V8H5v2h3.59L3 15.59 4.41 17 10 11.41z" />
<path fill="none" d="M0 0h24v24H0V0z" />
</symbol>
<symbol id="monetarization">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1.41 16.09V20h-2.67v-1.93c-1.71-.36-3.16-1.46-3.27-3.4h1.96c.1 1.05.82 1.87 2.65 1.87 1.96 0 2.4-.98 2.4-1.59 0-.83-.44-1.61-2.67-2.14-2.48-.6-4.18-1.62-4.18-3.67 0-1.72 1.39-2.84 3.11-3.21V4h2.67v1.95c1.86.45 2.79 1.86 2.85 3.39H14.3c-.05-1.11-.64-1.87-2.22-1.87-1.5 0-2.4.68-2.4 1.64 0 .84.65 1.39 2.67 1.91s4.18 1.39 4.18 3.91c-.01 1.83-1.38 2.83-3.12 3.16z" />
</symbol>
<symbol id="icon-theme" viewBox="0 0 24 24">
<rect fill="none" height="24" width="24"/><path d="M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36c-0.98,1.37-2.58,2.26-4.4,2.26 c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"/>
</symbol>
<symbol id="pair-device-icon" viewBox="0 0 640 512">
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/>
</symbol>
<symbol id="clear-pair-devices-icon" viewBox="0 0 640 512">
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path d="M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L489.3 358.2l90.5-90.5c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114l-96 96-31.9-25C430.9 239.6 420.1 175.1 377 132c-52.2-52.3-134.5-56.2-191.3-11.7L38.8 5.1zM239 162c30.1-14.9 67.7-9.9 92.8 15.3c20 20 27.5 48.3 21.7 74.5L239 162zM406.6 416.4L220.9 270c-2.1 39.8 12.2 80.1 42.2 110c38.9 38.9 94.4 51 143.6 36.3zm-290-228.5L60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5l61.8-61.8-50.6-39.9z"/>
</symbol>
</svg>
<!-- Scripts -->
<script src="scripts/util.js"></script>
<script src="scripts/network.js"></script>
<script src="scripts/ui.js"></script>
<script src="scripts/theme.js" async></script>
<script src="scripts/qrcode.js" async></script>
<script src="scripts/zip.min.js" async></script>
<script src="scripts/NoSleep.min.js" async></script>
<!-- Sounds -->
<audio id="blop" autobuffer="true">
<source src="sounds/blop.mp3" type="audio/mpeg">
<source src="sounds/blop.ogg" type="audio/ogg">
</audio>
<!-- no script -->
<noscript>
<x-noscript class="full center column">
<h1>Enable JavaScript</h1>
<h3>PairDrop works only with JavaScript</h3>
</x-noscript>
</noscript>
</body>

View file

@ -0,0 +1,281 @@
{
"name": "PairDrop",
"short_name": "PairDrop",
"icons": [{
"src": "images/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},{
"src": "images/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
},{
"src": "images/android-chrome-192x192-maskable.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},{
"src": "images/android-chrome-512x512-maskable.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
},{
"src": "images/favicon-96x96.png",
"sizes": "96x96",
"type": "image/png"
}],
"background_color": "#efefef",
"start_url": "/",
"scope": "/",
"display": "minimal-ui",
"theme_color": "#3367d6",
"screenshots" : [
{
"src": "images/pairdrop_screenshot_mobile_1.png",
"sizes": "1170x2532",
"type": "image/png"
},
{
"src": "images/pairdrop_screenshot_mobile_2.png",
"sizes": "1170x2532",
"type": "image/png"
},
{
"src": "images/pairdrop_screenshot_mobile_3.png",
"sizes": "1170x2532",
"type": "image/png"
},
{
"src": "images/pairdrop_screenshot_mobile_4.png",
"sizes": "1170x2532",
"type": "image/png"
},
{
"src": "images/pairdrop_screenshot_mobile_5.png",
"sizes": "1170x2532",
"type": "image/png"
},
{
"src": "images/pairdrop_screenshot_mobile_6.png",
"sizes": "1170x2532",
"type": "image/png"
},
{
"src": "images/pairdrop_screenshot_mobile_7.png",
"sizes": "1170x2532",
"type": "image/png"
}
],
"share_target": {
"action": "/",
"method":"POST",
"enctype": "multipart/form-data",
"params": {
"title": "title",
"text": "text",
"url": "url",
"files": [{
"name": "allfiles",
"accept": ["*/*"]
}]
}
},
"file_handlers": [
{
"action": "/?file_handler",
"name": "All Files",
"accept": {
"application/cpl+xml": [".cpl"],
"application/gpx+xml": [".gpx"],
"application/gzip": [".gz"],
"application/java-archive": [".jar", ".war", ".ear"],
"application/java-vm": [".class"],
"application/javascript": [".js", ".mjs"],
"application/json": [".json", ".map"],
"application/manifest+json": [".webmanifest"],
"application/msword": [".doc", ".dot", ".wiz"],
"application/octet-stream": [".bin", ".dms", ".lrf", ".mar", ".so", ".dist", ".distz", ".pkg", ".bpk", ".dump", ".elc", ".deploy", ".exe", ".dll", ".deb", ".dmg", ".iso", ".img", ".msi", ".msp", ".msm", ".buffer"],
"application/oda": [".oda"],
"application/oxps": [".oxps"],
"application/pdf": [".pdf"],
"application/pgp-signature": [".asc", ".sig"],
"application/pics-rules": [".prf"],
"application/pkcs7-mime": [".p7c"],
"application/pkix-cert": [".cer"],
"application/postscript": [".ai", ".eps", ".ps"],
"application/rtf": [".rtf"],
"application/vnd.android.package-archive": [".apk"],
"application/vnd.apple.mpegurl": [".m3u", ".m3u8"],
"application/vnd.apple.pkpass": [".pkpass"],
"application/vnd.google-earth.kml+xml": [".kml"],
"application/vnd.google-earth.kmz": [".kmz"],
"application/vnd.ms-cab-compressed": [".cab"],
"application/vnd.ms-excel": [".xls", ".xlm", ".xla", ".xlc", ".xlt", ".xlw"],
"application/vnd.ms-outlook": [".msg"],
"application/vnd.ms-powerpoint": [".ppt", ".pot", ".ppa", ".pps", ".pwz"],
"application/vnd.ms-project": [".mpp", ".mpt"],
"application/vnd.ms-xpsdocument": [".xps"],
"application/vnd.oasis.opendocument.database": [".odb"],
"application/vnd.oasis.opendocument.spreadsheet": [".ods"],
"application/vnd.oasis.opendocument.text": [".odt"],
"application/vnd.openstreetmap.data+xml": [".osm"],
"application/vnd.openxmlformats-officedocument.presentationml.presentation": [".pptx"],
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"],
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": [".docx"],
"application/vnd.tcpdump.pcap": [".pcap", ".cap", ".dmp"],
"application/vnd.wordperfect": [".wpd"],
"application/wasm": [".wasm"],
"application/x-7z-compressed": [".7z"],
"application/x-apple-diskimage": [".dmg"],
"application/x-bcpio": [".bcpio"],
"application/x-bittorrent": [".torrent"],
"application/x-cbr": [".cbr", ".cba", ".cbt", ".cbz", ".cb7"],
"application/x-cdlink": [".vcd"],
"application/x-chrome-extension": [".crx"],
"application/x-cpio": [".cpio"],
"application/x-csh": [".csh"],
"application/x-debian-package": [".deb", ".udeb"],
"application/x-dvi": [".dvi"],
"application/x-freearc": [".arc"],
"application/x-gtar": [".gtar"],
"application/x-hdf": [".hdf"],
"application/x-hdf5": [".h5"],
"application/x-httpd-php": [".php"],
"application/x-iso9660-image": [".iso"],
"application/x-iwork-keynote-sffkey": [".key"],
"application/x-iwork-numbers-sffnumbers": [".numbers"],
"application/x-iwork-pages-sffpages": [".pages"],
"application/x-latex": [".latex"],
"application/x-makeself": [".run"],
"application/x-mif": [".mif"],
"application/x-ms-shortcut": [".lnk"],
"application/x-msaccess": [".mdb"],
"application/x-msdownload": [".exe", ".dll", ".com", ".bat", ".msi"],
"application/x-mspublisher": [".pub"],
"application/x-netcdf": [".cdf", ".nc"],
"application/x-perl": [".pl", ".pm"],
"application/x-pilot": [".prc", ".pdb"],
"application/x-pkcs12": [".p12", ".pfx"],
"application/x-pn-realaudio": [".ram"],
"application/x-python-code": [".pyc", ".pyo"],
"application/x-rar-compressed": [".rar"],
"application/x-redhat-package-manager": [".rpm"],
"application/x-sh": [".sh"],
"application/x-shar": [".shar"],
"application/x-shockwave-flash": [".swf"],
"application/x-sql": [".sql"],
"application/x-subrip": [".srt"],
"application/x-sv4cpio": [".sv4cpio"],
"application/x-sv4crc": [".sv4crc"],
"application/x-tads": [".gam"],
"application/x-tar": [".tar"],
"application/x-tcl": [".tcl"],
"application/x-tex": [".tex"],
"application/x-troff": [".roff", ".t", ".tr"],
"application/x-troff-man": [".man"],
"application/x-troff-me": [".me"],
"application/x-troff-ms": [".ms"],
"application/x-ustar": [".ustar"],
"application/x-wais-source": [".src"],
"application/x-xpinstall": [".xpi"],
"application/xhtml+xml": [".xhtml", ".xht"],
"application/xml": [".xsl", ".rdf", ".wsdl", ".xpdl"],
"application/zip": [".zip"],
"audio/3gpp": [".3gp", ".3gpp"],
"audio/3gpp2": [".3g2", ".3gpp2"],
"audio/aac": [".aac", ".adts", ".loas", ".ass"],
"audio/basic": [".au", ".snd"],
"audio/midi": [".mid", ".midi", ".kar", ".rmi"],
"audio/mpeg": [".mpga", ".mp2", ".mp2a", ".mp3", ".m2a", ".m3a"],
"audio/ogg": [".oga", ".ogg", ".spx", ".opus"],
"audio/opus": [".opus"],
"audio/x-aiff": [".aif", ".aifc", ".aiff"],
"audio/x-flac": [".flac"],
"audio/x-m4a": [".m4a"],
"audio/x-mpegurl": [".m3u"],
"audio/x-ms-wma": [".wma"],
"audio/x-pn-realaudio": [".ra"],
"audio/x-wav": [".wav"],
"font/otf": [".otf"],
"font/ttf": [".ttf"],
"font/woff": [".woff"],
"font/woff2": [".woff2"],
"image/emf": [".emf"],
"image/gif": [".gif"],
"image/heic": [".heic"],
"image/heif": [".heif"],
"image/ief": [".ief"],
"image/jpeg": [".jpeg", ".jpg"],
"image/jpg": [".jpg"],
"image/pict": [".pict", ".pct", ".pic"],
"image/png": [".png"],
"image/svg+xml": [".svg", ".svgz"],
"image/tiff": [".tif", ".tiff"],
"image/vnd.adobe.photoshop": [".psd"],
"image/vnd.djvu": [".djvu", ".djv"],
"image/vnd.dwg": [".dwg"],
"image/vnd.dxf": [".dxf"],
"image/vnd.microsoft.icon": [".ico"],
"image/vnd.ms-dds": [".dds"],
"image/x-3ds": [".3ds"],
"image/x-cmu-raster": [".ras"],
"image/x-icon": [".ico"],
"image/x-ms-bmp": [".bmp"],
"image/x-portable-anymap": [".pnm"],
"image/x-portable-bitmap": [".pbm"],
"image/x-portable-graymap": [".pgm"],
"image/x-portable-pixmap": [".ppm"],
"image/x-rgb": [".rgb"],
"image/x-tga": [".tga"],
"image/x-xbitmap": [".xbm"],
"image/x-xpixmap": [".xpm"],
"image/x-xwindowdump": [".xwd"],
"message/rfc822": [".eml", ".mht", ".mhtml", ".nws"],
"model/obj": [".obj"],
"model/stl": [".stl"],
"model/vnd.collada+xml": [".dae"],
"text/calendar": [".ics", ".ifb"],
"text/css": [".css"],
"text/csv": [".csv"],
"text/html": [".html", ".htm", ".shtml"],
"text/markdown": [".markdown", ".md"],
"text/plain": [".txt", ".text", ".conf", ".def", ".list", ".log", ".in", ".ini"],
"text/richtext": [".rtx"],
"text/rtf": [".rtf"],
"text/tab-separated-values": [".tsv"],
"text/x-c": [".c", ".cc", ".cxx", ".cpp", ".h", ".hh", ".dic"],
"text/x-java-source": [".java"],
"text/x-lua": [".lua"],
"text/x-python": [".py"],
"text/x-setext": [".etx"],
"text/x-sgml": [".sgm", ".sgml"],
"text/x-vcard": [".vcf"],
"text/xml": [".xml"],
"text/xul": [".xul"],
"text/yaml": [".yaml", ".yml"],
"video/3gpp": [".3gp", ".3gpp"],
"video/mp2t": [".ts"],
"video/mp4": [".mp4", ".mp4v", ".mpg4"],
"video/mpeg": [".mpeg", ".m1v", ".mpa", ".mpe", ".mpg"],
"video/quicktime": [".mov", ".qt"],
"video/webm": [".webm"],
"video/x-flv": [".flv"],
"video/x-m4v": [".m4v"],
"video/x-ms-asf": [".asf", ".asx"],
"video/x-ms-vob": [".vob"],
"video/x-ms-wmv": [".wmv"],
"video/x-msvideo": [".avi"],
"video/x-sgi-movie": [".*"]
},
"icons": [
{
"src": "/images/android-chrome-192x192.png",
"sizes": "192x192"
}
]
}
],
"launch_handler": {
"client_mode": "focus-existing"
}
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,893 @@
window.URL = window.URL || window.webkitURL;
window.isRtcSupported = !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection);
class ServerConnection {
constructor() {
this._connect();
Events.on('pagehide', _ => this._disconnect());
document.addEventListener('visibilitychange', _ => this._onVisibilityChange());
if (navigator.connection) navigator.connection.addEventListener('change', _ => this._reconnect());
Events.on('room-secrets', e => this._sendRoomSecrets(e.detail));
Events.on('room-secret-deleted', e => this.send({ type: 'room-secret-deleted', roomSecret: e.detail}));
Events.on('room-secrets-cleared', e => this.send({ type: 'room-secrets-cleared', roomSecrets: e.detail}));
Events.on('resend-peers', _ => this.send({ type: 'resend-peers'}));
Events.on('pair-device-initiate', _ => this._onPairDeviceInitiate());
Events.on('pair-device-join', e => this._onPairDeviceJoin(e.detail));
Events.on('pair-device-cancel', _ => this.send({ type: 'pair-device-cancel' }));
Events.on('offline', _ => clearTimeout(this._reconnectTimer));
Events.on('online', _ => this._connect());
}
async _connect() {
clearTimeout(this._reconnectTimer);
if (this._isConnected() || this._isConnecting()) return;
const ws = new WebSocket(await this._endpoint());
ws.binaryType = 'arraybuffer';
ws.onopen = _ => this._onOpen();
ws.onmessage = e => this._onMessage(e.data);
ws.onclose = _ => this._onDisconnect();
ws.onerror = e => this._onError(e);
this._socket = ws;
}
_onOpen() {
console.log('WS: server connected');
Events.fire('ws-connected');
}
_sendRoomSecrets(roomSecrets) {
this.send({ type: 'room-secrets', roomSecrets: roomSecrets });
}
_onPairDeviceInitiate() {
if (!this._isConnected()) {
Events.fire('notify-user', 'You need to be online to pair devices.');
return;
}
this.send({ type: 'pair-device-initiate' })
}
_onPairDeviceJoin(roomKey) {
if (!this._isConnected()) {
setTimeout(_ => this._onPairDeviceJoin(roomKey), 5000);
return;
}
this.send({ type: 'pair-device-join', roomKey: roomKey })
}
_onMessage(msg) {
msg = JSON.parse(msg);
if (msg.type !== 'ping') console.log('WS:', msg);
switch (msg.type) {
case 'peers':
Events.fire('peers', msg);
break;
case 'peer-joined':
Events.fire('peer-joined', msg);
break;
case 'peer-left':
Events.fire('peer-left', msg.peerId);
break;
case 'signal':
Events.fire('signal', msg);
break;
case 'ping':
this.send({ type: 'pong' });
break;
case 'display-name':
this._onDisplayName(msg);
break;
case 'pair-device-initiated':
Events.fire('pair-device-initiated', msg);
break;
case 'pair-device-joined':
Events.fire('pair-device-joined', msg.roomSecret);
break;
case 'pair-device-join-key-invalid':
Events.fire('pair-device-join-key-invalid');
break;
case 'pair-device-canceled':
Events.fire('pair-device-canceled', msg.roomKey);
break;
case 'pair-device-join-key-rate-limit':
Events.fire('notify-user', 'Rate limit reached. Wait 10 seconds and try again.');
break;
case 'secret-room-deleted':
Events.fire('secret-room-deleted', msg.roomSecret);
break;
case 'request':
case 'header':
case 'partition':
case 'partition-received':
case 'progress':
case 'files-transfer-response':
case 'file-transfer-complete':
case 'message-transfer-complete':
case 'text':
case 'ws-chunk':
Events.fire('ws-relay', JSON.stringify(msg));
break;
default:
console.error('WS: unknown message type', msg);
}
}
send(msg) {
if (!this._isConnected()) return;
this._socket.send(JSON.stringify(msg));
}
_onDisplayName(msg) {
sessionStorage.setItem("peerId", msg.message.peerId);
PersistentStorage.get('peerId').then(peerId => {
if (!peerId) {
// save peerId to indexedDB to retrieve after PWA is installed
PersistentStorage.set('peerId', msg.message.peerId).then(peerId => {
console.log(`peerId saved to indexedDB: ${peerId}`);
});
}
}).catch(_ => _ => PersistentStorage.logBrowserNotCapable())
Events.fire('display-name', msg);
}
async _endpoint() {
// hack to detect if deployment or development environment
const protocol = location.protocol.startsWith('https') ? 'wss' : 'ws';
const webrtc = window.isRtcSupported ? '/webrtc' : '/fallback';
let ws_url = new URL(protocol + '://' + location.host + location.pathname + 'server' + webrtc);
const peerId = await this._peerId();
if (peerId) ws_url.searchParams.append('peer_id', peerId)
return ws_url.toString();
}
async _peerId() {
// make peerId persistent when pwa is installed
return window.matchMedia('(display-mode: minimal-ui)').matches
? await PersistentStorage.get('peerId')
: sessionStorage.getItem("peerId");
}
_disconnect() {
this.send({ type: 'disconnect' });
if (this._socket) {
this._socket.onclose = null;
this._socket.close();
this._socket = null;
Events.fire('ws-disconnected');
}
}
_onDisconnect() {
console.log('WS: server disconnected');
Events.fire('notify-user', 'No server connection. Retry in 5s...');
clearTimeout(this._reconnectTimer);
this._reconnectTimer = setTimeout(_ => this._connect(), 5000);
Events.fire('ws-disconnected');
}
_onVisibilityChange() {
if (document.hidden) return;
this._connect();
}
_isConnected() {
return this._socket && this._socket.readyState === this._socket.OPEN;
}
_isConnecting() {
return this._socket && this._socket.readyState === this._socket.CONNECTING;
}
_onError(e) {
console.error(e);
}
_reconnect() {
this._disconnect();
this._connect();
}
}
class Peer {
constructor(serverConnection, peerId, roomType, roomSecret) {
this._server = serverConnection;
this._peerId = peerId;
this._roomType = roomType;
this._roomSecret = roomSecret;
this._filesQueue = [];
this._busy = false;
}
sendJSON(message) {
this._send(JSON.stringify(message));
}
async createHeader(file) {
return {
name: file.name,
mime: file.type,
size: file.size,
};
}
getResizedImageDataUrl(file, width = undefined, height = undefined, quality = 0.7) {
return new Promise((resolve, reject) => {
let image = new Image();
image.src = URL.createObjectURL(file);
image.onload = _ => {
let imageWidth = image.width;
let imageHeight = image.height;
let canvas = document.createElement('canvas');
// resize the canvas and draw the image data into it
if (width && height) {
canvas.width = width;
canvas.height = height;
} else if (width) {
canvas.width = width;
canvas.height = Math.floor(imageHeight * width / imageWidth)
} else if (height) {
canvas.width = Math.floor(imageWidth * height / imageHeight);
canvas.height = height;
} else {
canvas.width = imageWidth;
canvas.height = imageHeight
}
var ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
let dataUrl = canvas.toDataURL("image/jpeg", quality);
resolve(dataUrl);
}
image.onerror = _ => reject(`Could not create an image thumbnail from type ${file.type}`);
}).then(dataUrl => {
return dataUrl;
}).catch(e => console.error(e));
}
async requestFileTransfer(files) {
let header = [];
let totalSize = 0;
let imagesOnly = true
for (let i=0; i<files.length; i++) {
Events.fire('set-progress', {peerId: this._peerId, progress: 0.8*i/files.length, status: 'prepare'})
header.push(await this.createHeader(files[i]));
totalSize += files[i].size;
if (files[i].type.split('/')[0] !== 'image') imagesOnly = false;
}
Events.fire('set-progress', {peerId: this._peerId, progress: 0.8, status: 'prepare'})
let dataUrl = '';
if (files[0].type.split('/')[0] === 'image') {
dataUrl = await this.getResizedImageDataUrl(files[0], 400, null, 0.9);
}
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'prepare'})
this._filesRequested = files;
this.sendJSON({type: 'request',
header: header,
totalSize: totalSize,
imagesOnly: imagesOnly,
thumbnailDataUrl: dataUrl
});
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'wait'})
}
async sendFiles() {
for (let i=0; i<this._filesRequested.length; i++) {
this._filesQueue.push(this._filesRequested[i]);
}
this._filesRequested = null
if (this._busy) return;
this._dequeueFile();
}
_dequeueFile() {
this._busy = true;
const file = this._filesQueue.shift();
this._sendFile(file);
}
async _sendFile(file) {
this.sendJSON({
type: 'header',
size: file.size,
name: file.name,
mime: file.type
});
this._chunker = new FileChunker(file,
chunk => this._send(chunk),
offset => this._onPartitionEnd(offset));
this._chunker.nextPartition();
}
_onPartitionEnd(offset) {
this.sendJSON({ type: 'partition', offset: offset });
}
_onReceivedPartitionEnd(offset) {
this.sendJSON({ type: 'partition-received', offset: offset });
}
_sendNextPartition() {
if (!this._chunker || this._chunker.isFileEnd()) return;
this._chunker.nextPartition();
}
_sendProgress(progress) {
this.sendJSON({ type: 'progress', progress: progress });
}
_onMessage(message, logMessage = true) {
if (typeof message !== 'string') {
this._onChunkReceived(message);
return;
}
message = JSON.parse(message);
if (logMessage) console.log('RTC:', message);
switch (message.type) {
case 'request':
this._onFilesTransferRequest(message);
break;
case 'header':
this._onFilesHeader(message);
break;
case 'partition':
this._onReceivedPartitionEnd(message);
break;
case 'partition-received':
this._sendNextPartition();
break;
case 'progress':
this._onDownloadProgress(message.progress);
break;
case 'files-transfer-response':
this._onFileTransferRequestResponded(message);
break;
case 'file-transfer-complete':
this._onFileTransferCompleted();
break;
case 'message-transfer-complete':
this._onMessageTransferCompleted();
break;
case 'text':
this._onTextReceived(message);
break;
}
}
_onFilesTransferRequest(request) {
if (this._requestPending) {
// Only accept one request at a time
this.sendJSON({type: 'files-transfer-response', accepted: false});
return;
}
if (window.iOS && request.totalSize >= 200*1024*1024) {
// iOS Safari can only put 400MB at once to memory.
// Request to send them in chunks of 200MB instead:
this.sendJSON({type: 'files-transfer-response', accepted: false, reason: 'ios-memory-limit'});
return;
}
this._requestPending = request;
Events.fire('files-transfer-request', {
request: request,
peerId: this._peerId
});
}
_respondToFileTransferRequest(accepted) {
this.sendJSON({type: 'files-transfer-response', accepted: accepted});
if (accepted) {
this._requestAccepted = this._requestPending;
this._totalBytesReceived = 0;
this._busy = true;
this._filesReceived = [];
}
this._requestPending = null;
}
_onFilesHeader(header) {
if (this._requestAccepted?.header.length) {
this._lastProgress = 0;
this._digester = new FileDigester({size: header.size, name: header.name, mime: header.mime},
this._requestAccepted.totalSize,
this._totalBytesReceived,
fileBlob => this._onFileReceived(fileBlob)
);
}
}
_abortTransfer() {
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'wait'});
Events.fire('notify-user', 'Files are incorrect.');
this._filesReceived = [];
this._requestAccepted = null;
this._digester = null;
throw new Error("Received files differ from requested files. Abort!");
}
_onChunkReceived(chunk) {
if(!this._digester || !(chunk.byteLength || chunk.size)) return;
this._digester.unchunk(chunk);
const progress = this._digester.progress;
if (progress > 1) {
this._abortTransfer();
}
this._onDownloadProgress(progress);
// occasionally notify sender about our progress
if (progress - this._lastProgress < 0.005 && progress !== 1) return;
this._lastProgress = progress;
this._sendProgress(progress);
}
_onDownloadProgress(progress) {
Events.fire('set-progress', {peerId: this._peerId, progress: progress, status: 'transfer'});
}
async _onFileReceived(fileBlob) {
const acceptedHeader = this._requestAccepted.header.shift();
this._totalBytesReceived += fileBlob.size;
this.sendJSON({type: 'file-transfer-complete'});
const sameSize = fileBlob.size === acceptedHeader.size;
const sameName = fileBlob.name === acceptedHeader.name
if (!sameSize || !sameName) {
this._abortTransfer();
}
this._filesReceived.push(fileBlob);
if (!this._requestAccepted.header.length) {
this._busy = false;
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'process'});
Events.fire('files-received', {sender: this._peerId, files: this._filesReceived, request: this._requestAccepted});
this._filesReceived = [];
this._requestAccepted = null;
}
}
_onFileTransferCompleted() {
this._chunker = null;
if (!this._filesQueue.length) {
this._busy = false;
Events.fire('notify-user', 'File transfer completed.');
} else {
this._dequeueFile();
}
}
_onFileTransferRequestResponded(message) {
if (!message.accepted) {
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'wait'});
this._filesRequested = null;
if (message.reason === 'ios-memory-limit') {
Events.fire('notify-user', "Sending files to iOS is only possible up to 200MB at once");
}
return;
}
Events.fire('file-transfer-accepted');
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'transfer'});
this.sendFiles();
}
_onMessageTransferCompleted() {
Events.fire('notify-user', 'Message transfer completed.');
}
sendText(text) {
const unescaped = btoa(unescape(encodeURIComponent(text)));
this.sendJSON({ type: 'text', text: unescaped });
}
_onTextReceived(message) {
if (!message.text) return;
const escaped = decodeURIComponent(escape(atob(message.text)));
Events.fire('text-received', { text: escaped, peerId: this._peerId });
this.sendJSON({ type: 'message-transfer-complete' });
}
}
class RTCPeer extends Peer {
constructor(serverConnection, peerId, roomType, roomSecret) {
super(serverConnection, peerId, roomType, roomSecret);
if (!peerId) return; // we will listen for a caller
this._connect(peerId, true);
}
_connect(peerId, isCaller) {
if (!this._conn || this._conn.signalingState === "closed") this._openConnection(peerId, isCaller);
if (isCaller) {
this._openChannel();
} else {
this._conn.ondatachannel = e => this._onChannelOpened(e);
}
}
_openConnection(peerId, isCaller) {
this._isCaller = isCaller;
this._peerId = peerId;
this._conn = new RTCPeerConnection(RTCPeer.config);
this._conn.onicecandidate = e => this._onIceCandidate(e);
this._conn.onconnectionstatechange = _ => this._onConnectionStateChange();
this._conn.oniceconnectionstatechange = e => this._onIceConnectionStateChange(e);
}
_openChannel() {
const channel = this._conn.createDataChannel('data-channel', {
ordered: true,
reliable: true // Obsolete. See https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/reliable
});
channel.onopen = e => this._onChannelOpened(e);
this._conn.createOffer().then(d => this._onDescription(d)).catch(e => this._onError(e));
}
_onDescription(description) {
// description.sdp = description.sdp.replace('b=AS:30', 'b=AS:1638400');
this._conn.setLocalDescription(description)
.then(_ => this._sendSignal({ sdp: description }))
.catch(e => this._onError(e));
}
_onIceCandidate(event) {
if (!event.candidate) return;
this._sendSignal({ ice: event.candidate });
}
onServerMessage(message) {
if (!this._conn) this._connect(message.sender.id, false);
if (message.sdp) {
this._conn.setRemoteDescription(message.sdp)
.then( _ => {
return this._conn.createAnswer()
.then(d => this._onDescription(d));
})
.catch(e => this._onError(e));
} else if (message.ice) {
this._conn.addIceCandidate(new RTCIceCandidate(message.ice));
}
}
_onChannelOpened(event) {
console.log('RTC: channel opened with', this._peerId);
Events.fire('peer-connected', this._peerId);
const channel = event.channel || event.target;
channel.binaryType = 'arraybuffer';
channel.onmessage = e => this._onMessage(e.data);
channel.onclose = _ => this._onChannelClosed();
Events.on('pagehide', _ => this._conn.close());
this._channel = channel;
}
_onChannelClosed() {
console.log('RTC: channel closed', this._peerId);
Events.fire('peer-disconnected', this._peerId);
if (this._channel) this._channel.onclose = null;
this._conn.close();
if (!this._isCaller) return;
this._connect(this._peerId, true); // reopen the channel
}
_onConnectionStateChange() {
console.log('RTC: state changed:', this._conn.connectionState);
switch (this._conn.connectionState) {
case 'disconnected':
this._onError('rtc connection disconnected');
break;
case 'failed':
this._onError('rtc connection failed');
break;
}
}
_onIceConnectionStateChange() {
switch (this._conn.iceConnectionState) {
case 'failed':
this._onError('ICE Gathering failed');
break;
default:
console.log('ICE Gathering', this._conn.iceConnectionState);
}
}
_onError(error) {
console.error(error);
}
_send(message) {
if (!this._channel) this.refresh();
this._channel.send(message);
}
_sendSignal(signal) {
signal.type = 'signal';
signal.to = this._peerId;
signal.roomType = this._roomType;
signal.roomSecret = this._roomSecret;
this._server.send(signal);
}
refresh() {
// check if channel is open. otherwise create one
if (this._isConnected() || this._isConnecting()) return;
this._connect(this._peerId, this._isCaller);
}
_isConnected() {
return this._channel && this._channel.readyState === 'open';
}
_isConnecting() {
return this._channel && this._channel.readyState === 'connecting';
}
}
class WSPeer extends Peer {
constructor(serverConnection, peerId, roomType, roomSecret) {
super(serverConnection, peerId, roomType, roomSecret);
if (!peerId) return; // we will listen for a caller
this._sendSignal();
}
_send(chunk) {
this.sendJSON({
type: 'ws-chunk',
chunk: arrayBufferToBase64(chunk)
});
}
sendJSON(message) {
message.to = this._peerId;
message.roomType = this._roomType;
message.roomSecret = this._roomSecret;
this._server.send(message);
}
_sendSignal() {
this.sendJSON({type: 'signal'});
}
onServerMessage(message) {
Events.fire('peer-connected', message.sender.id)
if (this._peerId) return;
this._peerId = message.sender.id;
this._sendSignal();
}
}
class PeersManager {
constructor(serverConnection) {
this.peers = {};
this._server = serverConnection;
Events.on('signal', e => this._onMessage(e.detail));
Events.on('peers', e => this._onPeers(e.detail));
Events.on('files-selected', e => this._onFilesSelected(e.detail));
Events.on('respond-to-files-transfer-request', e => this._onRespondToFileTransferRequest(e.detail))
Events.on('send-text', e => this._onSendText(e.detail));
Events.on('peer-left', e => this._onPeerLeft(e.detail));
Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail));
Events.on('secret-room-deleted', e => this._onSecretRoomDeleted(e.detail));
Events.on('beforeunload', e => this._onBeforeUnload(e));
Events.on('ws-relay', e => this._onWsRelay(e.detail));
}
_onBeforeUnload(e) {
for (const peerId in this.peers) {
if (this.peers[peerId]._busy) {
e.preventDefault();
return "There are unfinished transfers. Are you sure you want to close?";
}
}
}
_onMessage(message) {
// if different roomType -> abort
if (this.peers[message.sender.id] && this.peers[message.sender.id]._roomType !== message.roomType) return;
if (!this.peers[message.sender.id]) {
if (window.isRtcSupported && message.sender.rtcSupported) {
this.peers[message.sender.id] = new RTCPeer(this._server, undefined, message.roomType, message.roomSecret);
} else {
this.peers[message.sender.id] = new WSPeer(this._server, undefined, message.roomType, message.roomSecret);
}
}
this.peers[message.sender.id].onServerMessage(message);
}
_onWsRelay(message) {
const messageJSON = JSON.parse(message)
if (messageJSON.type === 'ws-chunk') message = base64ToArrayBuffer(messageJSON.chunk);
this.peers[messageJSON.sender.id]._onMessage(message, false)
}
_onPeers(msg) {
msg.peers.forEach(peer => {
if (this.peers[peer.id]) {
// if different roomType -> abort
if (this.peers[peer.id].roomType !== msg.roomType) return;
this.peers[peer.id].refresh();
return;
}
if (window.isRtcSupported && peer.rtcSupported) {
this.peers[peer.id] = new RTCPeer(this._server, peer.id, msg.roomType, msg.roomSecret);
} else {
this.peers[peer.id] = new WSPeer(this._server, peer.id, msg.roomType, msg.roomSecret);
}
})
}
sendTo(peerId, message) {
this.peers[peerId].send(message);
}
_onRespondToFileTransferRequest(detail) {
this.peers[detail.to]._respondToFileTransferRequest(detail.accepted);
}
_onFilesSelected(message) {
let inputFiles = Array.from(message.files);
delete message.files;
let files = [];
const l = inputFiles.length;
for (let i=0; i<l; i++) {
// when filetype is empty guess via suffix
const inputFile = inputFiles.shift();
const file = inputFile.type
? inputFile
: new File([inputFile], inputFile.name, {type: mime.getMimeByFilename(inputFile.name)});
files.push(file)
}
this.peers[message.to].requestFileTransfer(files);
}
_onSendText(message) {
this.peers[message.to].sendText(message.text);
}
_onPeerDisconnected(peerId) {
const peer = this.peers[peerId];
delete this.peers[peerId];
}
_onPeerLeft(peerId) {
if (!this.peers[peerId]?.rtcSupported) {
console.log('WSPeer left:', peerId)
Events.fire('peer-disconnected', peerId)
}
}
_onSecretRoomDeleted(roomSecret) {
for (const peerId in this.peers) {
const peer = this.peers[peerId];
if (peer._roomSecret === roomSecret) {
this._onPeerLeft(peerId);
}
}
}
}
class FileChunker {
constructor(file, onChunk, onPartitionEnd) {
this._chunkSize = 64000; // 64 KB
this._maxPartitionSize = 1e6; // 1 MB
this._offset = 0;
this._partitionSize = 0;
this._file = file;
this._onChunk = onChunk;
this._onPartitionEnd = onPartitionEnd;
this._reader = new FileReader();
this._reader.addEventListener('load', e => this._onChunkRead(e.target.result));
}
nextPartition() {
this._partitionSize = 0;
this._readChunk();
}
_readChunk() {
const chunk = this._file.slice(this._offset, this._offset + this._chunkSize);
this._reader.readAsArrayBuffer(chunk);
}
_onChunkRead(chunk) {
this._offset += chunk.byteLength;
this._partitionSize += chunk.byteLength;
this._onChunk(chunk);
if (this.isFileEnd()) return;
if (this._isPartitionEnd()) {
this._onPartitionEnd(this._offset);
return;
}
this._readChunk();
}
repeatPartition() {
this._offset -= this._partitionSize;
this.nextPartition();
}
_isPartitionEnd() {
return this._partitionSize >= this._maxPartitionSize;
}
isFileEnd() {
return this._offset >= this._file.size;
}
}
class FileDigester {
constructor(meta, totalSize, totalBytesReceived, callback) {
this._buffer = [];
this._bytesReceived = 0;
this._size = meta.size;
this._name = meta.name;
this._mime = meta.mime;
this._totalSize = totalSize;
this._totalBytesReceived = totalBytesReceived;
this._callback = callback;
}
unchunk(chunk) {
this._buffer.push(chunk);
this._bytesReceived += chunk.byteLength || chunk.size;
this.progress = (this._totalBytesReceived + this._bytesReceived) / this._totalSize;
if (isNaN(this.progress)) this.progress = 1
if (this._bytesReceived < this._size) return;
// we are done
const blob = new Blob(this._buffer)
this._buffer = null;
this._callback(new File([blob], this._name, {
type: this._mime,
lastModified: new Date().getTime()
}));
}
}
class Events {
static fire(type, detail) {
window.dispatchEvent(new CustomEvent(type, { detail: detail }));
}
static on(type, callback) {
return window.addEventListener(type, callback, false);
}
static off(type, callback) {
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',
},
]
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,39 @@
(function(){
// Select the button
const btnTheme = document.getElementById('theme');
// Check for dark mode preference at the OS level
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
// Get the user's theme preference from local storage, if it's available
const currentTheme = localStorage.getItem('theme');
// If the user's preference in localStorage is dark...
if (currentTheme === 'dark') {
// ...let's toggle the .dark-theme class on the body
document.body.classList.toggle('dark-theme');
// Otherwise, if the user's preference in localStorage is light...
} else if (currentTheme === 'light') {
// ...let's toggle the .light-theme class on the body
document.body.classList.toggle('light-theme');
}
// Listen for a click on the button
btnTheme.addEventListener('click', function(e) {
e.preventDefault();
// If the user's OS setting is dark and matches our .dark-theme class...
let theme;
if (prefersDarkScheme.matches) {
// ...then toggle the light mode class
document.body.classList.toggle('light-theme');
// ...but use .dark-theme if the .light-theme class is already on the body,
theme = document.body.classList.contains('light-theme') ? 'light' : 'dark';
} else {
// Otherwise, let's do the same thing, but for .dark-theme
document.body.classList.toggle('dark-theme');
theme = document.body.classList.contains('dark-theme') ? 'dark' : 'light';
}
// Finally, let's save the current preference to localStorage to keep using it
localStorage.setItem('theme', theme);
});
})();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,402 @@
// Polyfill for Navigator.clipboard.writeText
if (!navigator.clipboard) {
navigator.clipboard = {
writeText: text => {
// A <span> contains the text to copy
const span = document.createElement('span');
span.textContent = text;
span.style.whiteSpace = 'pre'; // Preserve consecutive spaces and newlines
// Paint the span outside the viewport
span.style.position = 'absolute';
span.style.left = '-9999px';
span.style.top = '-9999px';
const win = window;
const selection = win.getSelection();
win.document.body.appendChild(span);
const range = win.document.createRange();
selection.removeAllRanges();
range.selectNode(span);
selection.addRange(range);
let success = false;
try {
success = win.document.execCommand('copy');
} catch (err) {
return Promise.error();
}
selection.removeAllRanges();
span.remove();
return Promise.resolve();
}
}
}
const zipper = (() => {
let zipWriter;
return {
createNewZipWriter() {
zipWriter = new zip.ZipWriter(new zip.BlobWriter("application/zip"), { bufferedWrite: true, level: 0 });
},
addFile(file, options) {
return zipWriter.add(file.name, new zip.BlobReader(file), options);
},
async getBlobURL() {
if (zipWriter) {
const blobURL = URL.createObjectURL(await zipWriter.close());
zipWriter = null;
return blobURL;
} else {
throw new Error("Zip file closed");
}
},
async getZipFile(filename = "archive.zip") {
if (zipWriter) {
const file = new File([await zipWriter.close()], filename, {type: "application/zip"});
zipWriter = null;
return file;
} else {
throw new Error("Zip file closed");
}
},
async getEntries(file, options) {
return await (new zip.ZipReader(new zip.BlobReader(file))).getEntries(options);
},
async getData(entry, options) {
return await entry.getData(new zip.BlobWriter(), options);
},
};
})();
const mime = (() => {
return {
getMimeByFilename(filename) {
try {
const arr = filename.split('.');
const suffix = arr[arr.length - 1].toLowerCase();
return {
"cpl": "application/cpl+xml",
"gpx": "application/gpx+xml",
"gz": "application/gzip",
"jar": "application/java-archive",
"war": "application/java-archive",
"ear": "application/java-archive",
"class": "application/java-vm",
"js": "application/javascript",
"mjs": "application/javascript",
"json": "application/json",
"map": "application/json",
"webmanifest": "application/manifest+json",
"doc": "application/msword",
"dot": "application/msword",
"wiz": "application/msword",
"bin": "application/octet-stream",
"dms": "application/octet-stream",
"lrf": "application/octet-stream",
"mar": "application/octet-stream",
"so": "application/octet-stream",
"dist": "application/octet-stream",
"distz": "application/octet-stream",
"pkg": "application/octet-stream",
"bpk": "application/octet-stream",
"dump": "application/octet-stream",
"elc": "application/octet-stream",
"deploy": "application/octet-stream",
"img": "application/octet-stream",
"msp": "application/octet-stream",
"msm": "application/octet-stream",
"buffer": "application/octet-stream",
"oda": "application/oda",
"oxps": "application/oxps",
"pdf": "application/pdf",
"asc": "application/pgp-signature",
"sig": "application/pgp-signature",
"prf": "application/pics-rules",
"p7c": "application/pkcs7-mime",
"cer": "application/pkix-cert",
"ai": "application/postscript",
"eps": "application/postscript",
"ps": "application/postscript",
"apk": "application/vnd.android.package-archive",
"m3u8": "application/vnd.apple.mpegurl",
"pkpass": "application/vnd.apple.pkpass",
"kml": "application/vnd.google-earth.kml+xml",
"kmz": "application/vnd.google-earth.kmz",
"cab": "application/vnd.ms-cab-compressed",
"xls": "application/vnd.ms-excel",
"xlm": "application/vnd.ms-excel",
"xla": "application/vnd.ms-excel",
"xlc": "application/vnd.ms-excel",
"xlt": "application/vnd.ms-excel",
"xlw": "application/vnd.ms-excel",
"msg": "application/vnd.ms-outlook",
"ppt": "application/vnd.ms-powerpoint",
"pot": "application/vnd.ms-powerpoint",
"ppa": "application/vnd.ms-powerpoint",
"pps": "application/vnd.ms-powerpoint",
"pwz": "application/vnd.ms-powerpoint",
"mpp": "application/vnd.ms-project",
"mpt": "application/vnd.ms-project",
"xps": "application/vnd.ms-xpsdocument",
"odb": "application/vnd.oasis.opendocument.database",
"ods": "application/vnd.oasis.opendocument.spreadsheet",
"odt": "application/vnd.oasis.opendocument.text",
"osm": "application/vnd.openstreetmap.data+xml",
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"pcap": "application/vnd.tcpdump.pcap",
"cap": "application/vnd.tcpdump.pcap",
"dmp": "application/vnd.tcpdump.pcap",
"wpd": "application/vnd.wordperfect",
"wasm": "application/wasm",
"7z": "application/x-7z-compressed",
"dmg": "application/x-apple-diskimage",
"bcpio": "application/x-bcpio",
"torrent": "application/x-bittorrent",
"cbr": "application/x-cbr",
"cba": "application/x-cbr",
"cbt": "application/x-cbr",
"cbz": "application/x-cbr",
"cb7": "application/x-cbr",
"vcd": "application/x-cdlink",
"crx": "application/x-chrome-extension",
"cpio": "application/x-cpio",
"csh": "application/x-csh",
"deb": "application/x-debian-package",
"udeb": "application/x-debian-package",
"dvi": "application/x-dvi",
"arc": "application/x-freearc",
"gtar": "application/x-gtar",
"hdf": "application/x-hdf",
"h5": "application/x-hdf5",
"php": "application/x-httpd-php",
"iso": "application/x-iso9660-image",
"key": "application/x-iwork-keynote-sffkey",
"numbers": "application/x-iwork-numbers-sffnumbers",
"pages": "application/x-iwork-pages-sffpages",
"latex": "application/x-latex",
"run": "application/x-makeself",
"mif": "application/x-mif",
"lnk": "application/x-ms-shortcut",
"mdb": "application/x-msaccess",
"exe": "application/x-msdownload",
"dll": "application/x-msdownload",
"com": "application/x-msdownload",
"bat": "application/x-msdownload",
"msi": "application/x-msdownload",
"pub": "application/x-mspublisher",
"cdf": "application/x-netcdf",
"nc": "application/x-netcdf",
"pl": "application/x-perl",
"pm": "application/x-perl",
"prc": "application/x-pilot",
"pdb": "application/x-pilot",
"p12": "application/x-pkcs12",
"pfx": "application/x-pkcs12",
"ram": "application/x-pn-realaudio",
"pyc": "application/x-python-code",
"pyo": "application/x-python-code",
"rar": "application/x-rar-compressed",
"rpm": "application/x-redhat-package-manager",
"sh": "application/x-sh",
"shar": "application/x-shar",
"swf": "application/x-shockwave-flash",
"sql": "application/x-sql",
"srt": "application/x-subrip",
"sv4cpio": "application/x-sv4cpio",
"sv4crc": "application/x-sv4crc",
"gam": "application/x-tads",
"tar": "application/x-tar",
"tcl": "application/x-tcl",
"tex": "application/x-tex",
"roff": "application/x-troff",
"t": "application/x-troff",
"tr": "application/x-troff",
"man": "application/x-troff-man",
"me": "application/x-troff-me",
"ms": "application/x-troff-ms",
"ustar": "application/x-ustar",
"src": "application/x-wais-source",
"xpi": "application/x-xpinstall",
"xhtml": "application/xhtml+xml",
"xht": "application/xhtml+xml",
"xsl": "application/xml",
"rdf": "application/xml",
"wsdl": "application/xml",
"xpdl": "application/xml",
"zip": "application/zip",
"3gp": "audio/3gp",
"3gpp": "audio/3gpp",
"3g2": "audio/3gpp2",
"3gpp2": "audio/3gpp2",
"aac": "audio/aac",
"adts": "audio/aac",
"loas": "audio/aac",
"ass": "audio/aac",
"au": "audio/basic",
"snd": "audio/basic",
"mid": "audio/midi",
"midi": "audio/midi",
"kar": "audio/midi",
"rmi": "audio/midi",
"mpga": "audio/mpeg",
"mp2": "audio/mpeg",
"mp2a": "audio/mpeg",
"mp3": "audio/mpeg",
"m2a": "audio/mpeg",
"m3a": "audio/mpeg",
"oga": "audio/ogg",
"ogg": "audio/ogg",
"spx": "audio/ogg",
"opus": "audio/opus",
"aif": "audio/x-aiff",
"aifc": "audio/x-aiff",
"aiff": "audio/x-aiff",
"flac": "audio/x-flac",
"m4a": "audio/x-m4a",
"m3u": "audio/x-mpegurl",
"wma": "audio/x-ms-wma",
"ra": "audio/x-pn-realaudio",
"wav": "audio/x-wav",
"otf": "font/otf",
"ttf": "font/ttf",
"woff": "font/woff",
"woff2": "font/woff2",
"emf": "image/emf",
"gif": "image/gif",
"heic": "image/heic",
"heif": "image/heif",
"ief": "image/ief",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"pict": "image/pict",
"pct": "image/pict",
"pic": "image/pict",
"png": "image/png",
"svg": "image/svg+xml",
"svgz": "image/svg+xml",
"tif": "image/tiff",
"tiff": "image/tiff",
"psd": "image/vnd.adobe.photoshop",
"djvu": "image/vnd.djvu",
"djv": "image/vnd.djvu",
"dwg": "image/vnd.dwg",
"dxf": "image/vnd.dxf",
"dds": "image/vnd.ms-dds",
"webp": "image/webp",
"3ds": "image/x-3ds",
"ras": "image/x-cmu-raster",
"ico": "image/x-icon",
"bmp": "image/x-ms-bmp",
"pnm": "image/x-portable-anymap",
"pbm": "image/x-portable-bitmap",
"pgm": "image/x-portable-graymap",
"ppm": "image/x-portable-pixmap",
"rgb": "image/x-rgb",
"tga": "image/x-tga",
"xbm": "image/x-xbitmap",
"xpm": "image/x-xpixmap",
"xwd": "image/x-xwindowdump",
"eml": "message/rfc822",
"mht": "message/rfc822",
"mhtml": "message/rfc822",
"nws": "message/rfc822",
"obj": "model/obj",
"stl": "model/stl",
"dae": "model/vnd.collada+xml",
"ics": "text/calendar",
"ifb": "text/calendar",
"css": "text/css",
"csv": "text/csv",
"html": "text/html",
"htm": "text/html",
"shtml": "text/html",
"markdown": "text/markdown",
"md": "text/markdown",
"txt": "text/plain",
"text": "text/plain",
"conf": "text/plain",
"def": "text/plain",
"list": "text/plain",
"log": "text/plain",
"in": "text/plain",
"ini": "text/plain",
"rtx": "text/richtext",
"rtf": "text/rtf",
"tsv": "text/tab-separated-values",
"c": "text/x-c",
"cc": "text/x-c",
"cxx": "text/x-c",
"cpp": "text/x-c",
"h": "text/x-c",
"hh": "text/x-c",
"dic": "text/x-c",
"java": "text/x-java-source",
"lua": "text/x-lua",
"py": "text/x-python",
"etx": "text/x-setext",
"sgm": "text/x-sgml",
"sgml": "text/x-sgml",
"vcf": "text/x-vcard",
"xml": "text/xml",
"xul": "text/xul",
"yaml": "text/yaml",
"yml": "text/yaml",
"ts": "video/mp2t",
"mp4": "video/mp4",
"mp4v": "video/mp4",
"mpg4": "video/mp4",
"mpeg": "video/mpeg",
"m1v": "video/mpeg",
"mpa": "video/mpeg",
"mpe": "video/mpeg",
"mpg": "video/mpeg",
"mov": "video/quicktime",
"qt": "video/quicktime",
"webm": "video/webm",
"flv": "video/x-flv",
"m4v": "video/x-m4v",
"asf": "video/x-ms-asf",
"asx": "video/x-ms-asf",
"vob": "video/x-ms-vob",
"wmv": "video/x-ms-wmv",
"avi": "video/x-msvideo",
"*": "video/x-sgi-movie",
}[suffix] || '';
} catch (e) {
console.error(e);
return '';
}
}
};
})();
function arrayBufferToBase64(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa( binary );
}
function base64ToArrayBuffer(base64) {
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,121 @@
const cacheVersion = 'v9';
const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`;
const urlsToCache = [
'index.html',
'./',
'styles.css',
'scripts/network.js',
'scripts/ui.js',
'scripts/util.js',
'scripts/qrcode.js',
'scripts/zip.min.js',
'scripts/NoSleep.min.js',
'scripts/theme.js',
'sounds/blop.mp3',
'images/favicon-96x96.png',
'images/favicon-96x96-notification.png',
'images/android-chrome-192x192.png',
'images/android-chrome-192x192-maskable.png',
'images/android-chrome-512x512.png',
'images/android-chrome-512x512-maskable.png',
'images/apple-touch-icon.png',
];
self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
caches.open(cacheTitle)
.then(function(cache) {
return cache.addAll(urlsToCache).then(_ => {
console.log('All files cached.');
});
})
);
});
// fetch the resource from the network
const fromNetwork = (request, timeout) =>
new Promise((fulfill, reject) => {
const timeoutId = setTimeout(reject, timeout);
fetch(request).then(response => {
clearTimeout(timeoutId);
fulfill(response);
update(request);
}, reject);
});
// fetch the resource from the browser cache
const fromCache = request =>
caches
.open(cacheTitle)
.then(cache =>
cache
.match(request)
.then(matching => matching || cache.match('/offline/'))
);
// cache the current page to make it available for offline
const update = request =>
caches
.open(cacheTitle)
.then(cache =>
fetch(request).then(response => {
cache.put(request, response).then(_ => {
console.log("Page successfully cached.")
})
})
);
// general strategy when making a request (eg if online try to fetch it
// from the network with a timeout, if something fails serve from cache)
self.addEventListener('fetch', function(event) {
if (event.request.method === "POST") {
// Requests related to Web Share Target.
event.respondWith(
(async () => {
const formData = await event.request.formData();
const title = formData.get("title");
const text = formData.get("text");
const url = formData.get("url");
const files = formData.get("files");
let share_url = "/";
if (files.length > 0) {
share_url = "/?share-target=files";
const db = await window.indexedDB.open('pairdrop_store');
const tx = db.transaction('share_target_files', 'readwrite');
const store = tx.objectStore('share_target_files');
for (let i=0; i<files.length; i++) {
await store.add(files[i]);
}
await tx.complete
db.close()
} else if (title.length > 0 || text.length > 0 || url.length) {
share_url = `/?share-target=text&title=${title}&text=${text}&url=${url}`;
}
return Response.redirect(encodeURI(share_url), 303);
})()
);
} else {
// Regular requests not related to Web Share Target.
event.respondWith(
fromNetwork(event.request, 10000).catch(() => fromCache(event.request))
);
event.waitUntil(update(event.request));
}
});
// on activation, we clean up the previously registered service workers
self.addEventListener('activate', evt =>
evt.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== cacheTitle) {
return caches.delete(cacheName);
}
})
);
})
)
);

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff