2014-07-26 10:15:29 +02:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
//====================================================================
|
|
|
|
|
|
|
|
|
|
var assign = require('lodash.assign');
|
|
|
|
|
var forEach = require('lodash.foreach');
|
|
|
|
|
|
|
|
|
|
var Promise = require('bluebird');
|
|
|
|
|
|
|
|
|
|
// Support browsers.
|
|
|
|
|
var WebSocket = (function (root) {
|
|
|
|
|
return (root && 'WebSocket' in root) ?
|
|
|
|
|
root.WebSocket :
|
|
|
|
|
require('ws')
|
|
|
|
|
;
|
|
|
|
|
})(this);
|
|
|
|
|
|
|
|
|
|
//====================================================================
|
|
|
|
|
|
|
|
|
|
var notConnected = function () {
|
|
|
|
|
throw new Error('not connected');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//====================================================================
|
|
|
|
|
|
|
|
|
|
var Xo = function (url) {
|
|
|
|
|
this._url = url;
|
|
|
|
|
|
|
|
|
|
// Identifier of the next request.
|
|
|
|
|
this._nextId = 0;
|
|
|
|
|
|
|
|
|
|
// Promises linked to the requests.
|
|
|
|
|
this._deferreds = {};
|
|
|
|
|
|
|
|
|
|
// Current WebSocket.
|
|
|
|
|
this._socket = null;
|
|
|
|
|
|
|
|
|
|
// Current status which may be:
|
|
|
|
|
// - disconnected
|
|
|
|
|
// - connecting
|
|
|
|
|
// - connected
|
|
|
|
|
this.status = 'disconnected';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
assign(Xo.prototype, {
|
|
|
|
|
close: function () {
|
|
|
|
|
if (this._socket)
|
|
|
|
|
{
|
|
|
|
|
this._socket.close();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
connect: function () {
|
|
|
|
|
if (this.status === 'connected')
|
|
|
|
|
{
|
|
|
|
|
return Promise.cast();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var deferred = Promise.defer();
|
|
|
|
|
|
|
|
|
|
this.status = 'connecting';
|
|
|
|
|
|
2014-07-26 23:42:04 +02:00
|
|
|
var socket = this._socket = new WebSocket(this._url, {
|
|
|
|
|
// Due to imperfect TLS implementation in XO-Server.
|
|
|
|
|
rejectUnauthorized: false,
|
|
|
|
|
});
|
2014-07-26 10:15:29 +02:00
|
|
|
|
|
|
|
|
// When the socket opens, send any queued requests.
|
|
|
|
|
socket.on('open', function () {
|
|
|
|
|
this.status = 'connected';
|
|
|
|
|
|
|
|
|
|
// (Re)Opens accesses.
|
|
|
|
|
delete this.send;
|
|
|
|
|
|
|
|
|
|
// Resolves the promise.
|
|
|
|
|
deferred.resolve();
|
|
|
|
|
}.bind(this));
|
|
|
|
|
|
|
|
|
|
socket.on('message', function (data) {
|
|
|
|
|
// `ws` API is lightly different from standard API.
|
|
|
|
|
if (data.data)
|
|
|
|
|
{
|
|
|
|
|
data = data.data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Wraps in a promise to prevent releasing the Zalgo.
|
|
|
|
|
var response = JSON.parse(data);
|
|
|
|
|
|
|
|
|
|
var id = response.id;
|
|
|
|
|
|
|
|
|
|
var deferred = this._deferreds[id];
|
|
|
|
|
if (!deferred)
|
|
|
|
|
{
|
|
|
|
|
// Response already handled.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
delete this._deferreds[id];
|
|
|
|
|
|
|
|
|
|
if ('error' in response)
|
|
|
|
|
{
|
|
|
|
|
return deferred.reject(response.error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ('result' in response)
|
|
|
|
|
{
|
|
|
|
|
return deferred.resolve(response.result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deferred.reject({
|
|
|
|
|
message: 'invalid response received',
|
|
|
|
|
object: response,
|
|
|
|
|
});
|
|
|
|
|
}.bind(this));
|
|
|
|
|
|
|
|
|
|
socket.on('close', function () {
|
|
|
|
|
// Closes accesses.
|
|
|
|
|
this.send = notConnected;
|
|
|
|
|
|
|
|
|
|
// Fails all waiting requests.
|
|
|
|
|
forEach(this._deferreds, function (deferred) {
|
|
|
|
|
deferred.reject('not connected');
|
|
|
|
|
});
|
|
|
|
|
this._deferreds = {};
|
|
|
|
|
}.bind(this));
|
|
|
|
|
|
|
|
|
|
socket.on('error', function (error) {
|
|
|
|
|
// Fails the connect promise if possible.
|
|
|
|
|
deferred.reject(error);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return deferred.promise;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
call: function (method, params) {
|
|
|
|
|
return this.connect().then(function () {
|
|
|
|
|
var socket = this._socket;
|
|
|
|
|
|
|
|
|
|
var id = this._nextId++;
|
|
|
|
|
|
|
|
|
|
socket.send(JSON.stringify({
|
|
|
|
|
jsonrpc: '2.0',
|
|
|
|
|
id: id,
|
|
|
|
|
method: method,
|
|
|
|
|
params: params || [],
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
var deferred = this._deferreds[id] = Promise.defer();
|
|
|
|
|
|
|
|
|
|
return deferred.promise;
|
|
|
|
|
}.bind(this));
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//====================================================================
|
|
|
|
|
|
|
|
|
|
module.exports = Xo;
|