implement complete WSPeer as fallback if WebRTC is deactivated. Only ever use on self-hosted instances as clients need to trust the server!
|
@ -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)
|
||||
|
||||
|
|
21
index.js
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
After Width: | Height: | Size: 8.3 KiB |
BIN
public_included_ws_fallback/images/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 30 KiB |
BIN
public_included_ws_fallback/images/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
public_included_ws_fallback/images/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 7 KiB |
BIN
public_included_ws_fallback/images/favicon-96x96.png
Normal file
After Width: | Height: | Size: 7 KiB |
BIN
public_included_ws_fallback/images/logo_blue_512x512.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
public_included_ws_fallback/images/logo_transparent_128x128.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
public_included_ws_fallback/images/logo_transparent_512x512.png
Normal file
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 25 KiB |
BIN
public_included_ws_fallback/images/logo_white_512x512.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
public_included_ws_fallback/images/mstile-150x150.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 232 KiB |
After Width: | Height: | Size: 232 KiB |
After Width: | Height: | Size: 1 MiB |
After Width: | Height: | Size: 232 KiB |
After Width: | Height: | Size: 1.7 MiB |
After Width: | Height: | Size: 180 KiB |
After Width: | Height: | Size: 201 KiB |
251
public_included_ws_fallback/images/safari-pinned-tab.svg
Normal 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 |
BIN
public_included_ws_fallback/images/snapdrop-graphics.sketch
Normal file
345
public_included_ws_fallback/index.html
Normal 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=" "></div>
|
||||
<div class="font-body2">
|
||||
You can be discovered by everyone <span id="on-this-network">on this network</span>
|
||||
<span id="and-by-paired-devices" hidden> and by <span id="paired-devices">paired 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>
|
281
public_included_ws_fallback/manifest.json
Normal 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"
|
||||
}
|
||||
}
|
2
public_included_ws_fallback/scripts/NoSleep.min.js
vendored
Normal file
893
public_included_ws_fallback/scripts/network.js
Normal 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',
|
||||
},
|
||||
]
|
||||
}
|
2
public_included_ws_fallback/scripts/qrcode.js
Normal file
39
public_included_ws_fallback/scripts/theme.js
Normal 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);
|
||||
});
|
||||
|
||||
})();
|
1717
public_included_ws_fallback/scripts/ui.js
Normal file
402
public_included_ws_fallback/scripts/util.js
Normal 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;
|
||||
}
|
1
public_included_ws_fallback/scripts/zip.min.js
vendored
Normal file
121
public_included_ws_fallback/service-worker.js
Normal 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);
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|