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');
|
2015-02-05 17:05:49 +01:00
|
|
|
var EventEmitter = require('events').EventEmitter;
|
2014-07-26 10:15:29 +02:00
|
|
|
var forEach = require('lodash.foreach');
|
2015-02-05 17:05:49 +01:00
|
|
|
var inherits = require('util').inherits;
|
|
|
|
|
var jsonRpc = require('json-rpc');
|
|
|
|
|
var MethodNotFound = require('json-rpc/errors').MethodNotFound;
|
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 13:23:26 +01:00
|
|
|
function makeDeferred() {
|
|
|
|
|
var resolve, reject;
|
|
|
|
|
var promise = new Bluebird(function (resolve_, reject_) {
|
|
|
|
|
resolve = resolve_;
|
|
|
|
|
reject = reject_;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
promise: promise,
|
|
|
|
|
reject: reject,
|
|
|
|
|
resolve: resolve,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-05 13:19:47 +01:00
|
|
|
function startsWith(string, target) {
|
|
|
|
|
return (string.lastIndexOf(target, 0) === 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//====================================================================
|
|
|
|
|
|
2014-07-28 13:21:19 +02:00
|
|
|
// Fix URL if necessary.
|
2015-02-05 12:41:25 +01:00
|
|
|
var URL_RE = /^(?:(?:http|ws)(s)?:\/\/)?(.*?)\/*(?:\/api\/)?$/;
|
2015-02-05 11:57:05 +01:00
|
|
|
function fixUrl(url) {
|
2015-02-05 12:41:25 +01:00
|
|
|
var matches = URL_RE.exec(url);
|
|
|
|
|
var isSecure = !!matches[1];
|
|
|
|
|
var rest = matches[2];
|
|
|
|
|
|
2014-07-28 13:21:19 +02:00
|
|
|
return [
|
2015-02-05 12:41:25 +01:00
|
|
|
isSecure ? 'wss' : 'ws',
|
|
|
|
|
'://',
|
|
|
|
|
rest,
|
|
|
|
|
'/api/',
|
2014-07-28 13:21:19 +02:00
|
|
|
].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) {
|
2015-02-05 17:05:49 +01:00
|
|
|
// Super constructor.
|
|
|
|
|
EventEmitter.call(this);
|
2014-07-26 10:15:29 +02:00
|
|
|
|
2015-02-05 17:05:49 +01:00
|
|
|
// Fix the URL (ensure correct protocol and /api/ path).
|
|
|
|
|
this._url = fixUrl(url);
|
2014-07-26 10:15:29 +02:00
|
|
|
|
|
|
|
|
// Current status which may be:
|
|
|
|
|
// - disconnected
|
|
|
|
|
// - connecting
|
|
|
|
|
// - connected
|
|
|
|
|
this.status = 'disconnected';
|
2015-02-05 17:05:49 +01:00
|
|
|
|
|
|
|
|
// Will contains the WebSocket.
|
|
|
|
|
this._socket = null;
|
|
|
|
|
|
|
|
|
|
// The JSON-RPC server.
|
|
|
|
|
var this_ = this;
|
|
|
|
|
this._jsonRpc = jsonRpc.createServer(function (message) {
|
|
|
|
|
if (message.type === 'notification') {
|
|
|
|
|
this_.emit('notification', message);
|
|
|
|
|
} else {
|
|
|
|
|
// This object does not support requests.
|
|
|
|
|
throw new MethodNotFound(message.method);
|
|
|
|
|
}
|
|
|
|
|
}).on('data', function (message) {
|
|
|
|
|
this_._socket.send(JSON.stringify(message));
|
|
|
|
|
});
|
2015-02-05 11:57:05 +01:00
|
|
|
}
|
2015-02-05 17:05:49 +01:00
|
|
|
inherits(Xo, EventEmitter);
|
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();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2015-02-05 13:23:26 +01:00
|
|
|
connect: Bluebird.method(function () {
|
2015-02-05 17:05:49 +01:00
|
|
|
if (this._socket) {
|
2015-02-05 13:23:26 +01:00
|
|
|
return;
|
2014-07-26 10:15:29 +02:00
|
|
|
}
|
|
|
|
|
this.status = 'connecting';
|
|
|
|
|
|
2015-02-05 13:23:26 +01:00
|
|
|
var deferred = makeDeferred();
|
|
|
|
|
|
2014-07-28 13:33:29 +02:00
|
|
|
var opts = {};
|
2015-02-05 13:19:47 +01:00
|
|
|
if (startsWith(this._url, 'wss')) {
|
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;
|
|
|
|
|
}
|
2015-02-05 13:22:49 +01:00
|
|
|
var socket = this._socket = new WebSocket(this._url, '', opts);
|
2014-07-26 10:15:29 +02:00
|
|
|
|
2015-02-05 17:05:49 +01:00
|
|
|
// Used to avoid binding listeners to this object.
|
|
|
|
|
var this_ = this;
|
|
|
|
|
|
2014-07-26 10:15:29 +02:00
|
|
|
// When the socket opens, send any queued requests.
|
2015-02-05 13:22:49 +01:00
|
|
|
socket.addEventListener('open', function () {
|
2014-07-26 10:15:29 +02:00
|
|
|
this.status = 'connected';
|
|
|
|
|
|
|
|
|
|
// Resolves the promise.
|
|
|
|
|
deferred.resolve();
|
2015-02-05 17:05:49 +01:00
|
|
|
});
|
2014-07-26 10:15:29 +02:00
|
|
|
|
2015-02-05 17:05:49 +01:00
|
|
|
socket.addEventListener('message', function (message) {
|
|
|
|
|
this_._jsonRpc.write(message.data);
|
|
|
|
|
});
|
2014-07-26 10:15:29 +02:00
|
|
|
|
2015-02-05 13:22:49 +01:00
|
|
|
socket.addEventListener('close', function () {
|
2015-02-05 17:05:49 +01:00
|
|
|
this_.status = 'disconnected';
|
|
|
|
|
this_._socket = null;
|
2014-07-26 10:15:29 +02:00
|
|
|
|
2015-02-05 17:05:49 +01:00
|
|
|
this_._jsonRpc.failPendingRequests('connection lost');
|
|
|
|
|
});
|
2014-07-26 10:15:29 +02:00
|
|
|
|
2015-02-05 13:23:26 +01:00
|
|
|
socket.addEventListener('error', function (error) {
|
2014-07-26 10:15:29 +02:00
|
|
|
// Fails the connect promise if possible.
|
|
|
|
|
deferred.reject(error);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return deferred.promise;
|
2015-02-05 13:23:26 +01:00
|
|
|
}),
|
2014-07-26 10:15:29 +02:00
|
|
|
|
|
|
|
|
call: function (method, params) {
|
2015-02-05 17:05:49 +01:00
|
|
|
var jsonRpc = this._jsonRpc;
|
2014-07-26 10:15:29 +02:00
|
|
|
|
2015-02-05 17:05:49 +01:00
|
|
|
return this.connect().then(function () {
|
|
|
|
|
return jsonRpc.request(method, params);
|
|
|
|
|
});
|
2014-07-26 10:15:29 +02:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//====================================================================
|
|
|
|
|
|
2014-07-28 13:21:19 +02:00
|
|
|
exports = module.exports = Xo;
|
|
|
|
|
exports.fixUrl = fixUrl;
|