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.
464 lines (393 loc) • 16.8 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 _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })();
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 _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { var callNext = step.bind(null, 'next'); var callThrow = step.bind(null, 'throw'); function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(callNext, callThrow); } } callNext(); }); }; }
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 _assert2;
function _assert() {
return _assert2 = _interopRequireDefault(require('assert'));
}
var _nuclideRpc2;
function _nuclideRpc() {
return _nuclideRpc2 = require('../../nuclide-rpc');
}
var _nuclideServerLibServicesConfig2;
function _nuclideServerLibServicesConfig() {
return _nuclideServerLibServicesConfig2 = _interopRequireDefault(require('../../nuclide-server/lib/servicesConfig'));
}
var _RemoteConnectionConfigurationManager2;
function _RemoteConnectionConfigurationManager() {
return _RemoteConnectionConfigurationManager2 = require('./RemoteConnectionConfigurationManager');
}
var _ConnectionHealthNotifier2;
function _ConnectionHealthNotifier() {
return _ConnectionHealthNotifier2 = require('./ConnectionHealthNotifier');
}
var _RemoteFile2;
function _RemoteFile() {
return _RemoteFile2 = require('./RemoteFile');
}
var _RemoteDirectory2;
function _RemoteDirectory() {
return _RemoteDirectory2 = require('./RemoteDirectory');
}
var _atom2;
function _atom() {
return _atom2 = require('atom');
}
var _commonsNodeNuclideUri2;
function _commonsNodeNuclideUri() {
return _commonsNodeNuclideUri2 = _interopRequireDefault(require('../../commons-node/nuclideUri'));
}
var _nuclideServerLibNuclideSocket2;
function _nuclideServerLibNuclideSocket() {
return _nuclideServerLibNuclideSocket2 = require('../../nuclide-server/lib/NuclideSocket');
}
var _nuclideVersion2;
function _nuclideVersion() {
return _nuclideVersion2 = require('../../nuclide-version');
}
// key for https connection.
// ServerConnection represents the client side of a connection to a remote machine.
// There can be at most one connection to a given remote machine at a time. Clients should
// get a ServerConnection via ServerConnection.getOrCreate() and should never call the
// constructor directly. Alternately existing connections can be queried with getByHostname().
//
// getService() returns typed RPC services via the service framework.
//
// A ServerConnection keeps a list of RemoteConnections - one for each open directory on the remote
// machine. Once all RemoteConnections have been closed, then the ServerConnection will close.
var ServerConnection = (function () {
_createClass(ServerConnection, null, [{
key: 'getOrCreate',
value: _asyncToGenerator(function* (config) {
var existingConnection = ServerConnection.getByHostname(config.host);
if (existingConnection != null) {
return existingConnection;
}
var newConnection = new ServerConnection(config);
try {
yield newConnection.initialize();
return newConnection;
} catch (e) {
newConnection.close();
throw e;
}
})
// WARNING: This shuts down all Nuclide servers _without_ closing their
// RemoteConnections first! This is extremely unsafe and
// should only be used to forcibly kill Nuclide servers before restarting.
}, {
key: 'forceShutdownAllServers',
value: _asyncToGenerator(function* () {
yield Promise.all(Array.from(ServerConnection._connections).map(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2);
var _ = _ref2[0];
var connection = _ref2[1];
return connection._closeServerConnection(true);
}));
})
// Do NOT call this from outside this class. Use ServerConnection.getOrCreate() instead.
}, {
key: '_connections',
value: new Map(),
enumerable: true
}, {
key: '_emitter',
value: new (_atom2 || _atom()).Emitter(),
enumerable: true
}]);
function ServerConnection(config) {
_classCallCheck(this, ServerConnection);
this._entries = {};
this._config = config;
this._closed = false;
this._healthNotifier = null;
this._client = null;
this._connections = [];
}
_createClass(ServerConnection, [{
key: 'dispose',
value: function dispose() {
if (this._healthNotifier != null) {
this._healthNotifier.dispose();
}
}
}, {
key: '_monitorConnectionHeartbeat',
value: function _monitorConnectionHeartbeat() {
(0, (_assert2 || _assert()).default)(this._healthNotifier == null);
this._healthNotifier = new (_ConnectionHealthNotifier2 || _ConnectionHealthNotifier()).ConnectionHealthNotifier(this._config.host, this.getSocket());
}
}, {
key: 'getUriOfRemotePath',
value: function getUriOfRemotePath(remotePath) {
return 'nuclide://' + this.getRemoteHostname() + remotePath;
}
}, {
key: 'getPathOfUri',
value: function getPathOfUri(uri) {
return (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.parse(uri).path;
}
}, {
key: 'createDirectory',
value: function createDirectory(uri, hgRepositoryDescription) {
var symlink = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
var _default$parse = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.parse(uri);
var path = _default$parse.path;
path = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.normalize(path);
var entry = this._entries[path];
if (!entry || entry.getLocalPath() !== path || entry.isSymbolicLink() !== symlink) {
this._entries[path] = entry = new (_RemoteDirectory2 || _RemoteDirectory()).RemoteDirectory(this, this.getUriOfRemotePath(path), symlink, { hgRepositoryDescription: hgRepositoryDescription });
this._addHandlersForEntry(entry);
}
(0, (_assert2 || _assert()).default)(entry instanceof (_RemoteDirectory2 || _RemoteDirectory()).RemoteDirectory);
if (!entry.isDirectory()) {
throw new Error('Path is not a directory:' + uri);
}
return entry;
}
}, {
key: 'createFile',
value: function createFile(uri) {
var symlink = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
var _default$parse2 = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.parse(uri);
var path = _default$parse2.path;
path = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.normalize(path);
var entry = this._entries[path];
if (!entry || entry.getLocalPath() !== path || entry.isSymbolicLink() !== symlink) {
this._entries[path] = entry = new (_RemoteFile2 || _RemoteFile()).RemoteFile(this, this.getUriOfRemotePath(path), symlink);
this._addHandlersForEntry(entry);
}
(0, (_assert2 || _assert()).default)(entry instanceof (_RemoteFile2 || _RemoteFile()).RemoteFile);
if (entry.isDirectory()) {
throw new Error('Path is not a file');
}
return entry;
}
}, {
key: '_addHandlersForEntry',
value: function _addHandlersForEntry(entry) {
var _this = this;
// TODO(most): Subscribe to rename events when they're implemented.
var deleteSubscription = entry.onDidDelete(function () {
delete _this._entries[entry.getLocalPath()];
deleteSubscription.dispose();
});
}
}, {
key: 'initialize',
value: _asyncToGenerator(function* () {
this._startRpc();
var client = this.getClient();
var clientVersion = (0, (_nuclideVersion2 || _nuclideVersion()).getVersion)();
function throwVersionMismatch(version) {
var err = new Error('Version mismatch. Client at ' + clientVersion + ' while server at ' + version + '.');
err.name = 'VersionMismatchError';
throw err;
}
// Test connection first. First time we get here we're checking to reestablish
// connection using cached credentials. This will fail fast (faster than infoService)
// when we don't have cached credentials yet.
var heartbeatVersion = yield client.getTransport().testConnection();
if (clientVersion !== heartbeatVersion) {
throwVersionMismatch(heartbeatVersion);
}
// Do another version check over the RPC framework.
var serverVersion = yield this._getInfoService().getServerVersion();
if (clientVersion !== serverVersion) {
throwVersionMismatch(serverVersion);
}
this._monitorConnectionHeartbeat();
ServerConnection._connections.set(this.getRemoteHostname(), this);
(0, (_RemoteConnectionConfigurationManager2 || _RemoteConnectionConfigurationManager()).setConnectionConfig)(this._config);
ServerConnection._emitter.emit('did-add', this);
})
}, {
key: 'close',
value: function close() {
var _this2 = this;
if (this._closed) {
return;
}
// Future getClient calls should fail, if it has a cached ServerConnection instance.
this._closed = true;
Object.keys(this._entries).forEach(function (path) {
_this2._entries[path].dispose();
});
this._entries = {};
// The Rpc channel owns the socket.
if (this._client != null) {
this._client.dispose();
this._client = null;
}
// Remove from _connections to not be considered in future connection queries.
if (ServerConnection._connections.delete(this.getRemoteHostname())) {
ServerConnection._emitter.emit('did-close', this);
}
}
}, {
key: 'getClient',
value: function getClient() {
(0, (_assert2 || _assert()).default)(!this._closed && this._client != null, 'Server connection has been closed.');
return this._client;
}
}, {
key: '_startRpc',
value: function _startRpc() {
var uri = undefined;
var options = undefined;
// Use https if we have key, cert, and ca
if (this._isSecure()) {
(0, (_assert2 || _assert()).default)(this._config.certificateAuthorityCertificate != null);
(0, (_assert2 || _assert()).default)(this._config.clientCertificate != null);
(0, (_assert2 || _assert()).default)(this._config.clientKey != null);
options = {
ca: this._config.certificateAuthorityCertificate,
cert: this._config.clientCertificate,
key: this._config.clientKey
};
uri = 'https://' + this.getRemoteHostname() + ':' + this.getPort();
} else {
options = null;
uri = 'http://' + this.getRemoteHostname() + ':' + this.getPort();
}
var socket = new (_nuclideServerLibNuclideSocket2 || _nuclideServerLibNuclideSocket()).NuclideSocket(uri, options);
var client = (_nuclideRpc2 || _nuclideRpc()).RpcConnection.createRemote(this.getRemoteHostname(), socket, (_nuclideServerLibServicesConfig2 || _nuclideServerLibServicesConfig()).default);
this._client = client;
}
}, {
key: '_isSecure',
value: function _isSecure() {
return Boolean(this._config.certificateAuthorityCertificate && this._config.clientCertificate && this._config.clientKey);
}
}, {
key: 'getPort',
value: function getPort() {
return this._config.port;
}
}, {
key: 'getRemoteHostname',
value: function getRemoteHostname() {
return this._config.host;
}
}, {
key: 'getConfig',
value: function getConfig() {
return this._config;
}
}, {
key: 'addConnection',
value: function addConnection(connection) {
this._connections.push(connection);
}
}, {
key: 'removeConnection',
value: _asyncToGenerator(function* (connection, shutdownIfLast) {
(0, (_assert2 || _assert()).default)(this._connections.indexOf(connection) !== -1, 'Attempt to remove a non-existent RemoteConnection');
this._connections.splice(this._connections.indexOf(connection), 1);
if (this._connections.length === 0) {
// The await here is subtle, it ensures that the shutdown call is sent
// on the socket before the socket is closed on the next line.
yield this._closeServerConnection(shutdownIfLast);
this.close();
}
})
}, {
key: 'getRemoteConnectionForUri',
value: function getRemoteConnectionForUri(uri) {
var _default$parse3 = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.parse(uri);
var path = _default$parse3.path;
return this.getConnections().filter(function (connection) {
return path.startsWith(connection.getPathForInitialWorkingDirectory());
})[0];
}
}, {
key: 'getConnections',
value: function getConnections() {
return this._connections;
}
}, {
key: 'getService',
value: function getService(serviceName) {
return this.getClient().getService(serviceName);
}
}, {
key: 'getSocket',
value: function getSocket() {
return this.getClient().getTransport();
}
}, {
key: '_getInfoService',
value: function _getInfoService() {
return this.getService('InfoService');
}
}, {
key: '_closeServerConnection',
value: _asyncToGenerator(function* (shutdown) {
yield this._getInfoService().closeConnection(shutdown);
if (shutdown) {
// Clear the saved connection config so we don't try it again at startup.
(0, (_RemoteConnectionConfigurationManager2 || _RemoteConnectionConfigurationManager()).clearConnectionConfig)(this._config.host);
}
})
}], [{
key: '_createInsecureConnectionForTesting',
value: _asyncToGenerator(function* (cwd, port) {
var config = {
host: 'localhost',
port: port,
cwd: cwd
};
var connection = new ServerConnection(config);
yield connection.initialize();
return connection;
})
}, {
key: 'onDidAddServerConnection',
value: function onDidAddServerConnection(handler) {
return ServerConnection._emitter.on('did-add', handler);
}
}, {
key: 'onDidCloseServerConnection',
value: function onDidCloseServerConnection(handler) {
return ServerConnection._emitter.on('did-close', handler);
}
}, {
key: 'getForUri',
value: function getForUri(uri) {
var _default$parse4 = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.parse(uri);
var hostname = _default$parse4.hostname;
if (hostname == null) {
return null;
}
return ServerConnection.getByHostname(hostname);
}
}, {
key: 'getByHostname',
value: function getByHostname(hostname) {
return ServerConnection._connections.get(hostname);
}
}]);
return ServerConnection;
})();
module.exports = {
ServerConnection: ServerConnection,
__test__: {
connections: ServerConnection._connections
}
};
// host nuclide server is running on.
// port to connect to.
// certificate of certificate authority.
// client certificate for https connection.