From 49f026a022a03acb1978db1cf70a5ab95a76535d Mon Sep 17 00:00:00 2001 From: Daniel Borkan Date: Mon, 31 Oct 2016 17:17:02 -0400 Subject: [PATCH 1/7] Ping cloud server to check if it's online --- src/generic_core/remote-user.ts | 3 + src/generic_core/social.ts | 1 + src/generic_core/uproxy_core.ts | 16 ------ src/lib/cloud/social/provider.ts | 97 ++++++++++++++++++++++++++++---- src/lib/net/pinger.ts | 49 ++++++++-------- 5 files changed, 115 insertions(+), 51 deletions(-) diff --git a/src/generic_core/remote-user.ts b/src/generic_core/remote-user.ts index a6a9aad415..c958050e9a 100644 --- a/src/generic_core/remote-user.ts +++ b/src/generic_core/remote-user.ts @@ -574,6 +574,9 @@ var log :logging.Log = new logging.Log('remote-user'); // handshake is sent to the peer. log.error('Attempting to send instance handshake before ready'); return; + } else if (this.network.name === 'Cloud') { + // Don't send instance handshake to cloud servers. + return; } // Ensure that the user is loaded so that we have correct consent bits. return this.onceLoaded.then(() => { diff --git a/src/generic_core/social.ts b/src/generic_core/social.ts index e41fa8a13f..a402583935 100644 --- a/src/generic_core/social.ts +++ b/src/generic_core/social.ts @@ -440,6 +440,7 @@ export function notifyUI(networkName :string, userId :string) { log.error('Firewall: invalid client state:', freedomClient); return; } + console.log('got clientState: ', freedomClient) var client :social.ClientState = freedomClientToUproxyClient(freedomClient); if (client.userId == this.myInstance.userId && diff --git a/src/generic_core/uproxy_core.ts b/src/generic_core/uproxy_core.ts index 3812833122..037fe798cc 100644 --- a/src/generic_core/uproxy_core.ts +++ b/src/generic_core/uproxy_core.ts @@ -862,22 +862,6 @@ export class uProxyCore implements uproxy_core_api.CoreApi { networkName: args.networkName, userId: args.userId }); - }).then(() => { - // If we removed the only cloud friend, logout of the cloud network - if (args.networkName === 'Cloud') { - return this.logoutIfRosterEmpty_(network); - } }); } - - private logoutIfRosterEmpty_ = (network :social.Network) : Promise => { - if (Object.keys(network.roster).length === 0) { - return this.logout({ - name: network.name - }).then(() => { - log.info('Successfully logged out of %1 network because roster is empty', network.name); - }); - } - return Promise.resolve(); - } } // class uProxyCore diff --git a/src/lib/cloud/social/provider.ts b/src/lib/cloud/social/provider.ts index 66b55ff161..4b6bc10d7d 100644 --- a/src/lib/cloud/social/provider.ts +++ b/src/lib/cloud/social/provider.ts @@ -5,6 +5,7 @@ import * as crypto from 'crypto'; import * as linefeeder from '../../net/linefeeder'; import * as logging from '../../logging/logging'; import * as queue from '../../handler/queue'; +import Pinger from '../../net/pinger'; // https://github.com/borisyankov/DefinitelyTyped/blob/master/ssh2/ssh2-tests.ts import * as ssh2 from 'ssh2'; @@ -123,12 +124,12 @@ function makeInstanceMessage(address:string, description?:string): any { // To see how these fields are handled, see // generic_core/social.ts#handleClient in the uProxy repo. -function makeClientState(address: string): freedom.Social.ClientState { +function makeClientState(address: string, status: string): freedom.Social.ClientState { return { userId: address, clientId: address, // https://github.com/freedomjs/freedom/blob/master/interface/social.json - status: 'ONLINE', + status: status, timestamp: Date.now() }; } @@ -179,6 +180,13 @@ export class CloudSocialProvider { // SSH connections, keyed by host. private clients_: { [host: string]: Promise } = {}; + // Map from host to whether it is online. Hosts not in the map are assumed + // to be offline. + private onlineHosts_: { [host: string]: boolean } = {}; + + // Map from host to intervalId used for monitoring online presence. + private onlinePresenceMonitorIds_: { [host: string]: number } = {}; + constructor(private dispatchEvent_: (name: string, args: Object) => void) { } // Emits the messages necessary to make the user appear online @@ -187,16 +195,21 @@ export class CloudSocialProvider { this.dispatchEvent_('onUserProfile', makeUserProfile( contact.invite.host, contact.invite.isAdmin)); - var clientState = makeClientState(contact.invite.host); + var isOnline = this.isOnline_(contact.invite.host); + + var clientState = makeClientState(contact.invite.host, + isOnline ? 'ONLINE' : 'OFFLINE'); this.dispatchEvent_('onClientState', clientState); - // Pretend that we received a message from a remote uProxy client. - this.dispatchEvent_('onMessage', { - from: clientState, - // INSTANCE - message: JSON.stringify(makeVersionedPeerMessage(3000, makeInstanceMessage( - contact.invite.host, contact.description), contact.version)) - }); + if (isOnline) { + // Pretend that we received a message from a remote uProxy client. + this.dispatchEvent_('onMessage', { + from: clientState, + // INSTANCE + message: JSON.stringify(makeVersionedPeerMessage(3000, makeInstanceMessage( + contact.invite.host, contact.description), contact.version)) + }); + } } // Establishes an SSH connection to a server, first shutting down @@ -214,7 +227,7 @@ export class CloudSocialProvider { const connection = new Connection(invite, (message: Object) => { this.dispatchEvent_('onMessage', { - from: makeClientState(invite.host), + from: makeClientState(invite.host, 'ONLINE'), // SIGNAL_FROM_SERVER_PEER, message: JSON.stringify(makeVersionedPeerMessage(3002, message, connection.getVersion())) @@ -239,6 +252,8 @@ export class CloudSocialProvider { description: banner, version: connection.getVersion() }; + // Cloud server must be online if it is responding with it's banner. + this.setOnlineStatus_(invite.host, true); this.notifyOfUser_(this.savedContacts_[invite.host]); this.saveContacts_(); }); @@ -265,6 +280,7 @@ export class CloudSocialProvider { if (savedContacts.contacts) { for (let contact of savedContacts.contacts) { this.savedContacts_[contact.invite.host] = contact; + this.startMonitoringPresence_(contact.invite.host); this.notifyOfUser_(contact); } } @@ -298,7 +314,7 @@ export class CloudSocialProvider { // TODO: emit an onUserProfile event, which can include an image URL // TODO: base this on the user's public key? // (shown in the "connected accounts" page) - return Promise.resolve(makeClientState('me')); + return Promise.resolve(makeClientState('me', 'ONLINE')); } public sendMessage = (destinationClientId: string, message: string): Promise => { @@ -410,6 +426,7 @@ export class CloudSocialProvider { this.savedContacts_[invite.host] = { invite: invite }; + this.startMonitoringPresence_(invite.host); this.notifyOfUser_(this.savedContacts_[invite.host]); this.saveContacts_(); @@ -439,12 +456,68 @@ export class CloudSocialProvider { log.warn('cloud contact %1 is not in %2 - cannot remove from storage', host, STORAGE_KEY); return Promise.resolve(); } + this.stopMonitoringPresence_(host); // Remove host from savedContacts and clients delete this.savedContacts_[host]; delete this.clients_[host]; // Update storage with this.savedContacts_ return this.saveContacts_(); } + + private startMonitoringPresence_(host: string) { + if (this.onlinePresenceMonitorIds_[host]) { + log.error('unexpected call to startMonitoringPresence_ for ' + host); + return; + } + // Ping server every minute to see if it is online + const ping = this.ping_.bind(this, host); + const PING_INTERVAL = 60000; + this.onlinePresenceMonitorIds_[host] = setInterval(ping, PING_INTERVAL); + // Ping server immediately (so we don't have to wait 1 min for 1st result). + ping(); + } + + private stopMonitoringPresence_(host: string) { + if (!this.onlinePresenceMonitorIds_[host]) { + log.error('unexpected call to stopMonitoringPresence_ for ' + host); + return; + } + clearInterval(this.onlinePresenceMonitorIds_[host]); + delete this.onlinePresenceMonitorIds_[host]; + } + + private ping_(host: string) : Promise { + var pinger = new Pinger(host, 5000); + return pinger.pingOnce().then(() => { + return Promise.resolve(true); + }).catch(() => { + return Promise.resolve(false); + }).then((newOnlineValue: boolean) => { + console.log('ping returned ' + newOnlineValue); + var oldOnlineValue = this.isOnline_(host); + this.setOnlineStatus_(host, newOnlineValue); + if (newOnlineValue !== oldOnlineValue) { + // status changed, emit a new onClientState. + this.notifyOfUser_(this.savedContacts_[host]); + if (newOnlineValue) { + // Connect in the background in order to fetch metadata such as + // the banner (description). + const invite = this.savedContacts_[host].invite; + this.reconnect_(invite).catch((e: Error) => { + log.error('failed to log into cloud server once online: %1', e.message); + }); + } + } + }); + } + + private isOnline_(host: string) { + return this.onlineHosts_[host] === true; + } + + private setOnlineStatus_(host: string, isOnline: boolean) { + this.onlineHosts_[host] = isOnline; + } } enum ConnectionState { diff --git a/src/lib/net/pinger.ts b/src/lib/net/pinger.ts index 23bdbe2aec..a919e30205 100644 --- a/src/lib/net/pinger.ts +++ b/src/lib/net/pinger.ts @@ -20,28 +20,31 @@ export default class Pinger { public ping = (): Promise => { log.debug('pinging %1:%2...', this.host_ , this.port_); - return promises.retry(() => { - const socket = freedom['core.tcpsocket'](); - - const destructor = () => { - try { - freedom['core.tcpsocket'].close(socket); - } catch (e) { - log.warn('error destroying socket: ' + e.message); - } - }; - - // TODO: Worth thinking about timeouts here but because this times - // out almost immediately if nothing is listening on the port, - // it works well for our purposes. - return socket.connect(this.host_, this.port_).then((unused: any) => { - log.debug('connected to ' + this.host_ + ':' + this.port_ + '...'); - destructor(); - }, (e: Error) => { - log.debug('connection failed to ' + this.host_ + ':' + this.port_ + '...'); - destructor(); - throw e; - }); - }, this.timeout_, DEFAULT_INTERVAL_MS); + return promises.retry(this.pingOnce.bind(this), + this.timeout_, DEFAULT_INTERVAL_MS); + } + + public pingOnce = () : Promise => { + const socket = freedom['core.tcpsocket'](); + + const destructor = () => { + try { + freedom['core.tcpsocket'].close(socket); + } catch (e) { + log.warn('error destroying socket: ' + e.message); + } + }; + + // TODO: Worth thinking about timeouts here but because this times + // out almost immediately if nothing is listening on the port, + // it works well for our purposes. + return socket.connect(this.host_, this.port_).then((unused: any) => { + log.debug('connected to ' + this.host_ + ':' + this.port_ + '...'); + destructor(); + }, (e: Error) => { + log.debug('connection failed to ' + this.host_ + ':' + this.port_ + '...'); + destructor(); + throw e; + }); } } From 47400fd486fad114db79adc9e3a2a076309b9018 Mon Sep 17 00:00:00 2001 From: Daniel Borkan Date: Mon, 31 Oct 2016 17:34:42 -0400 Subject: [PATCH 2/7] Cleanup --- src/generic_core/social.ts | 1 - src/lib/cloud/social/provider.ts | 21 ++++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/generic_core/social.ts b/src/generic_core/social.ts index a402583935..e41fa8a13f 100644 --- a/src/generic_core/social.ts +++ b/src/generic_core/social.ts @@ -440,7 +440,6 @@ export function notifyUI(networkName :string, userId :string) { log.error('Firewall: invalid client state:', freedomClient); return; } - console.log('got clientState: ', freedomClient) var client :social.ClientState = freedomClientToUproxyClient(freedomClient); if (client.userId == this.myInstance.userId && diff --git a/src/lib/cloud/social/provider.ts b/src/lib/cloud/social/provider.ts index 4b6bc10d7d..b86a3aa869 100644 --- a/src/lib/cloud/social/provider.ts +++ b/src/lib/cloud/social/provider.ts @@ -124,12 +124,12 @@ function makeInstanceMessage(address:string, description?:string): any { // To see how these fields are handled, see // generic_core/social.ts#handleClient in the uProxy repo. -function makeClientState(address: string, status: string): freedom.Social.ClientState { +function makeClientState(address: string): freedom.Social.ClientState { return { userId: address, clientId: address, // https://github.com/freedomjs/freedom/blob/master/interface/social.json - status: status, + status: this.isOnline_(address) ? 'ONLINE' : 'OFFLINE', timestamp: Date.now() }; } @@ -195,13 +195,10 @@ export class CloudSocialProvider { this.dispatchEvent_('onUserProfile', makeUserProfile( contact.invite.host, contact.invite.isAdmin)); - var isOnline = this.isOnline_(contact.invite.host); - - var clientState = makeClientState(contact.invite.host, - isOnline ? 'ONLINE' : 'OFFLINE'); + var clientState = makeClientState(contact.invite.host); this.dispatchEvent_('onClientState', clientState); - if (isOnline) { + if (this.isOnline_(contact.invite.host)) { // Pretend that we received a message from a remote uProxy client. this.dispatchEvent_('onMessage', { from: clientState, @@ -226,8 +223,10 @@ export class CloudSocialProvider { } const connection = new Connection(invite, (message: Object) => { + // Set the server to online, since we are receiving messages from them. + this.setOnlineStatus_(invite.host, true); this.dispatchEvent_('onMessage', { - from: makeClientState(invite.host, 'ONLINE'), + from: makeClientState(invite.host), // SIGNAL_FROM_SERVER_PEER, message: JSON.stringify(makeVersionedPeerMessage(3002, message, connection.getVersion())) @@ -314,7 +313,7 @@ export class CloudSocialProvider { // TODO: emit an onUserProfile event, which can include an image URL // TODO: base this on the user's public key? // (shown in the "connected accounts" page) - return Promise.resolve(makeClientState('me', 'ONLINE')); + return Promise.resolve(makeClientState('me')); } public sendMessage = (destinationClientId: string, message: string): Promise => { @@ -487,7 +486,7 @@ export class CloudSocialProvider { } private ping_(host: string) : Promise { - var pinger = new Pinger(host, 5000); + var pinger = new Pinger(host, SSH_SERVER_PORT); return pinger.pingOnce().then(() => { return Promise.resolve(true); }).catch(() => { @@ -512,7 +511,7 @@ export class CloudSocialProvider { } private isOnline_(host: string) { - return this.onlineHosts_[host] === true; + return host === 'me' || this.onlineHosts_[host] === true; } private setOnlineStatus_(host: string, isOnline: boolean) { From debc0125cf0f51e5725016cc0098e7b816a7c8f1 Mon Sep 17 00:00:00 2001 From: Daniel Borkan Date: Mon, 31 Oct 2016 17:58:32 -0400 Subject: [PATCH 3/7] Remove trace --- src/lib/cloud/social/provider.ts | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/lib/cloud/social/provider.ts b/src/lib/cloud/social/provider.ts index b86a3aa869..d47809e864 100644 --- a/src/lib/cloud/social/provider.ts +++ b/src/lib/cloud/social/provider.ts @@ -122,18 +122,6 @@ function makeInstanceMessage(address:string, description?:string): any { }; } -// To see how these fields are handled, see -// generic_core/social.ts#handleClient in the uProxy repo. -function makeClientState(address: string): freedom.Social.ClientState { - return { - userId: address, - clientId: address, - // https://github.com/freedomjs/freedom/blob/master/interface/social.json - status: this.isOnline_(address) ? 'ONLINE' : 'OFFLINE', - timestamp: Date.now() - }; -} - // To see how these fields are handled, see // generic_core/social.ts#handleUserProfile in the uProxy repo. We omit // the status field since remote-user.ts#update will use FRIEND as a default. @@ -195,7 +183,7 @@ export class CloudSocialProvider { this.dispatchEvent_('onUserProfile', makeUserProfile( contact.invite.host, contact.invite.isAdmin)); - var clientState = makeClientState(contact.invite.host); + var clientState = this.makeClientState_(contact.invite.host); this.dispatchEvent_('onClientState', clientState); if (this.isOnline_(contact.invite.host)) { @@ -226,7 +214,7 @@ export class CloudSocialProvider { // Set the server to online, since we are receiving messages from them. this.setOnlineStatus_(invite.host, true); this.dispatchEvent_('onMessage', { - from: makeClientState(invite.host), + from: this.makeClientState_(invite.host), // SIGNAL_FROM_SERVER_PEER, message: JSON.stringify(makeVersionedPeerMessage(3002, message, connection.getVersion())) @@ -313,7 +301,7 @@ export class CloudSocialProvider { // TODO: emit an onUserProfile event, which can include an image URL // TODO: base this on the user's public key? // (shown in the "connected accounts" page) - return Promise.resolve(makeClientState('me')); + return Promise.resolve(this.makeClientState_('me')); } public sendMessage = (destinationClientId: string, message: string): Promise => { @@ -492,7 +480,6 @@ export class CloudSocialProvider { }).catch(() => { return Promise.resolve(false); }).then((newOnlineValue: boolean) => { - console.log('ping returned ' + newOnlineValue); var oldOnlineValue = this.isOnline_(host); this.setOnlineStatus_(host, newOnlineValue); if (newOnlineValue !== oldOnlineValue) { @@ -517,6 +504,18 @@ export class CloudSocialProvider { private setOnlineStatus_(host: string, isOnline: boolean) { this.onlineHosts_[host] = isOnline; } + + // To see how these fields are handled, see + // generic_core/social.ts#handleClient in the uProxy repo. + private makeClientState_(address: string): freedom.Social.ClientState { + return { + userId: address, + clientId: address, + // https://github.com/freedomjs/freedom/blob/master/interface/social.json + status: this.isOnline_(address) ? 'ONLINE' : 'OFFLINE', + timestamp: Date.now() + }; + } } enum ConnectionState { From 8f410fecf77a98a95740cef8b0dbbc433e9dee4a Mon Sep 17 00:00:00 2001 From: Daniel Borkan Date: Mon, 31 Oct 2016 18:01:40 -0400 Subject: [PATCH 4/7] Add comment to pingOnce --- src/lib/net/pinger.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/net/pinger.ts b/src/lib/net/pinger.ts index a919e30205..57ae8b0641 100644 --- a/src/lib/net/pinger.ts +++ b/src/lib/net/pinger.ts @@ -24,6 +24,8 @@ export default class Pinger { this.timeout_, DEFAULT_INTERVAL_MS); } + // Resolves if a connection has been established, or rejects if the connection + // fails. Does not retry. public pingOnce = () : Promise => { const socket = freedom['core.tcpsocket'](); From 8508793d86213451605a389c7d9f67416ce7bebc Mon Sep 17 00:00:00 2001 From: Daniel Borkan Date: Mon, 31 Oct 2016 18:27:46 -0400 Subject: [PATCH 5/7] Cleanup reconnect_ logic --- src/lib/cloud/social/provider.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/lib/cloud/social/provider.ts b/src/lib/cloud/social/provider.ts index d47809e864..ebeebab9b7 100644 --- a/src/lib/cloud/social/provider.ts +++ b/src/lib/cloud/social/provider.ts @@ -416,13 +416,6 @@ export class CloudSocialProvider { this.startMonitoringPresence_(invite.host); this.notifyOfUser_(this.savedContacts_[invite.host]); this.saveContacts_(); - - // Connect in the background in order to fetch metadata such as - // the banner (description). - this.reconnect_(invite).catch((e: Error) => { - log.warn('failed to log into cloud server during invite accept: %1', e.message); - }); - return Promise.resolve(); } catch (e) { return Promise.reject(new Error('could not parse invite code: ' + e.message)); From 7993842012eade6f6a1e35685d8c8db56530f27d Mon Sep 17 00:00:00 2001 From: Daniel Borkan Date: Tue, 1 Nov 2016 17:00:01 -0400 Subject: [PATCH 6/7] Changes for code review --- src/lib/cloud/social/provider.ts | 73 ++++++++++++++++---------------- src/lib/net/pinger.ts | 3 +- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/lib/cloud/social/provider.ts b/src/lib/cloud/social/provider.ts index ebeebab9b7..ba27dbc415 100644 --- a/src/lib/cloud/social/provider.ts +++ b/src/lib/cloud/social/provider.ts @@ -173,7 +173,9 @@ export class CloudSocialProvider { private onlineHosts_: { [host: string]: boolean } = {}; // Map from host to intervalId used for monitoring online presence. - private onlinePresenceMonitorIds_: { [host: string]: number } = {}; + private onlinePresenceMonitorIds_: { [host: string]: NodeJS.Timer } = {}; + + static PING_INTERVAL = 60000; constructor(private dispatchEvent_: (name: string, args: Object) => void) { } @@ -224,6 +226,9 @@ export class CloudSocialProvider { this.clients_[invite.host] = connection.connect().then(() => { log.info('connected to zork on %1', invite.host); + // Cloud server is online if a connection has succeeded. + this.setOnlineStatus_(invite.host, true); + // Fetch the banner, if available, then emit an instance message. connection.getBanner().then((banner: string) => { if (banner.length < 1) { @@ -239,8 +244,6 @@ export class CloudSocialProvider { description: banner, version: connection.getVersion() }; - // Cloud server must be online if it is responding with it's banner. - this.setOnlineStatus_(invite.host, true); this.notifyOfUser_(this.savedContacts_[invite.host]); this.saveContacts_(); }); @@ -444,20 +447,42 @@ export class CloudSocialProvider { return this.saveContacts_(); } - private startMonitoringPresence_(host: string) { + private startMonitoringPresence_ = (host: string) => { if (this.onlinePresenceMonitorIds_[host]) { log.error('unexpected call to startMonitoringPresence_ for ' + host); return; } - // Ping server every minute to see if it is online - const ping = this.ping_.bind(this, host); - const PING_INTERVAL = 60000; - this.onlinePresenceMonitorIds_[host] = setInterval(ping, PING_INTERVAL); + // Ping server every minute to see if it is online. A server is considered + // online if a connection can be established with the SSH port. + const ping = () : Promise => { + var pinger = new Pinger(host, SSH_SERVER_PORT); + return pinger.pingOnce().then(() => { + return true; + }).catch(() => { + return false; + }).then((newOnlineValue: boolean) => { + var oldOnlineValue = this.isOnline_(host); + this.setOnlineStatus_(host, newOnlineValue); + if (newOnlineValue !== oldOnlineValue) { + // status changed, emit a new onClientState. + this.notifyOfUser_(this.savedContacts_[host]); + if (newOnlineValue) { + // Connect in the background in order to fetch metadata such as + // the banner (description). + const invite = this.savedContacts_[host].invite; + this.reconnect_(invite).catch((e: Error) => { + log.error('failed to log into cloud server once online: %1', e.message); + }); + } + } + }); + } + this.onlinePresenceMonitorIds_[host] = setInterval(ping, CloudSocialProvider.PING_INTERVAL); // Ping server immediately (so we don't have to wait 1 min for 1st result). ping(); } - private stopMonitoringPresence_(host: string) { + private stopMonitoringPresence_ = (host: string) => { if (!this.onlinePresenceMonitorIds_[host]) { log.error('unexpected call to stopMonitoringPresence_ for ' + host); return; @@ -466,41 +491,17 @@ export class CloudSocialProvider { delete this.onlinePresenceMonitorIds_[host]; } - private ping_(host: string) : Promise { - var pinger = new Pinger(host, SSH_SERVER_PORT); - return pinger.pingOnce().then(() => { - return Promise.resolve(true); - }).catch(() => { - return Promise.resolve(false); - }).then((newOnlineValue: boolean) => { - var oldOnlineValue = this.isOnline_(host); - this.setOnlineStatus_(host, newOnlineValue); - if (newOnlineValue !== oldOnlineValue) { - // status changed, emit a new onClientState. - this.notifyOfUser_(this.savedContacts_[host]); - if (newOnlineValue) { - // Connect in the background in order to fetch metadata such as - // the banner (description). - const invite = this.savedContacts_[host].invite; - this.reconnect_(invite).catch((e: Error) => { - log.error('failed to log into cloud server once online: %1', e.message); - }); - } - } - }); - } - - private isOnline_(host: string) { + private isOnline_ = (host: string) => { return host === 'me' || this.onlineHosts_[host] === true; } - private setOnlineStatus_(host: string, isOnline: boolean) { + private setOnlineStatus_ = (host: string, isOnline: boolean) => { this.onlineHosts_[host] = isOnline; } // To see how these fields are handled, see // generic_core/social.ts#handleClient in the uProxy repo. - private makeClientState_(address: string): freedom.Social.ClientState { + private makeClientState_ = (address: string) : freedom.Social.ClientState => { return { userId: address, clientId: address, diff --git a/src/lib/net/pinger.ts b/src/lib/net/pinger.ts index 57ae8b0641..96e1e6ba39 100644 --- a/src/lib/net/pinger.ts +++ b/src/lib/net/pinger.ts @@ -20,8 +20,7 @@ export default class Pinger { public ping = (): Promise => { log.debug('pinging %1:%2...', this.host_ , this.port_); - return promises.retry(this.pingOnce.bind(this), - this.timeout_, DEFAULT_INTERVAL_MS); + return promises.retry(this.pingOnce, this.timeout_, DEFAULT_INTERVAL_MS); } // Resolves if a connection has been established, or rejects if the connection From 53cd82abf31bf1c65e0d74fbcc3f2a02cbe2e3f3 Mon Sep 17 00:00:00 2001 From: Daniel Borkan Date: Wed, 2 Nov 2016 16:01:44 -0400 Subject: [PATCH 7/7] Only ping servers until they are online --- src/lib/cloud/social/provider.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/lib/cloud/social/provider.ts b/src/lib/cloud/social/provider.ts index ba27dbc415..0921dfade2 100644 --- a/src/lib/cloud/social/provider.ts +++ b/src/lib/cloud/social/provider.ts @@ -175,7 +175,7 @@ export class CloudSocialProvider { // Map from host to intervalId used for monitoring online presence. private onlinePresenceMonitorIds_: { [host: string]: NodeJS.Timer } = {}; - static PING_INTERVAL = 60000; + private static PING_INTERVAL_ = 60000; constructor(private dispatchEvent_: (name: string, args: Object) => void) { } @@ -453,7 +453,9 @@ export class CloudSocialProvider { return; } // Ping server every minute to see if it is online. A server is considered - // online if a connection can be established with the SSH port. + // online if a connection can be established with the SSH port. We stop + // pinging for presence once the host is online, so as to not give away + // that we are pinging uProxy cloud servers with a regular heartbeat. const ping = () : Promise => { var pinger = new Pinger(host, SSH_SERVER_PORT); return pinger.pingOnce().then(() => { @@ -477,14 +479,15 @@ export class CloudSocialProvider { } }); } - this.onlinePresenceMonitorIds_[host] = setInterval(ping, CloudSocialProvider.PING_INTERVAL); + this.onlinePresenceMonitorIds_[host] = setInterval(ping, CloudSocialProvider.PING_INTERVAL_); // Ping server immediately (so we don't have to wait 1 min for 1st result). ping(); } private stopMonitoringPresence_ = (host: string) => { if (!this.onlinePresenceMonitorIds_[host]) { - log.error('unexpected call to stopMonitoringPresence_ for ' + host); + // We may have already stopped monitoring presence, e.g. because the + // host has come online. return; } clearInterval(this.onlinePresenceMonitorIds_[host]); @@ -497,6 +500,10 @@ export class CloudSocialProvider { private setOnlineStatus_ = (host: string, isOnline: boolean) => { this.onlineHosts_[host] = isOnline; + if (isOnline) { + // Stop monitoring presence once the client is online. + this.stopMonitoringPresence_(host); + } } // To see how these fields are handled, see