From 603f251824b55be022023633333b215cc996e4b9 Mon Sep 17 00:00:00 2001 From: johnyma22 Date: Wed, 12 Sep 2012 19:34:33 +0100 Subject: [PATCH 01/21] error handling and close is removed in express 3 --- src/node/hooks/express/errorhandling.js | 25 ++++++++++++++++++++++--- src/package.json | 2 +- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/node/hooks/express/errorhandling.js b/src/node/hooks/express/errorhandling.js index cb8c58987..3c5727ed4 100644 --- a/src/node/hooks/express/errorhandling.js +++ b/src/node/hooks/express/errorhandling.js @@ -16,9 +16,6 @@ exports.gracefulShutdown = function(err) { console.log("graceful shutdown..."); - //stop the http server - exports.app.close(); - //do the db shutdown db.db.doShutdown(function() { console.log("db sucessfully closed."); @@ -35,11 +32,33 @@ exports.gracefulShutdown = function(err) { exports.expressCreateServer = function (hook_name, args, cb) { exports.app = args.app; + + +/* + Below breaks Express 3, commented out to allow express 3o to run. For mroe details see: + https://github.com/visionmedia/express/wiki/Migrating-from-2.x-to-3.x +/* +/* args.app.error(function(err, req, res, next){ res.send(500); console.error(err.stack ? err.stack : err.toString()); exports.gracefulShutdown(); }); +*/ + + + +// args.app.on('close', function(){ +// console.log("Exited in a sloppy fashion"); +// }) + + args.app.use(function(err, req, res, next){ + // if an error occurs Connect will pass it down + // through these "error-handling" middleware + // allowing you to respond however you like + res.send(500, { error: 'Sorry something bad happened!' }); + }) + //connect graceful shutdown with sigint and uncaughtexception if(os.type().indexOf("Windows") == -1) { diff --git a/src/package.json b/src/package.json index a2fc147ab..a7550faed 100644 --- a/src/package.json +++ b/src/package.json @@ -17,7 +17,7 @@ "socket.io" : "0.9.x", "ueberDB" : "0.1.7", "async" : "0.1.x", - "express" : "2.5.x", + "express" : "3.x", "connect" : "1.x", "clean-css" : "0.3.2", "uglify-js" : "1.2.5", From c8b6d3b4f3bb8677b259a8c5be2e9611004347c0 Mon Sep 17 00:00:00 2001 From: johnyma22 Date: Wed, 12 Sep 2012 19:38:53 +0100 Subject: [PATCH 02/21] attempt to put correct init in right place but could be wrong --- src/node/hooks/express.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/node/hooks/express.js b/src/node/hooks/express.js index 1f4a6f2cc..e4ff40d96 100644 --- a/src/node/hooks/express.js +++ b/src/node/hooks/express.js @@ -42,12 +42,13 @@ exports.createServer = function () { } exports.restartServer = function () { + if (server) { console.log("Restarting express server"); server.close(); } - server = express.createServer(); + server = express(); // New syntax for express v3 server.use(function (req, res, next) { res.header("Server", serverName); @@ -60,4 +61,4 @@ exports.restartServer = function () { hooks.callAll("expressCreateServer", {"app": server}); server.listen(settings.port, settings.ip); -} \ No newline at end of file +} From 4416210471e1964d7c30babc354e0e03d1cc00ca Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Fri, 21 Sep 2012 17:12:22 +0200 Subject: [PATCH 03/21] Differentiate between http server and express app --- doc/api/hooks_server-side.md | 3 ++- src/node/hooks/express.js | 12 +++++++----- src/node/hooks/express/socketio.js | 4 ++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md index 518e12130..0bcd85a3b 100644 --- a/doc/api/hooks_server-side.md +++ b/doc/api/hooks_server-side.md @@ -54,7 +54,8 @@ Called from: src/node/server.js Things in context: -1. app - the main application object (helpful for adding new paths and such) +1. app - the main express application object (helpful for adding new paths and such) +1. server - the http server object This hook gets called after the application object has been created, but before it starts listening. This is similar to the expressConfigure hook, but it's not guaranteed that the application object will have all relevant configuration variables. diff --git a/src/node/hooks/express.js b/src/node/hooks/express.js index e4ff40d96..eb3f6188a 100644 --- a/src/node/hooks/express.js +++ b/src/node/hooks/express.js @@ -1,4 +1,5 @@ var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); +var http = require('http'); var express = require('express'); var settings = require('../utils/Settings'); var fs = require('fs'); @@ -48,17 +49,18 @@ exports.restartServer = function () { server.close(); } - server = express(); // New syntax for express v3 + var app = express(); // New syntax for express v3 + server = http.createServer(app); - server.use(function (req, res, next) { + app.use(function (req, res, next) { res.header("Server", serverName); next(); }); - server.configure(function() { - hooks.callAll("expressConfigure", {"app": server}); + app.configure(function() { + hooks.callAll("expressConfigure", {"app": app}); }); - hooks.callAll("expressCreateServer", {"app": server}); + hooks.callAll("expressCreateServer", {"app": app, "server": server}); server.listen(settings.port, settings.ip); } diff --git a/src/node/hooks/express/socketio.js b/src/node/hooks/express/socketio.js index 4f780cb0b..9e1a010fc 100644 --- a/src/node/hooks/express/socketio.js +++ b/src/node/hooks/express/socketio.js @@ -10,7 +10,7 @@ var connect = require('connect'); exports.expressCreateServer = function (hook_name, args, cb) { //init socket.io and redirect all requests to the MessageHandler - var io = socketio.listen(args.app); + var io = socketio.listen(args.server); /* Require an express session cookie to be present, and load the * session. See http://www.danielbaulig.de/socket-ioexpress for more @@ -62,5 +62,5 @@ exports.expressCreateServer = function (hook_name, args, cb) { socketIORouter.setSocketIO(io); socketIORouter.addComponent("pad", padMessageHandler); - hooks.callAll("socketio", {"app": args.app, "io": io}); + hooks.callAll("socketio", {"app": args.app, "io": io, "server": args.server}); } From ff7cf991c9f6bb6b7d1c9f9d1bd0963050170bb8 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Fri, 21 Sep 2012 21:39:08 +0200 Subject: [PATCH 04/21] Upgrade log4js to v0.5 --- src/node/hooks/express/webaccess.js | 1 + src/node/server.js | 10 +++++++--- src/package.json | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/node/hooks/express/webaccess.js b/src/node/hooks/express/webaccess.js index ffced0476..a6a270c02 100644 --- a/src/node/hooks/express/webaccess.js +++ b/src/node/hooks/express/webaccess.js @@ -95,6 +95,7 @@ exports.expressConfigure = function (hook_name, args, cb) { // Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway. if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR")) args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'})); + args.app.use(express.cookieParser()); /* Do not let express create the session, so that we can retain a diff --git a/src/node/server.js b/src/node/server.js index cca76c1f9..b91b0e17c 100755 --- a/src/node/server.js +++ b/src/node/server.js @@ -21,8 +21,15 @@ * limitations under the License. */ +// set up logger var log4js = require('log4js'); +log4js.replaceConsole(); + var settings = require('./utils/Settings'); + +//set loglevel +log4js.setGlobalLogLevel(settings.loglevel); + var db = require('./db/DB'); var async = require('async'); var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins"); @@ -31,9 +38,6 @@ var npm = require("npm/lib/npm.js"); hooks.plugins = plugins; -//set loglevel -log4js.setGlobalLogLevel(settings.loglevel); - async.waterfall([ //initalize the database function (callback) diff --git a/src/package.json b/src/package.json index a7550faed..bee732058 100644 --- a/src/package.json +++ b/src/package.json @@ -22,7 +22,7 @@ "clean-css" : "0.3.2", "uglify-js" : "1.2.5", "formidable" : "1.0.9", - "log4js" : "0.4.1", + "log4js" : "0.5.x", "jsdom-nocontextifiy" : "0.2.10", "async-stacktrace" : "0.0.2", "npm" : "1.1.24", From fa65f889ec8174b2b723fcc45b7d391780b9cd52 Mon Sep 17 00:00:00 2001 From: Chad Weider Date: Tue, 11 Sep 2012 20:52:48 -0700 Subject: [PATCH 05/21] Consolidate Ace2Editor frame's boot script. --- src/static/js/ace.js | 42 ++++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/src/static/js/ace.js b/src/static/js/ace.js index db62deb42..e7e4fca86 100644 --- a/src/static/js/ace.js +++ b/src/static/js/ace.js @@ -24,6 +24,8 @@ // requires: plugins // requires: undefined +var KERNEL_SOURCE = '../static/js/require-kernel.js'; + Ace2Editor.registry = { nextId: 1 }; @@ -155,24 +157,6 @@ function Ace2Editor() return {embeded: embededFiles, remote: remoteFiles}; } - function pushRequireScriptTo(buffer) { - var KERNEL_SOURCE = '../static/js/require-kernel.js'; - var KERNEL_BOOT = '\ -require.setRootURI("../javascripts/src");\n\ -require.setLibraryURI("../javascripts/lib");\n\ -require.setGlobalKeyPath("require");\n\ -'; - if (Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[KERNEL_SOURCE]) { - buffer.push('\ +\n\ + $ = jQuery = require("ep_etherpad-lite/static/js/rjquery").jQuery; // Expose jQuery #HACK\n\ + require("ep_etherpad-lite/static/js/ace2_inner");\n\ +\n\ '); - - iframeHTML.push('' + ) +} + function Ace2Editor() { var ace2 = Ace2Editor; @@ -226,23 +234,20 @@ function Ace2Editor() throw new Error("Require kernel could not be found."); } - iframeHTML.push('\ -\n\ -'); - iframeHTML.push('<\/script>'); +$ = jQuery = require("ep_etherpad-lite/static/js/rjquery").jQuery; // Expose jQuery #HACK\n\ +require("ep_etherpad-lite/static/js/ace2_inner");\n\ +')); iframeHTML.push(''); @@ -256,8 +261,32 @@ function Ace2Editor() var thisFunctionsName = "ChildAccessibleAce2Editor"; (function () {return this}())[thisFunctionsName] = Ace2Editor; - var outerScript = 'editorId = "' + info.id + '"; editorInfo = parent.' + thisFunctionsName + '.registry[editorId]; ' + 'window.onload = function() ' + '{ window.onload = null; setTimeout' + '(function() ' + '{ var iframe = document.createElement("IFRAME"); iframe.name = "ace_inner";' + 'iframe.scrolling = "no"; var outerdocbody = document.getElementById("outerdocbody"); ' + 'iframe.frameBorder = 0; iframe.allowTransparency = true; ' + // for IE - 'outerdocbody.insertBefore(iframe, outerdocbody.firstChild); ' + 'iframe.ace_outerWin = window; ' + 'readyFunc = function() { editorInfo.onEditorReady(); readyFunc = null; editorInfo = null; }; ' + 'var doc = iframe.contentWindow.document; doc.open(); var text = (' + JSON.stringify(iframeHTML.join('\n')) + ');doc.write(text); doc.close(); ' + '}, 0); }'; + var outerScript = '\ +editorId = ' + JSON.stringify(info.id) + ';\n\ +editorInfo = parent[' + JSON.stringify(thisFunctionsName) + '].registry[editorId];\n\ +window.onload = function () {\n\ + window.onload = null;\n\ + setTimeout(function () {\n\ + var iframe = document.createElement("IFRAME");\n\ + iframe.name = "ace_inner";\n\ + iframe.scrolling = "no";\n\ + var outerdocbody = document.getElementById("outerdocbody");\n\ + iframe.frameBorder = 0;\n\ + iframe.allowTransparency = true; // for IE\n\ + outerdocbody.insertBefore(iframe, outerdocbody.firstChild);\n\ + iframe.ace_outerWin = window;\n\ + readyFunc = function () {\n\ + editorInfo.onEditorReady();\n\ + readyFunc = null;\n\ + editorInfo = null;\n\ + };\n\ + var doc = iframe.contentWindow.document;\n\ + doc.open();\n\ + var text = (' + JSON.stringify(iframeHTML.join('\n')) + ');\n\ + doc.write(text);\n\ + doc.close();\n\ + }, 0);\n\ +}'; var outerHTML = [doctype, ''] @@ -275,7 +304,7 @@ function Ace2Editor() // bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly // (throbs busy while typing) - outerHTML.push('', '\x3cscript>\n', outerScript.replace(/<\//g, '<\\/'), '\n\x3c/script>', '
x
'); + outerHTML.push('', scriptTag(outerScript), '
x
'); var outerFrame = document.createElement("IFRAME"); outerFrame.name = "ace_outer"; From 622819ba930b5d4685c053eefaac77c1776fe0b0 Mon Sep 17 00:00:00 2001 From: Chad Weider Date: Wed, 12 Sep 2012 00:04:15 -0700 Subject: [PATCH 07/21] Make intialization of Ace2Inner analogous to other page controllers. --- src/static/js/ace.js | 6 ++- src/static/js/ace2_inner.js | 94 +++++++++++++++++++------------------ 2 files changed, 53 insertions(+), 47 deletions(-) diff --git a/src/static/js/ace.js b/src/static/js/ace.js index 1561dea5e..e50f75c76 100644 --- a/src/static/js/ace.js +++ b/src/static/js/ace.js @@ -246,7 +246,11 @@ hooks.plugins = plugins;\n\ plugins.adoptPluginsFromAncestorsOf(window);\n\ \n\ $ = jQuery = require("ep_etherpad-lite/static/js/rjquery").jQuery; // Expose jQuery #HACK\n\ -require("ep_etherpad-lite/static/js/ace2_inner");\n\ +var Ace2Inner = require("ep_etherpad-lite/static/js/ace2_inner");\n\ +\n\ +plugins.ensure(function () {\n\ + Ace2Inner.init();\n\ +});\n\ ')); iframeHTML.push(''); diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index 962eda2ac..652a3d259 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -19,7 +19,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -var editor, _, $, jQuery, plugins, Ace2Common; +var _, $, jQuery, plugins, Ace2Common; Ace2Common = require('./ace2_common'); @@ -5430,62 +5430,64 @@ function Ace2Inner(){ return documentAttributeManager.setAttributesOnRange.apply(documentAttributeManager, arguments); }; - $(document).ready(function(){ - doc = document; // defined as a var in scope outside - inCallStack("setup", function() - { - var body = doc.getElementById("innerdocbody"); - root = body; // defined as a var in scope outside - if (browser.mozilla) $(root).addClass("mozilla"); - if (browser.safari) $(root).addClass("safari"); - if (browser.msie) $(root).addClass("msie"); - if (browser.msie) + this.init = function () { + $(document).ready(function(){ + doc = document; // defined as a var in scope outside + inCallStack("setup", function() { - // cache CSS background images - try + var body = doc.getElementById("innerdocbody"); + root = body; // defined as a var in scope outside + if (browser.mozilla) $(root).addClass("mozilla"); + if (browser.safari) $(root).addClass("safari"); + if (browser.msie) $(root).addClass("msie"); + if (browser.msie) { - doc.execCommand("BackgroundImageCache", false, true); + // cache CSS background images + try + { + doc.execCommand("BackgroundImageCache", false, true); + } + catch (e) + { /* throws an error in some IE 6 but not others! */ + } } - catch (e) - { /* throws an error in some IE 6 but not others! */ - } - } - setClassPresence(root, "authorColors", true); - setClassPresence(root, "doesWrap", doesWrap); + setClassPresence(root, "authorColors", true); + setClassPresence(root, "doesWrap", doesWrap); - initDynamicCSS(); + initDynamicCSS(); - enforceEditability(); + enforceEditability(); - // set up dom and rep - while (root.firstChild) root.removeChild(root.firstChild); - var oneEntry = createDomLineEntry(""); - doRepLineSplice(0, rep.lines.length(), [oneEntry]); - insertDomLines(null, [oneEntry.domInfo], null); - rep.alines = Changeset.splitAttributionLines( - Changeset.makeAttribution("\n"), "\n"); + // set up dom and rep + while (root.firstChild) root.removeChild(root.firstChild); + var oneEntry = createDomLineEntry(""); + doRepLineSplice(0, rep.lines.length(), [oneEntry]); + insertDomLines(null, [oneEntry.domInfo], null); + rep.alines = Changeset.splitAttributionLines( + Changeset.makeAttribution("\n"), "\n"); - bindTheEventHandlers(); + bindTheEventHandlers(); - }); + }); - hooks.callAll('aceInitialized', { - editorInfo: editorInfo, - rep: rep, - documentAttributeManager: documentAttributeManager - }); + hooks.callAll('aceInitialized', { + editorInfo: editorInfo, + rep: rep, + documentAttributeManager: documentAttributeManager + }); - scheduler.setTimeout(function() - { - parent.readyFunc(); // defined in code that sets up the inner iframe - }, 0); + scheduler.setTimeout(function() + { + parent.readyFunc(); // defined in code that sets up the inner iframe + }, 0); - isSetUp = true; - }); + isSetUp = true; + }); + } } -// Ensure that plugins are loaded before initializing the editor -plugins.ensure(function () { - var editor = new Ace2Inner(); -}); +exports.init = function () { + var editor = new Ace2Inner() + editor.init(); +}; From 71579d14783aa0f020664353f6596142dddfb069 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 22 Sep 2012 13:51:39 +0200 Subject: [PATCH 08/21] Fix res.send (migrate to express v3) --- src/node/hooks/express/adminplugins.js | 8 ++------ src/node/hooks/express/padreadonly.js | 2 +- src/node/hooks/express/padurlsanitize.js | 4 ++-- src/node/hooks/express/webaccess.js | 4 ++-- src/node/padaccess.js | 2 +- 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js index fc274a075..97a0d602f 100644 --- a/src/node/hooks/express/adminplugins.js +++ b/src/node/hooks/express/adminplugins.js @@ -12,14 +12,10 @@ exports.expressCreateServer = function (hook_name, args, cb) { errors: [], }; - res.send(eejs.require( - "ep_etherpad-lite/templates/admin/plugins.html", - render_args), {}); + res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins.html", render_args) ); }); args.app.get('/admin/plugins/info', function(req, res) { - res.send(eejs.require( - "ep_etherpad-lite/templates/admin/plugins-info.html", - {}), {}); + res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html", {}) ); }); } diff --git a/src/node/hooks/express/padreadonly.js b/src/node/hooks/express/padreadonly.js index 60ece0add..af5cbed39 100644 --- a/src/node/hooks/express/padreadonly.js +++ b/src/node/hooks/express/padreadonly.js @@ -56,7 +56,7 @@ exports.expressCreateServer = function (hook_name, args, cb) { ERR(err); if(err == "notfound") - res.send('404 - Not Found', 404); + res.send(404, '404 - Not Found'); else res.send(html); }); diff --git a/src/node/hooks/express/padurlsanitize.js b/src/node/hooks/express/padurlsanitize.js index 24ec2c3d0..29782b692 100644 --- a/src/node/hooks/express/padurlsanitize.js +++ b/src/node/hooks/express/padurlsanitize.js @@ -7,7 +7,7 @@ exports.expressCreateServer = function (hook_name, args, cb) { //ensure the padname is valid and the url doesn't end with a / if(!padManager.isValidPadId(padId) || /\/$/.test(req.url)) { - res.send('Such a padname is forbidden', 404); + res.send(404, 'Such a padname is forbidden'); } else { @@ -19,7 +19,7 @@ exports.expressCreateServer = function (hook_name, args, cb) { var query = url.parse(req.url).query; if ( query ) real_url += '?' + query; res.header('Location', real_url); - res.send('You should be redirected to ' + real_url + '', 302); + res.send(302, 'You should be redirected to ' + real_url + ''); } //the pad id was fine, so just render it else diff --git a/src/node/hooks/express/webaccess.js b/src/node/hooks/express/webaccess.js index a6a270c02..99971206e 100644 --- a/src/node/hooks/express/webaccess.js +++ b/src/node/hooks/express/webaccess.js @@ -56,10 +56,10 @@ exports.basicAuth = function (req, res, next) { res.header('WWW-Authenticate', 'Basic realm="Protected Area"'); if (req.headers.authorization) { setTimeout(function () { - res.send('Authentication required', 401); + res.send(401, 'Authentication required'); }, 1000); } else { - res.send('Authentication required', 401); + res.send(401, 'Authentication required'); } })); } diff --git a/src/node/padaccess.js b/src/node/padaccess.js index a3d1df332..4388ab946 100644 --- a/src/node/padaccess.js +++ b/src/node/padaccess.js @@ -15,7 +15,7 @@ module.exports = function (req, res, callback) { callback(); //no access } else { - res.send("403 - Can't touch this", 403); + res.send(403, "403 - Can't touch this"); } }); } From 794c3d1afe9616fdd611fc8cc8f1c0131998a6a2 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 22 Sep 2012 14:05:41 +0200 Subject: [PATCH 09/21] Set secret on cookieParser (migrate to express v3) --- src/node/hooks/express/webaccess.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/node/hooks/express/webaccess.js b/src/node/hooks/express/webaccess.js index 99971206e..28cb649e4 100644 --- a/src/node/hooks/express/webaccess.js +++ b/src/node/hooks/express/webaccess.js @@ -95,8 +95,6 @@ exports.expressConfigure = function (hook_name, args, cb) { // Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway. if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR")) args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'})); - - args.app.use(express.cookieParser()); /* Do not let express create the session, so that we can retain a * reference to it for socket.io to use. Also, set the key (cookie @@ -107,11 +105,12 @@ exports.expressConfigure = function (hook_name, args, cb) { exports.sessionStore = new express.session.MemoryStore(); secret = randomString(32); } + + args.app.use(express.cookieParser(secret)); args.app.sessionStore = exports.sessionStore; args.app.use(express.session({store: args.app.sessionStore, - key: 'express_sid', - secret: secret})); + key: 'express_sid' })); args.app.use(exports.basicAuth); } From 0f436d5916807cde879617c85a5aea18b98ae1d4 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 22 Sep 2012 15:22:15 +0200 Subject: [PATCH 10/21] Migrate error handling middleware to express v3 --- src/node/hooks/express/errorhandling.js | 26 +++++-------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/src/node/hooks/express/errorhandling.js b/src/node/hooks/express/errorhandling.js index 3c5727ed4..749b04273 100644 --- a/src/node/hooks/express/errorhandling.js +++ b/src/node/hooks/express/errorhandling.js @@ -32,31 +32,15 @@ exports.gracefulShutdown = function(err) { exports.expressCreateServer = function (hook_name, args, cb) { exports.app = args.app; - - -/* - Below breaks Express 3, commented out to allow express 3o to run. For mroe details see: - https://github.com/visionmedia/express/wiki/Migrating-from-2.x-to-3.x -/* -/* - args.app.error(function(err, req, res, next){ - res.send(500); - console.error(err.stack ? err.stack : err.toString()); - exports.gracefulShutdown(); - }); -*/ - - - -// args.app.on('close', function(){ -// console.log("Exited in a sloppy fashion"); -// }) - + // Handle errors args.app.use(function(err, req, res, next){ // if an error occurs Connect will pass it down // through these "error-handling" middleware // allowing you to respond however you like - res.send(500, { error: 'Sorry something bad happened!' }); + res.send(500, { error: 'Sorry, something bad happened!' }); + console.error(err.stack? err.stack : err.toString()); + exports.gracefulShutdown(); + next(); }) From 0c9c1f514fba98b4333d92ce6a331818ec2ebe97 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 22 Sep 2012 16:03:40 +0200 Subject: [PATCH 11/21] Fix socket.io auth: Use connect to parse signed cookies (migrate to express v3) --- src/node/hooks/express/socketio.js | 22 ++++++++++++++++------ src/node/hooks/express/webaccess.js | 6 +++--- src/package.json | 2 +- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/node/hooks/express/socketio.js b/src/node/hooks/express/socketio.js index 9e1a010fc..546ba2af6 100644 --- a/src/node/hooks/express/socketio.js +++ b/src/node/hooks/express/socketio.js @@ -3,6 +3,7 @@ var socketio = require('socket.io'); var settings = require('../../utils/Settings'); var socketIORouter = require("../../handler/SocketIORouter"); var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); +var webaccess = require("ep_etherpad-lite/node/hooks/express/webaccess"); var padMessageHandler = require("../../handler/PadMessageHandler"); @@ -17,12 +18,21 @@ exports.expressCreateServer = function (hook_name, args, cb) { * info */ io.set('authorization', function (data, accept) { if (!data.headers.cookie) return accept('No session cookie transmitted.', false); - data.cookie = connect.utils.parseCookie(data.headers.cookie); - data.sessionID = data.cookie.express_sid; - args.app.sessionStore.get(data.sessionID, function (err, session) { - if (err || !session) return accept('Bad session / session has expired', false); - data.session = new connect.middleware.session.Session(data, session); - accept(null, true); + + // Use connect's cookie parser, because it knows how to parse signed cookies + connect.cookieParser(webaccess.secret)(data, {}, function(err){ + if(err) { + console.error(err); + accept("Couldn't parse request cookies. ", false); + return; + } + + data.sessionID = data.signedCookies.express_sid; + args.app.sessionStore.get(data.sessionID, function (err, session) { + if (err || !session) return accept('Bad session / session has expired', false); + data.session = new connect.middleware.session.Session(data, session); + accept(null, true); + }); }); }); diff --git a/src/node/hooks/express/webaccess.js b/src/node/hooks/express/webaccess.js index 28cb649e4..41bf38805 100644 --- a/src/node/hooks/express/webaccess.js +++ b/src/node/hooks/express/webaccess.js @@ -88,7 +88,7 @@ exports.basicAuth = function (req, res, next) { }); } -var secret = null; +exports.secret = null; exports.expressConfigure = function (hook_name, args, cb) { // If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158. @@ -103,10 +103,10 @@ exports.expressConfigure = function (hook_name, args, cb) { if (!exports.sessionStore) { exports.sessionStore = new express.session.MemoryStore(); - secret = randomString(32); + exports.secret = randomString(32); } - args.app.use(express.cookieParser(secret)); + args.app.use(express.cookieParser(exports.secret)); args.app.sessionStore = exports.sessionStore; args.app.use(express.session({store: args.app.sessionStore, diff --git a/src/package.json b/src/package.json index bee732058..67aa1037c 100644 --- a/src/package.json +++ b/src/package.json @@ -18,7 +18,7 @@ "ueberDB" : "0.1.7", "async" : "0.1.x", "express" : "3.x", - "connect" : "1.x", + "connect" : "2.4.x", "clean-css" : "0.3.2", "uglify-js" : "1.2.5", "formidable" : "1.0.9", From 1c38f5bab9a2c78655c9230e0fca0cfb80a7b8f3 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 22 Sep 2012 16:04:30 +0200 Subject: [PATCH 12/21] Update docs --- doc/api/hooks_server-side.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md index 0bcd85a3b..c60bbe733 100644 --- a/doc/api/hooks_server-side.md +++ b/doc/api/hooks_server-side.md @@ -55,7 +55,7 @@ Called from: src/node/server.js Things in context: 1. app - the main express application object (helpful for adding new paths and such) -1. server - the http server object +2. server - the http server object This hook gets called after the application object has been created, but before it starts listening. This is similar to the expressConfigure hook, but it's not guaranteed that the application object will have all relevant configuration variables. @@ -77,6 +77,7 @@ Things in context: 1. app - the application object 2. io - the socketio object +3. server - the http server object I have no idea what this is useful for, someone else will have to add this description. From 413ddb393e98b966fa14c934753e63be18d81912 Mon Sep 17 00:00:00 2001 From: Richard Braakman Date: Fri, 28 Sep 2012 22:49:20 +0300 Subject: [PATCH 13/21] Add some explanatory comments to handleUserChanges() --- src/node/handler/PadMessageHandler.js | 41 +++++++++++++++++++-------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 8a5a92bb6..f60d91da6 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -436,15 +436,22 @@ function handleUserInfoUpdate(client, message) } /** - * Handles a USERINFO_UPDATE, that means that a user have changed his color or name. Anyway, we get both informations - * This Method is nearly 90% copied out of the Etherpad Source Code. So I can't tell you what happens here exactly - * Look at https://github.com/ether/pad/blob/master/etherpad/src/etherpad/collab/collab_server.js in the function applyUserChanges() + * Handles a USER_CHANGES message, where the client submits its local + * edits as a changeset. + * + * This handler's job is to update the incoming changeset so that it applies + * to the latest revision, then add it to the pad, broadcast the changes + * to all other clients, and send a confirmation to the submitting client. + * + * This function is based on a similar one in the original Etherpad. + * See https://github.com/ether/pad/blob/master/etherpad/src/etherpad/collab/collab_server.js in the function applyUserChanges() + * * @param client the client that send this message * @param message the message from the client */ function handleUserChanges(client, message) { - //check if all ok + // Make sure all required fields are present if(message.data.baseRev == null) { messageLogger.warn("Dropped message, USER_CHANGES Message has no baseRev!"); @@ -487,22 +494,23 @@ function handleUserChanges(client, message) { //ex. _checkChangesetAndPool - //Copied from Etherpad, don't know what it does exactly try { - //this looks like a changeset check, it throws errors sometimes + // Verify that the changeset has valid syntax and is in canonical form Changeset.checkRep(changeset); - + + // Verify that the attribute indexes used in the changeset are all + // defined in the accompanying attribute pool. Changeset.eachAttribNumber(changeset, function(n) { if (! wireApool.getAttrib(n)) { throw "Attribute pool is missing attribute "+n+" for changeset "+changeset; } }); } - //there is an error in this changeset, so just refuse it catch(e) { - console.warn("Can't apply USER_CHANGES "+changeset+", cause it faild checkRep"); + // There is an error in this changeset, so just refuse it + console.warn("Can't apply USER_CHANGES "+changeset+", because it failed checkRep"); client.json.send({disconnect:"badChangeset"}); return; } @@ -515,7 +523,10 @@ function handleUserChanges(client, message) //ex. applyUserChanges apool = pad.pool; r = baseRev; - + + // The client's changeset might not be based on the latest revision, + // since other clients are sending changes at the same time. + // Update the changeset so that it can be applied to the latest revision. //https://github.com/caolan/async#whilst async.whilst( function() { return r < pad.getHeadRevisionNumber(); }, @@ -526,8 +537,13 @@ function handleUserChanges(client, message) pad.getRevisionChangeset(r, function(err, c) { if(ERR(err, callback)) return; - + + // At this point, both "c" (from the pad) and "changeset" (from the + // client) are relative to revision r - 1. The follow function + // rebases "changeset" so that it is relative to revision r + // and can be applied after "c". changeset = Changeset.follow(c, changeset, false, apool); + if ((r - baseRev) % 200 == 0) { // don't let the stack get too deep async.nextTick(callback); } else { @@ -558,7 +574,8 @@ function handleUserChanges(client, message) if (correctionChangeset) { pad.appendRevision(correctionChangeset); } - + + // Make sure the pad always ends with an empty line. if (pad.text().lastIndexOf("\n\n") != pad.text().length-2) { var nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length-1, 0, "\n"); pad.appendRevision(nlChangeset); From 2e72a1e4895688bc4cc8340a9b49f306b7d41600 Mon Sep 17 00:00:00 2001 From: Richard Braakman Date: Fri, 28 Sep 2012 23:03:42 +0300 Subject: [PATCH 14/21] Prevent server crash in handleClientReady The client might have disconnected between callbacks so don't try to write to the session before checking this. The main callback of this function now has a single check at its top. Removed a redundant check halfway through the callback. Also normalized use of client.id for the session index instead of a mix of client.id and sessionId. Added some explanatory comments. --- src/node/handler/PadMessageHandler.js | 48 ++++++++++++++------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index f60d91da6..10b259ae2 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -882,6 +882,13 @@ function handleClientReady(client, message) }, function(callback) { + //Check that the client is still here. It might have disconnected between callbacks. + if(sessioninfos[client.id] === undefined) + { + callback(); + return; + } + //Check if this author is already on the pad, if yes, kick the other sessions! if(pad2sessions[padIds.padId]) { @@ -896,10 +903,9 @@ function handleClientReady(client, message) } //Save in sessioninfos that this session belonges to this pad - var sessionId=String(client.id); - sessioninfos[sessionId].padId = padIds.padId; - sessioninfos[sessionId].readOnlyPadId = padIds.readOnlyPadId; - sessioninfos[sessionId].readonly = padIds.readonly; + sessioninfos[client.id].padId = padIds.padId; + sessioninfos[client.id].readOnlyPadId = padIds.readOnlyPadId; + sessioninfos[client.id].readonly = padIds.readonly; //check if there is already a pad2sessions entry, if not, create one if(!pad2sessions[padIds.padId]) @@ -908,7 +914,7 @@ function handleClientReady(client, message) } //Saves in pad2sessions that this session belongs to this pad - pad2sessions[padIds.padId].push(sessionId); + pad2sessions[padIds.padId].push(client.id); //prepare all values for the wire var atext = Changeset.cloneAText(pad.atext); @@ -973,26 +979,22 @@ function handleClientReady(client, message) clientVars.userName = authorName; } - if(sessioninfos[client.id] !== undefined) + //If this is a reconnect, we don't have to send the client the ClientVars again + if(message.reconnect == true) { - //This is a reconnect, so we don't have to send the client the ClientVars again - if(message.reconnect == true) - { - //Save the revision in sessioninfos, we take the revision from the info the client send to us - sessioninfos[client.id].rev = message.client_rev; - } - //This is a normal first connect - else - { - //Send the clientVars to the Client - client.json.send({type: "CLIENT_VARS", data: clientVars}); - //Save the revision in sessioninfos - sessioninfos[client.id].rev = pad.getHeadRevisionNumber(); - } - - //Save the revision and the author id in sessioninfos - sessioninfos[client.id].author = author; + //Save the revision in sessioninfos, we take the revision from the info the client send to us + sessioninfos[client.id].rev = message.client_rev; } + //This is a normal first connect + else + { + //Send the clientVars to the Client + client.json.send({type: "CLIENT_VARS", data: clientVars}); + //Save the current revision in sessioninfos, should be the same as in clientVars + sessioninfos[client.id].rev = pad.getHeadRevisionNumber(); + } + + sessioninfos[client.id].author = author; //prepare the notification for the other users on the pad, that this user joined var messageToTheOtherUsers = { From 3fe3df91aee6618737feb2a4c1366276c14f0cb1 Mon Sep 17 00:00:00 2001 From: Gedion Date: Sun, 30 Sep 2012 17:13:14 -0500 Subject: [PATCH 15/21] update docs for new hooks and ace exposures --- doc/api/editorInfo.md | 11 ++++++ doc/api/hooks_client-side.md | 68 ++++++++++++++++++++++++++++++++++++ doc/api/hooks_server-side.md | 13 +++++++ 3 files changed, 92 insertions(+) diff --git a/doc/api/editorInfo.md b/doc/api/editorInfo.md index e4322e9e1..a212ff085 100644 --- a/doc/api/editorInfo.md +++ b/doc/api/editorInfo.md @@ -45,3 +45,14 @@ Returns the `rep` object. ## editorInfo.ace_doInsertUnorderedList(?) ## editorInfo.ace_doInsertOrderedList(?) ## editorInfo.ace_performDocumentApplyAttributesToRange() +## editorInfo.ace_getAuthorInfos() +## editorInfo.ace_performDocumentReplaceRange(?) +## editorInfo.ace_performDocumentReplaceCharRange(?) +## editorInfo.ace_renumberList(?) +## editorInfo.ace_doReturnKey() +## editorInfo.ace_isBlockElement(?) +## editorInfo.ace_getLineListType(?) +## editorInfo.ace_caretLine() +## editorInfo.ace_caretColumn() +## editorInfo.ace_caretDocChar() +## editorInfo.ace_isWordChar(?) diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md index f706f6a12..707800f5e 100644 --- a/doc/api/hooks_client-side.md +++ b/doc/api/hooks_client-side.md @@ -174,3 +174,71 @@ Things in context: This hook gets called every time the client receives a message of type `name`. This can most notably be used with the new HTTP API call, "sendClientsMessage", which sends a custom message type to all clients connected to a pad. You can also use this to handle existing types. `collab_client.js` has a pretty extensive list of message types, if you want to take a look. + +##aceStartLineAndCharForPoint-aceEndLineAndCharForPoint +Called from: src/static/js/ace2_inner.js + +Things in context: + +1. callstack - a bunch of information about the current action +2. editorInfo - information about the user who is making the change +3. rep - information about where the change is being made +4. root - the span element of the current line +5. point - the starting/ending element where the cursor highlights +6. documentAttributeManager - information about attributes in the document + +This hook is provided to allow a plugin to turn DOM node selection into [line,char] selection. +The return value should be an array of [line,char] + +##aceKeyEvent +Called from: src/static/js/ace2_inner.js + +Things in context: + +1. callstack - a bunch of information about the current action +2. editorInfo - information about the user who is making the change +3. rep - information about where the change is being made +4. documentAttributeManager - information about attributes in the document +5. evt - the fired event + +This hook is provided to allow a plugin to handle key events. +The return value should be true if you have handled the event. + +##collectContentLineText +Called from: src/static/js/contentcollector.js + +Things in context: + +1. cc - the contentcollector object +2. state - the current state of the change being made +3. tname - the tag name of this node currently being processed +4. text - the text for that line + +This hook allows you to validate/manipulate the text before it's sent to the server side. +The return value should be the validated/manipulated text. + +##collectContentLineBreak +Called from: src/static/js/contentcollector.js + +Things in context: + +1. cc - the contentcollector object +2. state - the current state of the change being made +3. tname - the tag name of this node currently being processed + +This hook is provided to allow whether the br tag should induce a new magic domline or not. +The return value should be either true(break the line) or false. + +##disableAuthorColorsForThisLine +Called from: src/static/js/linestylefilter.js + +Things in context: + +1. linestylefilter - the JavaScript object that's currently processing the ace attributes +2. text - the line text +3. class - line class + +This hook is provided to allow whether a given line should be deliniated with multiple authors. +Multiple authors in one line cause the creation of magic span lines. This might not be suit you and +now you can disable it and handle your own deliniation. +The return value should be either true(disable ) or false. diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md index 06ec7374b..854b43394 100644 --- a/doc/api/hooks_server-side.md +++ b/doc/api/hooks_server-side.md @@ -136,3 +136,16 @@ function handleMessage ( hook, context, callback ) { } }; ``` + + +## getLineHTMLForExport +Called from: src/node/utils/ExportHtml.js + +Things in context: + +1. apool - pool object +2. attribLine - line attributes +3. text - line text + +This hook will allow a plug-in developer to re-write each line when exporting to HTML. + From 61022be6e422e3608bb15cb230f862e0417239e8 Mon Sep 17 00:00:00 2001 From: Gedion Date: Mon, 1 Oct 2012 19:14:27 -0500 Subject: [PATCH 16/21] added comments to ace exposed methods --- doc/api/editorInfo.md | 22 +++++++++++++++++----- doc/api/hooks_client-side.md | 4 ++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/doc/api/editorInfo.md b/doc/api/editorInfo.md index a212ff085..6aec326e2 100644 --- a/doc/api/editorInfo.md +++ b/doc/api/editorInfo.md @@ -45,14 +45,26 @@ Returns the `rep` object. ## editorInfo.ace_doInsertUnorderedList(?) ## editorInfo.ace_doInsertOrderedList(?) ## editorInfo.ace_performDocumentApplyAttributesToRange() + ## editorInfo.ace_getAuthorInfos() -## editorInfo.ace_performDocumentReplaceRange(?) -## editorInfo.ace_performDocumentReplaceCharRange(?) -## editorInfo.ace_renumberList(?) +Returns an info object about the author. Object key = author_id and info includes athour's bg color value. +Use to define your own authorship. +## editorInfo.ace_performDocumentReplaceRange(start, end, newText) +This function replaces a range (from [x1,y1] to [x2,y2]) with `newText`. +## editorInfo.ace_performDocumentReplaceCharRange(startChar, endChar, newText) +This function replaces a range (from y1 to y2) with `newText`. +## editorInfo.ace_renumberList(lineNum) +If you delete a line, calling this method will fix the line numbering. ## editorInfo.ace_doReturnKey() -## editorInfo.ace_isBlockElement(?) -## editorInfo.ace_getLineListType(?) +Forces a return key at the current carret position. +## editorInfo.ace_isBlockElement(element) +Returns true if your passed elment is registered as a block element +## editorInfo.ace_getLineListType(lineNum) +Returns the line's html list type. ## editorInfo.ace_caretLine() +Returns X position of the caret. ## editorInfo.ace_caretColumn() +Returns Y position of the caret. ## editorInfo.ace_caretDocChar() +Returns the Y offset starting from [x=0,y=0] ## editorInfo.ace_isWordChar(?) diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md index 707800f5e..55d1da000 100644 --- a/doc/api/hooks_client-side.md +++ b/doc/api/hooks_client-side.md @@ -239,6 +239,6 @@ Things in context: 3. class - line class This hook is provided to allow whether a given line should be deliniated with multiple authors. -Multiple authors in one line cause the creation of magic span lines. This might not be suit you and +Multiple authors in one line cause the creation of magic span lines. This might not suit you and now you can disable it and handle your own deliniation. -The return value should be either true(disable ) or false. +The return value should be either true(disable) or false. From 60099030952e997c9e5bdc9e72da250afb35792c Mon Sep 17 00:00:00 2001 From: Gedion Date: Mon, 1 Oct 2012 19:18:19 -0500 Subject: [PATCH 17/21] added comments to ace exposed methods --- doc/api/editorInfo.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/editorInfo.md b/doc/api/editorInfo.md index 6aec326e2..05656bce7 100644 --- a/doc/api/editorInfo.md +++ b/doc/api/editorInfo.md @@ -47,7 +47,7 @@ Returns the `rep` object. ## editorInfo.ace_performDocumentApplyAttributesToRange() ## editorInfo.ace_getAuthorInfos() -Returns an info object about the author. Object key = author_id and info includes athour's bg color value. +Returns an info object about the author. Object key = author_id and info includes author's bg color value. Use to define your own authorship. ## editorInfo.ace_performDocumentReplaceRange(start, end, newText) This function replaces a range (from [x1,y1] to [x2,y2]) with `newText`. From 56453409a5884f2142030c45f9b007ac30a1ebf7 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 2 Oct 2012 02:19:44 +0200 Subject: [PATCH 18/21] Update src/static/js/pad_editbar.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Somehow was no more.  Now it is back..  Oh boy. --- src/static/js/pad_editbar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/static/js/pad_editbar.js b/src/static/js/pad_editbar.js index 59658a046..24dd3d6f0 100644 --- a/src/static/js/pad_editbar.js +++ b/src/static/js/pad_editbar.js @@ -249,13 +249,13 @@ var padeditbar = (function() { var basePath = document.location.href.substring(0, document.location.href.indexOf("/p/")); var readonlyLink = basePath + "/p/" + clientVars.readOnlyId; - $('#embedinput').val(""); $('#linkinput').val(readonlyLink); } else { var padurl = window.location.href.split("?")[0]; - $('#embedinput').val(""); $('#linkinput').val(padurl); } } From 7656001cb5e7f3024cdf51ca8fbf0f460fdae016 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 2 Oct 2012 20:11:18 +0200 Subject: [PATCH 19/21] Don't shut down the whole server, if error handling middleware is called. The errors passed to error handling middleware aren't that severe, so it's fine to just stay alive... --- src/node/hooks/express/errorhandling.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/node/hooks/express/errorhandling.js b/src/node/hooks/express/errorhandling.js index cb8c58987..4f5dad4f6 100644 --- a/src/node/hooks/express/errorhandling.js +++ b/src/node/hooks/express/errorhandling.js @@ -38,7 +38,6 @@ exports.expressCreateServer = function (hook_name, args, cb) { args.app.error(function(err, req, res, next){ res.send(500); console.error(err.stack ? err.stack : err.toString()); - exports.gracefulShutdown(); }); //connect graceful shutdown with sigint and uncaughtexception From b29fc11e9d43324914a4766f859595c7761813a5 Mon Sep 17 00:00:00 2001 From: Chad Weider Date: Tue, 2 Oct 2012 19:57:18 -0700 Subject: [PATCH 20/21] Upgrade to Yajsml bug fix. --- src/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.json b/src/package.json index 2285f0ca8..e2f9c7740 100644 --- a/src/package.json +++ b/src/package.json @@ -10,7 +10,7 @@ "name": "Robin Buse" } ], "dependencies" : { - "yajsml" : "1.1.5", + "yajsml" : "1.1.6", "request" : "2.9.100", "require-kernel" : "1.0.5", "resolve" : "0.2.x", From a97c63b80955e9b2e960f12b4f0f075ca629fd1d Mon Sep 17 00:00:00 2001 From: johnyma22 Date: Thu, 4 Oct 2012 18:53:02 +0100 Subject: [PATCH 21/21] Fix issue caused by broken async update --- src/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.json b/src/package.json index 5f981a7b8..f729b3c16 100644 --- a/src/package.json +++ b/src/package.json @@ -16,7 +16,7 @@ "resolve" : "0.2.x", "socket.io" : "0.9.x", "ueberDB" : "0.1.7", - "async" : "0.1.x", + "async" : "0.1.22", "express" : "3.x", "connect" : "2.4.x", "clean-css" : "0.3.2",