Skip to content
Browse files

Add NodeDomain wrapper and refactor StaticServer to use it.

  • Loading branch information...
1 parent 39bac0b commit 834982186363941db9718b04d360bff684b1cb5c @iwehrman iwehrman committed
View
1 src/brackets.js
@@ -81,6 +81,7 @@ define(function (require, exports, module) {
Resizer = require("utils/Resizer"),
LiveDevelopmentMain = require("LiveDevelopment/main"),
NodeConnection = require("utils/NodeConnection"),
+ NodeDomain = require("utils/NodeDomain"),
ExtensionUtils = require("utils/ExtensionUtils"),
DragAndDrop = require("utils/DragAndDrop"),
ColorUtils = require("utils/ColorUtils"),
View
61 src/extensions/default/StaticServer/StaticServer.js
@@ -43,10 +43,10 @@ define(function (require, exports, module) {
* baseUrl - Optional base URL (populated by the current project)
* pathResolver - Function to covert absolute native paths to project relative paths
* root - Native path to the project root (and base URL)
- * nodeConnection - An active NodeConnection
+ * nodeDomain - An initialized NodeDomain
*/
function StaticServer(config) {
- this._nodeConnection = config.nodeConnection;
+ this._nodeDomain = config.nodeDomain;
this._onRequestFilter = this._onRequestFilter.bind(this);
BaseServer.call(this, config);
@@ -61,7 +61,7 @@ define(function (require, exports, module) {
* @return {boolean} true for yes, otherwise false.
*/
StaticServer.prototype.canServe = function (localPath) {
- if (!this._nodeConnection.connected()) {
+ if (!this._nodeDomain.ready()) {
return false;
}
@@ -87,17 +87,9 @@ define(function (require, exports, module) {
* @return {jQuery.Promise} Resolved by the StaticServer domain when the message is acknowledged.
*/
StaticServer.prototype._updateRequestFilterPaths = function () {
- if (!this._nodeConnection.connected()) {
- return;
- }
-
- var paths = [];
+ var paths = Object.keys(this._liveDocuments);
- Object.keys(this._liveDocuments).forEach(function (path) {
- paths.push(path);
- });
-
- return this._nodeConnection.domains.staticServer.setRequestFilterPaths(this._root, paths);
+ return this._nodeDomain.exec("setRequestFilterPaths", this._root, paths);
};
/**
@@ -109,37 +101,14 @@ define(function (require, exports, module) {
* the server is ready/failed.
*/
StaticServer.prototype.readyToServe = function () {
- var readyToServeDeferred = $.Deferred(),
- self = this;
-
- if (this._nodeConnection.connected()) {
- this._nodeConnection.domains.staticServer.getServer(self._root).done(function (address) {
+ var self = this;
+ return this._nodeDomain.exec("getServer", self._root)
+ .done(function (address) {
self._baseUrl = "http://" + address.address + ":" + address.port + "/";
- readyToServeDeferred.resolve();
- }).fail(function () {
+ })
+ .fail(function () {
self._baseUrl = "";
- readyToServeDeferred.reject();
});
- } else {
- // nodeConnection has been connected once (because the deferred
- // resolved, but is not currently connected).
- //
- // If we are in this case, then the node process has crashed
- // and is in the process of restarting. Once that happens, the
- // node connection will automatically reconnect and reload the
- // domain. Unfortunately, we don't have any promise to wait on
- // to know when that happens. The best we can do is reject this
- // readyToServe so that the user gets an error message to try
- // again later.
- //
- // The user will get the error immediately in this state, and
- // the new node process should start up in a matter of seconds
- // (assuming there isn't a more widespread error). So, asking
- // them to retry in a second is reasonable.
- readyToServeDeferred.reject();
- }
-
- return readyToServeDeferred.promise();
};
/**
@@ -181,9 +150,7 @@ define(function (require, exports, module) {
* Send HTTP response data back to the StaticServerSomain
*/
StaticServer.prototype._send = function (location, response) {
- if (this._nodeConnection.connected()) {
- this._nodeConnection.domains.staticServer.writeFilteredResponse(location.root, location.pathname, response);
- }
+ this._nodeDomain.exec("writeFilteredResponse", location.root, location.pathname, response);
};
/**
@@ -209,15 +176,15 @@ define(function (require, exports, module) {
* See BaseServer#start. Starts listenting to StaticServerDomain events.
*/
StaticServer.prototype.start = function () {
- $(this._nodeConnection).on("staticServer.requestFilter", this._onRequestFilter);
+ $(this._nodeDomain).on("requestFilter", this._onRequestFilter);
};
/**
* See BaseServer#stop. Remove event handlers from StaticServerDomain.
*/
StaticServer.prototype.stop = function () {
- $(this._nodeConnection).off("staticServer.requestFilter", this._onRequestFilter);
+ $(this._nodeDomain).off("requestFilter", this._onRequestFilter);
};
- exports.StaticServer = StaticServer;
+ module.exports = StaticServer;
});
View
71 src/extensions/default/StaticServer/main.js
@@ -34,31 +34,15 @@ define(function (require, exports, module) {
FileUtils = brackets.getModule("file/FileUtils"),
LiveDevServerManager = brackets.getModule("LiveDevelopment/LiveDevServerManager"),
BaseServer = brackets.getModule("LiveDevelopment/Servers/BaseServer").BaseServer,
- NodeConnection = brackets.getModule("utils/NodeConnection"),
+ NodeDomain = brackets.getModule("utils/NodeDomain"),
ProjectManager = brackets.getModule("project/ProjectManager"),
- StaticServer = require("StaticServer").StaticServer;
-
- /**
- * @const
- * Amount of time to wait before automatically rejecting the connection
- * deferred. If we hit this timeout, we'll never have a node connection
- * for the static server in this run of Brackets.
- */
- var NODE_CONNECTION_TIMEOUT = 5000; // 5 seconds
-
- /**
- * @private
- * @type{jQuery.Deferred.<NodeConnection>}
- * A deferred which is resolved with a NodeConnection or rejected if
- * we are unable to connect to Node.
- */
- var _nodeConnectionDeferred = new $.Deferred();
+ StaticServer = require("StaticServer");
/**
* @private
- * @type {NodeConnection}
+ * @type {NodeDomain}
*/
- var _nodeConnection = new NodeConnection();
+ var _nodeDomain;
/**
* @private
@@ -67,61 +51,26 @@ define(function (require, exports, module) {
*/
function _createStaticServer() {
var config = {
- nodeConnection : _nodeConnection,
+ nodeDomain : _nodeDomain,
pathResolver : ProjectManager.makeProjectRelativeIfPossible,
root : ProjectManager.getProjectRoot().fullPath
};
return new StaticServer(config);
}
-
- /**
- * Allows access to the deferred that manages the node connection. This
- * is *only* for unit tests. Messing with this not in testing will
- * potentially break everything.
- *
- * @private
- * @return {jQuery.Deferred} The deferred that manages the node connection
- */
- function _getNodeConnectionDeferred() {
- return _nodeConnectionDeferred;
- }
function initExtension() {
- // Start up the node connection, which is held in the
- // _nodeConnectionDeferred module variable. (Use
- // _nodeConnectionDeferred.done() to access it.
- var connectionTimeout = setTimeout(function () {
- console.error("[StaticServer] Timed out while trying to connect to node");
- _nodeConnectionDeferred.reject();
- }, NODE_CONNECTION_TIMEOUT);
-
- _nodeConnection.connect(true).then(function () {
- _nodeConnection.loadDomains(
- [ExtensionUtils.getModulePath(module, "node/StaticServerDomain")],
- true
- ).then(
- function () {
- clearTimeout(connectionTimeout);
-
- // Register as a Live Development server provider
- LiveDevServerManager.registerServer({ create: _createStaticServer }, 5);
+ var modulePath = ExtensionUtils.getModulePath(module, "node/StaticServerDomain");
+ _nodeDomain = new NodeDomain("staticServer", modulePath);
- _nodeConnectionDeferred.resolveWith(null, [_nodeConnection]);
- },
- function () { // Failed to connect
- console.error("[StaticServer] Failed to connect to node", arguments);
- _nodeConnectionDeferred.reject();
- }
- );
+ return _nodeDomain.promise().then(function () {
+ LiveDevServerManager.registerServer({ create: _createStaticServer }, 5);
+ return _nodeDomain.connection;
});
-
- return _nodeConnectionDeferred.promise();
}
exports.initExtension = initExtension;
// For unit tests only
exports._getStaticServerProvider = _createStaticServer;
- exports._getNodeConnectionDeferred = _getNodeConnectionDeferred;
});
View
8 src/extensions/default/StaticServer/unittests.js
@@ -32,7 +32,7 @@ define(function (require, exports, module) {
NodeConnection = brackets.getModule("utils/NodeConnection"),
FileUtils = brackets.getModule("file/FileUtils"),
SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"),
- StaticServer = require("StaticServer").StaticServer;
+ StaticServer = require("StaticServer");
var testFolder = FileUtils.getNativeModuleDirectoryPath(module) + "/unittest-files";
@@ -517,7 +517,7 @@ define(function (require, exports, module) {
// Unit tests for the StaticServerProvider that wraps the underlying node server.
describe("StaticServer", function () {
var projectPath = testFolder + "/",
- mockNodeConnection = { connected: function () { return true; } },
+ mockNodeDomain = { ready: function () { return true; } },
pathResolver = function (path) {
if (path.indexOf(projectPath) === 0) {
return path.slice(projectPath.length);
@@ -527,7 +527,7 @@ define(function (require, exports, module) {
},
config = {
baseUrl: "http://localhost/",
- nodeConnection: mockNodeConnection,
+ nodeDomain: mockNodeDomain,
pathResolver: pathResolver,
root: projectPath
};
@@ -576,7 +576,7 @@ define(function (require, exports, module) {
it("should decline serving if not connected to node", function () {
// mock NodeConnection state to be disconnected
- config.nodeConnection = { connected: function () { return false; } };
+ config.nodeDomain = { ready: function () { return false; } };
var server = new StaticServer(config);
View
208 src/utils/NodeDomain.js
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+
+/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
+/*global define, $ */
+
+define(function (require, exports, module) {
+ "use strict";
+
+ var NodeConnection = require("utils/NodeConnection");
+
+ // Used to remove all listeners at once when the connection drops
+ var EVENT_NAMESPACE = ".NodeDomainEvent";
+
+ /**
+ * Provides a simple abstraction for executing the commands of a single
+ * domain loaded via a NodeConnection. Automatically handles connection
+ * management and domain loading, and exposes each command in the domain as
+ * a promise-returning method that can safely be called regardless of the
+ * current status of the underlying connection. Example usage:
+ *
+ * var myDomain = new NodeDomain("someDomain", pathToSomeDomainDef);
+ * $result = myDomain.exec("someCommand", arg1, arg2);
+ *
+ * $result.done(function (value) {
+ * // the command succeeded!
+ * });
+ *
+ * $result.fail(function (err) {
+ * // the command failed; act accordingly!
+ * });
+ *
+ * To handle domain events, just listen for the event on the domain:
+ *
+ * $(myDomain).on("someEvent", someHandler);
+ */
+ function NodeDomain(domainName, domainPath) {
+ var connection = new NodeConnection();
+
+ this.connection = connection;
+ this._domainName = domainName;
+ this._domainPath = domainPath;
+ this._domainLoaded = false;
+ this._connectionPromise = connection.connect(true)
+ .then(this._load.bind(this));
+
+ $(connection).on("close", function (event, promise) {
+ $(this.connection).off(EVENT_NAMESPACE);
+ this._domainLoaded = false;
+ this._connectionPromise = promise.then(this._load.bind(this));
+ }.bind(this));
+ }
+
+ /**
+ * The underlying Node connection object for this domain.
+ *
+ * @type {!NodeConnection}
+ */
+ NodeDomain.prototype.connection = null;
+
+ /**
+ * A promise that is resolved once the NodeConnection is connected and the
+ * domain has been loaded.
+ *
+ * @type {?jQuery.Promise}
+ * @private
+ */
+ NodeDomain.prototype._connectionPromise = null;
+
+ /**
+ * The name of this domain.
+ *
+ * @type {string}
+ * @private
+ */
+ NodeDomain.prototype._domainName = null;
+
+ /**
+ * The path at which the Node definition of this domain resides.
+ *
+ * @type {string}
+ * @private
+ */
+ NodeDomain.prototype._domainPath = null;
+
+ /**
+ * Whether or not the domain has been successfully loaded.
+ *
+ * @type {boolean}
+ * @private
+ */
+ NodeDomain.prototype._domainLoaded = false;
+
+ /**
+ * Loads the domain via the underlying connection object and exposes the
+ * domain's commands as methods on this object. Assumes the underlying
+ * connection has already been opened.
+ *
+ * @return {jQuery.Promise} Resolves once the domain is been loaded.
+ * @private
+ */
+ NodeDomain.prototype._load = function () {
+ var connection = this.connection;
+ return connection.loadDomains(this._domainPath, true)
+ .done(function () {
+ this._domainLoaded = true;
+ this._connectionPromise = null;
+
+ var eventNames = Object.keys(connection.domainEvents[this._domainName]);
+ eventNames.forEach(function (domainEvent) {
+ var connectionEvent = this._domainName + "." + domainEvent + EVENT_NAMESPACE;
+
+ $(connection).on(connectionEvent, function () {
+ var params = Array.prototype.slice.call(arguments, 1);
+ $(this).triggerHandler(domainEvent, params);
+ }.bind(this));
+ }, this);
+ }.bind(this));
+ };
+
+ /**
+ * Synchronously determine whether the domain is ready; i.e., whether the
+ * connection is open and the domain is loaded.
+ *
+ * @return {boolean} Whether or not the domain is currently ready.
+ */
+ NodeDomain.prototype.ready = function () {
+ return this._domainLoaded && this.connection.connected();
+ };
+
+ /**
+ * Get a promise that resolves when the connection is open and the domain
+ * is loaded.
+ *
+ * @return {jQuery.Promise}
+ */
+ NodeDomain.prototype.promise = function () {
+ if (this._connectionPromise) {
+ return this._connectionPromise;
+ } else {
+ var deferred = $.Deferred();
+
+ if (this.ready()) {
+ deferred.resolve();
+ } else {
+ deferred.reject();
+ }
+
+ return deferred.promise();
+ }
+ };
+
+ /**
+ * Applies the named command from the domain to a list of parameters, which
+ * are passed as extra arguments to this method. If the connection is open
+ * and the domain is loaded, the function is applied immediately. Otherwise
+ * the function is applied as soon as the connection has been opened and the
+ * domain has finished loading.
+ *
+ * @param {string} name The name of the domain command to execute
+ * @return {jQuery.Promise} Resolves with the result of the command
+ */
+ NodeDomain.prototype.exec = function (name) {
+ var connection = this.connection,
+ params = Array.prototype.slice.call(arguments, 1),
+ execConnected = function () {
+ var domain = connection.domains[this._domainName],
+ fn = domain && domain[name];
+
+ if (fn) {
+ return fn.apply(domain, params);
+ } else {
+ return $.Deferred.reject().promise();
+ }
+ }.bind(this);
+
+ if (this.ready()) {
+ return execConnected();
+ } else if (this._connectionPromise) {
+ return this._connectionPromise.then(execConnected);
+ } else {
+ return $.Deferred.reject().promise();
+ }
+ };
+
+ module.exports = NodeDomain;
+});
View
1 test/SpecRunner.js
@@ -55,6 +55,7 @@ define(function (require, exports, module) {
UrlParams = require("utils/UrlParams").UrlParams,
UnitTestReporter = require("test/UnitTestReporter").UnitTestReporter,
NodeConnection = require("utils/NodeConnection"),
+ NodeDomain = require("utils/NodeDomain"),
BootstrapReporterView = require("test/BootstrapReporterView").BootstrapReporterView,
ColorUtils = require("utils/ColorUtils"),
NativeApp = require("utils/NativeApp");

1 comment on commit 8349821

@timburgess

Clearly a ninja at work here - very nice.

Please sign in to comment.
Something went wrong with that request. Please try again.