2014-07-26 10:15:29 +02:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
//====================================================================
|
|
|
|
|
|
|
|
|
|
var assign = require('lodash.assign');
|
2015-02-05 11:53:27 +01:00
|
|
|
var Bluebird = require('bluebird');
|
2014-07-26 10:15:29 +02:00
|
|
|
var forEach = require('lodash.foreach');
|
2014-07-28 13:21:19 +02:00
|
|
|
var parseUrl = require('url').parse;
|
2015-02-05 11:53:27 +01:00
|
|
|
var WebSocket = require('ws');
|
2014-07-26 10:15:29 +02:00
|
|
|
|
|
|
|
|
//====================================================================
|
|
|
|
|
|
2015-02-05 11:57:05 +01:00
|
|
|
function notConnected() {
|
2014-07-26 10:15:29 +02:00
|
|
|
throw new Error('not connected');
|
2015-02-05 11:57:05 +01:00
|
|
|
}
|
2014-07-26 10:15:29 +02:00
|
|
|
|
2014-07-28 13:21:19 +02:00
|
|
|
// Fix URL if necessary.
|
2015-02-05 11:57:05 +01:00
|
|
|
function fixUrl(url) {
|
2014-07-28 13:21:19 +02:00
|
|
|
// Add HTTP protocol if missing.
|
2014-07-28 13:30:57 +02:00
|
|
|
if (!/^https?:/.test(url)) {
|
|
|
|
|
url = 'http:'+ url;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
url = parseUrl(url);
|
2014-07-28 13:21:19 +02:00
|
|
|
|
|
|
|
|
// Suffix path with /api/ if missing.
|
2014-07-28 13:30:57 +02:00
|
|
|
var path = url.pathname || '';
|
2014-07-28 13:21:19 +02:00
|
|
|
if ('/' !== path[path.length - 1]) {
|
|
|
|
|
path += '/';
|
|
|
|
|
}
|
|
|
|
|
if (!/\/api\/$/.test(path)) {
|
|
|
|
|
path += 'api/';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reconstruct the URL.
|
|
|
|
|
return [
|
|
|
|
|
url.protocol, '//',
|
|
|
|
|
url.host,
|
|
|
|
|
path,
|
|
|
|
|
url.search,
|
|
|
|
|
url.hash,
|
|
|
|
|
].join('');
|
2015-02-05 11:57:05 +01:00
|
|
|
}
|
2014-07-28 13:21:19 +02:00
|
|
|
|
2014-07-26 10:15:29 +02:00
|
|
|
//====================================================================
|
|
|
|
|
|
2015-02-05 11:57:05 +01:00
|
|
|
function Xo(url) {
|
2014-07-28 13:21:19 +02:00
|
|
|
this._url = fixUrl(url);
|
2014-07-26 10:15:29 +02:00
|
|
|
|
|
|
|
|
// 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';
|
2015-02-05 11:57:05 +01:00
|
|
|
}
|
2014-07-26 10:15:29 +02:00
|
|
|
|
|
|
|
|
assign(Xo.prototype, {
|
|
|
|
|
close: function () {
|
2015-02-05 11:57:05 +01:00
|
|
|
if (this._socket) {
|
2014-07-26 10:15:29 +02:00
|
|
|
this._socket.close();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
connect: function () {
|
2015-02-05 11:57:05 +01:00
|
|
|
if (this.status === 'connected') {
|
2015-02-05 11:46:56 +01:00
|
|
|
return Bluebird.cast();
|
2014-07-26 10:15:29 +02:00
|
|
|
}
|
|
|
|
|
|
2015-02-05 11:46:56 +01:00
|
|
|
var deferred = Bluebird.defer();
|
2014-07-26 10:15:29 +02:00
|
|
|
|
|
|
|
|
this.status = 'connecting';
|
|
|
|
|
|
2014-07-28 13:33:29 +02:00
|
|
|
var opts = {};
|
|
|
|
|
if (/^https/.test(this._url)) {
|
2014-07-26 23:42:04 +02:00
|
|
|
// Due to imperfect TLS implementation in XO-Server.
|
2014-07-28 13:33:29 +02:00
|
|
|
opts.rejectUnauthorized = false;
|
|
|
|
|
}
|
|
|
|
|
var socket = this._socket = new WebSocket(this._url, opts);
|
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.
|
2015-02-05 11:57:05 +01:00
|
|
|
if (data.data) {
|
2014-07-26 10:15:29 +02:00
|
|
|
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];
|
2015-02-05 11:57:05 +01:00
|
|
|
if (!deferred) {
|
2014-07-26 10:15:29 +02:00
|
|
|
// Response already handled.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
delete this._deferreds[id];
|
|
|
|
|
|
2015-02-05 11:57:05 +01:00
|
|
|
if ('error' in response) {
|
2014-07-26 10:15:29 +02:00
|
|
|
return deferred.reject(response.error);
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-05 11:57:05 +01:00
|
|
|
if ('result' in response) {
|
2014-07-26 10:15:29 +02:00
|
|
|
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 || [],
|
|
|
|
|
}));
|
|
|
|
|
|
2015-02-05 11:46:56 +01:00
|
|
|
var deferred = this._deferreds[id] = Bluebird.defer();
|
2014-07-26 10:15:29 +02:00
|
|
|
|
|
|
|
|
return deferred.promise;
|
|
|
|
|
}.bind(this));
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//====================================================================
|
|
|
|
|
|
2014-07-28 13:21:19 +02:00
|
|
|
exports = module.exports = Xo;
|
|
|
|
|
exports.fixUrl = fixUrl;
|