Compare commits

...

34 Commits

Author SHA1 Message Date
schlagmichdoch
43824d0de2 increase version to v1.7.7 2023-08-10 17:09:51 +02:00
schlagmichdoch
2efb531765 Merge pull request #138
Bump express-rate-limit from 6.8.0 to 6.9.0
2023-08-10 17:06:22 +02:00
schlagmichdoch
d9686a6706 Merge pull request #137 from Zhongbing-Chen/master
revise the command line tool
2023-08-10 16:31:48 +02:00
dependabot[bot]
395c3e00a4 Bump express-rate-limit from 6.8.0 to 6.9.0
Bumps [express-rate-limit](https://github.com/express-rate-limit/express-rate-limit) from 6.8.0 to 6.9.0.
- [Release notes](https://github.com/express-rate-limit/express-rate-limit/releases)
- [Changelog](https://github.com/express-rate-limit/express-rate-limit/blob/main/changelog.md)
- [Commits](https://github.com/express-rate-limit/express-rate-limit/compare/v6.8.0...v6.9.0)

---
updated-dependencies:
- dependency-name: express-rate-limit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-07 04:24:11 +00:00
zhongbing
8869c3c27e revise the command line tool 2023-08-06 00:47:01 +08:00
schlagmichdoch
b07b8316ff Merge pull request #129 from schlagmichdoch/dependabot/npm_and_yarn/express-rate-limit-6.8.0
Bump express-rate-limit from 6.7.0 to 6.8.0
2023-07-25 18:58:11 +02:00
dependabot[bot]
445a295404 Bump express-rate-limit from 6.7.0 to 6.8.0
Bumps [express-rate-limit](https://github.com/express-rate-limit/express-rate-limit) from 6.7.0 to 6.8.0.
- [Release notes](https://github.com/express-rate-limit/express-rate-limit/releases)
- [Changelog](https://github.com/express-rate-limit/express-rate-limit/blob/main/changelog.md)
- [Commits](https://github.com/express-rate-limit/express-rate-limit/compare/v6.7.0...v6.8.0)

---
updated-dependencies:
- dependency-name: express-rate-limit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 04:58:27 +00:00
schlagmichdoch
29b91cb17a increase version to v1.7.6 2023-06-01 01:51:51 +02:00
schlagmichdoch
26bf4d6dc3 ensure that otherPeers never receive peer-left after peer-joined on reconnect by leaving room before rejoining it 2023-06-01 01:49:07 +02:00
schlagmichdoch
f195c686e7 increase version to v1.7.5 2023-06-01 01:32:06 +02:00
schlagmichdoch
3505f161c6 strip 'NO-BREAK SPACE' (U+00A0) of received text as some browsers seem to add them when pasting text 2023-06-01 01:29:00 +02:00
schlagmichdoch
3e2368c0c9 stabilize connection on reconnect by terminating websocket only on timeout and not always when peer leaves its ip room 2023-06-01 01:26:53 +02:00
schlagmichdoch
d36cd3524c Fix clearBrowserHistory: url should not always be replaced by "/" as PairDrop might not always be hosted at domain root 2023-05-30 02:34:50 +02:00
schlagmichdoch
a3a8228327 increase version to v1.7.4 2023-05-26 20:37:38 +02:00
schlagmichdoch
520b772bc8 fix #112 and differentiate between textContent and innerText 2023-05-26 20:36:12 +02:00
schlagmichdoch
27bf0fa74f fix #113 2023-05-26 20:36:12 +02:00
schlagmichdoch
e9f3c39f38 Merge pull request #114 from fm-sys/patch-1
Add 'files-sent' event used by 'Snapdrop & PairDrop for Android' app to support vibrations
2023-05-26 20:35:54 +02:00
fm-sys
58b7f6bb7c Add 'files-sent' event 2023-05-26 09:52:17 +02:00
schlagmichdoch
b5987cf017 increase version to v1.7.3 2023-05-23 02:45:29 +02:00
schlagmichdoch
4433e1c58f add version number to about page 2023-05-23 02:44:25 +02:00
schlagmichdoch
b106d90b64 Fix ReferenceError: ipv6_lcl is not defined 2023-05-23 02:43:17 +02:00
schlagmichdoch
c9e1c2504a Merge pull request #110 from luckman212/luckman212-patch-1
Add new env var `IPV6_LOCALIZE` to enable auto discovery for IPv6 addresses
2023-05-23 01:53:23 +02:00
luckman212
32e909b8c2 fixes for https://github.com/schlagmichdoch/PairDrop/issues/69
(squashed, docs updated, IPV6_LOCALIZE input validation)
2023-05-19 12:18:20 -04:00
schlagmichdoch
a444be226f Fix canvas selector 2023-05-16 19:15:47 +02:00
schlagmichdoch
df778ba42c Speed up canvas by removing fade-in animation 2023-05-16 19:09:59 +02:00
schlagmichdoch
8a17b82fa4 Fix _textInputEmpty() for Chromium based browsers
Co-authored-by: luckman212 <1992842+luckman212@users.noreply.github.com>
2023-05-16 02:53:56 +02:00
schlagmichdoch
56eb29c91b increase version to v1.7.2 2023-05-16 02:35:03 +02:00
schlagmichdoch
6e4bda0adf Fix message sending via submit button.
Co-authored-by: luckman212 <1992842+luckman212@users.noreply.github.com>
2023-05-16 02:25:50 +02:00
Lopolin-LP
0baced640a Fix About Background not filling up full viewport under certain circumstances (#109)
* Fix About Background Not filling up full viewport under certain circumstances

It is now based on vw/vh instead of px. It can also easily be adjusted, mostly. There is no way it will not fill up the viewport.

* add fix for about bg size to websocket fallback too and tidy up

---------

Co-authored-by: schlagmichdoch <schlagmichdoch@users.noreply.github.com>
2023-05-16 01:50:12 +02:00
schlagmichdoch
3c2e73fc0c fix position of about background circle 2023-05-12 04:59:44 +02:00
schlagmichdoch
c629d7cd88 increase version to v1.7.1 2023-05-12 01:41:10 +02:00
schlagmichdoch
ba20c72026 fix error on empty roomSecrets 2023-05-12 01:16:37 +02:00
schlagmichdoch
347f9b87c0 fix check whether peer is same browser 2023-05-12 01:16:37 +02:00
schlagmichdoch
ae9909f596 fix notification "Key null invalidated" on cancel device pairing 2023-05-11 19:56:47 +02:00
17 changed files with 458 additions and 302 deletions

View File

@@ -35,6 +35,14 @@ Set options by using the following flags in the `docker run` command:
```
> Limits clients to 1000 requests per 5 min
##### IPv6 Localization
```bash
-e IPV6_LOCALIZE=4
```
> To enable Peer Discovery among IPv6 peers, you can specify a reduced number of segments of the client IPv6 address to be evaluated as the peer's IP. This can be especially useful when using Cloudflare as a proxy.
>
> The flag must be set to an **integer** between `1` and `7`. The number represents the number of IPv6 [hextets](https://en.wikipedia.org/wiki/IPv6#Address_representation) to match the client IP against. The most common value would be `4`, which will group peers within the same `/64` subnet.
##### Websocket Fallback (for VPN)
```bash
-e WS_FALLBACK=true
@@ -200,6 +208,12 @@ $env:PORT=3010; npm start
```
> Specify the port PairDrop is running on. (Default: 3000)
#### IPv6 Localization
```bash
IPV6_LOCALIZE=4
```
> Truncate a portion of the client IPv6 address to make peers more discoverable. See [Options/Flags](#options--flags) above.
#### Specify STUN/TURN Server
On Unix based systems
```bash

View File

@@ -96,6 +96,17 @@ if (debugMode) {
console.log("DEBUG_MODE is active. To protect privacy, do not use in production.")
}
let ipv6_lcl;
if (process.env.IPV6_LOCALIZE) {
ipv6_lcl = parseInt(process.env.IPV6_LOCALIZE);
if (!ipv6_lcl || !(0 < ipv6_lcl && ipv6_lcl < 8)) {
console.error("IPV6_LOCALIZE must be an integer between 1 and 7");
return;
}
console.log("IPv6 client IPs will be localized to", ipv6_lcl, ipv6_lcl === 1 ? "segment" : "segments");
}
app.use(function(req, res) {
res.redirect('/');
});
@@ -133,7 +144,6 @@ class PairDropServer {
type: 'rtc-config',
config: rtcConfig
});
this._joinRoom(peer);
// send displayName
this._send(peer, {
@@ -162,6 +172,9 @@ class PairDropServer {
case 'pong':
sender.lastBeat = Date.now();
break;
case 'join-ip-room':
this._joinRoom(sender);
break;
case 'room-secrets':
this._onRoomSecrets(sender, message);
break;
@@ -206,16 +219,26 @@ class PairDropServer {
}
_onDisconnect(sender) {
this._disconnect(sender);
}
_disconnect(sender) {
this._leaveRoom(sender, 'ip', '', true);
this._leaveAllSecretRooms(sender, true);
this._removeRoomKey(sender.roomKey);
sender.roomKey = null;
sender.socket.terminate();
}
_onRoomSecrets(sender, message) {
if (!message.roomSecrets) return;
const roomSecrets = message.roomSecrets.filter(roomSecret => {
return /^[\x00-\x7F]{64,256}$/.test(roomSecret);
})
if (!roomSecrets) return;
this._joinSecretRooms(sender, roomSecrets);
}
@@ -286,13 +309,15 @@ class PairDropServer {
}
_onPairDeviceCancel(sender) {
if (sender.roomKey) {
this._removeRoomKey(sender.roomKey);
this._send(sender, {
type: 'pair-device-canceled',
roomKey: sender.roomKey,
});
}
const roomKey = sender.roomKey
if (!roomKey) return;
this._removeRoomKey(roomKey);
this._send(sender, {
type: 'pair-device-canceled',
roomKey: roomKey,
});
}
_onRegenerateRoomSecret(sender, message) {
@@ -338,6 +363,7 @@ class PairDropServer {
const room = roomType === 'ip' ? peer.ip : roomSecret;
if (this._rooms[room] && this._rooms[room][peer.id]) {
// ensures that otherPeers never receive `peer-left` after `peer-joined` on reconnect.
this._leaveRoom(peer, roomType, roomSecret);
}
@@ -365,10 +391,6 @@ class PairDropServer {
// delete the peer
delete this._rooms[room][peer.id];
if (roomType === 'ip') {
peer.socket.terminate();
}
//if room is empty, delete the room
if (!Object.keys(this._rooms[room]).length) {
delete this._rooms[room];
@@ -448,8 +470,7 @@ class PairDropServer {
peer.lastBeat = Date.now();
}
if (Date.now() - peer.lastBeat > 2 * timeout) {
this._leaveRoom(peer);
this._leaveAllSecretRooms(peer);
this._disconnect(peer);
return;
}
@@ -477,7 +498,7 @@ class Peer {
this._setIP(request);
// set peer id
this._setPeerId(request)
this._setPeerId(request);
// is WebRTC supported ?
this.rtcSupported = request.url.indexOf('webrtc') > -1;
@@ -507,11 +528,19 @@ class Peer {
if (this.ip.substring(0,7) === "::ffff:")
this.ip = this.ip.substring(7);
let ipv6_was_localized = false;
if (ipv6_lcl && this.ip.includes(':')) {
this.ip = this.ip.split(':',ipv6_lcl).join(':');
ipv6_was_localized = true;
}
if (debugMode) {
console.debug("----DEBUGGING-PEER-IP-START----");
console.debug("remoteAddress:", request.connection.remoteAddress);
console.debug("x-forwarded-for:", request.headers['x-forwarded-for']);
console.debug("cf-connecting-ip:", request.headers['cf-connecting-ip']);
if (ipv6_was_localized)
console.debug("IPv6 client IP was localized to", ipv6_lcl, ipv6_lcl > 1 ? "segments" : "segment");
console.debug("PairDrop uses:", this.ip);
console.debug("IP is private:", this.ipIsPrivate(this.ip));
console.debug("if IP is private, '127.0.0.1' is used instead");

20
package-lock.json generated
View File

@@ -1,16 +1,16 @@
{
"name": "pairdrop",
"version": "1.7.0",
"version": "1.7.7",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "pairdrop",
"version": "1.7.0",
"version": "1.7.7",
"license": "ISC",
"dependencies": {
"express": "^4.18.2",
"express-rate-limit": "^6.7.0",
"express-rate-limit": "^6.9.0",
"ua-parser-js": "^1.0.35",
"unique-names-generator": "^4.3.0",
"ws": "^8.13.0"
@@ -204,11 +204,11 @@
}
},
"node_modules/express-rate-limit": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz",
"integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==",
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.9.0.tgz",
"integrity": "sha512-AnISR3V8qy4gpKM62/TzYdoFO9NV84fBx0POXzTryHU/qGUJBWuVGd+JhbvtVmKBv37t8/afmqdnv16xWoQxag==",
"engines": {
"node": ">= 12.9.0"
"node": ">= 14.0.0"
},
"peerDependencies": {
"express": "^4 || ^5"
@@ -801,9 +801,9 @@
}
},
"express-rate-limit": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz",
"integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==",
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.9.0.tgz",
"integrity": "sha512-AnISR3V8qy4gpKM62/TzYdoFO9NV84fBx0POXzTryHU/qGUJBWuVGd+JhbvtVmKBv37t8/afmqdnv16xWoQxag==",
"requires": {}
},
"finalhandler": {

View File

@@ -1,6 +1,6 @@
{
"name": "pairdrop",
"version": "1.7.0",
"version": "1.7.7",
"description": "",
"main": "index.js",
"scripts": {
@@ -11,7 +11,7 @@
"license": "ISC",
"dependencies": {
"express": "^4.18.2",
"express-rate-limit": "^6.7.0",
"express-rate-limit": "^6.9.0",
"ua-parser-js": "^1.0.35",
"unique-names-generator": "^4.3.0",
"ws": "^8.13.0"

View File

@@ -38,7 +38,10 @@ openPairDrop()
else
xdg-open "$url"
fi
exit
}
setOs()
@@ -98,13 +101,19 @@ sendFiles()
[[ -a "$zipPath" ]] && echo "Cannot overwrite $zipPath. Please remove first." && exit
if [[ -d $path ]]; then
zipPathTemp="temp_${zipPath}"
zipPathTemp="${path}_pairdrop_temp.zip"
[[ -a "$zipPathTemp" ]] && echo "Cannot overwrite $zipPathTemp. Please remove first." && exit
echo "Processing directory..."
# Create zip files temporarily to send directory
zip -q -b /tmp/ -r "$zipPath" "$path"
zip -q -b /tmp/ "$zipPathTemp" "$zipPath"
if [[ $OS == "Windows" ]];then
powershell.exe -Command "Compress-Archive -Path ${path} -DestinationPath ${zipPath}"
echo "Compress-Archive -Path ${zipPath} -DestinationPath ${zipPathTemp}"
powershell.exe -Command "Compress-Archive -Path ${zipPath} -DestinationPath ${zipPathTemp}"
else
zip -q -b /tmp/ -r "$zipPath" "$path"
zip -q -b /tmp/ "$zipPathTemp" "$zipPath"
fi
if [[ $OS == "Mac" ]];then
hash=$(base64 -i "$zipPathTemp")
@@ -118,8 +127,12 @@ sendFiles()
echo "Processing file..."
# Create zip file temporarily to send file
zip -q -b /tmp/ "$zipPath" "$path"
if [[ $OS == "Windows" ]];then
powershell.exe -Command "Compress-Archive -Path ${path} -DestinationPath ${zipPath} -CompressionLevel Optimal"
else
zip -q -b /tmp/ "$zipPath" "$path"
fi
if [[ $OS == "Mac" ]];then
hash=$(base64 -i "$zipPath")
else
@@ -142,6 +155,7 @@ sendFiles()
hash=
fi
openPairDrop
exit
}

View File

@@ -224,7 +224,7 @@
<div class="row-separator"></div>
<div id="text-input" title="Message" class="textarea" role="textbox" autocapitalize="none" spellcheck="false" autofocus contenteditable></div>
<div class="center row-reverse">
<button class="button" type="submit" title="STR + ENTER" disabled close>Send</button>
<button class="button" type="submit" title="CTRL/⌘ + ENTER" disabled>Send</button>
<button class="button" type="button" title="ESCAPE" close>Cancel</button>
</div>
</x-paper>
@@ -276,7 +276,10 @@
<svg class="icon logo">
<use xlink:href="#wifi-tethering" />
</svg>
<h1>PairDrop</h1>
<div class="title-wrapper">
<h1>PairDrop</h1>
<div class="font-subheading">v1.7.7</div>
</div>
<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">
@@ -303,6 +306,7 @@
</section>
<x-background></x-background>
</x-about>
<canvas class="circles"></canvas>
<!-- SVG Icon Library -->
<svg style="display: none;">
<symbol id=wifi-tethering viewBox="0 0 24 24">

View File

@@ -19,7 +19,8 @@ class ServerConnection {
Events.on('pagehide', _ => this._disconnect());
document.addEventListener(window.visibilityChangeEvent, _ => this._onVisibilityChange());
if (navigator.connection) navigator.connection.addEventListener('change', _ => this._reconnect());
Events.on('room-secrets', e => this._sendRoomSecrets(e.detail));
Events.on('room-secrets', e => this.send({ type: 'room-secrets', roomSecrets: e.detail }));
Events.on('join-ip-room', e => this.send({ type: 'join-ip-room'}));
Events.on('room-secrets-deleted', e => this.send({ type: 'room-secrets-deleted', roomSecrets: e.detail}));
Events.on('regenerate-room-secret', e => this.send({ type: 'regenerate-room-secret', roomSecret: e.detail}));
Events.on('resend-peers', _ => this.send({ type: 'resend-peers'}));
@@ -48,10 +49,6 @@ class ServerConnection {
if (this._isReconnect) Events.fire('notify-user', '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.');
@@ -131,13 +128,6 @@ class ServerConnection {
_onPeers(msg) {
Events.fire('peers', msg);
if (msg.roomType === "ip" && msg.peers.length === 0) {
BrowserTabsConnector.removePeerIdsFromLocalStorage();
BrowserTabsConnector.addPeerIdToLocalStorage().then(peerId => {
if (peerId) return;
console.log("successfully added peerId from localStorage");
});
}
}
_onDisplayName(msg) {
@@ -145,10 +135,16 @@ class ServerConnection {
sessionStorage.setItem("peerId", msg.message.peerId);
sessionStorage.setItem("peerIdHash", msg.message.peerIdHash);
// Add peerId to localStorage to mark it on other PairDrop tabs on the same browser
// Add peerId to localStorage to mark it for other PairDrop tabs on the same browser
BrowserTabsConnector.addPeerIdToLocalStorage().then(peerId => {
if (peerId) return;
console.log("successfully added peerId from localStorage");
if (!peerId) return;
console.log("successfully added peerId to localStorage");
// Only now join rooms
Events.fire('join-ip-room');
PersistentStorage.getAllRoomSecrets().then(roomSecrets => {
Events.fire('room-secrets', roomSecrets);
});
});
Events.fire('display-name', msg);
@@ -219,9 +215,9 @@ class ServerConnection {
class Peer {
constructor(serverConnection, peerId, roomType, roomSecret) {
constructor(serverConnection, isCaller, peerId, roomType, roomSecret) {
this._server = serverConnection;
this._isCaller = !!peerId;
this._isCaller = isCaller;
this._peerId = peerId;
this._roomType = roomType;
this._updateRoomSecret(roomSecret);
@@ -241,15 +237,14 @@ class Peer {
this.sendJSON({type: 'display-name-changed', displayName: displayName});
}
_isSameBrowser() {
return BrowserTabsConnector.peerIsSameBrowser(this._peerId);
}
_updateRoomSecret(roomSecret) {
// if peer is another browser tab, peer is not identifiable with roomSecret as browser tabs share all roomSecrets
// -> abort
if (BrowserTabsConnector.peerIsSameBrowser(this._peerId)) {
this._roomSecret = "";
return;
}
if (this._roomSecret && this._roomSecret !== roomSecret) {
// -> do not delete duplicates and do not regenerate room secrets
if (!this._isSameBrowser() && this._roomSecret && this._roomSecret !== roomSecret) {
// remove old roomSecrets to prevent multiple pairings with same peer
PersistentStorage.deleteRoomSecret(this._roomSecret).then(deletedRoomSecret => {
if (deletedRoomSecret) console.log("Successfully deleted duplicate room secret with same peer: ", deletedRoomSecret);
@@ -258,7 +253,7 @@ class Peer {
this._roomSecret = roomSecret;
if (this._roomSecret && this._roomSecret.length !== 256 && this._isCaller) {
if (!this._isSameBrowser() && this._roomSecret && this._roomSecret.length !== 256 && this._isCaller) {
// increase security by increasing roomSecret length
console.log('RoomSecret is regenerated to increase security')
Events.fire('regenerate-room-secret', this._roomSecret);
@@ -534,7 +529,7 @@ class Peer {
this._abortTransfer();
}
// include for compatibility with Snapdrop for Android app
// include for compatibility with 'Snapdrop & PairDrop for Android' app
Events.fire('file-received', fileBlob);
this._filesReceived.push(fileBlob);
@@ -552,6 +547,7 @@ class Peer {
if (!this._filesQueue.length) {
this._busy = false;
Events.fire('notify-user', 'File transfer completed.');
Events.fire('files-sent'); // used by 'Snapdrop & PairDrop for Android' app
} else {
this._dequeueFile();
}
@@ -603,15 +599,15 @@ class Peer {
class RTCPeer extends Peer {
constructor(serverConnection, peerId, roomType, roomSecret) {
super(serverConnection, peerId, roomType, roomSecret);
constructor(serverConnection, isCaller, peerId, roomType, roomSecret) {
super(serverConnection, isCaller, peerId, roomType, roomSecret);
this.rtcSupported = true;
if (!this._isCaller) return; // we will listen for a caller
this._connect(peerId, true);
this._connect();
}
_connect(peerId) {
if (!this._conn || this._conn.signalingState === "closed") this._openConnection(peerId);
_connect() {
if (!this._conn || this._conn.signalingState === "closed") this._openConnection();
if (this._isCaller) {
this._openChannel();
@@ -620,8 +616,7 @@ class RTCPeer extends Peer {
}
}
_openConnection(peerId) {
this._peerId = peerId;
_openConnection() {
this._conn = new RTCPeerConnection(window.rtcConfig);
this._conn.onicecandidate = e => this._onIceCandidate(e);
this._conn.onicecandidateerror = e => this._onError(e);
@@ -653,7 +648,7 @@ class RTCPeer extends Peer {
}
onServerMessage(message) {
if (!this._conn) this._connect(message.sender.id, false);
if (!this._conn) this._connect();
if (message.sdp) {
this._conn.setRemoteDescription(message.sdp)
@@ -738,7 +733,7 @@ class RTCPeer extends Peer {
console.log('RTC: channel closed', this._peerId);
Events.fire('peer-disconnected', this._peerId);
if (!this._isCaller) return;
this._connect(this._peerId, true); // reopen the channel
this._connect(); // reopen the channel
}
_onConnectionStateChange() {
@@ -789,7 +784,7 @@ class RTCPeer extends Peer {
// only reconnect if peer is caller
if (!this._isCaller) return;
this._connect(this._peerId);
this._connect();
}
_isConnected() {
@@ -833,44 +828,47 @@ class PeersManager {
this.peers[peerId].onServerMessage(message);
}
_refreshExistingPeer(peerId, roomType, roomSecret) {
const peer = this.peers[peerId];
if (peer) {
const roomTypeIsSecret = roomType === "secret";
const roomSecretsDiffer = peer._roomSecret !== roomSecret;
_refreshPeer(peer, roomType, roomSecret) {
if (!peer) return false;
// if roomSecrets differs peer is already connected -> abort but update roomSecret and reevaluate auto accept
if (roomTypeIsSecret && roomSecretsDiffer) {
peer._updateRoomSecret(roomSecret);
peer._evaluateAutoAccept();
const roomTypeIsSecret = roomType === "secret";
const roomSecretsDiffer = peer._roomSecret !== roomSecret;
return true;
}
const roomTypesDiffer = peer._roomType !== roomType;
// if roomTypes differ peer is already connected -> abort
if (roomTypesDiffer) return true;
peer.refresh();
// if roomSecrets differs peer is already connected -> abort but update roomSecret and reevaluate auto accept
if (roomTypeIsSecret && roomSecretsDiffer) {
peer._updateRoomSecret(roomSecret);
peer._evaluateAutoAccept();
return true;
}
// peer does not yet exist: return false
return false;
const roomTypesDiffer = peer._roomType !== roomType;
// if roomTypes differ peer is already connected -> abort
if (roomTypesDiffer) return true;
peer.refresh();
return true;
}
_createOrRefreshPeer(isCaller, peerId, roomType, roomSecret) {
const peer = this.peers[peerId];
if (peer) {
this._refreshPeer(peer, roomType, roomSecret);
return;
}
this.peers[peerId] = new RTCPeer(this._server, isCaller, peerId, roomType, roomSecret);
}
_onPeerJoined(message) {
if (this._refreshExistingPeer(message.peer.id, message.roomType, message.roomSecret)) return;
this.peers[message.peer.id] = new RTCPeer(this._server, undefined, message.roomType, message.roomSecret);
this._createOrRefreshPeer(false, message.peer.id, message.roomType, message.roomSecret);
}
_onPeers(message) {
message.peers.forEach(messagePeer => {
if (this._refreshExistingPeer(messagePeer.id, message.roomType, message.roomSecret)) return;
this.peers[messagePeer.id] = new RTCPeer(this._server, messagePeer.id, message.roomType, message.roomSecret);
message.peers.forEach(peer => {
this._createOrRefreshPeer(true, peer.id, message.roomType, message.roomSecret);
})
}
@@ -902,6 +900,15 @@ class PeersManager {
if (message.disconnect === true) {
// if user actively disconnected from PairDrop server, disconnect all peer to peer connections immediately
Events.fire('peer-disconnected', message.peerId);
// If no peers are connected anymore, we can safely assume that no other tab on the same browser is connected:
// Tidy up peerIds in localStorage
if (Object.keys(this.peers).length === 0) {
BrowserTabsConnector.removeOtherPeerIdsFromLocalStorage().then(peerIds => {
if (!peerIds) return;
console.log("successfully removed other peerIds from localStorage");
});
}
}
}
@@ -962,7 +969,8 @@ class PeersManager {
_getPeerIdFromRoomSecret(roomSecret) {
for (const peerId in this.peers) {
const peer = this.peers[peerId];
if (peer._roomSecret === roomSecret) {
// peer must have same roomSecret and not be on the same browser.
if (peer._roomSecret === roomSecret && !peer._isSameBrowser()) {
return peer._peerId;
}
}

View File

@@ -169,8 +169,12 @@ class PeersUI {
return;
}
peer.sameBrowser = _ => BrowserTabsConnector.peerIsSameBrowser(peer.id);
peer.roomTypes = [roomType];
peer.roomSecret = roomSecret;
if (!(roomType === "secret" && peer.sameBrowser())) {
peer.roomTypes = [roomType];
peer.roomSecret = roomSecret;
}
this.peers[peer.id] = peer;
}
@@ -1020,16 +1024,14 @@ class PairDeviceDialog extends Dialog {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('room_key')) {
this._pairDeviceJoin(urlParams.get('room_key'));
window.history.replaceState({}, "title**", '/'); //remove room_key from url
const url = getUrlWithoutArguments();
window.history.replaceState({}, "Rewrite URL", url); //remove room_key from url
}
}
_onWsConnected() {
this.$pairDeviceBtn.removeAttribute('hidden');
PersistentStorage.getAllRoomSecrets().then(roomSecrets => {
Events.fire('room-secrets', roomSecrets);
this._evaluateNumberRoomSecrets();
});
this._evaluateNumberRoomSecrets();
}
_pairDeviceInitiate() {
@@ -1294,18 +1296,18 @@ class SendTextDialog extends Dialog {
}
async _onKeyDown(e) {
if (this.isShown()) {
if (e.code === "Escape") {
this.hide();
} else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) {
if (this._textInputEmpty()) return;
this._send();
}
if (!this.isShown()) return;
if (e.code === "Escape") {
this.hide();
} else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) {
if (this._textInputEmpty()) return;
this._send();
}
}
_textInputEmpty() {
return this.$text.innerText === "\n";
return !this.$text.innerText || this.$text.innerText === "\n";
}
_onChange(e) {
@@ -1418,7 +1420,8 @@ class ReceiveTextDialog extends Dialog {
}
async _onCopy() {
await navigator.clipboard.writeText(this.$text.textContent);
const sanitizedText = this.$text.innerText.replace(/\u00A0/gm, ' ');
await navigator.clipboard.writeText(sanitizedText);
Events.fire('notify-user', 'Copied to clipboard');
this.hide();
}
@@ -1574,7 +1577,8 @@ class Base64ZipDialog extends Dialog {
}
clearBrowserHistory() {
window.history.replaceState({}, "Rewrite URL", '/');
const url = getUrlWithoutArguments();
window.history.replaceState({}, "Rewrite URL", url);
}
hide() {
@@ -1593,7 +1597,7 @@ class Toast extends Dialog {
_onNotify(message) {
if (this.hideTimeout) clearTimeout(this.hideTimeout);
this.$el.textContent = message;
this.$el.innerText = message;
this.show();
this.hideTimeout = setTimeout(_ => this.hide(), 5000);
}
@@ -1790,7 +1794,8 @@ class WebShareTargetUI {
}
}
}
window.history.replaceState({}, "Rewrite URL", '/');
const url = getUrlWithoutArguments();
window.history.replaceState({}, "Rewrite URL", url);
}
}
}
@@ -1814,7 +1819,8 @@ class WebFileHandlersUI {
Events.fire('activate-paste-mode', {files: files, text: ""})
launchParams = null;
});
window.history.replaceState({}, "Rewrite URL", '/');
const url = getUrlWithoutArguments();
window.history.replaceState({}, "Rewrite URL", url);
}
}
}
@@ -1972,13 +1978,18 @@ class PersistentStorage {
}
static async getAllRoomSecrets() {
const roomSecrets = await this.getAllRoomSecretEntries();
let secrets = [];
for (let i=0; i<roomSecrets.length; i++) {
secrets.push(roomSecrets[i].secret);
try {
const roomSecrets = await this.getAllRoomSecretEntries();
let secrets = [];
for (let i = 0; i < roomSecrets.length; i++) {
secrets.push(roomSecrets[i].secret);
}
console.log(`Request successful. Retrieved ${secrets.length} room_secrets`);
return(secrets);
} catch (e) {
this.logBrowserNotCapable();
return false;
}
console.log(`Request successful. Retrieved ${secrets.length} room_secrets`);
return(secrets);
}
static getAllRoomSecretEntries() {
@@ -2167,11 +2178,13 @@ class BrowserTabsConnector {
let peerIdsBrowser = [];
let peerIdsBrowserOld = JSON.parse(localStorage.getItem("peerIdsBrowser"));
if (peerIdsBrowserOld) peerIdsBrowser.push(...peerIdsBrowserOld);
peerIdsBrowser.push(peerId);
peerIdsBrowser = peerIdsBrowser.filter(onlyUnique);
localStorage.setItem("peerIdsBrowser", JSON.stringify(peerIdsBrowser));
return peerId;
return peerIdsBrowser;
}
static async removePeerIdFromLocalStorage(peerId) {
@@ -2182,8 +2195,14 @@ class BrowserTabsConnector {
return peerId;
}
static removePeerIdsFromLocalStorage() {
localStorage.removeItem("peerIdsBrowser");
static async removeOtherPeerIdsFromLocalStorage() {
const peerId = sessionStorage.getItem("peerId");
if (!peerId) return false;
let peerIdsBrowser = [peerId];
localStorage.setItem("peerIdsBrowser", JSON.stringify(peerIdsBrowser));
return peerIdsBrowser;
}
}
@@ -2235,14 +2254,7 @@ window.addEventListener('beforeinstallprompt', e => {
// Background Circles
Events.on('load', () => {
let c = document.createElement('canvas');
let style = c.style;
style.width = '100%';
style.position = 'absolute';
style.zIndex = -1;
style.top = 0;
style.left = 0;
style.animation = "fade-in 800ms";
let c = $$('canvas');
let cCtx = c.getContext('2d');
let x0, y0, w, h, dw, offset;
@@ -2263,11 +2275,7 @@ Events.on('load', () => {
y0 = h - offset;
dw = Math.round(Math.max(w, h, 1000) / 13);
if (document.body.contains(c)) {
document.body.removeChild(c);
}
drawCircles(cCtx, dw);
document.body.appendChild(c);
}
Events.on('bg-resize', _ => init());
@@ -2283,6 +2291,7 @@ Events.on('load', () => {
}
function drawCircles(ctx, frame) {
ctx.clearRect(0, 0, w, h);
for (let i = 0; i < 13; i++) {
drawCircle(ctx, dw * i + frame + 33);
}

View File

@@ -5,7 +5,7 @@ if (!navigator.clipboard) {
// A <span> contains the text to copy
const span = document.createElement('span');
span.textContent = text;
span.innerText = text;
span.style.whiteSpace = 'pre'; // Preserve consecutive spaces and newlines
// Paint the span outside the viewport
@@ -402,3 +402,7 @@ const cyrb53 = function(str, seed = 0) {
function onlyUnique (value, index, array) {
return array.indexOf(value) === index;
}
function getUrlWithoutArguments() {
return `${window.location.protocol}//${window.location.host}${window.location.pathname}`;
}

View File

@@ -1,4 +1,4 @@
const cacheVersion = 'v1.7.0';
const cacheVersion = 'v1.7.7';
const cacheTitle = `pairdrop-cache-${cacheVersion}`;
const urlsToCache = [
'index.html',
@@ -72,8 +72,7 @@ self.addEventListener('fetch', function(event) {
if (event.request.method === "POST") {
// Requests related to Web Share Target.
event.respondWith((async () => {
let share_url = await evaluateRequestData(event.request);
share_url = event.request.url + share_url;
const share_url = await evaluateRequestData(event.request);
return Response.redirect(encodeURI(share_url), 302);
})());
} else {
@@ -101,15 +100,16 @@ self.addEventListener('activate', evt =>
)
);
const evaluateRequestData = async function (request) {
const formData = await request.formData();
const title = formData.get("title");
const text = formData.get("text");
const url = formData.get("url");
const files = formData.getAll("allfiles");
const evaluateRequestData = function (request) {
return new Promise(async (resolve) => {
const formData = await request.formData();
const title = formData.get("title");
const text = formData.get("text");
const url = formData.get("url");
const files = formData.getAll("allfiles");
const pairDropUrl = request.url;
if (files && files.length > 0) {
let fileObjects = [];
for (let i=0; i<files.length; i++) {
@@ -128,21 +128,21 @@ const evaluateRequestData = async function (request) {
const objectStoreRequest = objectStore.add(fileObjects[i]);
objectStoreRequest.onsuccess = _ => {
if (i === fileObjects.length - 1) resolve('?share-target=files');
if (i === fileObjects.length - 1) resolve(pairDropUrl + '?share-target=files');
}
}
}
DBOpenRequest.onerror = _ => {
resolve('');
resolve(pairDropUrl);
}
} else {
let share_url = '?share-target=text';
let urlArgument = '?share-target=text';
if (title) share_url += `&title=${title}`;
if (text) share_url += `&text=${text}`;
if (url) share_url += `&url=${url}`;
if (title) urlArgument += `&title=${title}`;
if (text) urlArgument += `&text=${text}`;
if (url) urlArgument += `&url=${url}`;
resolve(share_url);
resolve(pairDropUrl + urlArgument);
}
});
}

View File

@@ -1124,11 +1124,14 @@ button::-moz-focus-inner {
text-align: center;
}
#about header {
z-index: 1;
}
#about .fade-in {
transition: opacity 300ms;
will-change: opacity;
transition-delay: 300ms;
z-index: 11;
pointer-events: all;
}
@@ -1142,10 +1145,23 @@ button::-moz-focus-inner {
--icon-size: 96px;
}
#about .title-wrapper {
display: flex;
align-items: baseline;
}
#about .title-wrapper > div {
margin-left: 0.5em;
}
#about x-background {
position: absolute;
width: 500px;
height: 500px;
--size: max(max(230vw, 230vh), calc(150vh + 150vw));
--size-half: calc(var(--size)/2);
top: calc(28px - var(--size-half));
right: calc(36px - var(--size-half));
width: var(--size);
height: var(--size);
border-radius: 50%;
background: var(--primary-color);
transform: scale(0);
@@ -1159,7 +1175,7 @@ button::-moz-focus-inner {
}
#about:target x-background {
transform: scale(10);
transform: scale(1);
}
#about .row a {
@@ -1174,6 +1190,14 @@ button::-moz-focus-inner {
align-self: end;
}
canvas.circles {
width: 100vw;
position: absolute;
z-index: -10;
top: 0;
left: 0;
}
/* Loading Indicator */
.progress {
@@ -1282,7 +1306,7 @@ x-peers:empty~x-instructions {
@media (hover: none) and (pointer: coarse) {
x-peer {
transform: scale(0.95);
padding: 4px 0;
padding: 4px;
}
}

View File

@@ -227,7 +227,7 @@
<div class="row-separator"></div>
<div id="text-input" title="Message" class="textarea" role="textbox" autocapitalize="none" spellcheck="false" autofocus contenteditable></div>
<div class="center row-reverse">
<button class="button" type="submit" title="STR + ENTER" disabled close>Send</button>
<button class="button" type="submit" title="CTRL/⌘ + ENTER" disabled>Send</button>
<button class="button" type="button" title="ESCAPE" close>Cancel</button>
</div>
</x-paper>
@@ -279,7 +279,10 @@
<svg class="icon logo">
<use xlink:href="#wifi-tethering" />
</svg>
<h1>PairDrop</h1>
<div class="title-wrapper">
<h1>PairDrop</h1>
<div class="font-subheading">v1.7.7</div>
</div>
<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">
@@ -306,6 +309,7 @@
</section>
<x-background></x-background>
</x-about>
<canvas class="circles"></canvas>
<!-- SVG Icon Library -->
<svg style="display: none;">
<symbol id=wifi-tethering viewBox="0 0 24 24">

View File

@@ -17,7 +17,8 @@ class ServerConnection {
Events.on('pagehide', _ => this._disconnect());
document.addEventListener(window.visibilityChangeEvent, _ => this._onVisibilityChange());
if (navigator.connection) navigator.connection.addEventListener('change', _ => this._reconnect());
Events.on('room-secrets', e => this._sendRoomSecrets(e.detail));
Events.on('room-secrets', e => this.send({ type: 'room-secrets', roomSecrets: e.detail }));
Events.on('join-ip-room', e => this.send({ type: 'join-ip-room'}));
Events.on('room-secrets-deleted', e => this.send({ type: 'room-secrets-deleted', roomSecrets: e.detail}));
Events.on('regenerate-room-secret', e => this.send({ type: 'regenerate-room-secret', roomSecret: e.detail}));
Events.on('resend-peers', _ => this.send({ type: 'resend-peers'}));
@@ -46,10 +47,6 @@ class ServerConnection {
if (this._isReconnect) Events.fire('notify-user', '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.');
@@ -143,10 +140,9 @@ class ServerConnection {
_onPeers(msg) {
Events.fire('peers', msg);
if (msg.roomType === "ip" && msg.peers.length === 0) {
BrowserTabsConnector.removePeerIdsFromLocalStorage();
BrowserTabsConnector.addPeerIdToLocalStorage().then(peerId => {
if (peerId) return;
console.log("successfully added peerId from localStorage");
BrowserTabsConnector.removeOtherPeerIdsFromLocalStorage().then(peerId => {
if (!peerId) return;
console.log("successfully removed other peerIds from localStorage");
});
}
}
@@ -156,10 +152,16 @@ class ServerConnection {
sessionStorage.setItem("peerId", msg.message.peerId);
sessionStorage.setItem("peerIdHash", msg.message.peerIdHash);
// Add peerId to localStorage to mark it on other PairDrop tabs on the same browser
// Add peerId to localStorage to mark it for other PairDrop tabs on the same browser
BrowserTabsConnector.addPeerIdToLocalStorage().then(peerId => {
if (peerId) return;
console.log("successfully added peerId from localStorage");
if (!peerId) return;
console.log("successfully added peerId to localStorage");
// Only now join rooms
Events.fire('join-ip-room');
PersistentStorage.getAllRoomSecrets().then(roomSecrets => {
Events.fire('room-secrets', roomSecrets);
});
});
Events.fire('display-name', msg);
@@ -230,9 +232,9 @@ class ServerConnection {
class Peer {
constructor(serverConnection, peerId, roomType, roomSecret) {
constructor(serverConnection, isCaller, peerId, roomType, roomSecret) {
this._server = serverConnection;
this._isCaller = !!peerId;
this._isCaller = isCaller;
this._peerId = peerId;
this._roomType = roomType;
this._updateRoomSecret(roomSecret);
@@ -252,15 +254,14 @@ class Peer {
this.sendJSON({type: 'display-name-changed', displayName: displayName});
}
_isSameBrowser() {
return BrowserTabsConnector.peerIsSameBrowser(this._peerId);
}
_updateRoomSecret(roomSecret) {
// if peer is another browser tab, peer is not identifiable with roomSecret as browser tabs share all roomSecrets
// -> abort
if (BrowserTabsConnector.peerIsSameBrowser(this._peerId)) {
this._roomSecret = "";
return;
}
if (this._roomSecret && this._roomSecret !== roomSecret) {
// -> do not delete duplicates and do not regenerate room secrets
if (!this._isSameBrowser() && this._roomSecret && this._roomSecret !== roomSecret) {
// remove old roomSecrets to prevent multiple pairings with same peer
PersistentStorage.deleteRoomSecret(this._roomSecret).then(deletedRoomSecret => {
if (deletedRoomSecret) console.log("Successfully deleted duplicate room secret with same peer: ", deletedRoomSecret);
@@ -269,7 +270,7 @@ class Peer {
this._roomSecret = roomSecret;
if (this._roomSecret && this._roomSecret.length !== 256 && this._isCaller) {
if (!this._isSameBrowser() && this._roomSecret && this._roomSecret.length !== 256 && this._isCaller) {
// increase security by increasing roomSecret length
console.log('RoomSecret is regenerated to increase security')
Events.fire('regenerate-room-secret', this._roomSecret);
@@ -614,15 +615,15 @@ class Peer {
class RTCPeer extends Peer {
constructor(serverConnection, peerId, roomType, roomSecret) {
super(serverConnection, peerId, roomType, roomSecret);
constructor(serverConnection, isCaller, peerId, roomType, roomSecret) {
super(serverConnection, isCaller, peerId, roomType, roomSecret);
this.rtcSupported = true;
if (!this._isCaller) return; // we will listen for a caller
this._connect(peerId, true);
this._connect();
}
_connect(peerId) {
if (!this._conn || this._conn.signalingState === "closed") this._openConnection(peerId);
_connect() {
if (!this._conn || this._conn.signalingState === "closed") this._openConnection();
if (this._isCaller) {
this._openChannel();
@@ -631,8 +632,7 @@ class RTCPeer extends Peer {
}
}
_openConnection(peerId) {
this._peerId = peerId;
_openConnection() {
this._conn = new RTCPeerConnection(window.rtcConfig);
this._conn.onicecandidate = e => this._onIceCandidate(e);
this._conn.onicecandidateerror = e => this._onError(e);
@@ -664,7 +664,7 @@ class RTCPeer extends Peer {
}
onServerMessage(message) {
if (!this._conn) this._connect(message.sender.id, false);
if (!this._conn) this._connect();
if (message.sdp) {
this._conn.setRemoteDescription(message.sdp)
@@ -749,7 +749,7 @@ class RTCPeer extends Peer {
console.log('RTC: channel closed', this._peerId);
Events.fire('peer-disconnected', this._peerId);
if (!this._isCaller) return;
this._connect(this._peerId, true); // reopen the channel
this._connect(); // reopen the channel
}
_onConnectionStateChange() {
@@ -800,7 +800,7 @@ class RTCPeer extends Peer {
// only reconnect if peer is caller
if (!this._isCaller) return;
this._connect(this._peerId);
this._connect();
}
_isConnected() {
@@ -819,8 +819,8 @@ class RTCPeer extends Peer {
class WSPeer extends Peer {
constructor(serverConnection, peerId, roomType, roomSecret) {
super(serverConnection, peerId, roomType, roomSecret);
constructor(serverConnection, isCaller, peerId, roomType, roomSecret) {
super(serverConnection, isCaller, peerId, roomType, roomSecret);
this.rtcSupported = false;
if (!this._isCaller) return; // we will listen for a caller
this._sendSignal();
@@ -886,41 +886,52 @@ class PeersManager {
this.peers[peerId].onServerMessage(message);
}
_refreshExistingPeer(peerId, roomType, roomSecret) {
const peer = this.peers[peerId];
if (peer) {
const roomTypeIsSecret = roomType === "secret";
const roomSecretsDiffer = peer._roomSecret !== roomSecret;
_refreshPeer(peer, roomType, roomSecret) {
if (!peer) return false;
// if roomSecrets differs peer is already connected -> abort but update roomSecret and reevaluate auto accept
if (roomTypeIsSecret && roomSecretsDiffer) {
peer._updateRoomSecret(roomSecret);
peer._evaluateAutoAccept();
const roomTypeIsSecret = roomType === "secret";
const roomSecretsDiffer = peer._roomSecret !== roomSecret;
return true;
}
const roomTypesDiffer = peer._roomType !== roomType;
// if roomTypes differ peer is already connected -> abort
if (roomTypesDiffer) return true;
peer.refresh();
// if roomSecrets differs peer is already connected -> abort but update roomSecret and reevaluate auto accept
if (roomTypeIsSecret && roomSecretsDiffer) {
peer._updateRoomSecret(roomSecret);
peer._evaluateAutoAccept();
return true;
}
// peer does not yet exist: return false
return false;
const roomTypesDiffer = peer._roomType !== roomType;
// if roomTypes differ peer is already connected -> abort
if (roomTypesDiffer) return true;
peer.refresh();
return true;
}
_createOrRefreshPeer(isCaller, peerId, roomType, roomSecret, rtcSupported) {
const peer = this.peers[peerId];
if (peer) {
this._refreshPeer(peer, roomType, roomSecret);
return;
}
if (window.isRtcSupported && rtcSupported) {
this.peers[peerId] = new RTCPeer(this._server,isCaller, peerId, roomType, roomSecret);
} else {
this.peers[peerId] = new WSPeer(this._server, isCaller, peerId, roomType, roomSecret);
}
}
_onPeerJoined(message) {
if (this._refreshExistingPeer(message.peer.id, message.roomType, message.roomSecret)) return;
this._createOrRefreshPeer(false, message.peer.id, message.roomType, message.roomSecret, message.peer.rtcSupported);
}
if (window.isRtcSupported && message.sender.rtcSupported) {
this.peers[message.peer.id] = new RTCPeer(this._server, undefined, message.roomType, message.roomSecret);
} else {
this.peers[message.peer.id] = new WSPeer(this._server, undefined, message.roomType, message.roomSecret);
}
_onPeers(message) {
message.peers.forEach(peer => {
this._createOrRefreshPeer(true, peer.id, message.roomType, message.roomSecret, peer.rtcSupported);
})
}
_onWsRelay(message) {
@@ -929,18 +940,6 @@ class PeersManager {
this.peers[messageJSON.sender.id]._onMessage(message);
}
_onPeers(message) {
message.peers.forEach(messagePeer => {
if (this._refreshExistingPeer(messagePeer.id, message.roomType, message.roomSecret)) return;
if (window.isRtcSupported && messagePeer.rtcSupported) {
this.peers[messagePeer.id] = new RTCPeer(this._server, messagePeer.id, message.roomType, message.roomSecret);
} else {
this.peers[messagePeer.id] = new WSPeer(this._server, messagePeer.id, message.roomType, message.roomSecret);
}
})
}
_onRespondToFileTransferRequest(detail) {
this.peers[detail.to]._respondToFileTransferRequest(detail.accepted);
}
@@ -972,6 +971,15 @@ class PeersManager {
if (message.disconnect === true) {
// if user actively disconnected from PairDrop server, disconnect all peer to peer connections immediately
Events.fire('peer-disconnected', message.peerId);
// If no peers are connected anymore, we can safely assume that no other tab on the same browser is connected:
// Tidy up peerIds in localStorage
if (Object.keys(this.peers).length === 0) {
BrowserTabsConnector.removeOtherPeerIdsFromLocalStorage().then(peerIds => {
if (!peerIds) return;
console.log("successfully removed other peerIds from localStorage");
});
}
}
}
@@ -1040,7 +1048,8 @@ class PeersManager {
_getPeerIdFromRoomSecret(roomSecret) {
for (const peerId in this.peers) {
const peer = this.peers[peerId];
if (peer._roomSecret === roomSecret) {
// peer must have same roomSecret and not be on the same browser.
if (peer._roomSecret === roomSecret && !peer._isSameBrowser()) {
return peer._peerId;
}
}

View File

@@ -169,8 +169,12 @@ class PeersUI {
return;
}
peer.sameBrowser = _ => BrowserTabsConnector.peerIsSameBrowser(peer.id);
peer.roomTypes = [roomType];
peer.roomSecret = roomSecret;
if (!(roomType === "secret" && peer.sameBrowser())) {
peer.roomTypes = [roomType];
peer.roomSecret = roomSecret;
}
this.peers[peer.id] = peer;
}
@@ -1021,16 +1025,14 @@ class PairDeviceDialog extends Dialog {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('room_key')) {
this._pairDeviceJoin(urlParams.get('room_key'));
window.history.replaceState({}, "title**", '/'); //remove room_key from url
const url = getUrlWithoutArguments();
window.history.replaceState({}, "Rewrite URL", url); //remove room_key from url
}
}
_onWsConnected() {
this.$pairDeviceBtn.removeAttribute('hidden');
PersistentStorage.getAllRoomSecrets().then(roomSecrets => {
Events.fire('room-secrets', roomSecrets);
this._evaluateNumberRoomSecrets();
});
this._evaluateNumberRoomSecrets();
}
_pairDeviceInitiate() {
@@ -1295,18 +1297,18 @@ class SendTextDialog extends Dialog {
}
async _onKeyDown(e) {
if (this.isShown()) {
if (e.code === "Escape") {
this.hide();
} else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) {
if (this._textInputEmpty()) return;
this._send();
}
if (!this.isShown()) return;
if (e.code === "Escape") {
this.hide();
} else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) {
if (this._textInputEmpty()) return;
this._send();
}
}
_textInputEmpty() {
return this.$text.innerText === "\n";
return !this.$text.innerText || this.$text.innerText === "\n";
}
_onChange(e) {
@@ -1419,7 +1421,8 @@ class ReceiveTextDialog extends Dialog {
}
async _onCopy() {
await navigator.clipboard.writeText(this.$text.textContent);
const sanitizedText = this.$text.innerText.replace(/\u00A0/gm, ' ');
await navigator.clipboard.writeText(sanitizedText);
Events.fire('notify-user', 'Copied to clipboard');
this.hide();
}
@@ -1575,7 +1578,8 @@ class Base64ZipDialog extends Dialog {
}
clearBrowserHistory() {
window.history.replaceState({}, "Rewrite URL", '/');
const url = getUrlWithoutArguments();
window.history.replaceState({}, "Rewrite URL", url);
}
hide() {
@@ -1594,7 +1598,7 @@ class Toast extends Dialog {
_onNotify(message) {
if (this.hideTimeout) clearTimeout(this.hideTimeout);
this.$el.textContent = message;
this.$el.innerText = message;
this.show();
this.hideTimeout = setTimeout(_ => this.hide(), 5000);
}
@@ -1791,7 +1795,8 @@ class WebShareTargetUI {
}
}
}
window.history.replaceState({}, "Rewrite URL", '/');
const url = getUrlWithoutArguments();
window.history.replaceState({}, "Rewrite URL", url);
}
}
}
@@ -1815,7 +1820,8 @@ class WebFileHandlersUI {
Events.fire('activate-paste-mode', {files: files, text: ""})
launchParams = null;
});
window.history.replaceState({}, "Rewrite URL", '/');
const url = getUrlWithoutArguments();
window.history.replaceState({}, "Rewrite URL", url);
}
}
}
@@ -1973,13 +1979,18 @@ class PersistentStorage {
}
static async getAllRoomSecrets() {
const roomSecrets = await this.getAllRoomSecretEntries();
let secrets = [];
for (let i=0; i<roomSecrets.length; i++) {
secrets.push(roomSecrets[i].secret);
try {
const roomSecrets = await this.getAllRoomSecretEntries();
let secrets = [];
for (let i = 0; i < roomSecrets.length; i++) {
secrets.push(roomSecrets[i].secret);
}
console.log(`Request successful. Retrieved ${secrets.length} room_secrets`);
return(secrets);
} catch (e) {
this.logBrowserNotCapable();
return false;
}
console.log(`Request successful. Retrieved ${secrets.length} room_secrets`);
return(secrets);
}
static getAllRoomSecretEntries() {
@@ -2168,11 +2179,13 @@ class BrowserTabsConnector {
let peerIdsBrowser = [];
let peerIdsBrowserOld = JSON.parse(localStorage.getItem("peerIdsBrowser"));
if (peerIdsBrowserOld) peerIdsBrowser.push(...peerIdsBrowserOld);
peerIdsBrowser.push(peerId);
peerIdsBrowser = peerIdsBrowser.filter(onlyUnique);
localStorage.setItem("peerIdsBrowser", JSON.stringify(peerIdsBrowser));
return peerId;
return peerIdsBrowser;
}
static async removePeerIdFromLocalStorage(peerId) {
@@ -2183,8 +2196,14 @@ class BrowserTabsConnector {
return peerId;
}
static removePeerIdsFromLocalStorage() {
localStorage.removeItem("peerIdsBrowser");
static async removeOtherPeerIdsFromLocalStorage() {
const peerId = sessionStorage.getItem("peerId");
if (!peerId) return false;
let peerIdsBrowser = [peerId];
localStorage.setItem("peerIdsBrowser", JSON.stringify(peerIdsBrowser));
return peerIdsBrowser;
}
}
@@ -2236,14 +2255,7 @@ window.addEventListener('beforeinstallprompt', e => {
// Background Circles
Events.on('load', () => {
let c = document.createElement('canvas');
let style = c.style;
style.width = '100%';
style.position = 'absolute';
style.zIndex = -1;
style.top = 0;
style.left = 0;
style.animation = "fade-in 800ms";
let c = $$('canvas');
let cCtx = c.getContext('2d');
let x0, y0, w, h, dw, offset;
@@ -2263,11 +2275,7 @@ Events.on('load', () => {
y0 = h - offset;
dw = Math.round(Math.max(w, h, 1000) / 13);
if (document.body.contains(c)) {
document.body.removeChild(c);
}
drawCircles(cCtx, dw);
document.body.appendChild(c);
}
Events.on('bg-resize', _ => init());
@@ -2283,6 +2291,7 @@ Events.on('load', () => {
}
function drawCircles(ctx, frame) {
ctx.clearRect(0, 0, w, h);
for (let i = 0; i < 13; i++) {
drawCircle(ctx, dw * i + frame + 33);
}

View File

@@ -5,7 +5,7 @@ if (!navigator.clipboard) {
// A <span> contains the text to copy
const span = document.createElement('span');
span.textContent = text;
span.innerText = text;
span.style.whiteSpace = 'pre'; // Preserve consecutive spaces and newlines
// Paint the span outside the viewport
@@ -403,6 +403,10 @@ function onlyUnique (value, index, array) {
return array.indexOf(value) === index;
}
function getUrlWithoutArguments() {
return `${window.location.protocol}//${window.location.host}${window.location.pathname}`;
}
function arrayBufferToBase64(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);

View File

@@ -1,4 +1,4 @@
const cacheVersion = 'v1.7.0';
const cacheVersion = 'v1.7.7';
const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`;
const urlsToCache = [
'index.html',
@@ -72,8 +72,7 @@ self.addEventListener('fetch', function(event) {
if (event.request.method === "POST") {
// Requests related to Web Share Target.
event.respondWith((async () => {
let share_url = await evaluateRequestData(event.request);
share_url = event.request.url + share_url;
const share_url = await evaluateRequestData(event.request);
return Response.redirect(encodeURI(share_url), 302);
})());
} else {
@@ -101,15 +100,16 @@ self.addEventListener('activate', evt =>
)
);
const evaluateRequestData = async function (request) {
const formData = await request.formData();
const title = formData.get("title");
const text = formData.get("text");
const url = formData.get("url");
const files = formData.getAll("allfiles");
const evaluateRequestData = function (request) {
return new Promise(async (resolve) => {
const formData = await request.formData();
const title = formData.get("title");
const text = formData.get("text");
const url = formData.get("url");
const files = formData.getAll("allfiles");
const pairDropUrl = request.url;
if (files && files.length > 0) {
let fileObjects = [];
for (let i=0; i<files.length; i++) {
@@ -128,21 +128,21 @@ const evaluateRequestData = async function (request) {
const objectStoreRequest = objectStore.add(fileObjects[i]);
objectStoreRequest.onsuccess = _ => {
if (i === fileObjects.length - 1) resolve('?share-target=files');
if (i === fileObjects.length - 1) resolve(pairDropUrl + '?share-target=files');
}
}
}
DBOpenRequest.onerror = _ => {
resolve('');
resolve(pairDropUrl);
}
} else {
let share_url = '?share-target=text';
let urlArgument = '?share-target=text';
if (title) share_url += `&title=${title}`;
if (text) share_url += `&text=${text}`;
if (url) share_url += `&url=${url}`;
if (title) urlArgument += `&title=${title}`;
if (text) urlArgument += `&text=${text}`;
if (url) urlArgument += `&url=${url}`;
resolve(share_url);
resolve(pairDropUrl + urlArgument);
}
});
}

View File

@@ -1150,11 +1150,14 @@ button::-moz-focus-inner {
text-align: center;
}
#about header {
z-index: 1;
}
#about .fade-in {
transition: opacity 300ms;
will-change: opacity;
transition-delay: 300ms;
z-index: 11;
pointer-events: all;
}
@@ -1168,10 +1171,23 @@ button::-moz-focus-inner {
--icon-size: 96px;
}
#about .title-wrapper {
display: flex;
align-items: baseline;
}
#about .title-wrapper > div {
margin-left: 0.5em;
}
#about x-background {
position: absolute;
width: 500px;
height: 500px;
--size: max(max(230vw, 230vh), calc(150vh + 150vw));
--size-half: calc(var(--size)/2);
top: calc(28px - var(--size-half));
right: calc(36px - var(--size-half));
width: var(--size);
height: var(--size);
border-radius: 50%;
background: var(--primary-color);
transform: scale(0);
@@ -1185,7 +1201,7 @@ button::-moz-focus-inner {
}
#about:target x-background {
transform: scale(10);
transform: scale(1);
}
#about .row a {
@@ -1200,6 +1216,14 @@ button::-moz-focus-inner {
align-self: end;
}
canvas.circles {
width: 100vw;
position: absolute;
z-index: -10;
top: 0;
left: 0;
}
/* Loading Indicator */
.progress {
@@ -1308,7 +1332,7 @@ x-peers:empty~x-instructions {
@media (hover: none) and (pointer: coarse) {
x-peer {
transform: scale(0.95);
padding: 4px 0;
padding: 4px;
}
}