UNPKG

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.

765 lines (676 loc) 27.6 kB
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 _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 _rxjsBundlesRxUmdMinJs2; function _rxjsBundlesRxUmdMinJs() { return _rxjsBundlesRxUmdMinJs2 = require('rxjs/bundles/Rx.umd.min.js'); } var _ServiceRegistry2; function _ServiceRegistry() { return _ServiceRegistry2 = require('./ServiceRegistry'); } var _ObjectRegistry2; function _ObjectRegistry() { return _ObjectRegistry2 = require('./ObjectRegistry'); } var _messages2; function _messages() { return _messages2 = require('./messages'); } var _builtinTypes2; function _builtinTypes() { return _builtinTypes2 = require('./builtin-types'); } var _nuclideAnalytics2; function _nuclideAnalytics() { return _nuclideAnalytics2 = require('../../nuclide-analytics'); } var _nuclideLogging2; function _nuclideLogging() { return _nuclideLogging2 = require('../../nuclide-logging'); } var logger = (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)(); var SERVICE_FRAMEWORK_RPC_TIMEOUT_MS = 60 * 1000; var Subscription = (function () { function Subscription(message, observer) { _classCallCheck(this, Subscription); this._message = message; this._observer = observer; } _createClass(Subscription, [{ key: 'error', value: (function (_error) { function error(_x) { return _error.apply(this, arguments); } error.toString = function () { return _error.toString(); }; return error; })(function (error) { try { this._observer.error((0, (_messages2 || _messages()).decodeError)(this._message, error)); } catch (e) { logger.error('Caught exception in Subscription.error: ' + e.toString()); } }) }, { key: 'next', value: function next(data) { try { this._observer.next(data); } catch (e) { logger.error('Caught exception in Subscription.next: ' + e.toString()); } } }, { key: 'complete', value: function complete() { try { this._observer.complete(); } catch (e) { logger.error('Caught exception in Subscription.complete: ' + e.toString()); } } }]); return Subscription; })(); var Call = (function () { function Call(message, timeoutMessage, resolve, reject, cleanup) { var _this = this; _classCallCheck(this, Call); this._message = message; this._timeoutMessage = timeoutMessage; this._resolve = resolve; this._reject = reject; this._cleanup = cleanup; this._complete = false; this._timerId = setTimeout(function () { _this._timeout(); }, SERVICE_FRAMEWORK_RPC_TIMEOUT_MS); } _createClass(Call, [{ key: 'reject', value: function reject(error) { if (!this._complete) { this.cleanup(); this._reject((0, (_messages2 || _messages()).decodeError)(this._message, error)); } } }, { key: 'resolve', value: function resolve(result) { if (!this._complete) { this.cleanup(); this._resolve(result); } } }, { key: 'cleanup', value: function cleanup() { if (!this._complete) { this._complete = true; clearTimeout(this._timerId); this._timerId = null; this._cleanup(); } } }, { key: '_timeout', value: function _timeout() { if (!this._complete) { this.cleanup(); this._reject(new Error('Timeout after ' + SERVICE_FRAMEWORK_RPC_TIMEOUT_MS + ' for id: ' + (this._message.id + ', ' + this._timeoutMessage + '.'))); } } }]); return Call; })(); var RpcConnection = (function () { // Do not call this directly, use factory methods below. function RpcConnection(kind, serviceRegistry, transport) { var _this2 = this; _classCallCheck(this, RpcConnection); this._transport = transport; this._rpcRequestId = 1; this._serviceRegistry = serviceRegistry; this._objectRegistry = new (_ObjectRegistry2 || _ObjectRegistry()).ObjectRegistry(kind, this._serviceRegistry, this); this._transport.onMessage().subscribe(function (message) { _this2._handleMessage(message); }); this._subscriptions = new Map(); this._calls = new Map(); } // Creates a connection on the server side. _createClass(RpcConnection, [{ key: 'getService', value: function getService(serviceName) { var service = this._objectRegistry.getService(serviceName); (0, (_assert2 || _assert()).default)(service != null, 'No config found for service ' + serviceName); return service; } }, { key: 'addServices', value: function addServices(services) { services.forEach(this.addService, this); } }, { key: 'addService', value: function addService(service) { this._serviceRegistry.addService(service); } // Delegate marshalling to the type registry. }, { key: 'marshal', value: function marshal(value, type) { return this._getTypeRegistry().marshal(this._objectRegistry, value, type); } }, { key: 'unmarshal', value: function unmarshal(value, type) { return this._getTypeRegistry().unmarshal(this._objectRegistry, value, type); } }, { key: 'marshalArguments', value: function marshalArguments(args, argTypes) { return this._getTypeRegistry().marshalArguments(this._objectRegistry, args, argTypes); } }, { key: 'unmarshalArguments', value: function unmarshalArguments(args, argTypes) { return this._getTypeRegistry().unmarshalArguments(this._objectRegistry, args, argTypes); } /** * Call a remote function, through the service framework. * @param functionName - The name of the remote function to invoke. * @param returnType - The type of object that this function returns, so the the transport * layer can register the appropriate listeners. * @param args - The serialized arguments to invoke the remote function with. */ }, { key: 'callRemoteFunction', value: function callRemoteFunction(functionName, returnType, args) { return this._sendMessageAndListenForResult((0, (_messages2 || _messages()).createCallMessage)(functionName, this._generateRequestId(), args), returnType, 'Calling function ' + functionName); } /** * Call a method of a remote object, through the service framework. * @param objectId - The id of the remote object. * @param methodName - The name of the method to invoke. * @param returnType - The type of object that this function returns, so the the transport * layer can register the appropriate listeners. * @param args - The serialized arguments to invoke the remote method with. */ }, { key: 'callRemoteMethod', value: function callRemoteMethod(objectId, methodName, returnType, args) { return this._sendMessageAndListenForResult((0, (_messages2 || _messages()).createCallObjectMessage)(methodName, objectId, this._generateRequestId(), args), returnType, 'Calling remote method ' + methodName + '.'); } /** * Call a remote constructor, returning an id that eventually resolves to a unique identifier * for the object. * @param interfaceName - The name of the remote class for which to construct an object. * @param thisArg - The newly created proxy object. * @param unmarshalledArgs - Unmarshalled arguments to pass to the remote constructor. * @param argTypes - Types of arguments. */ }, { key: 'createRemoteObject', value: function createRemoteObject(interfaceName, thisArg, unmarshalledArgs, argTypes) { var _this3 = this; var idPromise = _asyncToGenerator(function* () { var marshalledArgs = yield _this3._getTypeRegistry().marshalArguments(_this3._objectRegistry, unmarshalledArgs, argTypes); return _this3._sendMessageAndListenForResult((0, (_messages2 || _messages()).createNewObjectMessage)(interfaceName, _this3._generateRequestId(), marshalledArgs), 'promise', 'Creating instance of ' + interfaceName); })(); this._objectRegistry.addProxy(thisArg, idPromise); } /** * Dispose a remote object. This makes it's proxies unsuable, and calls the `dispose` method on * the remote object. * @param object - The remote object. * @returns A Promise that resolves when the object disposal has completed. */ }, { key: 'disposeRemoteObject', value: _asyncToGenerator(function* (object) { var objectId = yield this._objectRegistry.disposeProxy(object); if (objectId != null) { return yield this._sendMessageAndListenForResult((0, (_messages2 || _messages()).createDisposeMessage)(this._generateRequestId(), objectId), 'promise', 'Disposing object ' + objectId); } else { logger.info('Duplicate dispose call on remote proxy'); } }) /** * Helper function that listens for a result for the given id. * @param returnType - Determines the type of messages we should subscribe to, and what this * function should return. * @param id - The id of the request who's result we are listening for. * @returns Depending on the expected return type, this function either returns undefined, a * Promise, or an Observable. */ }, { key: '_sendMessageAndListenForResult', value: function _sendMessageAndListenForResult(message, returnType, timeoutMessage) { var _this4 = this; switch (returnType) { case 'void': this._transport.send(JSON.stringify(message)); return; // No values to return. case 'promise': // Listen for a single message, and resolve or reject a promise on that message. return new Promise(function (resolve, reject) { _this4._transport.send(JSON.stringify(message)); _this4._calls.set(message.id, new Call(message, timeoutMessage, resolve, reject, function () { _this4._calls.delete(message.id); })); }); case 'observable': { var _ret = (function () { var id = message.id; (0, (_assert2 || _assert()).default)(!_this4._subscriptions.has(id)); var sendSubscribe = function sendSubscribe() { _this4._transport.send(JSON.stringify(message)); }; var sendUnsubscribe = function sendUnsubscribe() { _this4._transport.send(JSON.stringify((0, (_messages2 || _messages()).createUnsubscribeMessage)(id))); }; var hadSubscription = false; var observable = (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.create(function (observer) { // Only allow a single subscription. This will be the common case, // and adding this restriction allows disposing of the observable // on the remote side after the initial subscription is complete. if (hadSubscription) { throw new Error('Attempt to re-connect with a remote Observable.'); } hadSubscription = true; var subscription = new Subscription(message, observer); _this4._subscriptions.set(id, subscription); sendSubscribe(); // Observable dispose function, which is called on subscription dispose, on stream // completion, and on stream error. return { unsubscribe: function unsubscribe() { if (!_this4._subscriptions.has(id)) { // guard against multiple unsubscribe calls return; } _this4._subscriptions.delete(id); sendUnsubscribe(); } }; }); // Conversion to ConnectableObservable happens in the generated // proxies. return { v: observable }; })(); if (typeof _ret === 'object') return _ret.v; } default: throw new Error('Unkown return type: ' + returnType + '.'); } } }, { key: '_returnPromise', value: function _returnPromise(id, timingTracker, candidate, type) { var _this5 = this; var returnVal = candidate; // Ensure that the return value is a promise. if (!isThenable(returnVal)) { returnVal = Promise.reject(new Error('Expected a Promise, but the function returned something else.')); } // Marshal the result, to send over the network. (0, (_assert2 || _assert()).default)(returnVal != null); returnVal = returnVal.then(function (value) { return _this5._getTypeRegistry().marshal(_this5._objectRegistry, value, type); }); // Send the result of the promise across the socket. returnVal.then(function (result) { _this5._transport.send(JSON.stringify((0, (_messages2 || _messages()).createPromiseMessage)(id, result))); timingTracker.onSuccess(); }, function (error) { _this5._transport.send(JSON.stringify((0, (_messages2 || _messages()).createErrorResponseMessage)(id, error))); timingTracker.onError(error == null ? new Error() : error); }); } }, { key: '_returnObservable', value: function _returnObservable(id, returnVal, elementType) { var _this6 = this; var result = undefined; // Ensure that the return value is an observable. if (!isConnectableObservable(returnVal)) { result = (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.throw(new Error('Expected an Observable, but the function returned something else.')).publish(); } else { result = returnVal; } // Marshal the result, to send over the network. result.concatMap(function (value) { return _this6._getTypeRegistry().marshal(_this6._objectRegistry, value, elementType); }) // Send the next, error, and completion events of the observable across the socket. .subscribe(function (data) { _this6._transport.send(JSON.stringify((0, (_messages2 || _messages()).createNextMessage)(id, data))); }, function (error) { _this6._transport.send(JSON.stringify((0, (_messages2 || _messages()).createObserveErrorMessage)(id, error))); _this6._objectRegistry.removeSubscription(id); }, function (completed) { _this6._transport.send(JSON.stringify((0, (_messages2 || _messages()).createCompleteMessage)(id))); _this6._objectRegistry.removeSubscription(id); }); this._objectRegistry.addSubscription(id, result.connect()); } // Returns true if a promise was returned. }, { key: '_returnValue', value: function _returnValue(id, timingTracker, value, type) { switch (type.kind) { case 'void': break; // No need to send anything back to the user. case 'promise': this._returnPromise(id, timingTracker, value, type.type); return true; case 'observable': this._returnObservable(id, value, type.type); break; default: throw new Error('Unkown return type ' + type.kind + '.'); } return false; } }, { key: '_callFunction', value: _asyncToGenerator(function* (id, timingTracker, call) { var _getFunctionImplemention2 = this._getFunctionImplemention(call.method); var localImplementation = _getFunctionImplemention2.localImplementation; var type = _getFunctionImplemention2.type; var marshalledArgs = yield this._getTypeRegistry().unmarshalArguments(this._objectRegistry, call.args, type.argumentTypes); return this._returnValue(id, timingTracker, localImplementation.apply(this, marshalledArgs), type.returnType); }) }, { key: '_callMethod', value: _asyncToGenerator(function* (id, timingTracker, call) { var object = this._objectRegistry.unmarshal(call.objectId); (0, (_assert2 || _assert()).default)(object != null); var interfaceName = this._objectRegistry.getInterface(call.objectId); var classDefinition = this._getClassDefinition(interfaceName); (0, (_assert2 || _assert()).default)(classDefinition != null); var type = classDefinition.definition.instanceMethods.get(call.method); (0, (_assert2 || _assert()).default)(type != null); var marshalledArgs = yield this._getTypeRegistry().unmarshalArguments(this._objectRegistry, call.args, type.argumentTypes); return this._returnValue(id, timingTracker, object[call.method].apply(object, marshalledArgs), type.returnType); }) }, { key: '_callConstructor', value: _asyncToGenerator(function* (id, timingTracker, constructorMessage) { var classDefinition = this._getClassDefinition(constructorMessage.interface); (0, (_assert2 || _assert()).default)(classDefinition != null); var localImplementation = classDefinition.localImplementation; var definition = classDefinition.definition; var constructorArgs = definition.constructorArgs; (0, (_assert2 || _assert()).default)(constructorArgs != null); var marshalledArgs = yield this._getTypeRegistry().unmarshalArguments(this._objectRegistry, constructorMessage.args, constructorArgs); // Create a new object and put it in the registry. var newObject = construct(localImplementation, marshalledArgs); // Return the object, which will automatically be converted to an id through the // marshalling system. this._returnPromise(id, timingTracker, Promise.resolve(newObject), { kind: 'named', name: constructorMessage.interface, location: (_builtinTypes2 || _builtinTypes()).builtinLocation }); }) }, { key: 'getTransport', value: function getTransport() { return this._transport; } }, { key: '_parseMessage', value: function _parseMessage(value) { try { return JSON.parse(value); } catch (e) { logger.error('Recieved invalid JSON message: \'' + value + '\''); return null; } } }, { key: '_handleMessage', value: function _handleMessage(value) { var message = this._parseMessage(value); if (message == null) { return; } // TODO: advinsky uncomment after version 0.136 and below are phased out // invariant(message.protocol === SERVICE_FRAMEWORK3_PROTOCOL); switch (message.type) { case 'response': case 'error-response': case 'next': case 'complete': case 'error': this._handleResponseMessage(message); break; case 'call': case 'call-object': case 'new': case 'dispose': case 'unsubscribe': this._handleRequestMessage(message); break; default: throw new Error('Unexpected message type'); } } }, { key: '_handleResponseMessage', value: function _handleResponseMessage(message) { var id = message.id; switch (message.type) { case 'response': { var call = this._calls.get(id); if (call != null) { var _result = message.result; call.resolve(_result); } break; } case 'error-response': { var call = this._calls.get(id); if (call != null) { var _error2 = message.error; call.reject(_error2); } break; } case 'next': { var subscription = this._subscriptions.get(id); if (subscription != null) { var value = message.value; subscription.next(value); } break; } case 'complete': { var subscription = this._subscriptions.get(id); if (subscription != null) { subscription.complete(); this._subscriptions.delete(id); } break; } case 'error': { var subscription = this._subscriptions.get(id); if (subscription != null) { var _error3 = message.error; subscription.error(_error3); this._subscriptions.delete(id); } break; } default: throw new Error('Unexpected message type ' + JSON.stringify(message)); } } }, { key: '_handleRequestMessage', value: _asyncToGenerator(function* (message) { var id = message.id; // Track timings of all function calls, method calls, and object creations. // Note: for Observables we only track how long it takes to create the initial Observable. // while for Promises we track the length of time it takes to resolve or reject. // For returning void, we track the time for the call to complete. var timingTracker = (0, (_nuclideAnalytics2 || _nuclideAnalytics()).startTracking)(trackingIdOfMessage(this._objectRegistry, message)); // Here's the main message handler ... try { var returnedPromise = false; switch (message.type) { case 'call': returnedPromise = yield this._callFunction(id, timingTracker, message); break; case 'call-object': returnedPromise = yield this._callMethod(id, timingTracker, message); break; case 'new': yield this._callConstructor(id, timingTracker, message); returnedPromise = true; break; case 'dispose': yield this._objectRegistry.disposeObject(message.objectId); this._returnPromise(id, timingTracker, Promise.resolve(), (_builtinTypes2 || _builtinTypes()).voidType); returnedPromise = true; break; case 'unsubscribe': this._objectRegistry.disposeSubscription(id); break; default: throw new Error('Unkown message type ' + message.type); } if (!returnedPromise) { timingTracker.onSuccess(); } } catch (e) { logger.error(e != null ? e.message : e); timingTracker.onError(e == null ? new Error() : e); this._transport.send(JSON.stringify((0, (_messages2 || _messages()).createErrorResponseMessage)(id, e))); } }) }, { key: '_getFunctionImplemention', value: function _getFunctionImplemention(name) { return this._serviceRegistry.getFunctionImplemention(name); } }, { key: '_getClassDefinition', value: function _getClassDefinition(className) { return this._serviceRegistry.getClassDefinition(className); } }, { key: '_generateRequestId', value: function _generateRequestId() { return this._rpcRequestId++; } }, { key: '_getTypeRegistry', value: function _getTypeRegistry() { return this._serviceRegistry.getTypeRegistry(); } }, { key: 'dispose', value: function dispose() { this._transport.close(); this._objectRegistry.dispose(); this._calls.forEach(function (call) { call.reject(new Error('Connection Closed')); }); this._subscriptions.forEach(function (subscription) { subscription.error(new Error('Connection Closed')); }); this._subscriptions.clear(); } }], [{ key: 'createServer', value: function createServer(serviceRegistry, transport) { return new RpcConnection('server', serviceRegistry, transport); } // Creates a client side connection to a server on another machine. }, { key: 'createRemote', value: function createRemote(hostname, transport, services) { return new RpcConnection('client', (_ServiceRegistry2 || _ServiceRegistry()).ServiceRegistry.createRemote(hostname, services), transport); } // Creates a client side connection to a server on the same machine. }, { key: 'createLocal', value: function createLocal(transport, services) { return new RpcConnection('client', (_ServiceRegistry2 || _ServiceRegistry()).ServiceRegistry.createLocal(services), transport); } }]); return RpcConnection; })(); exports.RpcConnection = RpcConnection; function trackingIdOfMessage(registry, message) { switch (message.type) { case 'call': return 'service-framework:' + message.method; case 'call-object': var callInterface = registry.getInterface(message.objectId); return 'service-framework:' + callInterface + '.' + message.method; case 'new': return 'service-framework:new:' + message.interface; case 'dispose': var interfaceName = registry.getInterface(message.objectId); return 'service-framework:dispose:' + interfaceName; case 'unsubscribe': return 'service-framework:disposeObservable'; default: throw new Error('Unknown message type ' + message.type); } } /** * A helper function that let's us 'apply' an array of arguments to a constructor. * It works by creating a new constructor that has the same prototype as the original * constructor, and simply applies the original constructor directly to 'this'. * @returns An instance of classObject. */ function construct(classObject, args) { function F() { return classObject.apply(this, args); } F.prototype = classObject.prototype; return new F(); } /** * A helper function that checks if an object is thenable (Promise-like). */ function isThenable(object) { return Boolean(object && object.then); } /** * A helper function that checks if an object is an Observable. */ function isConnectableObservable(object) { return Boolean(object && object.concatMap && object.subscribe && object.connect); }