marsdb-sync-client
Version:
Standalone Meteor DDP client based on MarsDB
358 lines (306 loc) • 10.9 kB
JavaScript
'use strict';
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.CONN_STATUS = undefined;
var _try2 = require('fast.js/function/try');
var _try3 = _interopRequireDefault(_try2);
var _bind2 = require('fast.js/function/bind');
var _bind3 = _interopRequireDefault(_bind2);
var _HeartbeatManager = require('./HeartbeatManager');
var _HeartbeatManager2 = _interopRequireDefault(_HeartbeatManager);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var EventEmitter = typeof window !== 'undefined' && window.Mars ? window.Mars.EventEmitter : require('marsdb').EventEmitter;
var PromiseQueue = typeof window !== 'undefined' && window.Mars ? window.Mars.PromiseQueue : require('marsdb').PromiseQueue;
var EJSON = typeof window !== 'undefined' && window.Mars ? window.Mars.EJSON : require('marsdb').EJSON;
var Random = typeof window !== 'undefined' && window.Mars ? window.Mars.Random : require('marsdb').Random;
// Status of a DDP connection
var DDP_VERSION = '1';
var HEARTBEAT_INTERVAL = 17500;
var HEARTBEAT_TIMEOUT = 15000;
var RECONNECT_INTERVAL = 5000;
var CONN_STATUS = exports.CONN_STATUS = {
CONNECTING: 'CONNECTING',
CONNECTED: 'CONNECTED',
DISCONNECTED: 'DISCONNECTED'
};
var DDPConnection = function (_EventEmitter) {
_inherits(DDPConnection, _EventEmitter);
function DDPConnection(_ref) {
var url = _ref.url;
var socket = _ref.socket;
var _ref$autoReconnect = _ref.autoReconnect;
var autoReconnect = _ref$autoReconnect === undefined ? true : _ref$autoReconnect;
_classCallCheck(this, DDPConnection);
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(DDPConnection).call(this));
_this.url = url;
_this._processQueue = new PromiseQueue(1);
_this._sessionId = null;
_this._autoReconnect = autoReconnect;
_this._socket = socket;
_this._status = CONN_STATUS.DISCONNECTED;
_this._fullConnectedOnce = false;
_this._heartbeat = new _HeartbeatManager2.default(HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT);
_this._heartbeat.on('timeout', (0, _bind3.default)(_this._handleHearbeatTimeout, _this));
_this._heartbeat.on('sendPing', (0, _bind3.default)(_this.sendPing, _this));
_this._heartbeat.on('sendPong', (0, _bind3.default)(_this.sendPong, _this));
return _this;
}
/**
* Returns true if client is fully connected to a server
* @return {Boolean}
*/
_createClass(DDPConnection, [{
key: 'sendMethod',
/**
* Sends a "method" message to the server with given
* parameters
* @param {String} name
* @param {String} params
* @param {String} id
* @param {String} randomSeed
*/
value: function sendMethod(name) {
var params = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1];
var id = arguments[2];
var randomSeed = arguments[3];
var msg = {
msg: 'method',
id: id,
method: name,
params: params
};
if (randomSeed) {
msg.randomSeed = randomSeed;
}
this._sendMessage(msg);
}
/**
* Send "sub" message to the server with given
* publusher name and parameters
* @param {String} name
* @param {Array} params
* @param {String} id
*/
}, {
key: 'sendSub',
value: function sendSub(name) {
var params = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1];
var id = arguments[2];
this._sendMessage({
msg: 'sub',
id: id,
name: name,
params: params
});
}
/**
* Send "unsub" message to the server for given
* subscription id
* @param {String} id
*/
}, {
key: 'sendUnsub',
value: function sendUnsub(id) {
this._sendMessage({
msg: 'unsub',
id: id
});
}
/**
* Send a "ping" message with randomly generated ping id
*/
}, {
key: 'sendPing',
value: function sendPing() {
this._sendMessage({
msg: 'ping',
id: Random.default().id(20)
});
}
/**
* Sends a "pong" message for given id of ping message
* @param {String} id
*/
}, {
key: 'sendPong',
value: function sendPong(id) {
this._sendMessage({
msg: 'pong',
id: id
});
}
/**
* Make a new WebSocket connection to the server
* if we are not connected yet (isDicsonnected).
* Returns true if connecting, false if already connectiong
* @returns {Boolean}
*/
}, {
key: 'connect',
value: function connect() {
if (this.isDisconnected) {
this._rawConn = new this._socket(this.url);
this._rawConn.onopen = (0, _bind3.default)(this._handleOpen, this);
this._rawConn.onerror = (0, _bind3.default)(this._handleError, this);
this._rawConn.onclose = (0, _bind3.default)(this._handleClose, this);
this._rawConn.onmessage = (0, _bind3.default)(this._handleRawMessage, this);
this._setStatus(CONN_STATUS.CONNECTING);
return true;
}
return false;
}
/**
* Reconnect to the server with unlimited tries. A period
* of tries is 5 seconds. It reconnects only if not
* connected. It cancels previously scheduled `connect` by `reconnect`.
* Returns a function for canceling reconnection process or undefined
* if connection is not disconnected.
* @return {Function}
*/
}, {
key: 'reconnect',
value: function reconnect() {
var _this2 = this;
if (this.isDisconnected) {
clearTimeout(this._reconnTimer);
this._reconnecting = true;
this._reconnTimer = setTimeout((0, _bind3.default)(this.connect, this), RECONNECT_INTERVAL);
return function () {
clearTimeout(_this2._reconnTimer);
_this2._reconnecting = false;
_this2.disconnect();
};
}
}
/**
* Close WebSocket connection. If autoReconnect is enabled
* (enabled by default), then after 5 sec reconnection will
* be initiated.
*/
}, {
key: 'disconnect',
value: function disconnect() {
var _this3 = this;
(0, _try3.default)(function () {
return _this3._rawConn && _this3._rawConn.close();
});
}
}, {
key: '_handleOpen',
value: function _handleOpen() {
this._heartbeat.waitPing();
var connMsg = {
msg: 'connect',
version: DDP_VERSION,
support: [DDP_VERSION]
};
if (this._sessionId) {
connMsg.session = this._sessionId;
}
this._sendMessage(connMsg);
}
}, {
key: '_handleConnectedMessage',
value: function _handleConnectedMessage(msg) {
if (!this.isConnected) {
var isTrulyReconnected = this._fullConnectedOnce && this._reconnecting;
this._setStatus(CONN_STATUS.CONNECTED, isTrulyReconnected);
this._sessionId = msg.session;
this._reconnecting = false;
this._fullConnectedOnce = true;
}
}
}, {
key: '_handleClose',
value: function _handleClose() {
this._heartbeat._clearTimers();
this._setStatus(CONN_STATUS.DISCONNECTED, this._fullConnectedOnce);
if (this._autoReconnect) {
this._reconnecting = false;
this.reconnect();
}
}
}, {
key: '_handleHearbeatTimeout',
value: function _handleHearbeatTimeout() {
this.disconnect();
}
}, {
key: '_handleError',
value: function _handleError(error) {
this.emit('error', error);
}
}, {
key: '_handleRawMessage',
value: function _handleRawMessage(rawMsg) {
var _this4 = this;
return this._processQueue.add(function () {
var msgObj = EJSON.parse(rawMsg.data);
return _this4._processMessage(msgObj);
}).then(null, function (err) {
_this4._handleError(err);
});
}
}, {
key: '_processMessage',
value: function _processMessage(msg) {
switch (msg.msg) {
case 'connected':
return this._handleConnectedMessage(msg);
case 'ping':
return this._heartbeat.handlePing(msg);
case 'pong':
return this._heartbeat.handlePong(msg);
case 'removed':
case 'changed':
case 'added':
case 'updated':
case 'result':
case 'nosub':
case 'ready':
case 'error':
return this.emitAsync('message:' + msg.msg, msg);
default:
// just ignore unknown message
}
}
}, {
key: '_sendMessage',
value: function _sendMessage(msgObj) {
var _this5 = this;
var result = (0, _try3.default)(function () {
return _this5._rawConn.send(EJSON.stringify(msgObj));
});
if (result instanceof Error) {
this._handleError(result);
}
}
}, {
key: '_setStatus',
value: function _setStatus(status, a) {
this._status = status;
this.emit(('status:' + status).toLowerCase(), a);
}
}, {
key: 'isConnected',
get: function get() {
return this._status === CONN_STATUS.CONNECTED;
}
/**
* Returns true if client disconnected
* @return {Boolean}
*/
}, {
key: 'isDisconnected',
get: function get() {
return this._status === CONN_STATUS.DISCONNECTED;
}
}]);
return DDPConnection;
}(EventEmitter);
exports.default = DDPConnection;