From 23e7c0439382a3bb00cd817767cf6a1a82893d4a Mon Sep 17 00:00:00 2001 From: RobinLinus Date: Fri, 21 Sep 2018 19:51:29 +0200 Subject: [PATCH 1/2] Network fixes --- README.md | 16 ++++++++-------- client/scripts/network-v2.js | 34 ---------------------------------- client/scripts/network.js | 22 ++++++++++++++++++---- client/scripts/ui.js | 23 ++++++++++++++++------- server/index.js | 6 ++++-- 5 files changed, 46 insertions(+), 55 deletions(-) delete mode 100644 client/scripts/network-v2.js diff --git a/README.md b/README.md index fec4428..0744ba4 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ -# Snapdrop +# Snapdrop -[Snapdrop](https://snapdrop.net) is a Progressive Web App inspired by Apple's Airdrop. +[Snapdrop](https://snapdrop.net): local file sharing in your browser - inspired by Apple's Airdrop. -#### Snapdrop is built with the following awesome technologies: -* Vanilla HTML / JS / CSS +#### Snapdrop Version 2 is built with the following awesome technologies: +* Vanilla HTML5 / ES6 / CSS3 +* Progressive Web App * [WebRTC](http://webrtc.org/) * [WebSockets](http://www.websocket.org/) fallback (iDevices don't support WebRTC) * [NodeJS](https://nodejs.org/en/) @@ -24,7 +25,6 @@ It uses a P2P connection if WebRTC is supported by the browser. (WebRTC needs a If WebRTC isn’t supported (Safari, IE) it uses a Web Sockets fallback for the file transfer. The server connects the clients with a stream. - ##### What about privacy? Will files be saved on third-party-servers? None of your files are ever saved on any server. Snapdrop doesn't even use cookies or a database. If you are curious have a look [at the Server](https://github.com/RobinLinus/snapdrop/blob/master/server/ws-server.js). @@ -45,12 +45,12 @@ ShareDrop uses WebRTC only and isn't compatible with Safari Browsers. Snapdrop u ## Local Development ``` - git clone git@github.com:RobinLinus/secret-snapdrop.git - cd secret-snapdrop/server + git clone git@github.com:RobinLinus/snapdrop.git + cd snapdrop/server npm install node index.js cd ../client - python -m http.server + python -m SimpleHTTPServer ``` Now point your browser to http://localhost:8000. diff --git a/client/scripts/network-v2.js b/client/scripts/network-v2.js deleted file mode 100644 index 7a15cbd..0000000 --- a/client/scripts/network-v2.js +++ /dev/null @@ -1,34 +0,0 @@ -class ServerConnection { - -} - -class Connection { - -} - -class WSConnection extends Connection { - -} - -class RTCConnection extends Connection { - -} - -class Peer { - - constructor(serverConnection) { - this._ws = new WSConnection(serverConnection); - this._rtc = new RTCConnection(serverConnection); - this._fileReceiver = new FileReceiver(this); - this._fileSender = new FileSender(this); - } - - send(message) { - - } - -} - -class Peers { - -} \ No newline at end of file diff --git a/client/scripts/network.js b/client/scripts/network.js index e7ebd87..c986092 100644 --- a/client/scripts/network.js +++ b/client/scripts/network.js @@ -3,12 +3,14 @@ class ServerConnection { constructor() { this._connect(); Events.on('beforeunload', e => this._disconnect(), false); + Events.on('pagehide', e => this._disconnect(), false); } _connect() { + if (this._isConnected()) return; const ws = new WebSocket(this._endpoint()); ws.binaryType = 'arraybuffer'; - ws.onopen = e => console.log('WS: server connection opened'); + ws.onopen = e => console.log('WS: server connected'); ws.onmessage = e => this._onMessage(e.data); ws.onclose = e => this._onDisconnect(); ws.onerror = e => console.error(e); @@ -16,6 +18,10 @@ class ServerConnection { clearTimeout(this._reconnectTimer); } + _isConnected() { + return this._socket && this._socket.readyState === this._socket.OPEN; + } + _onMessage(msg) { msg = JSON.parse(msg); console.log('WS:', msg); @@ -224,7 +230,7 @@ class RTCPeer extends Peer { this._peerId = peerId; this._peer = new RTCPeerConnection(RTCPeer.config); this._peer.onicecandidate = e => this._onIceCandidate(e); - this._peer.onconnectionstatechange = e => console.log('RTC: state changed:', this._peer.connectionState); + this._peer.onconnectionstatechange = e => this._onConnectionStateChange(e); } if (isCaller) { @@ -237,7 +243,7 @@ class RTCPeer extends Peer { _createChannel() { const channel = this._peer.createDataChannel('data-channel', { reliable: true }); channel.binaryType = 'arraybuffer'; - channel.onopen = e => this._onChannelOpened(e) + channel.onopen = e => this._onChannelOpened(e); this._peer.createOffer(d => this._onDescription(d), e => this._onError(e)); } @@ -282,11 +288,19 @@ class RTCPeer extends Peer { } _onChannelClosed() { - console.log('RTC: channel closed ', this._peerId); + console.log('RTC: channel closed', this._peerId); if (!this.isCaller) return; this._start(this._peerId, true); // reopen the channel } + _onConnectionStateChange(e) { + console.log('RTC: state changed:', this._peer.connectionState); + switch (this._peer.connectionState) { + case 'disconnected': this._onChannelClosed(); + break; + } + } + _send(message) { this._channel.send(message); } diff --git a/client/scripts/ui.js b/client/scripts/ui.js index 45c9483..fa34e69 100644 --- a/client/scripts/ui.js +++ b/client/scripts/ui.js @@ -335,8 +335,8 @@ class Notifications { constructor() { // Check if the browser supports notifications if (!('Notification' in window)) return; + // Check whether notification permissions have already been granted - if (Notification.permission !== 'granted') { this.$button = $('notification'); this.$button.removeAttribute('hidden'); @@ -358,12 +358,17 @@ class Notifications { } _notify(message, body) { - var img = '/images/logo_transparent_128x128.png'; - return new Notification(message, { + const config = { body: body, - icon: img, + icon: '/images/logo_transparent_128x128.png', vibrate: [200, 100, 200, 100, 200, 100, 400], - }); + } + if (serviceWorker && serviceWorker.showNotification) { + // android doesn't support "new Notification" if service worker is installed + return serviceWorker.showNotification(message, config); + } else { + return new Notification(message, config); + } } _messageNotification(message) { @@ -434,10 +439,14 @@ document.copy = text => { return success; } -if ('serviceWorker' in navigator && isProductionEnvironment) { + +if ('serviceWorker' in navigator) { navigator.serviceWorker .register('/service-worker.js') - .then(e => console.log("Service Worker Registered")); + .then(serviceWorker => { + console.log('Service Worker registered'); + window.serviceWorker = serviceWorker + }); } // Background Animation diff --git a/server/index.js b/server/index.js index 2d8bbfd..f35a299 100644 --- a/server/index.js +++ b/server/index.js @@ -34,12 +34,14 @@ class SnapdropServer { switch (message.type) { case 'disconnect': this._leaveRoom(sender); + break; case 'pong': sender.lastBeat = Date.now(); + break; } // relay message to recipient - if (message.to) { + if (message.to && this._rooms[sender.ip]) { const recipientId = message.to; // TODO: sanitize const recipient = this._rooms[sender.ip][recipientId]; delete message.to; @@ -84,7 +86,7 @@ class SnapdropServer { _leaveRoom(peer) { // delete the peer this._cancelKeepAlive(peer); - if (!this._rooms[peer.ip]) return; + if (!this._rooms[peer.ip] || !this._rooms[peer.ip][peer.id]) return; delete this._rooms[peer.ip][peer.id]; From b67afca8ac5a60b6efe6edc153c4e6ff020b4fab Mon Sep 17 00:00:00 2001 From: RobinLinus Date: Fri, 21 Sep 2018 19:54:16 +0200 Subject: [PATCH 2/2] Cancel keep alive on join room --- server/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/index.js b/server/index.js index f35a299..7cc3626 100644 --- a/server/index.js +++ b/server/index.js @@ -53,6 +53,7 @@ class SnapdropServer { } _joinRoom(peer) { + this._cancelKeepAlive(peer); // if room doesn't exist, create it if (!this._rooms[peer.ip]) { this._rooms[peer.ip] = {}; @@ -84,8 +85,8 @@ class SnapdropServer { } _leaveRoom(peer) { - // delete the peer this._cancelKeepAlive(peer); + // delete the peer if (!this._rooms[peer.ip] || !this._rooms[peer.ip][peer.id]) return; delete this._rooms[peer.ip][peer.id];