atom-nuclide
Version:
A unified developer experience for web and mobile development, built as a suite of features on top of Atom to provide hackability and the support of an active community.
214 lines (184 loc) • 6.98 kB
JavaScript
Object.defineProperty(exports, '__esModule', {
value: true
});
/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
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; }; })();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _rxjsBundlesRxUmdMinJs2;
function _rxjsBundlesRxUmdMinJs() {
return _rxjsBundlesRxUmdMinJs2 = require('rxjs/bundles/Rx.umd.min.js');
}
var _assert2;
function _assert() {
return _assert2 = _interopRequireDefault(require('assert'));
}
var _nuclideLogging2;
function _nuclideLogging() {
return _nuclideLogging2 = require('../../nuclide-logging');
}
var _eventKit2;
function _eventKit() {
return _eventKit2 = require('event-kit');
}
var _compression2;
function _compression() {
return _compression2 = require('./compression');
}
var logger = (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)();
// Do not synchronously compress large payloads (risks blocking the event loop)
var MAX_SYNC_COMPRESS_LENGTH = 100000;
// An unreliable transport for sending JSON formatted messages
// over a WebSocket
//
// onClose handlers are guaranteed to be called exactly once.
// onMessage handlers are guaranteed to not be called after onClose has been called.
// send(data) yields false if the message failed to send, true on success.
// onClose handlers will be called before close() returns.
var WebSocketTransport = (function () {
function WebSocketTransport(clientId, socket, options) {
var _this = this;
_classCallCheck(this, WebSocketTransport);
this.id = clientId;
this._emitter = new (_eventKit2 || _eventKit()).Emitter();
this._socket = socket;
this._messages = new (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Subject();
this._syncCompression = options == null || options.syncCompression !== false;
logger.info('Client #%s connecting with a new socket!', this.id);
socket.on('message', function (data, flags) {
var message = data;
// Only compressed data will be sent as binary buffers.
if (flags.binary) {
message = (0, (_compression2 || _compression()).decompress)(data);
}
_this._onSocketMessage(message);
});
socket.on('close', function () {
if (_this._socket != null) {
(0, (_assert2 || _assert()).default)(_this._socket === socket);
logger.info('Client #%s socket close recieved on open socket!', _this.id);
_this._setClosed();
} else {
logger.info('Client #%s recieved socket close on already closed socket!', _this.id);
}
});
socket.on('error', function (e) {
if (_this._socket != null) {
logger.error('Client #' + _this.id + ' error: ' + e.message);
_this._emitter.emit('error', e);
} else {
logger.error('Client #' + _this.id + ' error after close: ' + e.message);
}
});
socket.on('pong', function (data, flags) {
if (_this._socket != null) {
// data may be a Uint8Array
_this._emitter.emit('pong', data != null ? String(data) : data);
} else {
logger.error('Received socket pong after connection closed');
}
});
}
_createClass(WebSocketTransport, [{
key: '_onSocketMessage',
value: function _onSocketMessage(message) {
if (this._socket == null) {
logger.error('Received socket message after connection closed', new Error());
return;
}
this._messages.next(message);
}
}, {
key: 'onMessage',
value: function onMessage() {
return this._messages;
}
}, {
key: 'onClose',
value: function onClose(callback) {
return this._emitter.on('close', callback);
}
}, {
key: 'onError',
value: function onError(callback) {
return this._emitter.on('error', callback);
}
}, {
key: 'send',
value: function send(message) {
var _this2 = this;
var socket = this._socket;
if (socket == null) {
logger.error('Attempt to send socket message after connection closed', new Error());
return Promise.resolve(false);
}
return new Promise(function (resolve, reject) {
var data = message;
var compressed = false;
if (_this2._syncCompression && message.length < MAX_SYNC_COMPRESS_LENGTH) {
data = (0, (_compression2 || _compression()).compress)(message);
compressed = true;
}
socket.send(data, { compress: !compressed }, function (err) {
if (err != null) {
logger.warn('Failed sending socket message to client:', _this2.id, JSON.parse(message));
resolve(false);
} else {
resolve(true);
}
});
});
}
// The WS socket automatically responds to pings with pongs.
}, {
key: 'ping',
value: function ping(data) {
if (this._socket != null) {
this._socket.ping(data);
} else {
logger.error('Attempted to send socket ping after connection closed');
}
}
}, {
key: 'onPong',
value: function onPong(callback) {
return this._emitter.on('pong', callback);
}
}, {
key: 'close',
value: function close() {
if (this._socket != null) {
// The call to socket.close may or may not cause our handler to be called
this._socket.close();
this._setClosed();
}
}
}, {
key: 'isClosed',
value: function isClosed() {
return this._socket == null;
}
}, {
key: '_setClosed',
value: function _setClosed() {
if (this._socket != null) {
// In certain (Error) conditions socket.close may not emit the on close
// event synchronously.
this._socket = null;
this._emitter.emit('close');
}
}
}]);
return WebSocketTransport;
})();
exports.WebSocketTransport = WebSocketTransport;
// The built-in WebSocket compression implementation can have poor performance characteristics:
// in particular, decompression takes a full event tick client-side.
// This option enables our own custom compression format that allows us to use custom
// decompression client-side.