This commit is contained in:
wltsmrz
2013-08-07 06:26:43 +09:00
parent fd0ce52cf6
commit 1aaee7526b
2 changed files with 152 additions and 166 deletions

View File

@@ -42,6 +42,7 @@ var sjcl = require('../../../build/sjcl');
trace trace
max_listeners : Set maxListeners for remote; prevents EventEmitter warnings max_listeners : Set maxListeners for remote; prevents EventEmitter warnings
connection_offset : Connect to remote servers on supplied interval (in seconds)
trusted : truthy, if remote is trusted trusted : truthy, if remote is trusted
max_fee : Maximum acceptable transaction fee max_fee : Maximum acceptable transaction fee
fee_cushion : Extra fee multiplier to account for async fee changes. fee_cushion : Extra fee multiplier to account for async fee changes.
@@ -85,8 +86,8 @@ function Remote(opts, trace) {
this.fee_cushion = (typeof opts.fee_cushion === 'undefined') ? 1.5 : Number(opts.fee_cushion); this.fee_cushion = (typeof opts.fee_cushion === 'undefined') ? 1.5 : Number(opts.fee_cushion);
this.max_fee = (typeof opts.max_fee === 'undefined') ? Infinity : Number(opts.max_fee); this.max_fee = (typeof opts.max_fee === 'undefined') ? Infinity : Number(opts.max_fee);
this.id = 0; this.id = 0;
this.trace = opts.trace || trace; this.trace = Boolean(opts.trace);
this._server_fatal = false; // True, if we know server exited. this._server_fatal = false; // True, if we know server exited.
this._ledger_current_index = void(0); this._ledger_current_index = void(0);
this._ledger_hash = void(0); this._ledger_hash = void(0);
this._ledger_time = void(0); this._ledger_time = void(0);
@@ -94,8 +95,8 @@ function Remote(opts, trace) {
this._testnet = void(0); this._testnet = void(0);
this._transaction_subs = 0; this._transaction_subs = 0;
this.online_target = false; this.online_target = false;
this._online_state = 'closed'; // 'open', 'closed', 'connecting', 'closing' this._online_state = 'closed'; // 'open', 'closed', 'connecting', 'closing'
this.state = 'offline'; // 'online', 'offline' this.state = 'offline'; // 'online', 'offline'
this.retry_timer = void(0); this.retry_timer = void(0);
this.retry = void(0); this.retry = void(0);
@@ -107,6 +108,7 @@ function Remote(opts, trace) {
this._reserve_inc = void(0); this._reserve_inc = void(0);
this._connection_count = 0; this._connection_count = 0;
this._connected = false; this._connected = false;
this._connection_offset = 1000 * (Number(opts.connection_offset) || 5);
this._last_tx = null; this._last_tx = null;
this._cur_path_find = null; this._cur_path_find = null;
@@ -207,12 +209,20 @@ Remote.flags = {
} }
}; };
function isTemMalformed(engine_result_code) {
return (engine_result_code >= -299 && engine_result_code < 199);
};
function isTefFailure(engine_result_code) {
return (engine_result_code >= -299 && engine_result_code < 199);
};
Remote.from_config = function (obj, trace) { Remote.from_config = function (obj, trace) {
var serverConfig = typeof obj === 'string' ? config.servers[obj] : obj; var serverConfig = typeof obj === 'string' ? config.servers[obj] : obj;
var remote = new Remote(serverConfig, trace); var remote = new Remote(serverConfig, trace);
Object.keys(config.accounts).forEach(function(account) { function initialize_account(account) {
var accountInfo = config.accounts[account]; var accountInfo = config.accounts[account];
if (typeof accountInfo === 'object') { if (typeof accountInfo === 'object') {
if (accountInfo.secret) { if (accountInfo.secret) {
@@ -222,7 +232,13 @@ Remote.from_config = function (obj, trace) {
remote.set_secret(accountInfo.account, accountInfo.secret); remote.set_secret(accountInfo.account, accountInfo.secret);
} }
} }
}); }
if (typeof config.accounts === 'object') {
for (var account in config.accounts) {
initialize_account(account);
}
}
return remote; return remote;
}; };
@@ -233,14 +249,6 @@ Remote.create_remote = function(options, callback) {
return remote; return remote;
}; };
var isTemMalformed = function (engine_result_code) {
return (engine_result_code >= -299 && engine_result_code < 199);
};
var isTefFailure = function (engine_result_code) {
return (engine_result_code >= -299 && engine_result_code < 199);
};
Remote.prototype.add_server = function (opts) { Remote.prototype.add_server = function (opts) {
var self = this; var self = this;
@@ -249,13 +257,13 @@ Remote.prototype.add_server = function (opts) {
(opts.port || opts.websocket_port) (opts.port || opts.websocket_port)
; ;
var server = new Server(this, {url: url}); var server = new Server(this, { url: url });
server.on('message', function (data) { function server_message(data) {
self._handle_message(data); self._handle_message(data, server);
}); }
server.on('connect', function () { function server_connect() {
self._connection_count++; self._connection_count++;
self._set_state('online'); self._set_state('online');
if (opts.primary || !self._primary_server) { if (opts.primary || !self._primary_server) {
@@ -264,14 +272,18 @@ Remote.prototype.add_server = function (opts) {
if (self._connection_count === self._servers.length) { if (self._connection_count === self._servers.length) {
self.emit('ready'); self.emit('ready');
} }
}); }
server.on('disconnect', function () { function server_disconnect() {
self._connection_count--; self._connection_count--;
if (!self._connection_count) { if (!self._connection_count) {
self._set_state('offline'); self._set_state('offline');
} }
}); }
server.on('message', server_message);
server.on('connect', server_connect);
server.on('disconnect', server_disconnect);
this._servers.push(server); this._servers.push(server);
@@ -285,9 +297,7 @@ Remote.prototype.server_fatal = function () {
// Set the emitted state: 'online' or 'offline' // Set the emitted state: 'online' or 'offline'
Remote.prototype._set_state = function (state) { Remote.prototype._set_state = function (state) {
if (this.trace) { this._trace('remote: set_state: %s', state);
console.log('remote: set_state: %s', state);
}
if (this.state !== state) { if (this.state !== state) {
this.state = state; this.state = state;
@@ -317,12 +327,18 @@ Remote.prototype.set_trace = function (trace) {
return this; return this;
}; };
Remote.prototype._trace = function() {
if (this.trace) {
utils.logObject.apply(utils, arguments);
}
};
/** /**
* Connect to the Ripple network. * Connect to the Ripple network.
*/ */
Remote.prototype.connect = function (online) { Remote.prototype.connect = function (online) {
// Downwards compatibility // Downwards compatibility
switch(typeof online) { switch (typeof online) {
case 'undefined': case 'undefined':
break; break;
@@ -340,13 +356,18 @@ Remote.prototype.connect = function (online) {
if (!this._servers.length) { if (!this._servers.length) {
throw new Error('No servers available.'); throw new Error('No servers available.');
} else { } else {
var servers = this._servers; var self = this;
;(function nextServer(i) { ;(function nextServer(i) {
var server = servers[i]; var server = self._servers[i];
server._sid = i;
server._sid = ++i;
server.connect(); server.connect();
if (++i < servers.length) {
setTimeout(nextServer.bind(this, i), 1000 * 5); if (i < self._servers.length) {
setTimeout(function() {
nextServer(i);
}, self._connection_offset);
} }
})(0); })(0);
} }
@@ -358,31 +379,22 @@ Remote.prototype.connect = function (online) {
* Disconnect from the Ripple network. * Disconnect from the Ripple network.
*/ */
Remote.prototype.disconnect = function (online) { Remote.prototype.disconnect = function (online) {
for (var i=0, l=this._servers.length; i<l; i++) { this._servers.forEach(function(server) {
this._servers[i].disconnect(); server.disconnect();
} });
this._set_state('offline'); this._set_state('offline');
return this; return this;
}; };
Remote.prototype.ledger_hash = function () {
return this._ledger_hash;
};
// It is possible for messages to be dispatched after the connection is closed. // It is possible for messages to be dispatched after the connection is closed.
Remote.prototype._handle_message = function (json) { Remote.prototype._handle_message = function (message, server) {
var self = this; var self = this;
var unexpected = false;
var message;
try { try { message = JSON.parse(message); } catch(e) { }
message = JSON.parse(json);
unexpected = typeof message !== 'object'; var unexpected = typeof message !== 'object' || typeof message.type !== 'string';
} catch(exception) {
unexpected = true;
}
if (unexpected) { if (unexpected) {
// Unexpected response from remote. // Unexpected response from remote.
@@ -408,7 +420,7 @@ Remote.prototype._handle_message = function (json) {
this._ledger_hash = message.ledger_hash; this._ledger_hash = message.ledger_hash;
this._ledger_current_index = message.ledger_index + 1; this._ledger_current_index = message.ledger_index + 1;
this.emit('ledger_closed', message); this.emit('ledger_closed', message, server);
break; break;
case 'transaction': case 'transaction':
@@ -424,9 +436,7 @@ Remote.prototype._handle_message = function (json) {
this._last_tx = message.transaction.hash; this._last_tx = message.transaction.hash;
if (this.trace) { this._trace('remote: tx: %s', message);
utils.logObject('remote: tx: %s', message);
}
// Process metadata // Process metadata
message.mmeta = new Meta(message.meta); message.mmeta = new Meta(message.meta);
@@ -459,8 +469,6 @@ Remote.prototype._handle_message = function (json) {
this.emit('path_find_all', message); this.emit('path_find_all', message);
break; break;
// XXX Should be tracked by the Server object
case 'serverStatus': case 'serverStatus':
self.emit('server_status', message); self.emit('server_status', message);
@@ -474,18 +482,20 @@ Remote.prototype._handle_message = function (json) {
self._load_factor = message.load_factor; self._load_factor = message.load_factor;
self.emit('load', { 'load_base' : self._load_base, 'load_factor' : self.load_factor }); self.emit('load', { 'load_base' : self._load_base, 'load_factor' : self.load_factor });
} }
break; break;
// All other messages // All other messages
default: default:
if (this.trace) { this._trace('remote: '+message.type+': %s', message);
utils.logObject('remote: '+message.type+': %s', message); this.emit('net_' + message.type, message);
} break;
this.emit('net_' + message.type, message);
break;
} }
}; };
Remote.prototype.ledger_hash = function () {
return this._ledger_hash;
};
Remote.prototype._set_primary_server = function (server) { Remote.prototype._set_primary_server = function (server) {
if (this._primary_server) { if (this._primary_server) {
this._primary_server._primary = false; this._primary_server._primary = false;
@@ -561,19 +571,25 @@ Remote.prototype.request_ledger = function (ledger, opts, callback) {
request.message.ledger = ledger; request.message.ledger = ledger;
} }
var props = [
'full'
, 'expand'
, 'transactions'
, 'accounts'
];
switch (typeof opts) { switch (typeof opts) {
case 'object': case 'object':
var valid_properties = [ 'full', 'expand', 'transactions', 'accounts' ]; for (var key in opts) {
valid_properties.forEach(function(prop) { if (~props.indexOf(key)) {
if (opts.hasOwnProperty(prop)) { request.message[key] = true;
request.message[prop] = true;
} }
}); }
break; break;
case 'function': case 'function':
callback = opts; callback = opts;
opts = void(0); opts = void(0);
break; break;
default: default:
@@ -614,7 +630,7 @@ Remote.prototype.request_ledger_current = function (callback) {
Remote.prototype.request_ledger_entry = function (type, callback) { Remote.prototype.request_ledger_entry = function (type, callback) {
//utils.assert(this.trusted); // If not trusted, need to check proof, maybe talk packet protocol. //utils.assert(this.trusted); // If not trusted, need to check proof, maybe talk packet protocol.
var self = this; var self = this;
var request = new Request(this, 'ledger_entry'); var request = new Request(this, 'ledger_entry');
// Transparent caching. When .request() is invoked, look in the Remote object for the result. // Transparent caching. When .request() is invoked, look in the Remote object for the result.
@@ -651,9 +667,7 @@ Remote.prototype.request_ledger_entry = function (type, callback) {
// Emulate fetch of ledger entry. // Emulate fetch of ledger entry.
// console.log('request_ledger_entry: emulating'); // console.log('request_ledger_entry: emulating');
// YYY Missing lots of fields. // YYY Missing lots of fields.
request.emit('success', { request.emit('success', { node: node });
node : node
});
bDefault = false; bDefault = false;
} else { // Was not cached. } else { // Was not cached.
// XXX Only allow with trusted mode. Must sync response with advance. // XXX Only allow with trusted mode. Must sync response with advance.
@@ -822,11 +836,11 @@ Remote.prototype.request_account_tx = function (obj, callback) {
, 'limit' , 'limit'
]; ];
props.forEach(function(prop) { for (var key in obj) {
if (obj.hasOwnProperty(prop)) { if (~props.indexOf(key)) {
request.message[prop] = obj[prop]; request.message[key] = obj[key];
} }
}); }
request.callback(callback); request.callback(callback);
@@ -860,7 +874,7 @@ Remote.prototype.request_book_offers = function (gets, pays, taker, callback) {
}; };
Remote.prototype.request_wallet_accounts = function (seed, callback) { Remote.prototype.request_wallet_accounts = function (seed, callback) {
utils.assert(this.trusted); // Don't send secrets. utils.assert(this.trusted); // Don't send secrets.
var request = new Request(this, 'wallet_accounts'); var request = new Request(this, 'wallet_accounts');
@@ -870,7 +884,7 @@ Remote.prototype.request_wallet_accounts = function (seed, callback) {
}; };
Remote.prototype.request_sign = function (secret, tx_json, callback) { Remote.prototype.request_sign = function (secret, tx_json, callback) {
utils.assert(this.trusted); // Don't send secrets. utils.assert(this.trusted); // Don't send secrets.
var request = new Request(this, 'sign'); var request = new Request(this, 'sign');
@@ -976,7 +990,6 @@ Remote.prototype.request_account_balance = function (account, current, callback)
request.account_root(account); request.account_root(account);
request.ledger_choose(current); request.ledger_choose(current);
request.once('success', function (message) { request.once('success', function (message) {
// If the caller also waits for 'success', they might run before this.
request.emit('account_balance', Amount.from_json(message.node.Balance)); request.emit('account_balance', Amount.from_json(message.node.Balance));
}); });
@@ -992,7 +1005,6 @@ Remote.prototype.request_account_flags = function (account, current, callback) {
request.account_root(account); request.account_root(account);
request.ledger_choose(current); request.ledger_choose(current);
request.on('success', function (message) { request.on('success', function (message) {
// If the caller also waits for 'success', they might run before this.
request.emit('account_flags', message.node.Flags); request.emit('account_flags', message.node.Flags);
}); });
@@ -1008,7 +1020,6 @@ Remote.prototype.request_owner_count = function (account, current, callback) {
request.account_root(account); request.account_root(account);
request.ledger_choose(current); request.ledger_choose(current);
request.on('success', function (message) { request.on('success', function (message) {
// If the caller also waits for 'success', they might run before this.
request.emit('owner_count', message.node.OwnerCount); request.emit('owner_count', message.node.OwnerCount);
}); });
@@ -1024,10 +1035,9 @@ Remote.prototype.account = function (accountId) {
if (!account) { if (!account) {
account = new Account(this, accountId); account = new Account(this, accountId);
if (!account.is_valid()) { if (account.is_valid()) {
return account; this._accounts[accountId] = account;
} }
this._accounts[accountId] = account;
} }
return account; return account;
@@ -1048,29 +1058,19 @@ Remote.prototype.path_find = function (src_account, dst_account, dst_amount, src
}; };
Remote.prototype.book = function (currency_gets, issuer_gets, currency_pays, issuer_pays) { Remote.prototype.book = function (currency_gets, issuer_gets, currency_pays, issuer_pays) {
var gets = currency_gets; var gets = currency_gets + (currency_gets === 'XRP' ? '' : ('/' + issuer_gets));
if (gets !== 'XRP') { var pays = currency_pays + (currency_pays === 'XRP' ? '' : ('/' + issuer_pays));
gets += '/' + issuer_gets;
}
var pays = currency_pays;
if (pays !== 'XRP') {
pays += '/' + issuer_pays;
}
var key = gets + ':' + pays; var key = gets + ':' + pays;
var book;
if (!this._books[key]) { if (!this._books.hasOwnProperty(key)) {
var book = new OrderBook(this, currency_gets, issuer_gets, currency_pays, issuer_pays); book = new OrderBook(this, currency_gets, issuer_gets, currency_pays, issuer_pays);
if (book.is_valid()) {
if (!book.is_valid()) { this._books[key] = book;
return book;
} }
this._books[key] = book;
} }
return this._books[key]; return book;
}; };
// Return the next account sequence if possible. // Return the next account sequence if possible.
@@ -1082,16 +1082,7 @@ Remote.prototype.account_seq = function (account, advance) {
if (account_info && account_info.seq) { if (account_info && account_info.seq) {
seq = account_info.seq; seq = account_info.seq;
account_info.seq += { ADVANCE: 1, REWIND: -1 }[advance] || 0;
if (advance === 'ADVANCE') {
account_info.seq += 1;
}
if (advance === 'REWIND') {
account_info.seq -= 1;
}
// console.log('cached: %s current=%d next=%d', account, seq, account_info.seq);
} }
return seq; return seq;
@@ -1100,7 +1091,9 @@ Remote.prototype.account_seq = function (account, advance) {
Remote.prototype.set_account_seq = function (account, seq) { Remote.prototype.set_account_seq = function (account, seq) {
var account = UInt160.json_rewrite(account); var account = UInt160.json_rewrite(account);
if (!this.accounts[account]) this.accounts[account] = {}; if (!this.accounts.hasOwnProperty(account)) {
this.accounts[account] = { };
}
this.accounts[account].seq = seq; this.accounts[account].seq = seq;
} }
@@ -1109,16 +1102,16 @@ Remote.prototype.set_account_seq = function (account, seq) {
Remote.prototype.account_seq_cache = function (account, current, callback) { Remote.prototype.account_seq_cache = function (account, current, callback) {
var self = this; var self = this;
if (!self.accounts[account]) { if (!this.accounts.hasOwnProperty(account)) {
self.accounts[account] = {}; self.accounts[account] = { };
} }
var account_info = self.accounts[account]; var account_info = this.accounts[account];
var request = account_info.caching_seq_request; var request = account_info.caching_seq_request;
if (!request) { if (!request) {
// console.log('starting: %s', account); // console.log('starting: %s', account);
request = self.request_ledger_entry('account_root'); request = this.request_ledger_entry('account_root');
request.account_root(account); request.account_root(account);
request.ledger_choose(current); request.ledger_choose(current);
@@ -1243,7 +1236,7 @@ Remote.prototype.request_path_find_create = function (src_account, dst_account,
request.message.destination_amount = Amount.json_rewrite(dst_amount); request.message.destination_amount = Amount.json_rewrite(dst_amount);
if (src_currencies) { if (src_currencies) {
request.message.source_currencies = src_currencies.map(function (ci) { request.message.source_currencies = src_currencies.map(function (ci) {
var ci_new = {}; var ci_new = {};
if (ci.hasOwnProperty('issuer')) { if (ci.hasOwnProperty('issuer')) {

View File

@@ -22,15 +22,12 @@ function Server(remote, opts) {
this._remote = remote; this._remote = remote;
this._opts = opts; this._opts = opts;
this._ws = void(0); this._ws = void(0);
this._connected = false; this._connected = false;
this._should_connect = false; this._should_connect = false;
this._state = void(0); this._state = void(0);
this._id = 0; this._id = 0;
this._retry = 0; this._retry = 0;
this._requests = { }; this._requests = { };
this.on('message', function(message) { this.on('message', function(message) {
@@ -68,21 +65,27 @@ Server.prototype._set_state = function (state) {
this.emit('state', state); this.emit('state', state);
if (state === 'online') { switch (state) {
this._connected = true; case 'online':
this.emit('connect'); this._connected = true;
} else if (state === 'offline') { this.emit('connect');
this._connected = false; break;
this.emit('disconnect'); case 'offline':
this._connected = false;
this.emit('disconnect');
break;
} }
} }
}; };
Server.prototype._remote_address = function() { Server.prototype._trace = function() {
var address = null; if (this._remote.trace) {
if (this._ws) { utils.logObject.apply(utils, arguments);
address = this._ws._socket.remoteAddress;
} }
};
Server.prototype._remote_address = function() {
try { var address = this._ws._socket.remoteAddress; } catch (e) { }
return address; return address;
}; };
@@ -97,9 +100,7 @@ Server.prototype.connect = function () {
return; return;
} }
if (this._remote.trace) { this._trace('server: connect: %s', this._opts.url);
console.log('server: connect: %s', this._opts.url);
}
// Ensure any existing socket is given the command to close first. // Ensure any existing socket is given the command to close first.
if (this._ws) { if (this._ws) {
@@ -128,9 +129,7 @@ Server.prototype.connect = function () {
ws.onerror = function (e) { ws.onerror = function (e) {
// If we are no longer the active socket, simply ignore any event // If we are no longer the active socket, simply ignore any event
if (ws === self._ws) { if (ws === self._ws) {
if (self._remote.trace) { self._trace('server: onerror: %s', e.data || e);
console.log('server: onerror: %s', e.data || e);
}
// Most connection errors for WebSockets are conveyed as 'close' events with // Most connection errors for WebSockets are conveyed as 'close' events with
// code 1006. This is done for security purposes and therefore unlikely to // code 1006. This is done for security purposes and therefore unlikely to
@@ -154,9 +153,7 @@ Server.prototype.connect = function () {
ws.onclose = function () { ws.onclose = function () {
// If we are no longer the active socket, simply ignore any event // If we are no longer the active socket, simply ignore any event
if (ws === self._ws) { if (ws === self._ws) {
if (self._remote.trace) { self._trace('server: onclose: %s', ws.readyState);
console.log('server: onclose: %s', ws.readyState);
}
handleConnectionClose(); handleConnectionClose();
} }
}; };
@@ -176,9 +173,7 @@ Server.prototype.connect = function () {
// Delay and retry. // Delay and retry.
self._retry += 1; self._retry += 1;
self._retry_timer = setTimeout(function () { self._retry_timer = setTimeout(function () {
if (self._remote.trace) { self._trace('server: retry');
console.log('server: retry');
}
if (!self._should_connect) { if (!self._should_connect) {
return; return;
} }
@@ -227,47 +222,45 @@ Server.prototype.request = function (request) {
// Advance message ID // Advance message ID
this._id++; this._id++;
if (this._connected || (request.message.command === 'subscribe' && this._ws.readyState === 1)) { var is_connected = this._connected || (request.message.command === 'subscribe' && this._ws.readyState === 1);
if (this._remote.trace) {
utils.logObject('server: request: %s', request.message); if (is_connected) {
} this._trace('server: request: %s', request.message);
this.send_message(request.message); this.send_message(request.message);
} else { } else {
// XXX There are many ways to make this smarter. // XXX There are many ways to make this smarter.
this.once('connect', function () { function server_reconnected() {
if (this._remote.trace) { self._trace('server: request: %s', request.message);
utils.logObject('server: request: %s', request.message);
}
self.send_message(request.message); self.send_message(request.message);
}); }
this.once('connect', server_reconnected);
} }
} else if (this._remote.trace) { } else {
utils.logObject('server: request: DROPPING: %s', request.message); this._trace('server: request: DROPPING: %s', request.message);
} }
}; };
Server.prototype._handle_message = function (json) { Server.prototype._handle_message = function (message) {
var self = this; var self = this;
var message;
try { message = JSON.parse(json); } catch(exception) { }
if (typeof message !== 'object' || typeof message.type === 'undefined') { try { message = JSON.parse(message); } catch(e) { }
return;
var unexpected = typeof message !== 'object' || typeof message.type !== 'string';
if (unexpected) {
return;
} }
switch(message.type) { switch (message.type) {
case 'response': case 'response':
// A response to a request. // A response to a request.
var request = self._requests[message.id]; var request = self._requests[message.id];
delete self._requests[message.id]; delete self._requests[message.id];
if (!request) { if (!request) {
if (self._remote.trace) utils.logObject('server: UNEXPECTED: %s', message); this._trace('server: UNEXPECTED: %s', message);
} else if ('success' === message.status) { } else if ('success' === message.status) {
if (self._remote.trace) utils.logObject('server: response: %s', message); this._trace('server: response: %s', message);
request.emit('success', message.result); request.emit('success', message.result);
@@ -275,7 +268,7 @@ Server.prototype._handle_message = function (json) {
emitter.emit('response_' + request.message.command, message.result, request, message); emitter.emit('response_' + request.message.command, message.result, request, message);
}); });
} else if (message.error) { } else if (message.error) {
if (self._remote.trace) utils.logObject('server: error: %s', message); this._trace('server: error: %s', message);
request.emit('error', { request.emit('error', {
error : 'remoteError', error : 'remoteError',
@@ -287,7 +280,7 @@ Server.prototype._handle_message = function (json) {
case 'serverStatus': case 'serverStatus':
// This message is only received when online. As we are connected, it is the definative final state. // This message is only received when online. As we are connected, it is the definative final state.
self._set_state(self._is_online(message.server_status) ? 'online' : 'offline'); this._set_state(this._is_online(message.server_status) ? 'online' : 'offline');
break; break;
} }
} }