From 72f3bb0e7c386837faf8d360e084442a0ae0a7c0 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Fri, 23 Dec 2022 02:38:56 +0100 Subject: [PATCH] fix small bugs - working version with turn server of relay.metered.ca --- client/scripts/network.js | 83 +++++++++++++++++++++++++++++++++------ client/scripts/ui.js | 15 ++++--- docker-compose.yml | 2 + server/index.js | 54 +++++++++++++++++++------ 4 files changed, 125 insertions(+), 29 deletions(-) diff --git a/client/scripts/network.js b/client/scripts/network.js index 7058b32..a5900bc 100644 --- a/client/scripts/network.js +++ b/client/scripts/network.js @@ -8,6 +8,7 @@ class ServerConnection { Events.on('beforeunload', e => this._disconnect()); Events.on('pagehide', e => this._disconnect()); document.addEventListener('visibilitychange', e => this._onVisibilityChange()); + Events.on('online', this._reconnect); } _connect() { @@ -15,11 +16,13 @@ class ServerConnection { if (this._isConnected() || this._isConnecting()) return; const ws = new WebSocket(this._endpoint()); ws.binaryType = 'arraybuffer'; - ws.onopen = e => console.log('WS: server connected'); + ws.onopen = _ => console.log('WS: server connected'); ws.onmessage = e => this._onMessage(e.data); - ws.onclose = e => this._onDisconnect(); - ws.onerror = e => console.error(e); + ws.onclose = _ => this._onDisconnect(); + ws.onerror = e => this._onError(e); this._socket = ws; + + Events.on('force-disconnect', this._onForceDisconnect); } _onMessage(msg) { @@ -45,7 +48,7 @@ class ServerConnection { Events.fire('display-name', msg); break; default: - console.error('WS: unkown message type', msg); + console.error('WS: unknown message type', msg); } } @@ -58,21 +61,28 @@ class ServerConnection { // hack to detect if deployment or development environment const protocol = location.protocol.startsWith('https') ? 'wss' : 'ws'; const webrtc = window.isRtcSupported ? '/webrtc' : '/fallback'; - const url = protocol + '://' + location.host + location.pathname + 'server' + webrtc; - return url; + return protocol + '://' + location.host + location.pathname + 'server' + webrtc; } _disconnect() { this.send({ type: 'disconnect' }); this._socket.onclose = null; this._socket.close(); + Events.fire('disconnect'); } _onDisconnect() { console.log('WS: server disconnected'); Events.fire('notify-user', 'Connection lost. Retry in 5 seconds...'); clearTimeout(this._reconnectTimer); - this._reconnectTimer = setTimeout(_ => this._connect(), 5000); + this._reconnectTimer = setTimeout(this._connect, 5000); + Events.fire('disconnect'); + } + + _onForceDisconnect() { + document.cookie = "peerid=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + this._disconnect(); + this._connect(); } _onVisibilityChange() { @@ -87,6 +97,17 @@ class ServerConnection { _isConnecting() { return this._socket && this._socket.readyState === this._socket.CONNECTING; } + + _reconnect() { + console.log("reconnect") + this._disconnect(); + this._connect(); + } + + _onError(e) { + console.error(e); + this._onForceDisconnect(); + } } class Peer { @@ -189,7 +210,7 @@ class Peer { } _onChunkReceived(chunk) { - if(!chunk.byteLength) return; + if(!(chunk.byteLength || chunk.size)) return; this._digester.unchunk(chunk); const progress = this._digester.progress; @@ -333,6 +354,7 @@ class RTCPeer extends Peer { switch (this._conn.iceConnectionState) { case 'failed': console.error('ICE Gathering failed'); + Events.fire('force-disconnect'); break; default: console.log('ICE Gathering', this._conn.iceConnectionState); @@ -379,6 +401,7 @@ class PeersManager { Events.on('files-selected', e => this._onFilesSelected(e.detail)); Events.on('send-text', e => this._onSendText(e.detail)); Events.on('peer-left', e => this._onPeerLeft(e.detail)); + Events.on('disconnect', this._clearPeers); } _onMessage(message) { @@ -421,6 +444,11 @@ class PeersManager { peer._peer.close(); } + _clearPeers() { + if (this.peers) { + Object.keys(this.peers).forEach(peerId => this._onPeerLeft(peerId)); + } + } } class WSPeer extends Peer { @@ -529,10 +557,41 @@ class Events { } } - RTCPeer.config = { 'sdpSemantics': 'unified-plan', - 'iceServers': [{ - urls: 'stun:stun.l.google.com:19302' - }] + // iceServers: [ + // { + // urls: 'stun:127.0.0.1:3478', + // }, + // { + // urls: 'turn:127.0.0.1:3478', + // username: 'snapdrop', + // credential: 'ifupvrwelijmoyjxmefcsvfxxmcphvxo' + // } + // ] + iceServers: [ + { + urls: "stun:relay.metered.ca:80", + }, + { + urls: "turn:relay.metered.ca:80", + username: "411061cd290de7ca6cc1a753", + credential: "CuCIGdVfA9Gias1E", + }, + { + urls: "turn:relay.metered.ca:443", + username: "411061cd290de7ca6cc1a753", + credential: "CuCIGdVfA9Gias1E", + }, + ], + // iceServers: [ + // { + // urls: 'stun:stun.l.google.com:19302' + // }, + // { + // urls: 'turn:om.wulingate.com', + // username: 'hmzJ0OHZivkod703', + // credential: 'KDF04PBYD9xHAp0s' + // }, + // ] } diff --git a/client/scripts/ui.js b/client/scripts/ui.js index 0988f2f..f70eb7b 100644 --- a/client/scripts/ui.js +++ b/client/scripts/ui.js @@ -23,6 +23,7 @@ class PeersUI { Events.on('peers', e => this._onPeers(e.detail)); Events.on('file-progress', e => this._onFileProgress(e.detail)); Events.on('paste', e => this._onPaste(e)); + Events.on('offline', () => this._clearPeers()); this.peers = {}; } @@ -32,7 +33,8 @@ class PeersUI { } _onPeerConnected(peerId) { - new PeerUI(this.peers[peerId]); + if(this.peers[peerId]) + new PeerUI(this.peers[peerId]); } _onPeers(peers) { @@ -47,6 +49,7 @@ class PeersUI { } _onPeerLeft(peerId) { + this._onPeerDisconnected(peerId); delete this.peers[peerId]; } @@ -59,7 +62,7 @@ class PeersUI { _clearPeers() { const $peers = $$('x-peers').innerHTML = ''; - Object.keys(this.peers).forEach(key => delete this.peers[key]); + Object.keys(this.peers).forEach(peerId => delete this.peers[peerId]); } _onPaste(e) { @@ -223,8 +226,8 @@ class Dialog { this.$el = $(id); this.$el.querySelectorAll('[close]').forEach(el => el.addEventListener('click', e => this.hide())) this.$el.querySelectorAll('[role="textbox"]').forEach((el) => { - el.addEventListener("keypress", (e) => { - if (e.key == "Escape") { + el.addEventListener("keydown", (e) => { + if (e.key === "Escape") { this.hide(); } }); @@ -511,8 +514,8 @@ class Notifications { class NetworkStatusUI { constructor() { - Events.on('offline', e => this._showOfflineMessage()); - Events.on('online', e => this._showOnlineMessage()); + Events.on('offline', this._showOfflineMessage); + Events.on('online', this._showOnlineMessage); if (!navigator.onLine) this._showOfflineMessage(); } diff --git a/docker-compose.yml b/docker-compose.yml index 08a7bcd..02e5bdb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,7 @@ services: volumes: - ./server/:/home/node/app command: ash -c "npm i && node index.js" + restart: unless-stopped nginx: build: context: ./docker/ @@ -23,3 +24,4 @@ services: env_file: ./docker/fqdn.env entrypoint: /mnt/openssl/create.sh command: ["nginx", "-g", "daemon off;"] + restart: unless-stopped diff --git a/server/index.js b/server/index.js index 949f6e2..688cd4f 100644 --- a/server/index.js +++ b/server/index.js @@ -1,4 +1,5 @@ var process = require('process') +var net = require('net') // Handle SIGINT process.on('SIGINT', () => { console.info("SIGINT Received, exiting...") @@ -43,10 +44,10 @@ class SnapdropServer { }); } - _onHeaders(headers, response) { - if (response.headers.cookie && response.headers.cookie.indexOf('peerid=') > -1) return; - response.peerId = Peer.uuid(); - headers.push('Set-Cookie: peerid=' + response.peerId + "; SameSite=Strict; Secure"); + _onHeaders(headers, request) { + if (request.headers.cookie && request.headers.cookie.indexOf('peerid=') > -1) return; + request.peerId = Peer.uuid(); + headers.push('Set-Cookie: peerid=' + request.peerId + "; SameSite=Strict; Secure"); } _onMessage(sender, message) { @@ -181,6 +182,7 @@ class Peer { // for keepalive this.timerId = 0; this.lastBeat = Date.now(); + console.debug(this.name.displayName) } _setIP(request) { @@ -192,18 +194,48 @@ class Peer { this.ip = request.connection.remoteAddress; } // IPv4 and IPv6 use different values to refer to localhost - if (this.ip === '::1' || this.ip === '::ffff:127.0.0.1') { - this.ip = '127.0.0.1'; - } - // put all peers on the same network as the server into the same room - if (this.ipIsPrivate(this.ip)) { + // put all peers on the same network as the server into the same room as well + if (this.ip === '::1' || this.ip === '::ffff:127.0.0.1' || this.ip === '::1' || this.ipIsPrivate(this.ip)) { this.ip = '127.0.0.1'; } + console.debug(this.ip) } ipIsPrivate(ip) { - // 10.0.0.0 - 10.255.255.255 || 172.16.0.0 - 172.31.255.255 || 192.168.0.0 - 192.168.255.255 - return /^(10)\.(.*)\.(.*)\.(.*)$/.test(ip) || /^(172)\.(1[6-9]|2[0-9]|3[0-1])\.(.*)\.(.*)$/.test(ip) || /^(192)\.(168)\.(.*)\.(.*)$/.test(ip) + if (ip.substring(0,7) === "::ffff:") + ip = ip.substring(7); + + if (net.isIPv4(ip)) { + // 10.0.0.0 - 10.255.255.255 || 172.16.0.0 - 172.31.255.255 || 192.168.0.0 - 192.168.255.255 + return /^(10)\.(.*)\.(.*)\.(.*)$/.test(ip) || /^(172)\.(1[6-9]|2[0-9]|3[0-1])\.(.*)\.(.*)$/.test(ip) || /^(192)\.(168)\.(.*)\.(.*)$/.test(ip) + } + + // else: ip is IPv6 + const firstWord = ip.split(":").find(el => !!el); //get first not empty word + + // The original IPv6 Site Local addresses (fec0::/10) are deprecated. Range: fec0 - feff + if (/^fe[c-f][0-f]$/.test(firstWord)) + return true; + + // These days Unique Local Addresses (ULA) are used in place of Site Local. + // Range: fc00 - fcff + else if (/^fc[0-f]{2}$/.test(firstWord)) + return true; + + // Range: fd00 - fcff + else if (/^fd[0-f]{2}$/.test(firstWord)) + return true; + + // Link local addresses (prefixed with fe80) are not routable + else if (firstWord === "fe80") + return true; + + // Discard Prefix + else if (firstWord === "100") + return true; + + // Any other IP address is not Unique Local Address (ULA) + return false; } _setPeerId(request) {