diff --git a/.brackets.json b/.brackets.json new file mode 100644 index 000000000..43b6f6922 --- /dev/null +++ b/.brackets.json @@ -0,0 +1,23 @@ +{ + "jslint.options": { + "vars": true, + "plusplus": true, + "node": true, + "browser": false, + "devel": true, + "nomen": true, + "indent": 4, + "maxerr": 50, + "regexp": true, + "es5": true + }, + "defaultExtension": "js", + "language": { + "javascript": { + "linting.prefer": ["ESLint", "JSLint"], + "linting.usePreferredOnly": true + } + }, + "spaceUnits": 4, + "useTabChar": false +} diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..2c8fa919f --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +# Third party modules +**/thirdparty/** diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..508174566 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,46 @@ +{ + "env": { + "node": true + }, + "rules": { + "no-bitwise": 2, + "curly": 2, + "eqeqeq": 2, + "guard-for-in": 2, + "wrap-iife": [2, "outside"], + "no-use-before-define": 2, + "new-cap": 2, + "no-caller": 2, + "no-empty": 2, + "no-new": 2, + "no-invalid-regexp": 2, + "no-control-regex": 2, + "no-regex-spaces": 2, + "no-undef": 2, + "strict": 2, + "no-unused-vars": [2, {"vars": "all", "args": "none"}], + "semi": 2, + + "no-iterator": 2, + "no-loop-func": 2, + "no-multi-str": 2, + "no-fallthrough": 2, + "no-proto": 2, + "no-script-url": 2, + "no-shadow": 0, + "no-shadow-restricted-names": 2, + "no-new-func": 2, + "no-new-wrappers": 2, + "no-new-require": 2, + "new-parens": 2, + "no-new-object": 2, + "no-invalid-this": 2, + "indent": [2, 4], + + "valid-jsdoc": 0, + "valid-typeof": 2, + + "no-trailing-spaces": [2, { "skipBlankLines": true }], + "eol-last": 2 + } +} diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 33c869c08..000000000 --- a/.jshintrc +++ /dev/null @@ -1,75 +0,0 @@ -{ - "bitwise" : true, - "curly" : true, - "eqeqeq" : true, - "forin" : true, - "immed" : true, - "latedef" : true, - "newcap" : true, - "noarg" : true, - "noempty" : true, - "nonew" : true, - "plusplus" : true, - "regexp" : true, - "undef" : true, - "strict" : true, - "trailing" : false, - - "asi" : false, - "boss" : false, - "debug" : false, - "eqnull" : false, - "es5" : false, - "esnext" : false, - "evil" : false, - "expr" : false, - "funcscope" : false, - "globalstrict" : false, - "iterator" : false, - "lastsemic" : false, - "laxbreak" : false, - "laxcomma" : false, - "loopfunc" : false, - "multistr" : false, - "onecase" : false, - "proto" : false, - "regexdash" : false, - "scripturl" : false, - "smarttabs" : false, - "shadow" : false, - "sub" : false, - "supernew" : false, - "validthis" : false, - - "browser" : true, - "couch" : false, - "devel" : false, - "dojo" : false, - "jquery" : false, - "mootools" : false, - "node" : false, - "nonstandard" : false, - "prototypejs" : false, - "rhino" : false, - "wsh" : false, - - "nomen" : false, - "onevar" : false, - "passfail" : false, - "white" : false, - - "maxerr" : 100, - "predef" : [ - ], - "indent" : 4, - "globals" : [ - "require", - "define", - "brackets", - "$", - "PathUtils", - "window", - "navigator", - "Mustache" - ] -} \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index 55aca48d0..1cef87045 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -20,13 +20,11 @@ * DEALINGS IN THE SOFTWARE. * */ -/*jslint regexp:true*/ -/*global module, require, process*/ -module.exports = function (grunt) { - "use strict"; +"use strict"; + +module.exports = function (grunt) { var common = require("./tasks/common")(grunt), - resolve = common.resolve, platform = common.platform(), staging; @@ -205,10 +203,14 @@ module.exports = function (grunt) { "dest" : "deps/cef" } }, - "jshint": { - "all" : ["Gruntfile.js", "tasks/**/*.js"], + "eslint": { + "all" : [ + "Gruntfile.js", + "tasks/**/*.js", + "appshell/node-core/*.js" + ], "options": { - "jshintrc" : ".jshintrc" + "quiet" : true } }, "build": { @@ -238,7 +240,7 @@ module.exports = function (grunt) { }); grunt.loadTasks("tasks"); - grunt.loadNpmTasks("grunt-contrib-jshint"); + grunt.loadNpmTasks("grunt-eslint"); grunt.loadNpmTasks("grunt-contrib-copy"); grunt.loadNpmTasks("grunt-contrib-clean"); grunt.loadNpmTasks("grunt-curl"); diff --git a/appshell/appshell_extensions.js b/appshell/appshell_extensions.js index 244e028a3..7e8db28d0 100644 --- a/appshell/appshell_extensions.js +++ b/appshell/appshell_extensions.js @@ -27,9 +27,6 @@ // Note: All file native file i/o functions are synchronous, but are exposed // here as asynchronous calls. -/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */ -/*global define, native */ - var appshell; if (!appshell) { appshell = {}; diff --git a/appshell/node-core/BaseDomain.js b/appshell/node-core/BaseDomain.js index 9a19373c5..a3e0b9760 100644 --- a/appshell/node-core/BaseDomain.js +++ b/appshell/node-core/BaseDomain.js @@ -1,141 +1,134 @@ /* * 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 + * 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, + * 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 + * 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, indent: 4, - maxerr: 50, node: true */ -/*global */ +"use strict"; -(function () { - "use strict"; - - var Launcher = require("./Launcher"), - Logger = require("./Logger"); - - /** - * @private - * @type {DomainManager} - * DomainManager provided at initialization time - */ - var _domainManager = null; - - /** - * @private - * Implementation of base.enableDebugger commnad. - * In the future, process._debugProcess may go away. In that case - * we will probably have to implement re-launching of the Node process - * with the --debug command line switch. - */ - function cmdEnableDebugger() { - // Unfortunately, there's no indication of whether this succeeded - // This is the case for _all_ of the methods for enabling the debugger. - process._debugProcess(process.pid); - } - - /** - * @private - * Implementation of base.restartNode command. - */ - function cmdRestartNode() { - Launcher.exit(); - } - - /** - * @private - * Implementation of base.loadDomainModulesFromPaths - * @param {Array.} paths Paths to load - * @return {boolean} Whether the load succeeded - */ - function cmdLoadDomainModulesFromPaths(paths) { - if (_domainManager) { - var success = _domainManager.loadDomainModulesFromPaths(paths); - if (success) { - _domainManager.emitEvent("base", "newDomains"); - } - return success; - } else { - return false; +var Launcher = require("./Launcher"), + Logger = require("./Logger"); + +/** + * @private + * @type {DomainManager} + * DomainManager provided at initialization time + */ +var _domainManager = null; + +/** + * @private + * Implementation of base.enableDebugger commnad. + * In the future, process._debugProcess may go away. In that case + * we will probably have to implement re-launching of the Node process + * with the --debug command line switch. + */ +function cmdEnableDebugger() { + // Unfortunately, there's no indication of whether this succeeded + // This is the case for _all_ of the methods for enabling the debugger. + process._debugProcess(process.pid); +} + +/** + * @private + * Implementation of base.restartNode command. + */ +function cmdRestartNode() { + Launcher.exit(); +} + +/** + * @private + * Implementation of base.loadDomainModulesFromPaths + * @param {Array.} paths Paths to load + * @return {boolean} Whether the load succeeded + */ +function cmdLoadDomainModulesFromPaths(paths) { + if (_domainManager) { + var success = _domainManager.loadDomainModulesFromPaths(paths); + if (success) { + _domainManager.emitEvent("base", "newDomains"); } + return success; + } else { + return false; } - - /** - * - * Registers commands with the DomainManager - * @param {DomainManager} domainManager The DomainManager to use - */ - function init(domainManager) { - _domainManager = domainManager; - - _domainManager.registerDomain("base", {major: 0, minor: 1}); - _domainManager.registerCommand( - "base", - "enableDebugger", - cmdEnableDebugger, - false, - "Attempt to enable the debugger", - [], // no parameters - [] // no return type - ); - _domainManager.registerCommand( - "base", - "restartNode", - cmdRestartNode, - false, - "Attempt to restart the Node server", - [], // no parameters - [] // no return type - ); - _domainManager.registerCommand( - "base", - "loadDomainModulesFromPaths", - cmdLoadDomainModulesFromPaths, - false, - "Attempt to load command modules from the given paths. " + - "The paths should be absolute.", - [{name: "paths", type: "array"}], - [{name: "success", type: "boolean"}] - ); +} - _domainManager.registerEvent( - "base", - "log", - [{name: "level", type: "string"}, - {name: "timestamp", type: "Date"}, - {name: "message", type: "string"}] - ); - Logger.on( - "log", - function (level, timestamp, message) { - _domainManager.emitEvent( - "base", - "log", - [level, timestamp, message] - ); - } - ); - - _domainManager.registerEvent("base", "newDomains", []); - } - - exports.init = init; - -}()); +/** + * + * Registers commands with the DomainManager + * @param {DomainManager} domainManager The DomainManager to use + */ +function init(domainManager) { + _domainManager = domainManager; + + _domainManager.registerDomain("base", {major: 0, minor: 1}); + _domainManager.registerCommand( + "base", + "enableDebugger", + cmdEnableDebugger, + false, + "Attempt to enable the debugger", + [], // no parameters + [] // no return type + ); + _domainManager.registerCommand( + "base", + "restartNode", + cmdRestartNode, + false, + "Attempt to restart the Node server", + [], // no parameters + [] // no return type + ); + _domainManager.registerCommand( + "base", + "loadDomainModulesFromPaths", + cmdLoadDomainModulesFromPaths, + false, + "Attempt to load command modules from the given paths. " + + "The paths should be absolute.", + [{name: "paths", type: "array"}], + [{name: "success", type: "boolean"}] + ); + + _domainManager.registerEvent( + "base", + "log", + [{name: "level", type: "string"}, + {name: "timestamp", type: "Date"}, + {name: "message", type: "string"}] + ); + Logger.on( + "log", + function (level, timestamp, message) { + _domainManager.emitEvent( + "base", + "log", + [level, timestamp, message] + ); + } + ); + + _domainManager.registerEvent("base", "newDomains", []); +} + +exports.init = init; diff --git a/appshell/node-core/ConnectionManager.js b/appshell/node-core/ConnectionManager.js index e2c3e8e33..35b0081f3 100644 --- a/appshell/node-core/ConnectionManager.js +++ b/appshell/node-core/ConnectionManager.js @@ -1,250 +1,248 @@ /* * 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 + * 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, + * 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 + * 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, indent: 4, -maxerr: 50, node: true */ -/*global */ - -(function () { - "use strict"; - - var DomainManager = require("./DomainManager"); - - /** - * @private - * @type{Array.} - * Currently active connections - */ - var _connections = []; - - /** - * @private - * @constructor - * A WebSocket connection to a client. This is a private constructor. - * Callers should use the ConnectionManager.createConnection function - * instead. - * @param {WebSocket} ws The WebSocket representing the client - */ - function Connection(ws) { - this._ws = ws; - this._connected = true; - this._ws.on("message", this._receive.bind(this)); - this._ws.on("close", this.close.bind(this)); - } - - /** - * @private - * @type {boolean} - * Whether the connection is connected. - */ - Connection.prototype._connected = false; - - /** - * @private - * @type {WebSocket} - * The connection's WebSocket - */ - Connection.prototype._ws = null; - - /** - * @private - * Sends a message over the WebSocket. Called by public sendX commands. - * @param {string} type Message type. Currently supported types are - "event", "commandResponse", "commandError", "error" - * @param {object} message Message body, must be JSON.stringify-able - */ - Connection.prototype._send = function (type, message) { - if (this._ws && this._connected) { - try { - this._ws.send(JSON.stringify({type: type, message: message})); - } catch (e) { - console.error("[Connection] Unable to stringify message: " + e.message); - } - } - }; - - /** - * @private - * Sends a binary message over the WebSocket. Implicitly interpreted as a - * message of type "commandResponse". - * @param {Buffer} message - */ - Connection.prototype._sendBinary = function (message) { - if (this._ws && this._connected) { - this._ws.send(message, {binary: true, mask: false}); - } - }; + * + */ + +"use strict"; + +var DomainManager = require("./DomainManager"); - /** - * @private - * Receive event handler for the WebSocket. Responsible for parsing - * message and handing it off to the appropriate handler. - * @param {string} message Message received by WebSocket - */ - Connection.prototype._receive = function (message) { - var m; +/** + * @private + * @type{Array.} + * Currently active connections + */ +var _connections = []; + +/** + * @private + * @constructor + * A WebSocket connection to a client. This is a private constructor. + * Callers should use the ConnectionManager.createConnection function + * instead. + * @param {WebSocket} ws The WebSocket representing the client + */ +function Connection(ws) { + this._ws = ws; + this._connected = true; + this._ws.on("message", this._receive.bind(this)); + this._ws.on("close", this.close.bind(this)); +} + +/** + * @private + * @type {boolean} + * Whether the connection is connected. + */ +Connection.prototype._connected = false; + +/** + * @private + * @type {WebSocket} + * The connection's WebSocket + */ +Connection.prototype._ws = null; + +/** + * @private + * Sends a message over the WebSocket. Called by public sendX commands. + * @param {string} type Message type. Currently supported types are + "event", "commandResponse", "commandError", "error" + * @param {object} message Message body, must be JSON.stringify-able + */ +Connection.prototype._send = function (type, message) { + if (this._ws && this._connected) { try { - m = JSON.parse(message); - } catch (parseError) { - this.sendError("Unable to parse message: " + message); - return; + this._ws.send(JSON.stringify({type: type, message: message})); + } catch (e) { + console.error("[Connection] Unable to stringify message: " + e.message); } - - if (m.id !== null && m.id !== undefined - && m.domain && m.command) { - // okay if m.parameters is null/undefined - try { - DomainManager.executeCommand(this, m.id, m.domain, - m.command, m.parameters); - } catch (executionError) { - this.sendCommandError(m.id, executionError.message, - executionError.stack); - } - } else { - this.sendError("Malformed message: " + message); - } - }; + } +}; + +/** + * @private + * Sends a binary message over the WebSocket. Implicitly interpreted as a + * message of type "commandResponse". + * @param {Buffer} message + */ +Connection.prototype._sendBinary = function (message) { + if (this._ws && this._connected) { + this._ws.send(message, {binary: true, mask: false}); + } +}; + +/** + * @private + * Receive event handler for the WebSocket. Responsible for parsing + * message and handing it off to the appropriate handler. + * @param {string} message Message received by WebSocket + */ +Connection.prototype._receive = function (message) { + var m; + try { + m = JSON.parse(message); + } catch (parseError) { + this.sendError("Unable to parse message: " + message); + return; + } - /** - * Closes the connection and does necessary cleanup - */ - Connection.prototype.close = function () { - if (this._ws) { - try { - this._ws.close(); - } catch (e) { } + if (m.id !== null && m.id !== undefined + && m.domain && m.command) { + // okay if m.parameters is null/undefined + try { + DomainManager.executeCommand(this, m.id, m.domain, + m.command, m.parameters); + } catch (executionError) { + this.sendCommandError(m.id, executionError.message, + executionError.stack); } - this._connected = false; - _connections.splice(_connections.indexOf(this), 1); - }; - - /** - * Sends an Error message - * @param {object} message Error message. Must be JSON.stringify-able. - */ - Connection.prototype.sendError = function (message) { - this._send("error", {message: message}); - }; + } else { + this.sendError("Malformed message: " + message); + } +}; - /** - * Sends a response to a command execution - * @param {number} id unique ID of the command that was executed. ID is - * generated by the client when the command is issued. - * @param {object|Buffer} response Result of the command execution. Must - * either be JSON.stringify-able or a raw Buffer. In the latter case, - * the result will be sent as a binary response. - */ - Connection.prototype.sendCommandResponse = function (id, response) { - if (Buffer.isBuffer(response)) { - // Assume the id is an unsigned 32-bit integer, which is encoded - // as a four-byte header - var header = new Buffer(4); - - header.writeUInt32LE(id, 0); - - // Prepend the header to the message - var message = Buffer.concat([header, response], response.length + 4); - - this._sendBinary(message); - } else { - this._send("commandResponse", {id: id, response: response }); +/** + * Closes the connection and does necessary cleanup + */ +Connection.prototype.close = function () { + if (this._ws) { + try { + this._ws.close(); + } catch (e) { + // Do nothing } - }; - - /** - * Sends a progress message to command execution (async commands only) - * @param {number} id unique ID of the command that was executed. ID is - * generated by the client when the command is issued. - * @param {string} message Progress message - */ - Connection.prototype.sendCommandProgress = function (id, message) { - this._send("commandProgress", {id: id, message: message}); - }; + } + this._connected = false; + _connections.splice(_connections.indexOf(this), 1); +}; - /** - * Sends a response indicating that an error occurred during command - * execution - * @param {number} id unique ID of the command that was executed. ID is - * generated by the client when the command is issued. - * @param {string} message Error message - * @param {?object} stack Call stack from the exception, if possible. Must - * be JSON.stringify-able. - */ - Connection.prototype.sendCommandError = function (id, message, stack) { - this._send("commandError", {id: id, message: message, stack: stack}); - }; +/** + * Sends an Error message + * @param {object} message Error message. Must be JSON.stringify-able. + */ +Connection.prototype.sendError = function (message) { + this._send("error", {message: message}); +}; + +/** + * Sends a response to a command execution + * @param {number} id unique ID of the command that was executed. ID is + * generated by the client when the command is issued. + * @param {object|Buffer} response Result of the command execution. Must + * either be JSON.stringify-able or a raw Buffer. In the latter case, + * the result will be sent as a binary response. + */ +Connection.prototype.sendCommandResponse = function (id, response) { + if (Buffer.isBuffer(response)) { + // Assume the id is an unsigned 32-bit integer, which is encoded + // as a four-byte header + var header = new Buffer(4); + + header.writeUInt32LE(id, 0); - /** - * Sends an event message - * @param {number} id unique ID for the event. - * @param {string} domain Domain of the event. - * @param {string} event Name of the event - * @param {object} parameters Event parameters. Must be JSON.stringify-able. - */ - Connection.prototype.sendEventMessage = - function (id, domain, event, parameters) { - this._send("event", {id: id, - domain: domain, - event: event, - parameters: parameters - }); - }; - - /** - * Factory function for creating a new Connection - * @param {WebSocket} ws The WebSocket connected to the client. - */ - function createConnection(ws) { - _connections.push(new Connection(ws)); + // Prepend the header to the message + var message = Buffer.concat([header, response], response.length + 4); + + this._sendBinary(message); + } else { + this._send("commandResponse", {id: id, response: response }); } - - /** - * Closes all connections gracefully. Should be called during shutdown. - */ - function closeAllConnections() { - var i; - for (i = 0; i < _connections.length; i++) { - try { - _connections[i].close(); - } catch (err) { } +}; + +/** + * Sends a progress message to command execution (async commands only) + * @param {number} id unique ID of the command that was executed. ID is + * generated by the client when the command is issued. + * @param {string} message Progress message + */ +Connection.prototype.sendCommandProgress = function (id, message) { + this._send("commandProgress", {id: id, message: message}); +}; + +/** + * Sends a response indicating that an error occurred during command + * execution + * @param {number} id unique ID of the command that was executed. ID is + * generated by the client when the command is issued. + * @param {string} message Error message + * @param {?object} stack Call stack from the exception, if possible. Must + * be JSON.stringify-able. + */ +Connection.prototype.sendCommandError = function (id, message, stack) { + this._send("commandError", {id: id, message: message, stack: stack}); +}; + +/** + * Sends an event message + * @param {number} id unique ID for the event. + * @param {string} domain Domain of the event. + * @param {string} event Name of the event + * @param {object} parameters Event parameters. Must be JSON.stringify-able. + */ +Connection.prototype.sendEventMessage = + function (id, domain, event, parameters) { + this._send("event", {id: id, + domain: domain, + event: event, + parameters: parameters + }); + }; + +/** + * Factory function for creating a new Connection + * @param {WebSocket} ws The WebSocket connected to the client. + */ +function createConnection(ws) { + _connections.push(new Connection(ws)); +} + +/** + * Closes all connections gracefully. Should be called during shutdown. + */ +function closeAllConnections() { + var i; + for (i = 0; i < _connections.length; i++) { + try { + _connections[i].close(); + } catch (err) { + // Do nothing } - _connections = []; - } - - /** - * Sends all open connections the specified event - * @param {number} id unique ID for the event. - * @param {string} domain Domain of the event. - * @param {string} event Name of the event - * @param {object} parameters Event parameters. Must be JSON.stringify-able. - */ - function sendEventToAllConnections(id, domain, event, parameters) { - _connections.forEach(function (c) { - c.sendEventMessage(id, domain, event, parameters); - }); } - - exports.createConnection = createConnection; - exports.closeAllConnections = closeAllConnections; - exports.sendEventToAllConnections = sendEventToAllConnections; -}()); + _connections = []; +} + +/** + * Sends all open connections the specified event + * @param {number} id unique ID for the event. + * @param {string} domain Domain of the event. + * @param {string} event Name of the event + * @param {object} parameters Event parameters. Must be JSON.stringify-able. + */ +function sendEventToAllConnections(id, domain, event, parameters) { + _connections.forEach(function (c) { + c.sendEventMessage(id, domain, event, parameters); + }); +} + +exports.createConnection = createConnection; +exports.closeAllConnections = closeAllConnections; +exports.sendEventToAllConnections = sendEventToAllConnections; diff --git a/appshell/node-core/DomainManager.js b/appshell/node-core/DomainManager.js index 7db643da4..cc66c8ed3 100644 --- a/appshell/node-core/DomainManager.js +++ b/appshell/node-core/DomainManager.js @@ -1,314 +1,310 @@ /* * 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 + * 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, + * 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 + * 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, indent: 4, -maxerr: 50, node: true */ -/*global */ +"use strict"; -(function () { - "use strict"; - - var util = require("util"), - server = require("./Server"), - ConnectionManager = require("./ConnectionManager"); - - /** - * @constructor - * DomainManager is a module/class that handles the loading, registration, - * and execution of all commands and events. It is a singleton, and is passed - * to a domain in its init() method. - */ - var self = exports; - - /** - * @private - * @type {object} - * Map of all the registered domains - */ - var _domains = {}; +// TODO: verify if this has any side effects, and if not remove it. +require("./Server"); - /** - * @private - * @type {Array.} - * Array of all modules we have loaded. Used for avoiding duplicate loading. - */ - var _initializedDomainModules = []; +var util = require("util"), + ConnectionManager = require("./ConnectionManager"); - /** - * @private - * @type {number} - * Used for generating unique IDs for events. - */ - var _eventCount = 1; +/** + * @constructor + * DomainManager is a module/class that handles the loading, registration, + * and execution of all commands and events. It is a singleton, and is passed + * to a domain in its init() method. + */ +var self = exports; - /** - * @private - * @type {Array} - * JSON.stringify-able Array of the current API. In the format of - * Inspector.json. This is a cache that we invalidate every time the - * API changes. - */ - var _cachedDomainDescriptions = null; - - /** - * Returns whether a domain with the specified name exists or not. - * @param {string} domainName The domain name. - * @return {boolean} Whether the domain exists - */ - function hasDomain(domainName) { - return !!_domains[domainName]; - } - - /** - * Returns a new empty domain. Throws error if the domain already exists. - * @param {string} domainName The domain name. - * @param {{major: number, minor: number}} version The domain version. - * The version has a format like {major: 1, minor: 2}. It is reported - * in the API spec, but serves no other purpose on the server. The client - * can make use of this. - */ - function registerDomain(domainName, version) { - if (!hasDomain(domainName)) { - // invalidate the cache - _cachedDomainDescriptions = null; - - _domains[domainName] = {version: version, commands: {}, events: {}}; - } else { - console.error("[DomainManager] Domain " + domainName + " already registered"); - } - } - - /** - * Registers a new command with the specified domain. If the domain does - * not yet exist, it registers the domain with a null version. - * @param {string} domainName The domain name. - * @param {string} commandName The command name. - * @param {Function} commandFunction The callback handler for the function. - * The function is called with the arguments specified by the client in the - * command message. Additionally, if the command is asynchronous (isAsync - * parameter is true), the function is called with an automatically- - * constructed callback function of the form cb(err, result). The function - * can then use this to send a response to the client asynchronously. - * @param {boolean} isAsync See explanation for commandFunction param - * @param {?string} description Used in the API documentation - * @param {?Array.<{name: string, type: string, description:string}>} parameters - * Used in the API documentation. - * @param {?Array.<{name: string, type: string, description:string}>} returns - * Used in the API documentation. - */ - function registerCommand(domainName, commandName, commandFunction, isAsync, - description, parameters, returns) { +/** + * @private + * @type {object} + * Map of all the registered domains + */ +var _domains = {}; + +/** + * @private + * @type {Array.} + * Array of all modules we have loaded. Used for avoiding duplicate loading. + */ +var _initializedDomainModules = []; + +/** + * @private + * @type {number} + * Used for generating unique IDs for events. + */ +var _eventCount = 1; + +/** + * @private + * @type {Array} + * JSON.stringify-able Array of the current API. In the format of + * Inspector.json. This is a cache that we invalidate every time the + * API changes. + */ +var _cachedDomainDescriptions = null; + +/** + * Returns whether a domain with the specified name exists or not. + * @param {string} domainName The domain name. + * @return {boolean} Whether the domain exists + */ +function hasDomain(domainName) { + return !!_domains[domainName]; +} + +/** + * Returns a new empty domain. Throws error if the domain already exists. + * @param {string} domainName The domain name. + * @param {{major: number, minor: number}} version The domain version. + * The version has a format like {major: 1, minor: 2}. It is reported + * in the API spec, but serves no other purpose on the server. The client + * can make use of this. + */ +function registerDomain(domainName, version) { + if (!hasDomain(domainName)) { // invalidate the cache _cachedDomainDescriptions = null; - - if (!hasDomain(domainName)) { - registerDomain(domainName, null); - } - if (!_domains[domainName].commands[commandName]) { - _domains[domainName].commands[commandName] = { - commandFunction: commandFunction, - isAsync: isAsync, - description: description, - parameters: parameters, - returns: returns - }; - } else { - throw new Error("Command " + domainName + "." + - commandName + " already registered"); - } + _domains[domainName] = {version: version, commands: {}, events: {}}; + } else { + console.error("[DomainManager] Domain " + domainName + " already registered"); } +} + +/** + * Registers a new command with the specified domain. If the domain does + * not yet exist, it registers the domain with a null version. + * @param {string} domainName The domain name. + * @param {string} commandName The command name. + * @param {Function} commandFunction The callback handler for the function. + * The function is called with the arguments specified by the client in the + * command message. Additionally, if the command is asynchronous (isAsync + * parameter is true), the function is called with an automatically- + * constructed callback function of the form cb(err, result). The function + * can then use this to send a response to the client asynchronously. + * @param {boolean} isAsync See explanation for commandFunction param + * @param {?string} description Used in the API documentation + * @param {?Array.<{name: string, type: string, description:string}>} parameters + * Used in the API documentation. + * @param {?Array.<{name: string, type: string, description:string}>} returns + * Used in the API documentation. + */ +function registerCommand(domainName, commandName, commandFunction, isAsync, + description, parameters, returns) { + // invalidate the cache + _cachedDomainDescriptions = null; - /** - * Executes a command by domain name and command name. Called by a connection's - * message parser. Sends response or error (possibly asynchronously) to the - * connection. - * @param {Connection} connection The requesting connection object. - * @param {number} id The unique command ID. - * @param {string} domainName The domain name. - * @param {string} commandName The command name. - * @param {Array} parameters The parameters to pass to the command function. If - * the command is asynchronous, will be augmented with a callback function - * and progressCallback function - * (see description in registerCommand documentation) - */ - function executeCommand(connection, id, domainName, - commandName, parameters) { - if (_domains[domainName] && - _domains[domainName].commands[commandName]) { - var command = _domains[domainName].commands[commandName]; - if (command.isAsync) { - var callback = function (err, result) { - if (err) { - connection.sendCommandError(id, err); - } else { - connection.sendCommandResponse(id, result); - } - }; - var progressCallback = function (msg) { - connection.sendCommandProgress(id, msg); - }; - parameters.push(callback, progressCallback); - command.commandFunction.apply(connection, parameters); - } else { // synchronous command - try { - connection.sendCommandResponse( - id, - command.commandFunction.apply(connection, parameters) - ); - } catch (e) { - connection.sendCommandError(id, e.message); + if (!hasDomain(domainName)) { + registerDomain(domainName, null); + } + + if (!_domains[domainName].commands[commandName]) { + _domains[domainName].commands[commandName] = { + commandFunction: commandFunction, + isAsync: isAsync, + description: description, + parameters: parameters, + returns: returns + }; + } else { + throw new Error("Command " + domainName + "." + + commandName + " already registered"); + } +} + +/** + * Executes a command by domain name and command name. Called by a connection's + * message parser. Sends response or error (possibly asynchronously) to the + * connection. + * @param {Connection} connection The requesting connection object. + * @param {number} id The unique command ID. + * @param {string} domainName The domain name. + * @param {string} commandName The command name. + * @param {Array} parameters The parameters to pass to the command function. If + * the command is asynchronous, will be augmented with a callback function + * and progressCallback function + * (see description in registerCommand documentation) + */ +function executeCommand(connection, id, domainName, + commandName, parameters) { + if (_domains[domainName] && + _domains[domainName].commands[commandName]) { + var command = _domains[domainName].commands[commandName]; + if (command.isAsync) { + var callback = function (err, result) { + if (err) { + connection.sendCommandError(id, err); + } else { + connection.sendCommandResponse(id, result); } + }; + var progressCallback = function (msg) { + connection.sendCommandProgress(id, msg); + }; + parameters.push(callback, progressCallback); + command.commandFunction.apply(connection, parameters); + } else { // synchronous command + try { + connection.sendCommandResponse( + id, + command.commandFunction.apply(connection, parameters) + ); + } catch (e) { + connection.sendCommandError(id, e.message); } - } else { - connection.sendCommandError(id, "no such command: " + - domainName + "." + commandName); } + } else { + connection.sendCommandError(id, "no such command: " + + domainName + "." + commandName); } +} - /** - * Registers an event domain and name. - * @param {string} domainName The domain name. - * @param {string} eventName The event name. - * @param {?Array.<{name: string, type: string, description:string}>} parameters - * Used in the API documentation. - */ - function registerEvent(domainName, eventName, parameters) { - // invalidate the cache - _cachedDomainDescriptions = null; - - if (!hasDomain(domainName)) { - registerDomain(domainName, null); - } +/** + * Registers an event domain and name. + * @param {string} domainName The domain name. + * @param {string} eventName The event name. + * @param {?Array.<{name: string, type: string, description:string}>} parameters + * Used in the API documentation. + */ +function registerEvent(domainName, eventName, parameters) { + // invalidate the cache + _cachedDomainDescriptions = null; - if (!_domains[domainName].events[eventName]) { - _domains[domainName].events[eventName] = { - parameters: parameters - }; - } else { - console.error("[DomainManager] Event " + domainName + "." + - eventName + " already registered"); - } + if (!hasDomain(domainName)) { + registerDomain(domainName, null); } - /** - * Emits an event with the specified name and parameters to all connections. - * - * TODO: Future: Potentially allow individual connections to register - * for which events they want to receive. Right now, we have so few events - * that it's fine to just send all events to everyone and decide on the - * client side if the client wants to handle them. - * - * @param {string} domainName The domain name. - * @param {string} eventName The event name. - * @param {?Array} parameters The parameters. Must be JSON.stringify-able - */ - function emitEvent(domainName, eventName, parameters) { - if (_domains[domainName] && _domains[domainName].events[eventName]) { - ConnectionManager.sendEventToAllConnections( - _eventCount++, - domainName, - eventName, - parameters - ); - } else { - console.error("[DomainManager] No such event: " + domainName + - "." + eventName); - } + if (!_domains[domainName].events[eventName]) { + _domains[domainName].events[eventName] = { + parameters: parameters + }; + } else { + console.error("[DomainManager] Event " + domainName + "." + + eventName + " already registered"); } - - /** - * Loads and initializes domain modules using the specified paths. Checks to - * make sure that a module is not loaded/initialized more than once. - * - * @param {Array.} paths The paths to load. The paths can be relative - * to the DomainManager or absolute. However, modules that aren't in core - * won't know where the DomainManager module is, so in general, all paths - * should be absolute. - * @return {boolean} Whether loading succeded. (Failure will throw an exception). - */ - function loadDomainModulesFromPaths(paths) { - var pathArray = paths; - if (!util.isArray(paths)) { - pathArray = [paths]; - } - pathArray.forEach(function (path) { - var m = require(path); - if (m && m.init && _initializedDomainModules.indexOf(m) < 0) { - m.init(self); - _initializedDomainModules.push(m); // don't init more than once - } - }); - return true; // if we fail, an exception will be thrown +} + +/** + * Emits an event with the specified name and parameters to all connections. + * + * TODO: Future: Potentially allow individual connections to register + * for which events they want to receive. Right now, we have so few events + * that it's fine to just send all events to everyone and decide on the + * client side if the client wants to handle them. + * + * @param {string} domainName The domain name. + * @param {string} eventName The event name. + * @param {?Array} parameters The parameters. Must be JSON.stringify-able + */ +function emitEvent(domainName, eventName, parameters) { + if (_domains[domainName] && _domains[domainName].events[eventName]) { + ConnectionManager.sendEventToAllConnections( + _eventCount++, + domainName, + eventName, + parameters + ); + } else { + console.error("[DomainManager] No such event: " + domainName + + "." + eventName); } - - /** - * Returns a description of all registered domains in the format of WebKit's - * Inspector.json. Used for sending API documentation to clients. - * - * @return {Array} Array describing all domains. - */ - function getDomainDescriptions() { - if (!_cachedDomainDescriptions) { - _cachedDomainDescriptions = []; - - var domainNames = Object.keys(_domains); - domainNames.forEach(function (domainName) { - var d = { - domain: domainName, - version: _domains[domainName].version, - commands: [], - events: [] - }; - var commandNames = Object.keys(_domains[domainName].commands); - commandNames.forEach(function (commandName) { - var c = _domains[domainName].commands[commandName]; - d.commands.push({ - name: commandName, - description: c.description, - parameters: c.parameters, - returns: c.returns - }); +} + +/** + * Loads and initializes domain modules using the specified paths. Checks to + * make sure that a module is not loaded/initialized more than once. + * + * @param {Array.} paths The paths to load. The paths can be relative + * to the DomainManager or absolute. However, modules that aren't in core + * won't know where the DomainManager module is, so in general, all paths + * should be absolute. + * @return {boolean} Whether loading succeded. (Failure will throw an exception). + */ +function loadDomainModulesFromPaths(paths) { + var pathArray = paths; + if (!util.isArray(paths)) { + pathArray = [paths]; + } + pathArray.forEach(function (path) { + var m = require(path); + if (m && m.init && _initializedDomainModules.indexOf(m) < 0) { + m.init(self); + _initializedDomainModules.push(m); // don't init more than once + } + }); + return true; // if we fail, an exception will be thrown +} + +/** + * Returns a description of all registered domains in the format of WebKit's + * Inspector.json. Used for sending API documentation to clients. + * + * @return {Array} Array describing all domains. + */ +function getDomainDescriptions() { + if (!_cachedDomainDescriptions) { + _cachedDomainDescriptions = []; + + var domainNames = Object.keys(_domains); + domainNames.forEach(function (domainName) { + var d = { + domain: domainName, + version: _domains[domainName].version, + commands: [], + events: [] + }; + var commandNames = Object.keys(_domains[domainName].commands); + commandNames.forEach(function (commandName) { + var c = _domains[domainName].commands[commandName]; + d.commands.push({ + name: commandName, + description: c.description, + parameters: c.parameters, + returns: c.returns }); - var eventNames = Object.keys(_domains[domainName].events); - eventNames.forEach(function (eventName) { - d.events.push({ - name: eventName, - parameters: _domains[domainName].events[eventName].parameters - }); + }); + var eventNames = Object.keys(_domains[domainName].events); + eventNames.forEach(function (eventName) { + d.events.push({ + name: eventName, + parameters: _domains[domainName].events[eventName].parameters }); - _cachedDomainDescriptions.push(d); }); - } - return _cachedDomainDescriptions; + _cachedDomainDescriptions.push(d); + }); } - - exports.hasDomain = hasDomain; - exports.registerDomain = registerDomain; - exports.registerCommand = registerCommand; - exports.executeCommand = executeCommand; - exports.registerEvent = registerEvent; - exports.emitEvent = emitEvent; - exports.loadDomainModulesFromPaths = loadDomainModulesFromPaths; - exports.getDomainDescriptions = getDomainDescriptions; -}()); + return _cachedDomainDescriptions; +} + +exports.hasDomain = hasDomain; +exports.registerDomain = registerDomain; +exports.registerCommand = registerCommand; +exports.executeCommand = executeCommand; +exports.registerEvent = registerEvent; +exports.emitEvent = emitEvent; +exports.loadDomainModulesFromPaths = loadDomainModulesFromPaths; +exports.getDomainDescriptions = getDomainDescriptions; diff --git a/appshell/node-core/Launcher.js b/appshell/node-core/Launcher.js index ae2ed57af..0a6fc5f2d 100644 --- a/appshell/node-core/Launcher.js +++ b/appshell/node-core/Launcher.js @@ -1,139 +1,132 @@ /* * 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 + * 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, + * 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 + * 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, indent: 4, -maxerr: 50, node: true */ -/*global */ +"use strict"; -(function () { - "use strict"; - - var Logger = require("./Logger"), - Server = require("./Server"), - os = require("os"); - - /** @define {boolean} Whether debugger should be enabled at launch - * - * If true, the debugger is enabled automatically, and the init function is - * *not* called. This gives the user time to connect with the remote - * debugger and set breakpoints before running "launch" him/herself from the - * console. In this case, launch is run by calling the global function - * debugLaunch(), which only exists if DEBUG_ON_LAUNCH is set. - * - * NOTE: This is only useful for debugging the launch routine. If you - * want to simply debug some command module, enable the debugger by - * sending the base.enableDebugger command and then connect to it. - * - * NOTE: On Mac, if you leave the debugger stopped at a breakpoint and then - * exit Brackets, the node process will become abandoned. (Because it is - * stopped, it will never check its stdin to see if it's closed, so it - * won't know that it's parent process has died.) - */ - var DEBUG_ON_LAUNCH = false; +var Logger = require("./Logger"), + Server = require("./Server"), + os = require("os"); + +/** @define {boolean} Whether debugger should be enabled at launch + * + * If true, the debugger is enabled automatically, and the init function is + * *not* called. This gives the user time to connect with the remote + * debugger and set breakpoints before running "launch" him/herself from the + * console. In this case, launch is run by calling the global function + * debugLaunch(), which only exists if DEBUG_ON_LAUNCH is set. + * + * NOTE: This is only useful for debugging the launch routine. If you + * want to simply debug some command module, enable the debugger by + * sending the base.enableDebugger command and then connect to it. + * + * NOTE: On Mac, if you leave the debugger stopped at a breakpoint and then + * exit Brackets, the node process will become abandoned. (Because it is + * stopped, it will never check its stdin to see if it's closed, so it + * won't know that it's parent process has died.) + */ +var DEBUG_ON_LAUNCH = false; + +/** @define {?string} Filename to dump all log data to. + * If not null, all log data is appended to the specified file. + */ +var LOG_FILENAME_ON_LAUNCH = null; - /** @define {?string} Filename to dump all log data to. - * If not null, all log data is appended to the specified file. - */ - var LOG_FILENAME_ON_LAUNCH = null; - - function exit() { - if (Server) { - Server.stop(); - } - process.exit(0); +function exit() { + if (Server) { + Server.stop(); } - - /** - * Top-level handler for any uncaught exception. Attempts to log - * the exception and shut down gracefully. (Though that may of course - * fail depending on the exception.) - * - * TODO: Switch to using Node Domains once they're stable enough. In v0.8.x - * Domains are "Stability: 1 - Experimental". What we really want to - * do is run every connection in a separate Domain. Then, if an individual - * connection has an uncaught exception, we can just close it and still - * continue to run normally. - */ - function uncaughtExceptionHandler() { - var args = Array.prototype.slice.call(arguments, 0); - args = args.map(function (arg) { - return arg instanceof Error ? arg.stack : arg; - }); - args.unshift("[Launcher] uncaught exception at top level, exiting."); - Logger.error.apply(null, args); - exit(); + process.exit(0); +} + +/** + * Top-level handler for any uncaught exception. Attempts to log + * the exception and shut down gracefully. (Though that may of course + * fail depending on the exception.) + * + * TODO: Switch to using Node Domains once they're stable enough. In v0.8.x + * Domains are "Stability: 1 - Experimental". What we really want to + * do is run every connection in a separate Domain. Then, if an individual + * connection has an uncaught exception, we can just close it and still + * continue to run normally. + */ +function uncaughtExceptionHandler() { + var args = Array.prototype.slice.call(arguments, 0); + args = args.map(function (arg) { + return arg instanceof Error ? arg.stack : arg; + }); + args.unshift("[Launcher] uncaught exception at top level, exiting."); + Logger.error.apply(null, args); + exit(); +} + +/** + * Setup the Logger and launch the server. If DEBUG_ON_LAUNCH is false, + * this is called immediately on module load. If DEBUG_ON_LAUNCH is true, + * this method can be called by calling the global debugLaunch function + * from the console. + */ +function launch() { + process.on("uncaughtException", uncaughtExceptionHandler); + if (LOG_FILENAME_ON_LAUNCH) { + Logger.setLogFilename(LOG_FILENAME_ON_LAUNCH); } + Logger.remapConsole(); + Server.on("end", function () { + Server = null; + Logger.info( + "[Launcher] received Server \"end\" event, exiting process" + ); + exit(); + }); + Server.start(); +} + +if (!DEBUG_ON_LAUNCH) { + launch(); +} else { + var noopTimer = setInterval(function () { + // no-op so that we don't exit the process + }, 100000); - /** - * Setup the Logger and launch the server. If DEBUG_ON_LAUNCH is false, - * this is called immediately on module load. If DEBUG_ON_LAUNCH is true, - * this method can be called by calling the global debugLaunch function - * from the console. - */ - function launch() { - process.on("uncaughtException", uncaughtExceptionHandler); - if (LOG_FILENAME_ON_LAUNCH) { - Logger.setLogFilename(LOG_FILENAME_ON_LAUNCH); - } - Logger.remapConsole(); - Server.on("end", function () { - Server = null; - Logger.info( - "[Launcher] received Server \"end\" event, exiting process" - ); - exit(); + // Inject a global so that user can call launch from the console + // The debugger won't stop at breakpoints if they're reached as a + // direct result of evaluation of the console. So, we call launch() + // from a timer. This will allow hitting breakpoints set in launch() + // and in the functions it calls. + global.debugLaunch = function () { + process.nextTick(function () { + clearInterval(noopTimer); + launch(); }); - Server.start(); - } - - if (!DEBUG_ON_LAUNCH) { - launch(); - } else { - var noopTimer = setInterval(function () { - // no-op so that we don't exit the process - }, 100000); + }; + process._debugProcess(process.pid); +} - // Inject a global so that user can call launch from the console - // The debugger won't stop at breakpoints if they're reached as a - // direct result of evaluation of the console. So, we call launch() - // from a timer. This will allow hitting breakpoints set in launch() - // and in the functions it calls. - global.debugLaunch = function () { - process.nextTick(function () { - clearInterval(noopTimer); - launch(); - }); - }; - process._debugProcess(process.pid); - } +// Set environment variable to use built-in Node.js API for temp directory +if (!process.env["TMPDIR"] && !process.env["TMP"] && !process.env["TEMP"]) { + process.env["TMPDIR"] = process.env["TMP"] = process.env["TEMP"] = os.tmpdir(); +} - // Set environment variable to use built-in Node.js API for temp directory - if (!process.env["TMPDIR"] && !process.env["TMP"] && !process.env["TEMP"]) { - process.env["TMPDIR"] = process.env["TMP"] = process.env["TEMP"] = os.tmpdir(); - } - - exports.launch = launch; - exports.exit = exit; - -}()); +exports.launch = launch; +exports.exit = exit; diff --git a/appshell/node-core/Logger.js b/appshell/node-core/Logger.js index 61a9ba42b..eed6097bf 100644 --- a/appshell/node-core/Logger.js +++ b/appshell/node-core/Logger.js @@ -1,174 +1,169 @@ /* * 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 + * 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, + * 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 + * 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, indent: 4, -maxerr: 50, node: true */ -/*global */ - -(function () { - "use strict"; - - var fs = require("fs"), - util = require("util"), - EventEmitter = require("events").EventEmitter; - - /** - * @constructor - * The Logger module is a singleton object used for logging. - * Logger inherits from the EventEmitter class and exports itself - * as the module. - */ - var Logger = module.exports = new EventEmitter(); - - /** - * @private - * @type{?string} - * Filename to append all log data to. - */ - var _logFilename = null; - - /** - * @private - * @type{Array.<{level: string, timestamp: Date, message: string}>} - * Complete log history - */ - var _logHistory = []; - - /** - * @private - * Helper function for logging functions. Handles string formatting. - * @param {string} level Log level ("log", "info", etc.) - * @param {Array.} Array of objects for logging. Works identically - * to how objects can be passed to console.log. Uses util.format to - * format into a single string. - */ - function logReplacement(level, args) { - var message = util.format.apply(null, args); - var timestamp = new Date(); - if (_logFilename) { - try { - var timestampString = - "[" + level + ": " + - timestamp.toLocaleTimeString() + "] "; - - fs.appendFileSync(_logFilename, - timestampString + message + "\n"); - } catch (e) { } - } - _logHistory.push({ - level: level, - timestamp: timestamp, - message: message - }); - Logger.emit("log", level, timestamp, message); - } - - /** - * Log a "log" message - * @param {...Object} log arguments as in console.log etc. - * First parameter can be a "format" string. - */ - function log() { logReplacement("log", arguments); } - - /** - * Log an "info" message - * @param {...Object} log arguments as in console.log etc. - * First parameter can be a "format" string. - */ - function info() { logReplacement("info", arguments); } - - /** - * Log a "warn" message - * @param {...Object} log arguments as in console.log etc. - * First parameter can be a "format" string. - */ - function warn() { logReplacement("warn", arguments); } - - /** - * Log an "error" message - * @param {...Object} log arguments as in console.log etc. - * First parameter can be a "format" string. - */ - function error() { logReplacement("error", arguments); } - - /** - * Log a "dir" message - * @param {...Object} log arguments as in console.dir - * Note that (just like console.dir) this does NOT do string - * formatting using the first argument. - */ - function dir() { - // dir does not do optional string formatting - var args = Array.prototype.slice.call(arguments, 0); - args.unshift("%s"); - logReplacement("dir", args); - } - - /** - * Remaps the console.log, etc. functions to the logging functions - * defined in this module. Useful so that modules can simply call - * console.log to call into this Logger (since client doesn't have) - * access to stdout. - */ - function remapConsole() { - // Reassign logging functions to our logger - // NOTE: console.timeEnd uses console.log and console.trace uses - // console.error, so we don't need to change it explicitly - console.log = log; - console.info = info; - console.warn = warn; - console.error = error; - console.dir = dir; - } - - /** - * Retrieves the entire log history - * @return {Array.<{level: string, timestamp: Date, message: string}>} - */ - function getLogHistory(count) { - if (count === null) { - count = 0; +"use strict"; + +var fs = require("fs"), + util = require("util"), + EventEmitter = require("events").EventEmitter; + +/** + * @constructor + * The Logger module is a singleton object used for logging. + * Logger inherits from the EventEmitter class and exports itself + * as the module. + */ +var Logger = module.exports = new EventEmitter(); + +/** + * @private + * @type{?string} + * Filename to append all log data to. + */ +var _logFilename = null; + +/** + * @private + * @type{Array.<{level: string, timestamp: Date, message: string}>} + * Complete log history + */ +var _logHistory = []; + +/** + * @private + * Helper function for logging functions. Handles string formatting. + * @param {string} level Log level ("log", "info", etc.) + * @param {Array.} Array of objects for logging. Works identically + * to how objects can be passed to console.log. Uses util.format to + * format into a single string. + */ +function logReplacement(level, args) { + var message = util.format.apply(null, args); + var timestamp = new Date(); + if (_logFilename) { + try { + var timestampString = + "[" + level + ": " + + timestamp.toLocaleTimeString() + "] "; + + fs.appendFileSync(_logFilename, + timestampString + message + "\n"); + } catch (e) { + // Do nothing } - return _logHistory.slice(-count); } - - /** - * Sets the filename to which the log messages are appended. - * Specifying a null filename will turn off logging to a file. - * @param {?string} filename The filename. - */ - function setLogFilename(filename) { - _logFilename = filename; + _logHistory.push({ + level: level, + timestamp: timestamp, + message: message + }); + Logger.emit("log", level, timestamp, message); +} + +/** + * Log a "log" message + * @param {...Object} log arguments as in console.log etc. + * First parameter can be a "format" string. + */ +function log() { logReplacement("log", arguments); } + +/** + * Log an "info" message + * @param {...Object} log arguments as in console.log etc. + * First parameter can be a "format" string. + */ +function info() { logReplacement("info", arguments); } + +/** + * Log a "warn" message + * @param {...Object} log arguments as in console.log etc. + * First parameter can be a "format" string. + */ +function warn() { logReplacement("warn", arguments); } + +/** + * Log an "error" message + * @param {...Object} log arguments as in console.log etc. + * First parameter can be a "format" string. + */ +function error() { logReplacement("error", arguments); } + +/** + * Log a "dir" message + * @param {...Object} log arguments as in console.dir + * Note that (just like console.dir) this does NOT do string + * formatting using the first argument. + */ +function dir() { + // dir does not do optional string formatting + var args = Array.prototype.slice.call(arguments, 0); + args.unshift("%s"); + logReplacement("dir", args); +} + +/** + * Remaps the console.log, etc. functions to the logging functions + * defined in this module. Useful so that modules can simply call + * console.log to call into this Logger (since client doesn't have) + * access to stdout. + */ +function remapConsole() { + // Reassign logging functions to our logger + // NOTE: console.timeEnd uses console.log and console.trace uses + // console.error, so we don't need to change it explicitly + console.log = log; + console.info = info; + console.warn = warn; + console.error = error; + console.dir = dir; +} + +/** + * Retrieves the entire log history + * @return {Array.<{level: string, timestamp: Date, message: string}>} + */ +function getLogHistory(count) { + if (count === null) { + count = 0; } - - // Public interface - Logger.log = log; - Logger.info = info; - Logger.warn = warn; - Logger.error = error; - Logger.dir = dir; - Logger.remapConsole = remapConsole; - Logger.getLogHistory = getLogHistory; - Logger.setLogFilename = setLogFilename; - -}()); + return _logHistory.slice(-count); +} + +/** + * Sets the filename to which the log messages are appended. + * Specifying a null filename will turn off logging to a file. + * @param {?string} filename The filename. + */ +function setLogFilename(filename) { + _logFilename = filename; +} + +// Public interface +Logger.log = log; +Logger.info = info; +Logger.warn = warn; +Logger.error = error; +Logger.dir = dir; +Logger.remapConsole = remapConsole; +Logger.getLogHistory = getLogHistory; +Logger.setLogFilename = setLogFilename; diff --git a/appshell/node-core/Server.js b/appshell/node-core/Server.js index 7049bd6d4..7ba1436f9 100644 --- a/appshell/node-core/Server.js +++ b/appshell/node-core/Server.js @@ -1,276 +1,272 @@ /* * 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 + * 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, + * 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 + * 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, indent: 4, -maxerr: 50, node: true */ -/*global */ +"use strict"; -(function () { - "use strict"; - - /** @define{number} Number of ms to wait for the server to start */ - var SETUP_TIMEOUT = 5000; // wait up to 5 seconds for server to start +/** @define{number} Number of ms to wait for the server to start */ +var SETUP_TIMEOUT = 5000; // wait up to 5 seconds for server to start - /** @define{number} Number of ms between pings to parent process */ - var PING_DELAY = 1000; // send ping to parent process every 1 second - - var fs = require("fs"), - http = require("http"), - WebSocket = require("./thirdparty/ws"), - EventEmitter = require("events").EventEmitter, - Logger = require("./Logger"), - ConnectionManager = require("./ConnectionManager"), - DomainManager = require("./DomainManager"); - - /** - * @constructor - * The Server module is a singleton object that manages both the - * connection to the parent process (over stdin/stdout) and to clients - * over WebSockets. - * - * Server inherits from the EventEmitter class and exports itself - * as the module. - */ - var Server = module.exports = new EventEmitter(); - - /** - * @private - * @type{number} unique IDs for messages to parent process - */ - var _commandCount = 1; - - /** - * @private - * @type{http.Server} the HTTP server - */ - var _httpServer = null; +/** @define{number} Number of ms between pings to parent process */ +var PING_DELAY = 1000; // send ping to parent process every 1 second - /** - * @private - * @type{ws.WebSocketServer} the WebSocket server - */ - var _wsServer = null; - - /** - * Stops the server and does appropriate cleanup. - * Emits an "end" event when shutdown is complete. - */ - function stop() { - Logger.info("[Server] stopping"); - if (_wsServer) { - try { - _wsServer.close(); - } catch (err1) { } - } - if (_httpServer) { - try { - _httpServer.close(); - } catch (err2) { } +var http = require("http"), + WebSocket = require("./thirdparty/ws"), + EventEmitter = require("events").EventEmitter, + Logger = require("./Logger"), + ConnectionManager = require("./ConnectionManager"), + DomainManager = require("./DomainManager"); + +/** + * @constructor + * The Server module is a singleton object that manages both the + * connection to the parent process (over stdin/stdout) and to clients + * over WebSockets. + * + * Server inherits from the EventEmitter class and exports itself + * as the module. + */ +var Server = module.exports = new EventEmitter(); + +/** + * @private + * @type{number} unique IDs for messages to parent process + */ +var _commandCount = 1; + +/** + * @private + * @type{http.Server} the HTTP server + */ +var _httpServer = null; + +/** + * @private + * @type{ws.WebSocketServer} the WebSocket server + */ +var _wsServer = null; + +/** + * Stops the server and does appropriate cleanup. + * Emits an "end" event when shutdown is complete. + */ +function stop() { + Logger.info("[Server] stopping"); + if (_wsServer) { + try { + _wsServer.close(); + } catch (err1) { + // Do nothing } - ConnectionManager.closeAllConnections(); - Logger.info("[Server] stopped"); - Server.emit("end"); - Server.removeAllListeners(); } - - /** - * Starts the server. - */ - function start() { - function sendCommandToParentProcess() { - var cmd = "\n\n" + (_commandCount++) + "|" - + Array.prototype.join.call(arguments, "|") + "\n\n"; - process.stdout.write(cmd); + if (_httpServer) { + try { + _httpServer.close(); + } catch (err2) { + // Do nothing } - - function httpRequestHandler(req, res) { - if (req.method === "GET") { - if (req.url === "/api" || req.url.indexOf("/api/") === 0) { - res.setHeader("Content-Type", "application/json"); - res.end( - JSON.stringify(DomainManager.getDomainDescriptions(), - null, - 4) - ); - } else { - res.setHeader("Content-Type", "text/plain"); - res.end("Brackets-Shell Server\n"); - } - } else { // Not a GET request - res.statusCode = 501; - res.end(); + } + ConnectionManager.closeAllConnections(); + Logger.info("[Server] stopped"); + Server.emit("end"); + Server.removeAllListeners(); +} + +/** + * Starts the server. + */ +function start() { + function sendCommandToParentProcess() { + var cmd = "\n\n" + (_commandCount++) + "|" + + Array.prototype.join.call(arguments, "|") + "\n\n"; + process.stdout.write(cmd); + } + + function httpRequestHandler(req, res) { + if (req.method === "GET") { + if (req.url === "/api" || req.url.indexOf("/api/") === 0) { + res.setHeader("Content-Type", "application/json"); + res.end( + JSON.stringify(DomainManager.getDomainDescriptions(), + null, + 4) + ); + } else { + res.setHeader("Content-Type", "text/plain"); + res.end("Brackets-Shell Server\n"); } + } else { // Not a GET request + res.statusCode = 501; + res.end(); } - - function setupStdin() { - // re-enable getting events from stdin - try { - process.stdin.resume(); - process.stdin.setEncoding("utf8"); - } catch (e) { - // Couldn't resume stdin, so something is terribly wrong - Logger.error("[Server] unable to resume stdin, stopping"); - stop(); - } - - // set up event handlers for stdin - process.stdin.on("data", function (data) { - // no-op, but make sure we read the data so the buffer - // doesn't fill up - }); - - process.stdin.on("end", function receiveStdInClose() { - Logger.info("[Server] stopping because stdin closed"); - stop(); - }); + } + + function setupStdin() { + // re-enable getting events from stdin + try { + process.stdin.resume(); + process.stdin.setEncoding("utf8"); + } catch (e) { + // Couldn't resume stdin, so something is terribly wrong + Logger.error("[Server] unable to resume stdin, stopping"); + stop(); } - - function setupStdout() { - // Routinely check if stdout is closed. Stdout will close when our - // parent process closes (either expectedly or unexpectedly) so this - // is our signal to shutdown to prevent process abandonment. - // - // We need to continually ping because that's the only way to actually - // check if the pipe is closed in a robust way (writable may only get - // set to false after trying to write a ping to a closed pipe). - setInterval(function () { - if (!process.stdout.writable) { - // If stdout closes, our parent process has terminated or - // has explicitly closed it. Either way, we should exit. - Logger.info("[Server] stopping because stdout closed"); + + // set up event handlers for stdin + process.stdin.on("data", function (data) { + // no-op, but make sure we read the data so the buffer + // doesn't fill up + }); + + process.stdin.on("end", function receiveStdInClose() { + Logger.info("[Server] stopping because stdin closed"); + stop(); + }); + } + + function setupStdout() { + // Routinely check if stdout is closed. Stdout will close when our + // parent process closes (either expectedly or unexpectedly) so this + // is our signal to shutdown to prevent process abandonment. + // + // We need to continually ping because that's the only way to actually + // check if the pipe is closed in a robust way (writable may only get + // set to false after trying to write a ping to a closed pipe). + setInterval(function () { + if (!process.stdout.writable) { + // If stdout closes, our parent process has terminated or + // has explicitly closed it. Either way, we should exit. + Logger.info("[Server] stopping because stdout closed"); + stop(); + } else { + try { + sendCommandToParentProcess("ping"); + } catch (e) { + Logger.info("[Server] stopping because stdout was not writable"); stop(); - } else { - try { - sendCommandToParentProcess("ping"); - } catch (e) { - Logger.info("[Server] stopping because stdout was not writable"); - stop(); - } } - }, PING_DELAY); + } + }, PING_DELAY); + } + + function setupHttpAndWebSocketServers(callback, timeout) { + var timeoutTimer = null; + var httpServer = null; + + if (timeout) { + timeoutTimer = setTimeout(function () { + callback("ERR_TIMEOUT", null); + }, timeout); } - - function setupHttpAndWebSocketServers(callback, timeout) { - var timeoutTimer = null; - var httpServer = null; - - if (timeout) { - timeoutTimer = setTimeout(function () { - callback("ERR_TIMEOUT", null); - }, timeout); + + httpServer = http.createServer(httpRequestHandler); + + httpServer.on("error", function () { + if (callback) { + callback("ERR_CREATE_SERVER", null); } - - httpServer = http.createServer(httpRequestHandler); - - httpServer.on("error", function () { - if (callback) { - callback("ERR_CREATE_SERVER", null); - } - }); - - httpServer.listen(0, "127.0.0.1", function () { - var wsServer = null; - var address = httpServer.address(); - if (address !== null) { - httpServer.removeAllListeners("error"); - httpServer.on("error", function () { - Logger.error("[Server] stopping due to HTTP error", - arguments); - stop(); - }); - - wsServer = new WebSocket.Server({ - server: httpServer, - verifyClient : function (info, callback) { - // Accept connections originated from local system only - // Also do a loose check on user-agent to accept connection only from Brackets CEF shell - if (info.origin === "file://" && info.req.headers["user-agent"].indexOf(" Brackets") !== -1) { - callback(true); - } else { - // Reject the connection - callback(false); - } - } - }); + }); - wsServer.on("error", function () { - Logger.error( - "[Server] stopping due to WebSocket error", - arguments - ); - stop(); - }); - wsServer.on("connection", - ConnectionManager.createConnection); - - if (timeoutTimer) { - clearTimeout(timeoutTimer); - } - - callback(null, {httpServer : httpServer, - wsServer : wsServer, - port : address.port - }); - } else { - // address is null - // This shouldn't happen, because if we didn't get a socket - // we wouldn't have called this callback - if (timeoutTimer) { - clearTimeout(timeoutTimer); - } - - if (callback) { - callback("ERR_UNKNOWN", null); + httpServer.listen(0, "127.0.0.1", function () { + var wsServer = null; + var address = httpServer.address(); + if (address !== null) { + httpServer.removeAllListeners("error"); + httpServer.on("error", function () { + Logger.error("[Server] stopping due to HTTP error", + arguments); + stop(); + }); + + wsServer = new WebSocket.Server({ + server: httpServer, + verifyClient : function (info, callback) { + // Accept connections originated from local system only + // Also do a loose check on user-agent to accept connection only from Brackets CEF shell + if (info.origin === "file://" && info.req.headers["user-agent"].indexOf(" Brackets") !== -1) { + callback(true); + } else { + // Reject the connection + callback(false); + } } + }); + + wsServer.on("error", function () { + Logger.error( + "[Server] stopping due to WebSocket error", + arguments + ); + stop(); + }); + wsServer.on("connection", + ConnectionManager.createConnection); + + if (timeoutTimer) { + clearTimeout(timeoutTimer); } - }); - } - - // Do initialization - Logger.info("[Server] beginning startup"); - setupStdin(); - setupStdout(); - setupHttpAndWebSocketServers(function (err, servers) { - if (err) { - Logger.error( - "[Server] stopping due to error while starting http/ws servers: " - + err - ); - stop(); + + callback(null, {httpServer : httpServer, + wsServer : wsServer, + port : address.port + }); } else { - Logger.info("[Server] serving on port", servers.port); - _httpServer = servers.httpServer; - _wsServer = servers.wsServer; - // tell the parent process what port we're on - sendCommandToParentProcess("port", servers.port); + // address is null + // This shouldn't happen, because if we didn't get a socket + // we wouldn't have called this callback + if (timeoutTimer) { + clearTimeout(timeoutTimer); + } + + if (callback) { + callback("ERR_UNKNOWN", null); + } } - }, SETUP_TIMEOUT); - DomainManager.loadDomainModulesFromPaths(["./BaseDomain"]); - Logger.info("[Server] startup complete"); + }); } - // Public interface - Server.start = start; - Server.stop = stop; - -}()); + // Do initialization + Logger.info("[Server] beginning startup"); + setupStdin(); + setupStdout(); + setupHttpAndWebSocketServers(function (err, servers) { + if (err) { + Logger.error( + "[Server] stopping due to error while starting http/ws servers: " + + err + ); + stop(); + } else { + Logger.info("[Server] serving on port", servers.port); + _httpServer = servers.httpServer; + _wsServer = servers.wsServer; + // tell the parent process what port we're on + sendCommandToParentProcess("port", servers.port); + } + }, SETUP_TIMEOUT); + DomainManager.loadDomainModulesFromPaths(["./BaseDomain"]); + Logger.info("[Server] startup complete"); +} + +// Public interface +Server.start = start; +Server.stop = stop; diff --git a/package.json b/package.json index eae3ee41b..9529b605b 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "devDependencies": { "grunt": "0.4.1", "grunt-cli": "0.1.6", - "grunt-contrib-jshint": "0.2.0", + "grunt-eslint": "18.1.0", "grunt-contrib-copy": "0.5.0", "grunt-contrib-clean": "0.4.0", "guid": "0.0.10", diff --git a/tasks/.eslintrc.json b/tasks/.eslintrc.json new file mode 100644 index 000000000..653a5abb7 --- /dev/null +++ b/tasks/.eslintrc.json @@ -0,0 +1,7 @@ +{ + "extends": "../.eslintrc.json", + + "rules": { + "no-invalid-this": 0 + } +} diff --git a/tasks/build.js b/tasks/build.js index b5517e093..fb25debae 100644 --- a/tasks/build.js +++ b/tasks/build.js @@ -20,15 +20,12 @@ * DEALINGS IN THE SOFTWARE. * */ -/*jslint vars:true, regexp:true, nomen:true*/ -/*global module, require, process*/ -module.exports = function (grunt) { - "use strict"; +"use strict"; +module.exports = function (grunt) { var fs = require("fs"), common = require("./common")(grunt), - q = require("q"), semver = require("semver"), spawn = common.spawn, resolve = common.resolve, diff --git a/tasks/common.js b/tasks/common.js index 226dcbce6..2120ced90 100644 --- a/tasks/common.js +++ b/tasks/common.js @@ -1,30 +1,29 @@ /* * 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 + * 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, + * 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 + * 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 es5:true, vars: true, plusplus: true, regexp: true, nomen: true*/ -/*global module, require, process, Buffer*/ + +"use strict"; + module.exports = function (grunt) { - "use strict"; - var q = require("q"), fs = require("fs"), child_process = require("child_process"), @@ -141,10 +140,7 @@ module.exports = function (grunt) { */ function spawn(commands, opts) { var result = q.resolve(), - children = [], - i, - current, - next; + children = []; opts = opts || {}; opts.env = opts.env || process.env; diff --git a/tasks/set-release.js b/tasks/set-release.js index 77cccbbd6..9ff1b4fa5 100644 --- a/tasks/set-release.js +++ b/tasks/set-release.js @@ -20,11 +20,10 @@ * DEALINGS IN THE SOFTWARE. * */ -/*global module, require*/ -module.exports = function (grunt) { - "use strict"; +"use strict"; +module.exports = function (grunt) { var common = require("./common")(grunt), semver = require("semver"); @@ -48,7 +47,6 @@ module.exports = function (grunt) { packageJSON = grunt.file.readJSON(packageJsonPath), winInstallerBuildXmlPath = "installer/win/brackets-win-install-build.xml", buildInstallerScriptPath = "installer/mac/buildInstaller.sh", - wxsPath = "installer/win/Brackets.wxs", versionRcPath = "appshell/version.rc", infoPlistPath = "appshell/mac/Info.plist", release = grunt.option("release") || "", diff --git a/tasks/setup.js b/tasks/setup.js index 54c156622..43b918da5 100644 --- a/tasks/setup.js +++ b/tasks/setup.js @@ -20,14 +20,12 @@ * DEALINGS IN THE SOFTWARE. * */ -/*jslint vars:true*/ -/*global module, require, process*/ -module.exports = function (grunt) { - "use strict"; +"use strict"; + +module.exports = function (grunt) { var common = require("./common")(grunt), fs = require("fs"), - child_process = require("child_process"), path = require("path"), q = require("q"), /* win only (lib), mac only (Resources, tools) */ @@ -102,8 +100,6 @@ module.exports = function (grunt) { // task: cef-clean grunt.registerTask("cef-clean", "Removes CEF binaries and linked folders", function () { - var path; - // delete dev symlinks from "setup_for_hacking" common.deleteFile("Release/dev", { force: true }); common.deleteFile("Debug/dev", { force: true }); @@ -276,8 +272,7 @@ module.exports = function (grunt) { return rename("deps/" + zipName, "deps/cef"); }).then(function () { var memo = path.resolve(process.cwd(), "deps/cef/" + zipName + ".txt"), - permissionsPromise, - defer = q.defer(); + permissionsPromise; if (platform === "mac") { // FIXME figure out how to use fs.chmod to only do additive mode u+x @@ -334,7 +329,6 @@ module.exports = function (grunt) { curlTask = "curl-dir:" + config, setupTask = "node-" + platform, nodeVersion = grunt.config("node.version"), - npmVersion = grunt.config("npm.version"), txtName = "version-" + nodeVersion + ".txt", missingDest = false;